-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.py
More file actions
105 lines (85 loc) · 4.28 KB
/
Copy pathapp.py
File metadata and controls
105 lines (85 loc) · 4.28 KB
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# app.py - Il Cuore del Nostro Servizio API
# Importiamo le librerie necessarie
import datetime
import logging
from flask import Flask, request, jsonify
import joblib
import numpy as np
# 1. Inizializzazione dell'applicazione Flask
app = Flask(__name__)
# Configurazione base del logging per tracciare richieste ed errori
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 2. Caricamento del modello
# Questa operazione viene eseguita UNA SOLA VOLTA all'avvio del server per massimizzare le performance.
try:
model = joblib.load('modello.pkl')
app.logger.info("Modello 'modello.pkl' caricato con successo.")
except FileNotFoundError:
app.logger.error("ERRORE: File 'modello.pkl' non trovato. Il servizio non potrà effettuare previsioni.")
model = None
# 3. Endpoint di benvenuto
# Utile per verificare rapidamente se il servizio è online e funzionante.
@app.route('/', methods=['GET'])
def welcome():
return "Benvenuto nella API di previsione Churn!"
# 3. Endpoint di Health Check
# Scopo: Fornire un modo semplice e leggero per i sistemi di monitoraggio
# per verificare che l'applicazione sia in esecuzione e responsiva.
# Non esegue logica complessa, deve solo rispondere velocemente.
@app.route('/health', methods=['GET'])
def health_check():
# Un timestamp è utile per confermare che non sia una risposta in cache.
timestamp = datetime.datetime.now().isoformat()
return jsonify({'status': 'ok', 'timestamp': timestamp})
# 4. Endpoint di previsione principale
# Accetta solo richieste di tipo POST contenenti dati in formato JSON.
@app.route('/predict', methods=['POST'])
def predict():
if model is None:
return jsonify({'errore': 'Modello non disponibile a causa di un errore di caricamento.'}), 500
app.logger.info("Richiesta di previsione ricevuta.")
try:
# 5. Ricezione dei dati JSON
data = request.get_json(force=True)
app.logger.info(f"Dati ricevuti: {data}")
# --- BLOCCO DI VALIDAZIONE PARANOICA ---
# Mai fidarsi dell'input esterno! Questo blocco è cruciale per la robustezza.
required_features = ['eta', 'sessioni_mensili', 'spesa_totale']
# Controllo presenza chiavi
if not all(feature in data for feature in required_features):
raise ValueError(f"Input JSON incompleto. Sono richieste le seguenti chiavi: {required_features}")
# Controllo tipo e valori
for feature in required_features:
if not isinstance(data[feature], (int, float)):
raise ValueError(f"La feature '{feature}' deve essere un valore numerico.")
if data[feature] < 0:
raise ValueError(f"Il valore per '{feature}' non può essere negativo.")
# 6. Preparazione delle feature per il modello
# Il modello scikit-learn si aspetta un array 2D (anche per una sola previsione).
features = np.array([[
data['eta'],
data['sessioni_mensili'],
data['spesa_totale']
]])
# 7. Esecuzione della previsione
prediction = model.predict(features)
# Convertiamo il risultato (spesso un array numpy) in un tipo nativo Python.
output = int(prediction[0])
app.logger.info(f"Predizione calcolata: {output}")
# 8. Restituzione della risposta in formato JSON
return jsonify({'predizione_churn': output})
except ValueError as e:
# Errore "atteso" dovuto a dati di input non validi (es. campo mancante).
app.logger.warning(f"Errore di validazione: {str(e)}")
# Restituiamo un codice di stato 400 (Bad Request).
return jsonify({'errore': str(e)}), 400
except Exception as e:
# Catch-all per qualsiasi altro errore imprevisto (es. problemi nel modello).
app.logger.error(f"Errore imprevisto durante la previsione: {e}", exc_info=True)
# Restituiamo un codice di stato 500 (Internal Server Error).
return jsonify({'errore': 'Si è verificato un errore interno del server.'}), 500
# 9. Avvio del server per lo sviluppo locale
# Questo blocco viene eseguito solo lanciando `python app.py`.
# Gunicorn o altri server WSGI non lo eseguono, ma importano l'oggetto `app`.
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)