Spaces:
Sleeping
Sleeping
File size: 3,115 Bytes
ff7bcc1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
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
@app.on_event("startup")
def _load_model():
global predictor
predictor = Predictor()
log.info(f"Model loaded in {predictor.load_seconds:.2f}s")
@app.get("/health")
def health():
return {"ok": predictor is not None, "uptime_s": time.time()-t0}
@app.post("/predict")
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
|