|
|
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
import pandas as pd
|
|
|
import os
|
|
|
from fastapi.responses import JSONResponse
|
|
|
from fastapi import Depends, FastAPI, HTTPException
|
|
|
from fastapi.security.api_key import APIKeyHeader
|
|
|
from fastapi import Security
|
|
|
from fastapi.responses import JSONResponse
|
|
|
from dotenv import load_dotenv
|
|
|
from src.api.load import USE_MODEL
|
|
|
|
|
|
|
|
|
load_dotenv()
|
|
|
|
|
|
model = USE_MODEL()
|
|
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
API_KEY = os.getenv("API_KEY")
|
|
|
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
|
|
|
|
|
|
async def get_api_key(api_key: str = Security(api_key_header)):
|
|
|
"""Validar API Key"""
|
|
|
if api_key != API_KEY:
|
|
|
raise HTTPException(
|
|
|
status_code=401,
|
|
|
detail="API Key inválida o faltante"
|
|
|
)
|
|
|
return api_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def convert_to_native(val):
|
|
|
"""Convierte tipos NumPy/Pandas a tipos nativos de Python"""
|
|
|
if isinstance(val, (np.integer, np.int64, np.int32, np.int16, np.int8)):
|
|
|
return int(val)
|
|
|
elif isinstance(val, (np.floating, np.float64, np.float32, np.float16)):
|
|
|
return float(val)
|
|
|
elif isinstance(val, np.ndarray):
|
|
|
return [convert_to_native(item) for item in val.tolist()]
|
|
|
elif isinstance(val, dict):
|
|
|
return {key: convert_to_native(value) for key, value in val.items()}
|
|
|
elif isinstance(val, (list, tuple)):
|
|
|
return [convert_to_native(item) for item in val]
|
|
|
elif isinstance(val, pd.Series):
|
|
|
return convert_to_native(val.to_dict())
|
|
|
elif isinstance(val, pd.DataFrame):
|
|
|
return convert_to_native(val.to_dict(orient='records'))
|
|
|
elif pd.isna(val):
|
|
|
return None
|
|
|
else:
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/")
|
|
|
def read_root():
|
|
|
"""Endpoint raíz con información de la API"""
|
|
|
return {
|
|
|
"api": "Corners Prediction API",
|
|
|
"version": "1.0.0",
|
|
|
"status": "active",
|
|
|
"endpoints": {
|
|
|
"/": "Información de la API",
|
|
|
"/items/": "Predicción de corners (requiere API Key)",
|
|
|
"/health": "Estado de salud"
|
|
|
},
|
|
|
"auth": "Requiere header: X-API-Key"
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/items/")
|
|
|
def predict_corners(
|
|
|
local: str,
|
|
|
visitante: str,
|
|
|
jornada: int,
|
|
|
league_code: str,
|
|
|
temporada: str = "2526",
|
|
|
api_key: str = Depends(get_api_key)
|
|
|
):
|
|
|
"""
|
|
|
Predecir corners para un partido de fútbol
|
|
|
|
|
|
Args:
|
|
|
local: Nombre del equipo local (requerido)
|
|
|
visitante: Nombre del equipo visitante (requerido)
|
|
|
jornada: Número de jornada (requerido, min: 1)
|
|
|
league_code: Código de liga (requerido: ESP, GER, FRA, ITA, ENG, NED, POR, BEL)
|
|
|
temporada: Temporada en formato AABB (default: "2526")
|
|
|
|
|
|
Returns:
|
|
|
JSON con predicción y análisis completo
|
|
|
|
|
|
Example:
|
|
|
GET /items/?local=Barcelona&visitante=Real%20Madrid&jornada=15&league_code=ESP&temporada=2526
|
|
|
Headers: X-API-Key: tu-clave-secreta-aqui
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not local or not visitante:
|
|
|
raise HTTPException(
|
|
|
status_code=400,
|
|
|
detail="Los parámetros 'local' y 'visitante' son obligatorios"
|
|
|
)
|
|
|
|
|
|
|
|
|
if jornada < 1:
|
|
|
raise HTTPException(
|
|
|
status_code=400,
|
|
|
detail="La jornada debe ser mayor o igual a 1"
|
|
|
)
|
|
|
|
|
|
|
|
|
valid_leagues = ["ESP", "GER", "FRA", "ITA", "ENG", "NED", "POR", "BEL"]
|
|
|
if league_code not in valid_leagues:
|
|
|
raise HTTPException(
|
|
|
status_code=400,
|
|
|
detail=f"Liga inválida. Ligas válidas: {', '.join(valid_leagues)}"
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
resultado = model.consume_model_single(
|
|
|
local=local,
|
|
|
visitante=visitante,
|
|
|
jornada=jornada,
|
|
|
temporada=temporada,
|
|
|
league_code=league_code
|
|
|
)
|
|
|
|
|
|
|
|
|
if resultado.get("error"):
|
|
|
raise HTTPException(
|
|
|
status_code=422,
|
|
|
detail=f"Error en predicción: {resultado['error']}"
|
|
|
)
|
|
|
|
|
|
|
|
|
resultado_limpio = convert_to_native(resultado)
|
|
|
|
|
|
|
|
|
resultado_limpio["metadata"] = {
|
|
|
"api_version": "1.0.0",
|
|
|
"model_version": "v4",
|
|
|
"timestamp": pd.Timestamp.now().isoformat()
|
|
|
}
|
|
|
|
|
|
return JSONResponse(
|
|
|
status_code=200,
|
|
|
content=resultado_limpio
|
|
|
)
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
import traceback
|
|
|
error_detail = {
|
|
|
"error": str(e),
|
|
|
"type": type(e).__name__,
|
|
|
"traceback": traceback.format_exc() if app.debug else None
|
|
|
}
|
|
|
|
|
|
return JSONResponse(
|
|
|
status_code=500,
|
|
|
content=error_detail
|
|
|
) |