This commit is contained in:
2026-04-16 17:21:48 +03:00
parent c8fa4c442d
commit c8e7e4e927
116 changed files with 3720 additions and 4197 deletions
+52 -52
View File
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';
import { Injectable, Logger } from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { firstValueFrom } from "rxjs";
export interface AIPredictionResult {
matchId: string;
@@ -46,7 +46,7 @@ export class AiService {
private readonly configService: ConfigService,
) {
this.pythonEngineUrl =
this.configService.get('AI_ENGINE_URL') || 'http://127.0.0.1:8000';
this.configService.get("AI_ENGINE_URL") || "http://127.0.0.1:8000";
}
/**
@@ -61,9 +61,9 @@ export class AiService {
_eventData: any[],
): Promise<AIPredictionResult | null> {
try {
const matchId = String(matchDetails?.matchId || '').trim();
const matchId = String(matchDetails?.matchId || "").trim();
if (!matchId) {
this.logger.warn('Skipping AI call: missing matchId');
this.logger.warn("Skipping AI call: missing matchId");
return null;
}
@@ -102,18 +102,18 @@ export class AiService {
.map((p: any) => `${p.market}: ${p.pick}`);
const mappedPredictions = picks.map((p: any) => ({
betType: String(p.market || ''),
prediction: String(p.pick || ''),
betType: String(p.market || ""),
prediction: String(p.pick || ""),
confidence: Number(p.calibrated_confidence ?? p.confidence ?? 0),
probabilities: {},
reasoning: Array.isArray(p.reasons)
? p.reasons.join(' | ')
? p.reasons.join(" | ")
: Array.isArray(p.decision_reasons)
? p.decision_reasons.join(' | ')
: '',
odd: typeof p.odds === 'number' ? p.odds : undefined,
? p.decision_reasons.join(" | ")
: "",
odd: typeof p.odds === "number" ? p.odds : undefined,
valueBet:
typeof p.edge === 'number'
typeof p.edge === "number"
? {
isValue: p.edge > 0,
edge: p.edge,
@@ -138,8 +138,8 @@ export class AiService {
: recommendedBets,
homeAnalysis: undefined,
awayAnalysis: undefined,
expertComment: data.ai_commentary || data.expert_comment || '',
modelVersion: data.model_version || 'v25.main',
expertComment: data.ai_commentary || data.expert_comment || "",
modelVersion: data.model_version || "v25.main",
confidenceScore:
confidenceScore > 1 ? confidenceScore : confidenceScore * 100,
expectedGoals: data?.score_prediction?.xg_total,
@@ -161,10 +161,10 @@ export class AiService {
prediction: p.pick,
confidence: p.calibrated_confidence ?? p.confidence ?? 0,
probabilities: {},
reasoning: Array.isArray(p.reasons) ? p.reasons.join(' | ') : '',
reasoning: Array.isArray(p.reasons) ? p.reasons.join(" | ") : "",
odd: p.odds || 0,
valueBet: {
is_value: typeof p.edge === 'number' ? p.edge > 0 : false,
is_value: typeof p.edge === "number" ? p.edge > 0 : false,
edge: p.edge || 0,
},
}));
@@ -176,21 +176,21 @@ export class AiService {
valueBets: allPredictions.filter((p: any) => p.valueBet?.is_value),
homeAnalysis: null,
awayAnalysis: null,
expertComment: pyData.ai_commentary || '',
winnerPrediction: firstPick?.prediction || 'N/A',
scorePrediction: pyData.score_prediction?.ft || '-',
expertComment: pyData.ai_commentary || "",
winnerPrediction: firstPick?.prediction || "N/A",
scorePrediction: pyData.score_prediction?.ft || "-",
confidenceScore:
typeof firstPick?.confidence === 'number' ? firstPick.confidence : 0,
modelVersion: pyData.model_version || 'v25.main',
typeof firstPick?.confidence === "number" ? firstPick.confidence : 0,
modelVersion: pyData.model_version || "v25.main",
expectedGoals: pyData.score_prediction?.xg_total || 0,
keyInsights: [
`Model: ${pyData.model_version || 'v25.main'}`,
`Risk: ${pyData.risk?.level || 'N/A'} (${pyData.risk?.score ?? 0})`,
`Data Quality: ${pyData.data_quality?.label || 'N/A'}`,
`Model: ${pyData.model_version || "v25.main"}`,
`Risk: ${pyData.risk?.level || "N/A"} (${pyData.risk?.score ?? 0})`,
`Data Quality: ${pyData.data_quality?.label || "N/A"}`,
`xG Beklentisi: ${
typeof pyData.score_prediction?.xg_total === 'number'
typeof pyData.score_prediction?.xg_total === "number"
? pyData.score_prediction.xg_total.toFixed(2)
: 'N/A'
: "N/A"
}`,
],
};
@@ -206,33 +206,33 @@ export class AiService {
// MS 1 oranını bul
const ms1 = odds.find(
(o: any) =>
o.category?.toLowerCase().includes('maç sonucu') && o.selection === '1',
o.category?.toLowerCase().includes("maç sonucu") && o.selection === "1",
);
// KG Var oranını bul
const kgVar = odds.find(
(o: any) =>
o.category?.toLowerCase().includes('karşılıklı gol') &&
o.selection?.toLowerCase() === 'var',
o.category?.toLowerCase().includes("karşılıklı gol") &&
o.selection?.toLowerCase() === "var",
);
// Alt 2.5 oranını bul
const alt25 = odds.find(
(o: any) =>
o.category?.toLowerCase().includes('alt/üst') &&
o.selection?.toLowerCase() === 'alt',
o.category?.toLowerCase().includes("alt/üst") &&
o.selection?.toLowerCase() === "alt",
);
// Tactic 1: Benzer MS oranları
if (ms1?.odd_value) {
tactics.push({
tacticName: 'Benzer Maç Sonucu Oranları',
tacticName: "Benzer Maç Sonucu Oranları",
description:
'Ev sahibi galibiyeti için benzer oran aralığındaki maçlar',
"Ev sahibi galibiyeti için benzer oran aralığındaki maçlar",
odds: [
{
categoryName: 'Maç Sonucu',
selectionName: '1',
categoryName: "Maç Sonucu",
selectionName: "1",
value: parseFloat(ms1.odd_value),
tolerance: 0.3,
},
@@ -243,18 +243,18 @@ export class AiService {
// Tactic 2: Benzer KG + AU oranları
if (kgVar?.odd_value && alt25?.odd_value) {
tactics.push({
tacticName: 'Benzer Gol Beklentisi',
description: 'Karşılıklı gol ve toplam gol benzerliği',
tacticName: "Benzer Gol Beklentisi",
description: "Karşılıklı gol ve toplam gol benzerliği",
odds: [
{
categoryName: 'Karşılıklı Gol',
selectionName: 'Var',
categoryName: "Karşılıklı Gol",
selectionName: "Var",
value: parseFloat(kgVar.odd_value),
tolerance: 0.4,
},
{
categoryName: '2,5 Alt/Üst',
selectionName: 'Alt',
categoryName: "2,5 Alt/Üst",
selectionName: "Alt",
value: parseFloat(alt25.odd_value),
tolerance: 0.3,
},
@@ -265,12 +265,12 @@ export class AiService {
// Tactic 3: Favori analizi
if (ms1?.odd_value && parseFloat(ms1.odd_value) < 1.8) {
tactics.push({
tacticName: 'Favori Takım Analizi',
description: 'Benzer şekilde favori olan ev sahibi takımların maçları',
tacticName: "Favori Takım Analizi",
description: "Benzer şekilde favori olan ev sahibi takımların maçları",
odds: [
{
categoryName: 'Maç Sonucu',
selectionName: '1',
categoryName: "Maç Sonucu",
selectionName: "1",
value: parseFloat(ms1.odd_value),
tolerance: 0.2,
},
@@ -291,7 +291,7 @@ export class AiService {
timeout: 5000,
}),
);
return response.data?.status === 'healthy';
return response.data?.status === "healthy";
} catch {
return false;
}
@@ -307,11 +307,11 @@ export class AiService {
valueBets: [],
homeAnalysis: null,
awayAnalysis: null,
expertComment: 'Analiz verisi alınamadı (Python Servis Hatası).',
winnerPrediction: 'N/A',
scorePrediction: '-',
expertComment: "Analiz verisi alınamadı (Python Servis Hatası).",
winnerPrediction: "N/A",
scorePrediction: "-",
confidenceScore: 0,
modelVersion: 'v25.main',
modelVersion: "v25.main",
expectedGoals: 0,
keyInsights: [],
};