from fastapi import APIRouter from pydantic import BaseModel import joblib import pandas as pd from typing import Optional, Any from .config_huggingface import build_model_url, download_artifact_if_needed router = APIRouter(tags=["Machine Learning"]) class GradientBoostingRequest(BaseModel): lot_size_sqm: int = 200 floors: int = 2 rooms: int = 5 crime_rate: float = 3.0 school_rating: int = 8 MODEL_STATE: dict[str, Optional[Any]] = { "model": None, "error": None, } MODEL_URL = build_model_url("ML_GradientBoosting_HousePricePredictor.joblib") def _ensure_model_loaded() -> None: if MODEL_STATE["model"] is not None: return try: model_path = download_artifact_if_needed(MODEL_URL) MODEL_STATE["model"] = joblib.load(model_path) MODEL_STATE["error"] = None except Exception as e: MODEL_STATE["error"] = str(e) raise @router.post("/models/gradient_boosting", summary="Predict house price with Gradient Boosting") def predict_gradient_boosting(data: GradientBoostingRequest): import traceback try: _ensure_model_loaded() except Exception: detail = "Model not loaded." if MODEL_STATE["error"]: detail = f"Model not loaded: {MODEL_STATE['error']}" return {"error": detail, "traceback": traceback.format_exc(), "status": 500} model = MODEL_STATE["model"] if model is None: return {"error": f"Model is None after loading. Error: {MODEL_STATE['error']}", "status": 500} input_df = pd.DataFrame( [[data.lot_size_sqm, data.floors, data.rooms, data.crime_rate, data.school_rating]], columns=["lot_size_sqm", "floors", "rooms", "crime_rate", "school_rating"], ) try: pred = model.predict(input_df)[0] except Exception as e: return {"error": f"Prediction failed: {str(e)}", "traceback": traceback.format_exc(), "status": 500} price = max(round(float(pred), 2), 0.0) return { "predicted_price_thousands": price, "predicted_price_formatted": f"${price:.1f}k", }