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 | |