"""AVM v2 ensemble router — residential property valuation.""" from fastapi import APIRouter, HTTPException from app.models.avm_v2 import ( ABComparisonRequest, ABComparisonResponse, AVMv2FeatureImportanceResponse, AVMv2ModelInfo, AVMv2PredictRequest, AVMv2PredictResponse, AVMv2RollbackRequest, AVMv2TrainRequest, AVMv2TrainResponse, ) from app.services.avm_v2_service import avm_v2_service router = APIRouter(prefix="/avm/v2", tags=["AVM v2 Ensemble"]) @router.post("/predict", response_model=AVMv2PredictResponse) def predict_v2(req: AVMv2PredictRequest) -> AVMv2PredictResponse: """Predict residential property price using the multi-model ensemble. Ensemble: XGBoost (0.4) + LightGBM (0.35) + CatBoost (0.25). Falls back to heuristic when trained models are not available. """ return avm_v2_service.predict(req) @router.post("/train", response_model=AVMv2TrainResponse) def train_v2(req: AVMv2TrainRequest) -> AVMv2TrainResponse: """Trigger model retraining with Optuna hyperparameter optimization. Loads training data from the model directory, runs Optuna for each model in the ensemble, saves versioned artifacts, and registers the new version in the model registry. """ return avm_v2_service.train(req) @router.post("/compare-v1", response_model=ABComparisonResponse) def compare_v1(req: ABComparisonRequest) -> ABComparisonResponse: """Compare v1 (single-model) vs v2 (ensemble) predictions side by side. Runs both models on the same property and returns price difference, confidence delta, and a recommendation on which to prefer. """ return avm_v2_service.compare_v1(req) @router.get("/model-info", response_model=AVMv2ModelInfo) def model_info_v2() -> AVMv2ModelInfo: """Get current active ensemble model information.""" return avm_v2_service.get_model_info() @router.get("/feature-importance", response_model=AVMv2FeatureImportanceResponse) def feature_importance_v2() -> AVMv2FeatureImportanceResponse: """Global feature importance for the active ensemble. Aggregates XGBoost gain (0.4) + LightGBM gain (0.35) + CatBoost importance (0.25) when trained boosters are loaded. Falls back to a curated heuristic ranking when the service is running without artifacts. """ return avm_v2_service.get_feature_importance() @router.get("/versions", response_model=list[AVMv2ModelInfo]) def list_versions() -> list[AVMv2ModelInfo]: """List all registered model versions with their metrics and status.""" return avm_v2_service.list_versions() @router.post("/rollback", response_model=AVMv2ModelInfo) def rollback(req: AVMv2RollbackRequest) -> AVMv2ModelInfo: """Rollback to a previously trained model version. Copies the target version's artifacts to the active model directory, reloads models, and updates the registry. """ try: return avm_v2_service.rollback(req.target_version) except ValueError as e: raise HTTPException(status_code=404, detail=str(e))