Spaces:
Sleeping
Sleeping
Update server.py
Browse files
server.py
CHANGED
|
@@ -8,20 +8,19 @@ from pydantic import BaseModel
|
|
| 8 |
import traceback
|
| 9 |
import logging
|
| 10 |
import sys
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
# Setup logging
|
| 13 |
logging.basicConfig(
|
| 14 |
-
level=logging.
|
| 15 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 16 |
handlers=[logging.StreamHandler(sys.stdout)]
|
| 17 |
)
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
-
app = FastAPI(
|
| 21 |
-
title="Incassi API - Debug Mode",
|
| 22 |
-
description="API per debug del modello incassi",
|
| 23 |
-
version="debug-1.0"
|
| 24 |
-
)
|
| 25 |
|
| 26 |
app.add_middleware(
|
| 27 |
CORSMiddleware,
|
|
@@ -31,53 +30,35 @@ app.add_middleware(
|
|
| 31 |
allow_headers=["*"],
|
| 32 |
)
|
| 33 |
|
| 34 |
-
# Variabile globale per il modello
|
| 35 |
mdl = None
|
| 36 |
|
| 37 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
def load_model():
|
| 39 |
global mdl
|
| 40 |
try:
|
| 41 |
-
logger.info("π§
|
| 42 |
|
| 43 |
-
# Verifica esistenza file
|
| 44 |
import os
|
| 45 |
if not os.path.exists("incassi_model.pkl"):
|
| 46 |
-
raise FileNotFoundError("File
|
| 47 |
-
|
| 48 |
-
file_size = os.path.getsize("incassi_model.pkl")
|
| 49 |
-
logger.info(f"π File trovato - Dimensione: {file_size} bytes")
|
| 50 |
|
| 51 |
-
# Carica il modello
|
| 52 |
-
logger.info("π§ STEP 2: Caricamento pickle...")
|
| 53 |
with open("incassi_model.pkl", "rb") as f:
|
| 54 |
mdl = cp.load(f)
|
| 55 |
|
| 56 |
-
logger.info("
|
| 57 |
-
logger.info(f"π
|
| 58 |
-
logger.info(f"π Versione: {getattr(mdl, 'model_version', 'N/A')}")
|
| 59 |
|
| 60 |
-
# Test attributi base (SENZA chiamare predict)
|
| 61 |
-
logger.info("π§ STEP 4: Verifica attributi modello...")
|
| 62 |
-
logger.info(f"π p100_thr: {getattr(mdl, 'p100_thr', 'N/A')}")
|
| 63 |
-
logger.info(f"π feat_cols_full length: {len(getattr(mdl, 'feat_cols_full', []))}")
|
| 64 |
-
logger.info(f"π stage1 type: {type(getattr(mdl, 'stage1', None))}")
|
| 65 |
-
logger.info(f"π stage2 type: {type(getattr(mdl, 'stage2', None))}")
|
| 66 |
-
|
| 67 |
-
logger.info("β
STEP 5: Modello caricato con successo (SENZA test predict)")
|
| 68 |
return True
|
| 69 |
-
|
| 70 |
except Exception as e:
|
| 71 |
-
logger.error(f"β
|
| 72 |
-
logger.error(f"β Traceback completo:\n{traceback.format_exc()}")
|
| 73 |
return False
|
| 74 |
|
| 75 |
-
# Carica modello all'import
|
| 76 |
model_loaded = load_model()
|
| 77 |
-
if not model_loaded:
|
| 78 |
-
logger.error("β FATALE: Impossibile caricare il modello")
|
| 79 |
|
| 80 |
-
# ===== PYDANTIC MODELS SEMPLIFICATI =====
|
| 81 |
class PredictIn(BaseModel):
|
| 82 |
Debitore_cluster: str | None = None
|
| 83 |
Stato_Giudizio: str | None = None
|
|
@@ -95,9 +76,9 @@ class PredictIn(BaseModel):
|
|
| 95 |
giorni_da_cessione: int | None = None
|
| 96 |
Zona: str | None = None
|
| 97 |
|
| 98 |
-
def
|
| 99 |
-
"""Conversione
|
| 100 |
-
logger.info("π Conversione input
|
| 101 |
|
| 102 |
row = {
|
| 103 |
"Debitore_cluster": d.get("Debitore_cluster"),
|
|
@@ -118,111 +99,179 @@ def _to_model_format(d: dict) -> pd.DataFrame:
|
|
| 118 |
}
|
| 119 |
|
| 120 |
df = pd.DataFrame([row])
|
| 121 |
-
logger.info(f"β
DataFrame
|
| 122 |
return df
|
| 123 |
|
| 124 |
-
# ===== ENDPOINTS =====
|
| 125 |
@app.get("/")
|
| 126 |
def root():
|
| 127 |
return {
|
| 128 |
"ok": True,
|
| 129 |
-
"service": "incassi-
|
| 130 |
"model_loaded": model_loaded,
|
| 131 |
-
"model_version": getattr(mdl, "model_version", "N/A") if mdl else "N/A"
|
| 132 |
}
|
| 133 |
|
| 134 |
@app.get("/status")
|
| 135 |
def status():
|
| 136 |
-
"""Status dettagliato del sistema"""
|
| 137 |
if not mdl:
|
| 138 |
return {"error": "Modello non caricato"}
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
return {
|
| 142 |
-
"
|
| 143 |
-
"
|
| 144 |
-
"
|
| 145 |
-
"
|
| 146 |
-
"features_count": len(getattr(mdl, "feat_cols_full", [])),
|
| 147 |
-
"stage1_loaded": hasattr(mdl, "stage1") and mdl.stage1 is not None,
|
| 148 |
-
"stage2_loaded": hasattr(mdl, "stage2") and mdl.stage2 is not None,
|
| 149 |
}
|
|
|
|
| 150 |
except Exception as e:
|
| 151 |
-
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
@app.post("/
|
| 154 |
-
def
|
| 155 |
-
"""Test
|
| 156 |
try:
|
| 157 |
-
logger.info("π§ͺ Test
|
| 158 |
|
| 159 |
if not mdl:
|
| 160 |
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
df_ensured = mdl._ensure_raw_cols(df)
|
| 169 |
-
logger.info(f"β
_ensure_raw_cols riuscito: {df_ensured.shape}")
|
| 170 |
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
}
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
except Exception as e:
|
| 179 |
-
logger.error(f"β Errore
|
| 180 |
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 181 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 182 |
|
| 183 |
-
@app.post("/predict")
|
| 184 |
def predict(inp: PredictIn):
|
| 185 |
-
"""Predizione completa con
|
| 186 |
try:
|
| 187 |
-
logger.info("π PREDICT
|
| 188 |
|
| 189 |
if not mdl:
|
| 190 |
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 191 |
|
| 192 |
-
|
| 193 |
-
logger.info("π§ Step 1: Conversione input...")
|
| 194 |
-
df = _to_model_format(inp.dict())
|
| 195 |
|
| 196 |
-
#
|
| 197 |
-
|
| 198 |
-
|
| 199 |
result = mdl.predict(df)
|
| 200 |
-
logger.info("β
mdl.predict completato")
|
| 201 |
-
|
| 202 |
-
logger.error(f"β ERRORE SPECIFICO in mdl.predict: {predict_error}")
|
| 203 |
-
logger.error(f"β Tipo errore: {type(predict_error)}")
|
| 204 |
-
logger.error(f"β Traceback predict:\n{traceback.format_exc()}")
|
| 205 |
-
raise HTTPException(status_code=500, detail=f"Errore in predict: {predict_error}")
|
| 206 |
-
|
| 207 |
-
# Step 3: Formattazione output
|
| 208 |
-
logger.info("π§ Step 3: Formattazione output...")
|
| 209 |
-
p100, prob_ord, yhat, final_class, _ = result
|
| 210 |
-
|
| 211 |
-
response = {
|
| 212 |
-
"p100": float(p100),
|
| 213 |
-
"prob_ord": np.asarray(prob_ord, dtype=float).tolist(),
|
| 214 |
-
"yhat": float(yhat),
|
| 215 |
-
"final_class": str(final_class),
|
| 216 |
-
}
|
| 217 |
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
except HTTPException:
|
| 222 |
raise
|
| 223 |
except Exception as e:
|
| 224 |
-
logger.error(f"β ERRORE
|
| 225 |
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 226 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 227 |
|
| 228 |
-
logger.info("π Server inizializzato
|
|
|
|
| 8 |
import traceback
|
| 9 |
import logging
|
| 10 |
import sys
|
| 11 |
+
import signal
|
| 12 |
+
import threading
|
| 13 |
+
import time
|
| 14 |
|
| 15 |
+
# Setup logging ultra-dettagliato
|
| 16 |
logging.basicConfig(
|
| 17 |
+
level=logging.DEBUG,
|
| 18 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 19 |
handlers=[logging.StreamHandler(sys.stdout)]
|
| 20 |
)
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
| 23 |
+
app = FastAPI(title="Incassi API - Super Debug", version="debug-2.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
app.add_middleware(
|
| 26 |
CORSMiddleware,
|
|
|
|
| 30 |
allow_headers=["*"],
|
| 31 |
)
|
| 32 |
|
|
|
|
| 33 |
mdl = None
|
| 34 |
|
| 35 |
+
# Funzione di timeout per debug
|
| 36 |
+
def timeout_handler(signum, frame):
|
| 37 |
+
logger.error("β° TIMEOUT! Operazione bloccata")
|
| 38 |
+
raise TimeoutError("Operazione bloccata oltre il timeout")
|
| 39 |
+
|
| 40 |
def load_model():
|
| 41 |
global mdl
|
| 42 |
try:
|
| 43 |
+
logger.info("π§ Caricamento modello...")
|
| 44 |
|
|
|
|
| 45 |
import os
|
| 46 |
if not os.path.exists("incassi_model.pkl"):
|
| 47 |
+
raise FileNotFoundError("File non trovato")
|
|
|
|
|
|
|
|
|
|
| 48 |
|
|
|
|
|
|
|
| 49 |
with open("incassi_model.pkl", "rb") as f:
|
| 50 |
mdl = cp.load(f)
|
| 51 |
|
| 52 |
+
logger.info(f"β
Modello caricato: {type(mdl)}")
|
| 53 |
+
logger.info(f"π Attributi: {dir(mdl)[:10]}...") # primi 10 attributi
|
|
|
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
return True
|
|
|
|
| 56 |
except Exception as e:
|
| 57 |
+
logger.error(f"β Errore caricamento: {e}")
|
|
|
|
| 58 |
return False
|
| 59 |
|
|
|
|
| 60 |
model_loaded = load_model()
|
|
|
|
|
|
|
| 61 |
|
|
|
|
| 62 |
class PredictIn(BaseModel):
|
| 63 |
Debitore_cluster: str | None = None
|
| 64 |
Stato_Giudizio: str | None = None
|
|
|
|
| 76 |
giorni_da_cessione: int | None = None
|
| 77 |
Zona: str | None = None
|
| 78 |
|
| 79 |
+
def safe_to_model_format(d: dict) -> pd.DataFrame:
|
| 80 |
+
"""Conversione sicura con timeout"""
|
| 81 |
+
logger.info("π Conversione input...")
|
| 82 |
|
| 83 |
row = {
|
| 84 |
"Debitore_cluster": d.get("Debitore_cluster"),
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
df = pd.DataFrame([row])
|
| 102 |
+
logger.info(f"β
DataFrame: {df.shape}, cols: {list(df.columns)[:5]}...")
|
| 103 |
return df
|
| 104 |
|
|
|
|
| 105 |
@app.get("/")
|
| 106 |
def root():
|
| 107 |
return {
|
| 108 |
"ok": True,
|
| 109 |
+
"service": "incassi-super-debug",
|
| 110 |
"model_loaded": model_loaded,
|
|
|
|
| 111 |
}
|
| 112 |
|
| 113 |
@app.get("/status")
|
| 114 |
def status():
|
|
|
|
| 115 |
if not mdl:
|
| 116 |
return {"error": "Modello non caricato"}
|
| 117 |
|
| 118 |
+
return {
|
| 119 |
+
"model_loaded": True,
|
| 120 |
+
"model_type": str(type(mdl)),
|
| 121 |
+
"has_predict": hasattr(mdl, "predict"),
|
| 122 |
+
"has_stage1": hasattr(mdl, "stage1"),
|
| 123 |
+
"has_stage2": hasattr(mdl, "stage2"),
|
| 124 |
+
"methods": [m for m in dir(mdl) if not m.startswith('_')][:10]
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
@app.post("/test_conversion")
|
| 128 |
+
def test_conversion(inp: PredictIn):
|
| 129 |
+
"""Test solo conversione dati"""
|
| 130 |
try:
|
| 131 |
+
logger.info("π§ͺ Test conversione...")
|
| 132 |
+
|
| 133 |
+
if not mdl:
|
| 134 |
+
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 135 |
+
|
| 136 |
+
# Solo conversione
|
| 137 |
+
df = safe_to_model_format(inp.dict())
|
| 138 |
+
logger.info("β
Conversione OK")
|
| 139 |
+
|
| 140 |
return {
|
| 141 |
+
"status": "conversion_ok",
|
| 142 |
+
"shape": df.shape,
|
| 143 |
+
"columns_sample": list(df.columns)[:10],
|
| 144 |
+
"first_row_sample": {k: str(v)[:50] for k, v in df.iloc[0].to_dict().items() if k in list(df.columns)[:5]}
|
|
|
|
|
|
|
|
|
|
| 145 |
}
|
| 146 |
+
|
| 147 |
except Exception as e:
|
| 148 |
+
logger.error(f"β Errore conversione: {e}")
|
| 149 |
+
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 150 |
+
raise HTTPException(status_code=500, detail=f"Errore conversione: {str(e)}")
|
| 151 |
|
| 152 |
+
@app.post("/test_ensure_cols")
|
| 153 |
+
def test_ensure_cols(inp: PredictIn):
|
| 154 |
+
"""Test solo _ensure_raw_cols"""
|
| 155 |
try:
|
| 156 |
+
logger.info("π§ͺ Test ensure_raw_cols...")
|
| 157 |
|
| 158 |
if not mdl:
|
| 159 |
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 160 |
|
| 161 |
+
df = safe_to_model_format(inp.dict())
|
| 162 |
+
logger.info("π§ Chiamata _ensure_raw_cols...")
|
| 163 |
+
|
| 164 |
+
# Test con timeout di 10 secondi
|
| 165 |
+
def run_ensure():
|
| 166 |
+
return mdl._ensure_raw_cols(df)
|
| 167 |
+
|
| 168 |
+
# Esegui in thread separato per timeout
|
| 169 |
+
import concurrent.futures
|
| 170 |
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 171 |
+
future = executor.submit(run_ensure)
|
| 172 |
+
try:
|
| 173 |
+
df_ensured = future.result(timeout=10)
|
| 174 |
+
logger.info("β
_ensure_raw_cols OK")
|
| 175 |
+
return {
|
| 176 |
+
"status": "ensure_cols_ok",
|
| 177 |
+
"original_shape": df.shape,
|
| 178 |
+
"ensured_shape": df_ensured.shape
|
| 179 |
+
}
|
| 180 |
+
except concurrent.futures.TimeoutError:
|
| 181 |
+
logger.error("β° _ensure_raw_cols TIMEOUT")
|
| 182 |
+
raise HTTPException(status_code=408, detail="_ensure_raw_cols timeout")
|
| 183 |
|
| 184 |
+
except HTTPException:
|
| 185 |
+
raise
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logger.error(f"β Errore ensure_cols: {e}")
|
| 188 |
+
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 189 |
+
raise HTTPException(status_code=500, detail=f"Errore ensure_cols: {str(e)}")
|
| 190 |
+
|
| 191 |
+
@app.post("/test_preprocess")
|
| 192 |
+
def test_preprocess(inp: PredictIn):
|
| 193 |
+
"""Test solo _preprocess_apply"""
|
| 194 |
+
try:
|
| 195 |
+
logger.info("π§ͺ Test preprocess...")
|
| 196 |
+
|
| 197 |
+
if not mdl:
|
| 198 |
+
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 199 |
+
|
| 200 |
+
df = safe_to_model_format(inp.dict())
|
| 201 |
df_ensured = mdl._ensure_raw_cols(df)
|
|
|
|
| 202 |
|
| 203 |
+
logger.info("π§ Chiamata _preprocess_apply...")
|
| 204 |
+
|
| 205 |
+
# Test con timeout
|
| 206 |
+
def run_preprocess():
|
| 207 |
+
return mdl._preprocess_apply(df_ensured)
|
|
|
|
| 208 |
|
| 209 |
+
import concurrent.futures
|
| 210 |
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 211 |
+
future = executor.submit(run_preprocess)
|
| 212 |
+
try:
|
| 213 |
+
df_processed = future.result(timeout=15)
|
| 214 |
+
logger.info("β
_preprocess_apply OK")
|
| 215 |
+
return {
|
| 216 |
+
"status": "preprocess_ok",
|
| 217 |
+
"processed_shape": df_processed.shape,
|
| 218 |
+
"processed_columns_count": len(df_processed.columns)
|
| 219 |
+
}
|
| 220 |
+
except concurrent.futures.TimeoutError:
|
| 221 |
+
logger.error("β° _preprocess_apply TIMEOUT")
|
| 222 |
+
raise HTTPException(status_code=408, detail="_preprocess_apply timeout")
|
| 223 |
+
|
| 224 |
+
except HTTPException:
|
| 225 |
+
raise
|
| 226 |
except Exception as e:
|
| 227 |
+
logger.error(f"β Errore preprocess: {e}")
|
| 228 |
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 229 |
+
raise HTTPException(status_code=500, detail=f"Errore preprocess: {str(e)}")
|
| 230 |
|
| 231 |
+
@app.post("/predict")
|
| 232 |
def predict(inp: PredictIn):
|
| 233 |
+
"""Predizione completa con timeout estremo"""
|
| 234 |
try:
|
| 235 |
+
logger.info("π PREDICT con timeout...")
|
| 236 |
|
| 237 |
if not mdl:
|
| 238 |
raise HTTPException(status_code=503, detail="Modello non caricato")
|
| 239 |
|
| 240 |
+
df = safe_to_model_format(inp.dict())
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
# Predict con timeout di 30 secondi
|
| 243 |
+
def run_predict():
|
| 244 |
+
logger.info("π§ Esecuzione mdl.predict()...")
|
| 245 |
result = mdl.predict(df)
|
| 246 |
+
logger.info("β
mdl.predict() completato")
|
| 247 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
+
import concurrent.futures
|
| 250 |
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
| 251 |
+
future = executor.submit(run_predict)
|
| 252 |
+
try:
|
| 253 |
+
result = future.result(timeout=30)
|
| 254 |
+
|
| 255 |
+
p100, prob_ord, yhat, final_class, _ = result
|
| 256 |
+
response = {
|
| 257 |
+
"p100": float(p100),
|
| 258 |
+
"prob_ord": np.asarray(prob_ord, dtype=float).tolist(),
|
| 259 |
+
"yhat": float(yhat),
|
| 260 |
+
"final_class": str(final_class),
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
logger.info(f"β
Predizione completata: {response['yhat']:.2f}")
|
| 264 |
+
return response
|
| 265 |
+
|
| 266 |
+
except concurrent.futures.TimeoutError:
|
| 267 |
+
logger.error("β° PREDICT TIMEOUT dopo 30 secondi")
|
| 268 |
+
raise HTTPException(status_code=408, detail="Predizione timeout - modello bloccato")
|
| 269 |
|
| 270 |
except HTTPException:
|
| 271 |
raise
|
| 272 |
except Exception as e:
|
| 273 |
+
logger.error(f"β ERRORE PREDICT: {e}")
|
| 274 |
logger.error(f"β Traceback:\n{traceback.format_exc()}")
|
| 275 |
+
raise HTTPException(status_code=500, detail=f"Errore predict: {str(e)}")
|
| 276 |
|
| 277 |
+
logger.info("π Super Debug Server inizializzato")
|