beta3's picture
Upload Files
4d18cf9 verified
"""
backend/main.py
─────────────────────────────────────────────
FastAPI application β€” Rift Breakdown Backend
Carga los 3 modelos ML al startup y expone endpoints
para predicciΓ³n de win probability minuto-a-minuto.
─────────────────────────────────────────────
"""
import logging
import os
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from backend.routers import predictions
from backend.services.model_service import model_service
# ─────────────────────────────────────────────
# Logging
# ─────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
)
logger = logging.getLogger(__name__)
# ─────────────────────────────────────────────
# Lifespan (startup / shutdown)
# ─────────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Load models once on startup, release on shutdown."""
logger.info("πŸš€ Starting Rift Breakdown backend…")
try:
model_service.load_models()
logger.info("βœ… All models loaded β€” server ready.")
except Exception as e:
logger.error("❌ Model loading failed: %s", e, exc_info=True)
# Server starts anyway but /predict will return 503
yield
logger.info("πŸ›‘ Shutting down Rift Breakdown backend.")
# ─────────────────────────────────────────────
# App
# ─────────────────────────────────────────────
app = FastAPI(
title="Rift Breakdown β€” LoL Win Probability Predictor",
version="0.2.0",
description="Post-match analysis with 3 ML models (XGBoost, LSTM, LogReg)",
lifespan=lifespan,
# Docs bajo /api para no colisionar con React SPA
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json",
)
# ─────────────────────────────────────────────
# CORS (Secured for Production)
# ─────────────────────────────────────────────
allowed_origins_env = os.environ.get(
"ALLOWED_ORIGINS",
"http://localhost:5173,http://127.0.0.1:5173,http://localhost:3000,https://huggingface.co"
)
origins = [origin.strip() for origin in allowed_origins_env.split(",") if origin.strip()]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "Accept"],
)
# ─────────────────────────────────────────────
# Routers
# ─────────────────────────────────────────────
app.include_router(predictions.router, prefix="/api/v1", tags=["predictions"])
# ─────────────────────────────────────────────
# Health check
# ─────────────────────────────────────────────
@app.get("/api/health", tags=["health"])
def health_check():
return {
"status": "ok",
"version": "0.2.0",
"models_loaded": model_service.is_loaded,
}
DIST = Path(__file__).parent.parent / "frontend" / "dist"
if DIST.exists():
# Archivos estΓ‘ticos (JS, CSS, imΓ‘genes)
app.mount("/assets", StaticFiles(directory=DIST / "assets"), name="assets")
@app.get("/{full_path:path}", include_in_schema=False)
def serve_spa(full_path: str):
"""Catch-all: cualquier ruta no-API sirve el index.html de React."""
return FileResponse(DIST / "index.html")