Spaces:
Sleeping
Sleeping
import time, logging, json, traceback | |
from typing import Optional, Dict, Any | |
from fastapi import FastAPI, HTTPException | |
from fastapi.middleware.cors import CORSMiddleware | |
from pydantic import BaseModel, Field | |
from model_pipeline import Predictor, FEATURE_MAP | |
logging.basicConfig( | |
level=logging.INFO, | |
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s" | |
) | |
log = logging.getLogger("api") | |
# ----------- input model ----------- | |
class PredictIn(BaseModel): | |
include_neg: bool = False | |
Debitore_cluster: Optional[str] = None | |
Stato_Giudizio: Optional[str] = None | |
Cedente: Optional[str] = None | |
# alias con spazi/punti | |
Importo_iniziale_outstanding: Optional[float] = Field(None, alias="Importo iniziale outstanding") | |
Decreto_sospeso: Optional[str] = Field(None, alias="Decreto sospeso") | |
Notifica_Decreto: Optional[str] = Field(None, alias="Notifica Decreto") | |
Opposizione_al_decreto_ingiuntivo: Optional[str] = Field(None, alias="Opposizione al decreto ingiuntivo") | |
Ricorso_al_TAR: Optional[str] = Field(None, alias="Ricorso al TAR") | |
Sentenza_TAR: Optional[str] = Field(None, alias="Sentenza TAR") | |
Atto_di_Precetto: Optional[str] = Field(None, alias="Atto di Precetto") | |
Decreto_Ingiuntivo: Optional[str] = Field(None, alias="Decreto Ingiuntivo") | |
Sentenza_giudizio_opposizione: Optional[str] = Field(None, alias="Sentenza giudizio opposizione") | |
giorni_da_iscrizione: Optional[int] = None | |
giorni_da_cessione: Optional[int] = None | |
Zona: Optional[str] = None | |
model_config = {"populate_by_name": True, "extra": "allow"} | |
# ----------- app ----------- | |
app = FastAPI(title="Predizione+SHAP API", version="1.0.0") | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["*"], allow_methods=["*"], allow_headers=["*"] | |
) | |
t0 = time.time() | |
predictor: Predictor | None = None | |
def _load_model(): | |
global predictor | |
predictor = Predictor() | |
log.info(f"Model loaded in {predictor.load_seconds:.2f}s") | |
def health(): | |
return {"ok": predictor is not None, "uptime_s": time.time()-t0} | |
def predict(inp: PredictIn): | |
if predictor is None: | |
raise HTTPException(503, "Model not ready") | |
# ricomponi payload secondo i nomi originali delle feature | |
payload: Dict[str, Any] = {} | |
for k in FEATURE_MAP.values(): | |
ak = k.replace(" ", "_").replace(".", "_") | |
payload[k] = getattr(inp, ak, None) | |
payload["include_neg"] = inp.include_neg | |
try: | |
out = predictor.predict_dict(payload, include_neg=inp.include_neg) | |
# assicura chiave 'class' (nessuna alias confusion) | |
if "class_" in out and "class" not in out: | |
out["class"] = out.pop("class_") | |
log.info(json.dumps({ | |
"event":"predict_ok", | |
"class": out.get("class"), | |
"stage": out.get("stage_used"), | |
"p100": round(out.get("p100", 0.0), 4) | |
})) | |
return out | |
except Exception as e: | |
log.exception("predict_error") | |
raise HTTPException(500, f"Prediction error: {e}") from e | |