feat: league tier system + retrained V25 models (48 quality leagues)
Deploy Iddaai Backend / build-and-deploy (push) Failing after 3m56s
Deploy Iddaai Backend / build-and-deploy (push) Failing after 3m56s
- Add LeagueTier DB model and Prisma schema - Add league-tiers service (CRUD, sync, retrain trigger) - Add league-tiers controller with admin API endpoints - Add /v1/admin/retrain endpoint in AI engine (extract→train→reload pipeline) - Retrain V25 Pro with 48 quality leagues (MS accuracy: 26.9%→51.4%) - Update qualified_leagues.json (443→48 leagues) - Include V25 model files in repo for Docker deployment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,11 +7,14 @@ import time
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import uvicorn
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
import subprocess
|
||||
from pydantic import BaseModel
|
||||
|
||||
try:
|
||||
@@ -38,6 +41,23 @@ class CouponRequest(BaseModel):
|
||||
min_confidence: float | None = None
|
||||
|
||||
|
||||
class RetrainRequest(BaseModel):
|
||||
reason: str | None = "manual"
|
||||
markets: str | None = None # comma-separated, e.g. "MS,OU25,BTTS"
|
||||
trials: int | None = 50
|
||||
|
||||
|
||||
# ─── Retrain state tracking ──────────────────────────────────
|
||||
_retrain_state: dict[str, Any] = {
|
||||
"running": False,
|
||||
"last_started": None,
|
||||
"last_completed": None,
|
||||
"last_status": None,
|
||||
"last_error": None,
|
||||
"pid": None,
|
||||
}
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
try:
|
||||
@@ -115,6 +135,8 @@ def read_root() -> dict[str, Any]:
|
||||
"GET /v20plus/reversal-watchlist",
|
||||
"POST /v20plus/coupon",
|
||||
"GET /v20plus/daily-banker",
|
||||
"POST /v1/admin/retrain",
|
||||
"GET /v1/admin/retrain/status",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -298,6 +320,111 @@ async def get_reversal_watchlist_v20plus(
|
||||
)
|
||||
|
||||
|
||||
# ─── ADMIN: Retrain Pipeline ─────────────────────────────────
|
||||
|
||||
def _run_retrain_pipeline(markets: str | None, trials: int):
|
||||
"""Background function: extract data → train model → reload."""
|
||||
global _retrain_state
|
||||
ai_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
scripts_dir = os.path.join(ai_dir, "scripts")
|
||||
python = os.path.join(ai_dir, "venv", "bin", "python3")
|
||||
if not os.path.exists(python):
|
||||
python = sys.executable # fallback
|
||||
|
||||
try:
|
||||
# Step 1: Extract training data
|
||||
print("🔄 [RETRAIN] Step 1/3: Extracting training data...", flush=True)
|
||||
result = subprocess.run(
|
||||
[python, os.path.join(scripts_dir, "extract_training_data.py")],
|
||||
capture_output=True, text=True, timeout=600, cwd=ai_dir,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Extract failed:\n{result.stderr[-500:]}")
|
||||
print(f"✅ [RETRAIN] Extract done", flush=True)
|
||||
|
||||
# Step 2: Train V25 Pro
|
||||
print("🔄 [RETRAIN] Step 2/3: Training V25 Pro model...", flush=True)
|
||||
train_cmd = [python, os.path.join(scripts_dir, "train_v25_pro.py")]
|
||||
if markets:
|
||||
train_cmd += ["--markets", markets]
|
||||
train_cmd += ["--trials", str(trials)]
|
||||
|
||||
result = subprocess.run(
|
||||
train_cmd, capture_output=True, text=True, timeout=3600, cwd=ai_dir,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Training failed:\n{result.stderr[-500:]}")
|
||||
print(f"✅ [RETRAIN] Training done", flush=True)
|
||||
|
||||
# Step 3: Reload models in memory
|
||||
print("🔄 [RETRAIN] Step 3/3: Reloading models...", flush=True)
|
||||
try:
|
||||
orchestrator = get_single_match_orchestrator()
|
||||
v25 = orchestrator._get_v25_predictor()
|
||||
v25._loaded = False
|
||||
v25.load_models()
|
||||
print("✅ [RETRAIN] Models reloaded in memory", flush=True)
|
||||
except Exception as reload_err:
|
||||
print(f"⚠️ [RETRAIN] Hot reload failed (restart needed): {reload_err}", flush=True)
|
||||
|
||||
_retrain_state.update({
|
||||
"running": False,
|
||||
"last_completed": datetime.now().isoformat(),
|
||||
"last_status": "success",
|
||||
"last_error": None,
|
||||
})
|
||||
print("🎉 [RETRAIN] Pipeline complete!", flush=True)
|
||||
|
||||
except Exception as err:
|
||||
_retrain_state.update({
|
||||
"running": False,
|
||||
"last_completed": datetime.now().isoformat(),
|
||||
"last_status": "failed",
|
||||
"last_error": str(err),
|
||||
})
|
||||
print(f"❌ [RETRAIN] Pipeline failed: {err}", flush=True)
|
||||
|
||||
|
||||
@app.post("/v1/admin/retrain")
|
||||
async def admin_retrain(request: RetrainRequest) -> dict[str, Any]:
|
||||
"""Trigger full retrain pipeline: extract → train → reload."""
|
||||
if _retrain_state["running"]:
|
||||
return {
|
||||
"status": "already_running",
|
||||
"message": f"Retrain in progress since {_retrain_state['last_started']}",
|
||||
}
|
||||
|
||||
_retrain_state.update({
|
||||
"running": True,
|
||||
"last_started": datetime.now().isoformat(),
|
||||
"last_status": "running",
|
||||
"last_error": None,
|
||||
})
|
||||
|
||||
# Run in background thread
|
||||
import threading
|
||||
thread = threading.Thread(
|
||||
target=_run_retrain_pipeline,
|
||||
args=(request.markets, request.trials or 50),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
|
||||
return {
|
||||
"status": "triggered",
|
||||
"message": "Retrain pipeline started in background",
|
||||
"reason": request.reason,
|
||||
"markets": request.markets or "all",
|
||||
"trials": request.trials or 50,
|
||||
}
|
||||
|
||||
|
||||
@app.get("/v1/admin/retrain/status")
|
||||
async def admin_retrain_status() -> dict[str, Any]:
|
||||
"""Check retrain pipeline status."""
|
||||
return {**_retrain_state}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(os.getenv("PORT", "8000"))
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
|
||||
|
||||
Reference in New Issue
Block a user