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