+46
-18
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user