from __future__ import annotations import json import sys from pathlib import Path import psycopg2 from psycopg2.extras import RealDictCursor AI_ENGINE_DIR = Path(__file__).resolve().parents[1] if str(AI_ENGINE_DIR) not in sys.path: sys.path.insert(0, str(AI_ENGINE_DIR)) from services.single_match_orchestrator import SingleMatchOrchestrator def _resolve_dsn() -> str: env_path = AI_ENGINE_DIR / ".env" if env_path.exists(): for line in env_path.read_text(encoding="utf-8").splitlines(): if line.startswith("DATABASE_URL="): return line.split("=", 1)[1].strip().split("?schema=")[0] raise SystemExit("DATABASE_URL not found in ai-engine/.env") def _fetch_matches(dsn: str, limit: int = 60) -> list[str]: query = """ SELECT m.id FROM matches m WHERE m.status = 'FT' AND m.sport = 'football' AND m.score_home IS NOT NULL AND m.score_away IS NOT NULL ORDER BY m.mst_utc DESC LIMIT %s """ with psycopg2.connect(dsn) as conn: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute(query, (limit,)) return [str(row["id"]) for row in cur.fetchall()] def _score_prediction(package: dict) -> dict[str, float]: rows = package.get("bet_summary", []) or [] playable = [row for row in rows if row.get("playable")] return { "playable_count": float(len(playable)), "avg_edge": round( sum(float(row.get("ev_edge", 0.0)) for row in playable) / len(playable), 4, ) if playable else 0.0, "avg_confidence": round( sum(float(row.get("calibrated_confidence", 0.0)) for row in playable) / len(playable), 2, ) if playable else 0.0, } def main() -> None: dsn = _resolve_dsn() match_ids = _fetch_matches(dsn) orchestrator = SingleMatchOrchestrator() results: list[dict[str, object]] = [] for match_id in match_ids: orchestrator.engine_mode = "v25" v25 = orchestrator.analyze_match(match_id) orchestrator.engine_mode = "v26" v26 = orchestrator.analyze_match(match_id) if not v25 or not v26: continue results.append( { "match_id": match_id, "v25": _score_prediction(v25), "v26": _score_prediction(v26), "v25_main": (v25.get("main_pick") or {}).get("pick"), "v26_main": (v26.get("main_pick") or {}).get("pick"), } ) out_path = AI_ENGINE_DIR / "reports" / "backtest_v26_shadow.json" out_path.write_text(json.dumps(results, indent=2), encoding="utf-8") print(f"[OK] Shadow backtest summary written to {out_path}") if __name__ == "__main__": main()