main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s

This commit is contained in:
2026-05-17 02:17:22 +03:00
parent 17ace9bd12
commit 94c7a4481a
53 changed files with 29602 additions and 7832 deletions
+46 -18
View File
@@ -21,6 +21,7 @@ except ImportError:
HAS_BASKETBALL = False
from services.single_match_orchestrator import get_single_match_orchestrator
from services.v26_shadow_engine import get_v26_shadow_engine
from models.league_model import get_league_model_loader
load_dotenv()
@@ -123,7 +124,15 @@ def health_check() -> dict[str, Any]:
try:
orchestrator = get_single_match_orchestrator()
shadow_engine = get_v26_shadow_engine()
# Per-market V25 model status
v25_readiness: dict[str, Any] = {"fully_loaded": False}
try:
v25_predictor = orchestrator._get_v25_predictor()
v25_readiness = v25_predictor.readiness_summary()
except Exception as v25_err:
v25_readiness = {"fully_loaded": False, "error": str(v25_err)}
if HAS_BASKETBALL:
basketball_predictor = get_basketball_v25_predictor()
basketball_readiness = basketball_predictor.readiness_summary()
@@ -131,35 +140,52 @@ def health_check() -> dict[str, Any]:
else:
basketball_readiness = {"fully_loaded": False, "error": "Basketball module not found"}
ready = True
league_readiness = get_league_model_loader().readiness_summary()
overall_ready = ready and v25_readiness.get("fully_loaded", False)
return {
"status": "healthy" if ready else "degraded",
"status": "healthy" if overall_ready else "degraded",
"engine": "v28.main",
"mode": os.getenv("AI_ENGINE_MODE", "v28"),
"ready": ready,
"ready": overall_ready,
"v25_football": v25_readiness,
"league_specific": league_readiness,
"basketball_v25": basketball_readiness,
"v26_shadow": shadow_engine.readiness_summary(),
"prediction_service_ready": True,
"model_loaded": ready,
"model_loaded": overall_ready,
"orchestrator_mode": getattr(orchestrator, "engine_mode", "v28"),
}
except Exception as error:
return {"status": "unhealthy", "ready": False, "error": str(error)}
_REQUIRED_RESPONSE_FIELDS = ("match_info", "market_board", "main_pick", "bet_summary", "data_quality")
@app.post("/v20plus/analyze/{match_id}")
async def analyze_match_v20plus(match_id: str) -> dict[str, Any]:
started_at = time.time()
orchestrator = get_single_match_orchestrator()
result = orchestrator.analyze_match(match_id)
result = await asyncio.to_thread(orchestrator.analyze_match, match_id)
elapsed_ms = int((time.time() - started_at) * 1000)
if not result:
raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")
# Response validation: log missing required fields (non-fatal)
missing_fields = [f for f in _REQUIRED_RESPONSE_FIELDS if f not in result]
if missing_fields:
print(f"⚠️ [API] analyze/{match_id} response missing fields: {missing_fields} ({elapsed_ms}ms)")
result["timing_ms"] = elapsed_ms
return result
@app.get("/v20plus/analyze-htms/{match_id}")
async def analyze_match_htms_v20plus(match_id: str) -> dict[str, Any]:
orchestrator = get_single_match_orchestrator()
result = orchestrator.analyze_match_htms(match_id)
result = await asyncio.to_thread(orchestrator.analyze_match_htms, match_id)
if not result:
raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")
return result
@@ -230,11 +256,12 @@ async def analyze_match_htft_v20plus(match_id: str, timeout_sec: int = 30) -> di
@app.post("/v20plus/coupon")
async def generate_coupon_v20plus(request: CouponRequest) -> dict[str, Any]:
orchestrator = get_single_match_orchestrator()
return orchestrator.build_coupon(
match_ids=request.match_ids,
strategy=request.strategy or "BALANCED",
max_matches=request.max_matches,
min_confidence=request.min_confidence,
return await asyncio.to_thread(
orchestrator.build_coupon,
request.match_ids,
request.strategy or "BALANCED",
request.max_matches,
request.min_confidence,
)
@@ -244,7 +271,7 @@ async def get_daily_banker_v20plus(count: int = 3) -> dict[str, Any]:
raise HTTPException(status_code=400, detail="count must be >= 1")
orchestrator = get_single_match_orchestrator()
bankers = orchestrator.get_daily_bankers(count=count)
bankers = await asyncio.to_thread(orchestrator.get_daily_bankers, count)
return {"count": len(bankers), "bankers": bankers}
@app.get("/v20plus/reversal-watchlist")
@@ -262,11 +289,12 @@ async def get_reversal_watchlist_v20plus(
raise HTTPException(status_code=400, detail="min_score must be between 0 and 100")
orchestrator = get_single_match_orchestrator()
return orchestrator.get_reversal_watchlist(
count=count,
horizon_hours=horizon_hours,
min_score=min_score,
top_leagues_only=top_leagues_only,
return await asyncio.to_thread(
orchestrator.get_reversal_watchlist,
count,
horizon_hours,
min_score,
top_leagues_only,
)