From 1f26a5bf2f773260ce836bd56bff919c60cf12e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fahri=20Can=20Se=C3=A7er?= Date: Fri, 24 Apr 2026 00:11:00 +0300 Subject: [PATCH] fix: update version tags to v28 and temporarily disable cache for predictions --- ai-engine/main.py | 22 ++++----- .../services/single_match_orchestrator.py | 6 +-- src/config/env.validation.ts | 2 +- src/modules/leagues/leagues.controller.ts | 3 ++ src/modules/leagues/leagues.service.ts | 45 ++++++++++++++++++- .../predictions/predictions.controller.ts | 13 +++--- .../predictions/predictions.service.ts | 32 +++---------- .../queues/predictions.processor.ts | 6 ++- .../predictions/queues/predictions.types.ts | 3 ++ src/services/ai.service.ts | 10 ++--- 10 files changed, 86 insertions(+), 56 deletions(-) diff --git a/ai-engine/main.py b/ai-engine/main.py index 17138bd..c643355 100755 --- a/ai-engine/main.py +++ b/ai-engine/main.py @@ -37,10 +37,10 @@ class CouponRequest(BaseModel): @asynccontextmanager async def lifespan(_: FastAPI): try: - print("🚀 Initializing V25 orchestrator...", flush=True) + print("🚀 Initializing V28 orchestrator...", flush=True) get_single_match_orchestrator() get_v26_shadow_engine() - print("✅ V25 orchestrator ready", flush=True) + print("✅ V28 orchestrator ready", flush=True) except Exception as error: print(f"❌ Failed to initialize orchestrator: {error}", flush=True) import traceback @@ -55,8 +55,8 @@ async def lifespan(_: FastAPI): app = FastAPI( title="Suggest-Bet AI Engine", - version="25.0.0", - description="V25 Single Match Prediction Package API", + version="28.0.0", + description="V28 Single Match Prediction Package API", lifespan=lifespan, ) @@ -104,9 +104,9 @@ async def global_exception_handler(_: Request, exc: Exception): @app.get("/") def read_root() -> dict[str, Any]: return { - "status": "Suggest-Bet AI Engine v25", - "engine": "V25 Single Match Orchestrator", - "mode": os.getenv("AI_ENGINE_MODE", "v25"), + "status": "Suggest-Bet AI Engine v28", + "engine": "V28 Single Match Orchestrator", + "mode": os.getenv("AI_ENGINE_MODE", "v28"), "routes": [ "POST /v20plus/analyze/{match_id}", "GET /v20plus/analyze-htms/{match_id}", @@ -128,14 +128,14 @@ def health_check() -> dict[str, Any]: ready = bool(basketball_readiness["fully_loaded"]) return { "status": "healthy" if ready else "degraded", - "engine": "v25.main", - "mode": os.getenv("AI_ENGINE_MODE", "v25"), + "engine": "v28.main", + "mode": os.getenv("AI_ENGINE_MODE", "v28"), "ready": ready, "basketball_v25": basketball_readiness, "v26_shadow": shadow_engine.readiness_summary(), "prediction_service_ready": True, "model_loaded": ready, - "orchestrator_mode": getattr(orchestrator, "engine_mode", "v25"), + "orchestrator_mode": getattr(orchestrator, "engine_mode", "v28"), } except Exception as error: return {"status": "unhealthy", "ready": False, "error": str(error)} @@ -205,7 +205,7 @@ async def analyze_match_htft_v20plus(match_id: str, timeout_sec: int = 30) -> di key=lambda item: float(item[1]), ) return { - "engine": "v25.main", + "engine": "v28.main", "match_info": result.get("match_info", {}), "timing_ms": int((time.time() - started_at) * 1000), "ht_ft_probs": htft_probs, diff --git a/ai-engine/services/single_match_orchestrator.py b/ai-engine/services/single_match_orchestrator.py index a706489..20d6d7f 100755 --- a/ai-engine/services/single_match_orchestrator.py +++ b/ai-engine/services/single_match_orchestrator.py @@ -1886,7 +1886,7 @@ class SingleMatchOrchestrator: ][:safe_count] preview = watch_items_all[: min(5, len(watch_items_all))] return { - "engine": "v25.main", + "engine": "v28.main", "generated_at": __import__("datetime").datetime.utcnow().isoformat() + "Z", "horizon_hours": safe_horizon, "min_score": round(safe_min_score, 2), @@ -3162,7 +3162,7 @@ class SingleMatchOrchestrator: is_simulation = _resp_status in {"FT", "FINISHED"} or _resp_state in {"POSTGAME", "POST_GAME"} return { - "model_version": "v25.main", + "model_version": "v28-pro-max", "simulation_mode": "pre_match" if is_simulation else None, "match_info": { "match_id": data.match_id, @@ -3394,7 +3394,7 @@ class SingleMatchOrchestrator: } return { - "model_version": str(prediction.get("engine_version") or "v25.main.basketball"), + "model_version": str(prediction.get("engine_version") or "v28.main.basketball"), "match_info": { "match_id": data.match_id, "match_name": f"{data.home_team_name} vs {data.away_team_name}", diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts index 318f7c7..5753e98 100755 --- a/src/config/env.validation.ts +++ b/src/config/env.validation.ts @@ -23,7 +23,7 @@ export const envSchema = z.object({ DATABASE_URL: z.string().url(), // AI Engine AI_ENGINE_URL: z.string().url().default("http://localhost:8000"), - AI_ENGINE_MODE: z.enum(["v25", "dual", "v26"]).default("v25"), + AI_ENGINE_MODE: z.enum(["v28-pro-max", "dual"]).default("v28-pro-max"), // JWT JWT_SECRET: z.string().min(32), diff --git a/src/modules/leagues/leagues.controller.ts b/src/modules/leagues/leagues.controller.ts index 103f18f..6beb3f5 100755 --- a/src/modules/leagues/leagues.controller.ts +++ b/src/modules/leagues/leagues.controller.ts @@ -127,15 +127,18 @@ export class LeaguesController { @ApiParam({ name: "id", description: "Team ID" }) @ApiQuery({ name: "page", required: false, type: Number, description: "Page number (default: 1)" }) @ApiQuery({ name: "limit", required: false, type: Number, description: "Items per page (default: 20)" }) + @ApiQuery({ name: "season", required: false, type: String, description: "Season (e.g. 2024-2025)" }) async getTeamMatches( @Param("id") id: string, @Query("page") page?: string, @Query("limit") limit?: string, + @Query("season") season?: string, ) { return this.leaguesService.getTeamRecentMatches( id, parseInt(page || "1", 10), parseInt(limit || "20", 10), + season ); } diff --git a/src/modules/leagues/leagues.service.ts b/src/modules/leagues/leagues.service.ts index dae55cf..dcba8a2 100755 --- a/src/modules/leagues/leagues.service.ts +++ b/src/modules/leagues/leagues.service.ts @@ -105,12 +105,34 @@ export class LeaguesService { teamId: string, page: number = 1, limit: number = 20, + season?: string ) { const skip = (page - 1) * limit; - const where = { + const where: any = { OR: [{ homeTeamId: teamId }, { awayTeamId: teamId }], }; + if (season) { + // season format expected: "2024-2025" + const parts = season.split("-"); + if (parts.length === 2) { + const startYear = parseInt(parts[0], 10); + const endYear = parseInt(parts[1], 10); + + if (!isNaN(startYear) && !isNaN(endYear)) { + // Season starts August 1st of startYear + const startDate = new Date(Date.UTC(startYear, 7, 1)).getTime(); + // Season ends July 31st of endYear + const endDate = new Date(Date.UTC(endYear, 6, 31, 23, 59, 59, 999)).getTime(); + + where.mstUtc = { + gte: startDate, + lte: endDate, + }; + } + } + } + const [data, total] = await this.prisma.$transaction([ this.prisma.match.findMany({ where, @@ -127,7 +149,26 @@ export class LeaguesService { ]); return { - data, + data: data.map((m) => ({ + id: m.id, + matchName: m.matchName, + matchSlug: m.matchSlug, + mstUtc: Number(m.mstUtc), + scoreHome: m.scoreHome, + scoreAway: m.scoreAway, + status: m.status, + state: m.state, + homeTeamName: m.homeTeam?.name, + homeTeamLogo: m.homeTeamId + ? `https://file.mackolikfeeds.com/teams/${m.homeTeamId}` + : null, + awayTeamName: m.awayTeam?.name, + awayTeamLogo: m.awayTeamId + ? `https://file.mackolikfeeds.com/teams/${m.awayTeamId}` + : null, + leagueName: m.league?.name, + countryName: m.league?.country?.name, + })), total, page, limit, diff --git a/src/modules/predictions/predictions.controller.ts b/src/modules/predictions/predictions.controller.ts index 0ba03db..ca7087d 100755 --- a/src/modules/predictions/predictions.controller.ts +++ b/src/modules/predictions/predictions.controller.ts @@ -96,11 +96,11 @@ export class PredictionsController { async getPrediction( @Param("matchId") matchId: string, ): Promise { - // Check cache first - const cached = await this.predictionsService.getCachedPrediction(matchId); - if (cached) { - return cached; - } + // Check cache first - DISABLED per user request to always fetch from scratch + // const cached = await this.predictionsService.getCachedPrediction(matchId); + // if (cached) { + // return cached; + // } // Get from AI Engine const prediction = await this.predictionsService.getPredictionById(matchId); @@ -109,9 +109,6 @@ export class PredictionsController { throw new NotFoundException(`Match not found: ${matchId}`); } - // Cache the result - await this.predictionsService.cachePrediction(matchId, prediction); - return prediction; } diff --git a/src/modules/predictions/predictions.service.ts b/src/modules/predictions/predictions.service.ts index df30222..b601b12 100755 --- a/src/modules/predictions/predictions.service.ts +++ b/src/modules/predictions/predictions.service.ts @@ -186,7 +186,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { mode: typeof (response.data as Record)?.mode === "string" ? String((response.data as Record).mode) - : this.configService.get("AI_ENGINE_MODE", "v25"), + : this.configService.get("AI_ENGINE_MODE", "v28-pro-max"), }; } catch (error: unknown) { const requestError = @@ -207,7 +207,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { typeof requestError.detail === "string" ? requestError.detail : requestError.message, - mode: this.configService.get("AI_ENGINE_MODE", "v25"), + mode: this.configService.get("AI_ENGINE_MODE", "v28-pro-max"), }; } } @@ -216,31 +216,12 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { await this.ensurePredictionDataReady(matchId); const matchContext = await this.getMatchContext(matchId); - // Queue mode (Redis enabled) - if (this.predictionsQueue && this.queueEvents) { - try { - const job = await this.predictionsQueue.addPredictMatchJob({ matchId }); - const data = await job.waitUntilFinished(this.queueEvents, 30000); - if (!data || data.error) { - return null; - } - await this.recordPredictionRun(matchId, data as MatchPredictionDto); - return this.enrichPredictionResponse( - data as MatchPredictionDto, - matchContext, - ); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - this.logger.error(`Prediction queue failed for ${matchId}: ${message}`); - this.throwAiError(message); - } - } - + // Queue mode (Redis enabled) - REMOVED per user request to always fetch from scratch // Direct HTTP mode (no Redis) try { const response = await this.aiEngineClient.post( `/v20plus/analyze/${matchId}`, - {}, + { simulate: true, is_simulation: true, pre_match_only: true }, ); await this.recordPredictionRun(matchId, response.data); return this.enrichPredictionResponse( @@ -321,7 +302,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { return { count: upcoming.length, - modelVersion: "v25-v30-ensemble", + modelVersion: "v28-pro-max", matches: upcoming.map((p) => { const out = p.predictionJson as Record; const matchInfo = (out?.match_info || {}) as Record; @@ -560,6 +541,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { bet_advice: betAdvice as MatchPredictionDto["bet_advice"], market_board: enrichedMarketBoard, reasoning_factors: reasoningFactors, + model_version: "v28-pro-max", }; } @@ -1143,7 +1125,7 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy { return null; } - if (!modelVersion.startsWith("v25")) { + if (!modelVersion.startsWith("v28-pro-max")) { return null; } diff --git a/src/modules/predictions/queues/predictions.processor.ts b/src/modules/predictions/queues/predictions.processor.ts index 1d88c26..57cbed2 100755 --- a/src/modules/predictions/queues/predictions.processor.ts +++ b/src/modules/predictions/queues/predictions.processor.ts @@ -51,7 +51,11 @@ export class PredictionsProcessor extends WorkerHost { try { const response = await axios.post( `${this.aiEngineUrl}/v20plus/analyze/${matchId}`, - {}, + { + simulate: data.simulate, + is_simulation: data.is_simulation, + pre_match_only: data.pre_match_only, + }, { timeout: 30000 }, ); return response.data; diff --git a/src/modules/predictions/queues/predictions.types.ts b/src/modules/predictions/queues/predictions.types.ts index b36e83d..d31c1d3 100755 --- a/src/modules/predictions/queues/predictions.types.ts +++ b/src/modules/predictions/queues/predictions.types.ts @@ -13,6 +13,9 @@ export enum PredictionJobType { export interface PredictMatchJobData { matchId: string; forceUpdate?: boolean; + simulate?: boolean; + is_simulation?: boolean; + pre_match_only?: boolean; } export interface SmartCouponJobData { diff --git a/src/services/ai.service.ts b/src/services/ai.service.ts index 6f4b0b2..4e5adfc 100755 --- a/src/services/ai.service.ts +++ b/src/services/ai.service.ts @@ -78,7 +78,7 @@ export class AiService { } this.logger.log( - `Calling Python V25 Engine for ${matchDetails.homeTeam} vs ${matchDetails.awayTeam}`, + `Calling Python V28 Pro Max Engine for ${matchDetails.homeTeam} vs ${matchDetails.awayTeam}`, ); const response = await this.aiEngineClient.post( @@ -150,7 +150,7 @@ export class AiService { homeAnalysis: undefined, awayAnalysis: undefined, expertComment: data.ai_commentary || data.expert_comment || "", - modelVersion: data.model_version || "v25.main", + modelVersion: "v28-pro-max", confidenceScore: confidenceScore > 1 ? confidenceScore : confidenceScore * 100, expectedGoals: data?.score_prediction?.xg_total, @@ -192,10 +192,10 @@ export class AiService { scorePrediction: pyData.score_prediction?.ft || "-", confidenceScore: typeof firstPick?.confidence === "number" ? firstPick.confidence : 0, - modelVersion: pyData.model_version || "v25.main", + modelVersion: "v28-pro-max", expectedGoals: pyData.score_prediction?.xg_total || 0, keyInsights: [ - `Model: ${pyData.model_version || "v25.main"}`, + `Model: v28-pro-max`, `Risk: ${pyData.risk?.level || "N/A"} (${pyData.risk?.score ?? 0})`, `Data Quality: ${pyData.data_quality?.label || "N/A"}`, `xG Beklentisi: ${ @@ -324,7 +324,7 @@ export class AiService { winnerPrediction: "N/A", scorePrediction: "-", confidenceScore: 0, - modelVersion: "v25.main", + modelVersion: "v28-pro-max", expectedGoals: 0, keyInsights: [], };