Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -60,6 +60,76 @@ def _load_model():
|
|
| 60 |
def health():
|
| 61 |
return {"ok": predictor is not None, "uptime_s": time.time()-t0}
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
@app.post("/predict")
|
| 64 |
def predict(inp: PredictIn):
|
| 65 |
if predictor is None:
|
|
@@ -89,3 +159,34 @@ def predict(inp: PredictIn):
|
|
| 89 |
except Exception as e:
|
| 90 |
log.exception("predict_error")
|
| 91 |
raise HTTPException(500, f"Prediction error: {e}") from e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
def health():
|
| 61 |
return {"ok": predictor is not None, "uptime_s": time.time()-t0}
|
| 62 |
|
| 63 |
+
# Ordine delle classi (stesso usato dal modello)
|
| 64 |
+
_CLASS_ORDER = LABELS + ["100%"]
|
| 65 |
+
_CLASS_TO_IDX = {c: i for i, c in enumerate(_CLASS_ORDER)}
|
| 66 |
+
|
| 67 |
+
def _payload_from_inp(inp) -> dict:
|
| 68 |
+
"""Ricostruisce un dict 'payload' a partire dall'input pydantic."""
|
| 69 |
+
payload = {}
|
| 70 |
+
for k in FEATURE_MAP.values():
|
| 71 |
+
ak = k.replace(" ", "_").replace(".", "_")
|
| 72 |
+
payload[k] = getattr(inp, ak, None)
|
| 73 |
+
return payload
|
| 74 |
+
|
| 75 |
+
def _moving_average(y: np.ndarray, window: int = 9):
|
| 76 |
+
"""Applica una media mobile semplice per smoothing."""
|
| 77 |
+
w = int(window)
|
| 78 |
+
if w < 1:
|
| 79 |
+
return y
|
| 80 |
+
if w % 2 == 0:
|
| 81 |
+
w += 1
|
| 82 |
+
if w > len(y):
|
| 83 |
+
w = max(1, len(y)//2*2+1)
|
| 84 |
+
kernel = np.ones(w) / w
|
| 85 |
+
return np.convolve(y, kernel, mode="same")
|
| 86 |
+
|
| 87 |
+
def _class_curve_png(predictor, base_payload: dict, var_name: str,
|
| 88 |
+
vmin: int = 0, vmax: int = 3000,
|
| 89 |
+
n_base: int = 80, # punti reali (inferenze)
|
| 90 |
+
n_dense: int = 400, # punti interpolati
|
| 91 |
+
ma_window: int = 9,
|
| 92 |
+
title: str = "") -> bytes:
|
| 93 |
+
xs_base = np.linspace(vmin, vmax, n_base).round().astype(int)
|
| 94 |
+
xs_base = np.clip(xs_base, vmin, vmax)
|
| 95 |
+
xs_base = np.unique(xs_base)
|
| 96 |
+
|
| 97 |
+
# classe → indice
|
| 98 |
+
y_base = []
|
| 99 |
+
for v in xs_base:
|
| 100 |
+
p = dict(base_payload)
|
| 101 |
+
p[var_name] = int(v)
|
| 102 |
+
out = predictor.predict_class_fast(p)
|
| 103 |
+
y_base.append(_CLASS_TO_IDX[out["class"]])
|
| 104 |
+
y_base = np.array(y_base, dtype=float)
|
| 105 |
+
|
| 106 |
+
# interpolazione
|
| 107 |
+
xs_dense = np.linspace(vmin, vmax, n_dense)
|
| 108 |
+
y_dense = np.interp(xs_dense, xs_base, y_base)
|
| 109 |
+
|
| 110 |
+
# smoothing
|
| 111 |
+
y_smooth = _moving_average(y_dense, ma_window)
|
| 112 |
+
y_smooth = np.clip(y_smooth, 0, len(_CLASS_ORDER)-1)
|
| 113 |
+
|
| 114 |
+
# plot
|
| 115 |
+
fig, ax = plt.subplots(figsize=(9, 4))
|
| 116 |
+
ax.plot(xs_dense, y_smooth, linewidth=2)
|
| 117 |
+
ax.set_xlim(vmin, vmax)
|
| 118 |
+
ax.set_ylim(-0.2, len(_CLASS_ORDER)-1 + 0.2)
|
| 119 |
+
ax.set_yticks(range(len(_CLASS_ORDER)))
|
| 120 |
+
ax.set_yticklabels(_CLASS_ORDER)
|
| 121 |
+
ax.set_xlabel(var_name)
|
| 122 |
+
ax.set_ylabel("Classe (smooth)")
|
| 123 |
+
ax.set_title(title or f"Classe (smooth) vs {var_name}")
|
| 124 |
+
ax.grid(True, linestyle="--", alpha=0.35)
|
| 125 |
+
fig.tight_layout()
|
| 126 |
+
|
| 127 |
+
buf = io.BytesIO()
|
| 128 |
+
fig.savefig(buf, format="png", dpi=150, bbox_inches="tight")
|
| 129 |
+
plt.close(fig)
|
| 130 |
+
return buf.getvalue()
|
| 131 |
+
|
| 132 |
+
|
| 133 |
@app.post("/predict")
|
| 134 |
def predict(inp: PredictIn):
|
| 135 |
if predictor is None:
|
|
|
|
| 159 |
except Exception as e:
|
| 160 |
log.exception("predict_error")
|
| 161 |
raise HTTPException(500, f"Prediction error: {e}") from e
|
| 162 |
+
|
| 163 |
+
@app.post("/plot/curve-class-cessione.png")
|
| 164 |
+
def plot_curve_class_cessione(inp: PredictIn,
|
| 165 |
+
vmin: int = 0, vmax: int = 3000,
|
| 166 |
+
n_base: int = 80, n_dense: int = 400, ma_window: int = 9):
|
| 167 |
+
if predictor is None:
|
| 168 |
+
raise HTTPException(503, "Model not ready")
|
| 169 |
+
base_payload = _payload_from_inp(inp)
|
| 170 |
+
img = _class_curve_png(
|
| 171 |
+
predictor, base_payload,
|
| 172 |
+
var_name="giorni_da_cessione",
|
| 173 |
+
vmin=vmin, vmax=vmax, n_base=n_base, n_dense=n_dense, ma_window=ma_window,
|
| 174 |
+
title="Classe predetta vs Giorni da Cessione"
|
| 175 |
+
)
|
| 176 |
+
return Response(content=img, media_type="image/png")
|
| 177 |
+
|
| 178 |
+
@app.post("/plot/curve-class-iscrizione.png")
|
| 179 |
+
def plot_curve_class_iscrizione(inp: PredictIn,
|
| 180 |
+
vmin: int = 0, vmax: int = 3000,
|
| 181 |
+
n_base: int = 80, n_dense: int = 400, ma_window: int = 9):
|
| 182 |
+
if predictor is None:
|
| 183 |
+
raise HTTPException(503, "Model not ready")
|
| 184 |
+
base_payload = _payload_from_inp(inp)
|
| 185 |
+
img = _class_curve_png(
|
| 186 |
+
predictor, base_payload,
|
| 187 |
+
var_name="giorni_da_iscrizione",
|
| 188 |
+
vmin=vmin, vmax=vmax, n_base=n_base, n_dense=n_dense, ma_window=ma_window,
|
| 189 |
+
title="Classe predetta (smooth) vs Giorni da Iscrizione"
|
| 190 |
+
)
|
| 191 |
+
return Response(content=img, media_type="image/png")
|
| 192 |
+
|