From 07872f50d0ba2f82a84ebbfd7f2379df9f203e4a Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Tue, 29 Jul 2025 18:37:23 +0200 Subject: [PATCH] Start fixing forecast service API 10 --- .../app/services/forecasting_service.py | 2 +- .../forecasting/app/services/model_client.py | 4 ++-- services/training/app/api/models.py | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/services/forecasting/app/services/forecasting_service.py b/services/forecasting/app/services/forecasting_service.py index 5243a86b..e765b530 100644 --- a/services/forecasting/app/services/forecasting_service.py +++ b/services/forecasting/app/services/forecasting_service.py @@ -251,7 +251,7 @@ class ForecastingService: # Pass the product_name to the model client model_data = await self.model_client.get_best_model_for_forecasting( tenant_id=tenant_id, - product_id=product_name # Make sure to pass product_name + product_name=product_name # Make sure to pass product_name ) return model_data except Exception as e: diff --git a/services/forecasting/app/services/model_client.py b/services/forecasting/app/services/model_client.py index df102a3d..3cb19922 100644 --- a/services/forecasting/app/services/model_client.py +++ b/services/forecasting/app/services/model_client.py @@ -57,7 +57,7 @@ class ModelClient: async def get_best_model_for_forecasting( self, tenant_id: str, - product_id: Optional[str] = None + product_name: Optional[str] = None ) -> Optional[Dict[str, Any]]: """ Get the best model for forecasting based on performance metrics @@ -66,7 +66,7 @@ class ModelClient: # Get latest model latest_model = await self.clients.training.get_active_model_for_product( tenant_id=tenant_id, - product_name=product_id + product_name=product_name ) if not latest_model: diff --git a/services/training/app/api/models.py b/services/training/app/api/models.py index 5b8efe78..acecb32f 100644 --- a/services/training/app/api/models.py +++ b/services/training/app/api/models.py @@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Path from sqlalchemy.ext.asyncio import AsyncSession from typing import List import structlog +from sqlalchemy import text from app.core.database import get_db from app.schemas.training import TrainedModelResponse @@ -31,7 +32,8 @@ async def get_active_model( Get the active model for a product - used by forecasting service """ try: - query = """ + # ✅ FIX: Wrap SQL with text() for SQLAlchemy 2.0 + query = text(""" SELECT * FROM trained_models WHERE tenant_id = :tenant_id AND product_name = :product_name @@ -39,7 +41,7 @@ async def get_active_model( AND is_production = true ORDER BY created_at DESC LIMIT 1 - """ + """) result = await db.execute(query, { "tenant_id": tenant_id, @@ -54,12 +56,12 @@ async def get_active_model( detail=f"No active model found for product {product_name}" ) - # Update last_used_at - update_query = """ + # ✅ FIX: Wrap update query with text() too + update_query = text(""" UPDATE trained_models SET last_used_at = :now WHERE id = :model_id - """ + """) await db.execute(update_query, { "now": datetime.utcnow(), @@ -78,10 +80,10 @@ async def get_active_model( "rmse": model_record.rmse, "r2_score": model_record.r2_score }, - "created_at": model_record.created_at.isoformat(), + "created_at": model_record.created_at.isoformat() if model_record.created_at else None, "training_period": { - "start_date": model_record.training_start_date.isoformat(), - "end_date": model_record.training_end_date.isoformat() + "start_date": model_record.training_start_date.isoformat() if model_record.training_start_date else None, + "end_date": model_record.training_end_date.isoformat() if model_record.training_end_date else None } }