REFACTOR: Split code and dynamic models
All checks were successful
Build docker container / Build image (push) Successful in 11m15s

This commit is contained in:
2025-12-21 13:06:10 +01:00
parent ff51af276d
commit c25ec806a5
15 changed files with 827 additions and 108 deletions

View File

@@ -1,94 +1,182 @@
from flask import Flask, jsonify, request
import joblib
import os
from datetime import date
from db import fetch_data
import threading
from model_registry import register, load_meta
from models_train import train_demand_model, train_nulo_model
from datetime import datetime
from core.model_loader import ModelManager
from core.predictor import PredictionHandler
from core.model_trainer import ModelTrainer
from config.models_config import ModelConfig
app = Flask(__name__)
MODEL_DIR = "models"
def load_model(model_type):
meta = load_meta()
file = meta["current"].get(model_type)
if not file:
return None
return joblib.load(os.path.join(MODEL_DIR, file))
# Inicializar componentes
model_config = ModelConfig()
model_manager = ModelManager(model_config)
prediction_handler = PredictionHandler(model_manager, model_config)
model_trainer = ModelTrainer(model_manager, model_config)
def background_train():
print("Fetching data...")
df = fetch_data()
print(f"Data fetched: {len(df)} rows")
# Comprobar que las funciones estan disponibles en el código
@app.before_first_request
def check_functions():
"""Verificar que todas las funciones existen"""
from utils.dynamic_loader import execute_function
today = date.today().isoformat()
os.makedirs(MODEL_DIR, exist_ok=True)
for model_type in model_config.get_all_model_types():
config = model_config.get_model_config(model_type)
print(f"Verificando modelo: {model_type}")
print(f" Módulo: {config.get('module')}")
print(f" Función entrenamiento: {config.get('train_function')}")
print(f" Función datos: {config.get('data_function')}")
# Verificar que la función de entrenamiento existe
try:
module = importlib.import_module(config.get('module'))
if hasattr(module, config.get('train_function')):
print(f" ✓ Función de entrenamiento encontrada")
else:
print(f" ✗ Función de entrenamiento NO encontrada")
except Exception as e:
print(f" ✗ Error: {str(e)}")
print("Training demand model...")
demand_model = train_demand_model(df)
demand_file = f"demand_xgb_{today}.joblib"
joblib.dump(demand_model, f"{MODEL_DIR}/{demand_file}")
register("demand", demand_file, len(df))
print("Demand model trained and saved.")
print("Training nulo model...")
nulo_model = train_nulo_model(df)
nulo_file = f"nulo_xgb_{today}.joblib"
joblib.dump(nulo_model, f"{MODEL_DIR}/{nulo_file}")
register("nulo", nulo_file, len(df))
print("Nulo model trained and saved.")
# Inicializar modelos al arrancar
model_manager.init_models()
@app.route("/train", methods=["POST"])
def train():
threading.Thread(target=background_train).start()
return jsonify({"status": "training started"})
@app.route("/predict_demand", methods=["GET"])
def predict_demand():
h3 = int(request.args["h3"])
week = int(request.args["week"])
dow = int(request.args["dow"])
hour = int(request.args["hour"])
model = load_model("demand")
X = [[h3, week, dow, hour]]
pred = model.predict(X)[0]
return jsonify({"expected_demand": float(pred)})
@app.route("/predict_nulo", methods=["GET"])
def predict_nulo():
h3 = int(request.args["h3"])
week = int(request.args["week"])
dow = int(request.args["dow"])
hour = int(request.args["hour"])
model = load_model("nulo")
X = [[h3, week, dow, hour]]
prob = model.predict_proba(X)[0][1]
return jsonify({"nulo_probability": float(prob)})
@app.route("/predict", methods=["GET"])
def predict_legacy():
h3 = int(request.args["h3"])
week = int(request.args["week"])
dow = int(request.args["dow"])
hour = int(request.args["hour"])
model = load_model("nulo")
X = [[h3, week, dow, hour]]
prob = model.predict_proba(X)[0][1]
"""Entrenar modelos"""
model_type = request.args.get('model_type')
# Validar que el tipo de modelo existe
if model_type and not model_config.model_exists(model_type):
return jsonify({
"error": f"Model type '{model_type}' not found",
"available_models": model_config.get_all_model_types()
}), 400
threading.Thread(target=model_trainer.background_train, args=(model_type,)).start()
return jsonify({
"predicted_nulo_prob": float(prob)
"status": "training started",
"model_type": model_type or "all",
"timestamp": datetime.now().isoformat()
})
@app.route("/predict", methods=["GET"])
def predict():
"""Endpoint único para predicciones"""
return prediction_handler.handle_predict_request(request.args)
@app.route("/demand", methods=["GET"])
def demand():
"""Endpoint para obtener todas las predicciones"""
return prediction_handler.handle_demand_request(request.args)
@app.route("/models/register", methods=["POST"])
def register_new_model():
"""Registrar un nuevo tipo de modelo dinámicamente"""
data = request.get_json()
try:
new_config = model_config.register_model_type(data)
return jsonify({
"status": "model type registered",
"model_type": data["type"],
"config": new_config
})
except ValueError as e:
return jsonify({"error": str(e)}), 400
except Exception as e:
return jsonify({"error": f"Registration failed: {str(e)}"}), 500
@app.route("/models", methods=["GET"])
def models():
return jsonify(load_meta())
def list_models():
"""Listar todos los modelos disponibles"""
models_info = []
for model_type in model_config.get_all_model_types():
model = model_manager.get_model(model_type)
model_config_info = model_config.get_model_config(model_type)
model_info = {
"type": model_type,
"description": model_config_info.get("description", ""),
"loaded": model is not None,
"required_params": model_config_info.get("required_params", []),
"module": model_config_info.get("module"),
"train_function": model_config_info.get("train_function")
}
# Añadir información del archivo si existe
from model_registry import load_meta
meta = load_meta()
if model_type in meta.get("current", {}):
model_info["file"] = meta["current"][model_type]
models_info.append(model_info)
return jsonify({
"available_models": models_info,
"total": len(models_info)
})
@app.route("/health", methods=["GET"])
def health():
"""Endpoint de salud"""
loaded_models = model_manager.get_loaded_models_status()
all_models = model_config.get_all_model_types()
return jsonify({
"status": "healthy" if all(loaded_models.values()) else "partial",
"models_loaded": loaded_models,
"total_configured": len(all_models),
"loaded_count": sum(1 for v in loaded_models.values() if v)
})
@app.route("/", methods=["GET"])
def index():
"""Página principal con documentación"""
endpoints = [
{
"path": "/predict",
"method": "GET",
"description": "Realizar predicción",
"parameters": "model_type (required), otros según modelo"
},
{
"path": "/demand",
"method": "GET",
"description": "Obtener predicciones masivas",
"parameters": "model_type (required), limit (opcional)"
},
{
"path": "/train",
"method": "POST",
"description": "Entrenar modelos",
"parameters": "model_type (opcional)"
},
{
"path": "/models",
"method": "GET",
"description": "Listar modelos disponibles"
},
{
"path": "/models/register",
"method": "POST",
"description": "Registrar nuevo tipo de modelo"
},
{
"path": "/health",
"method": "GET",
"description": "Estado del sistema"
}
]
return jsonify({
"service": "Model Prediction API",
"version": "1.0.0",
"endpoints": endpoints,
"available_models": model_config.get_all_model_types()
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
app.run(host="0.0.0.0", port=5000, debug=True)