Fix new services implementation 3

This commit is contained in:
Urtzi Alfaro
2025-08-14 16:47:34 +02:00
parent 0951547e92
commit 03737430ee
51 changed files with 657 additions and 982 deletions

View File

@@ -78,7 +78,7 @@ class EnhancedForecastingService:
logger.error("Batch forecast generation failed", error=str(e))
raise
async def get_tenant_forecasts(self, tenant_id: str, product_name: str = None,
async def get_tenant_forecasts(self, tenant_id: str, inventory_product_id: str = None,
start_date: date = None, end_date: date = None,
skip: int = 0, limit: int = 100) -> List[Dict]:
"""Get tenant forecasts with filtering"""
@@ -149,7 +149,7 @@ class EnhancedForecastingService:
logger.error("Batch predictions failed", error=str(e))
raise
async def get_cached_predictions(self, tenant_id: str, product_name: str = None,
async def get_cached_predictions(self, tenant_id: str, inventory_product_id: str = None,
skip: int = 0, limit: int = 100) -> List[Dict]:
"""Get cached predictions"""
try:
@@ -159,7 +159,7 @@ class EnhancedForecastingService:
logger.error("Failed to get cached predictions", error=str(e))
raise
async def clear_prediction_cache(self, tenant_id: str, product_name: str = None) -> int:
async def clear_prediction_cache(self, tenant_id: str, inventory_product_id: str = None) -> int:
"""Clear prediction cache"""
try:
# Implementation would use repository pattern
@@ -195,7 +195,7 @@ class EnhancedForecastingService:
try:
logger.info("Generating enhanced forecast",
tenant_id=tenant_id,
product=request.product_name,
inventory_product_id=request.inventory_product_id,
date=request.forecast_date.isoformat())
# Get session and initialize repositories
@@ -204,20 +204,20 @@ class EnhancedForecastingService:
# Step 1: Check cache first
cached_prediction = await repos['cache'].get_cached_prediction(
tenant_id, request.product_name, request.location, request.forecast_date
tenant_id, request.inventory_product_id, request.location, request.forecast_date
)
if cached_prediction:
logger.debug("Using cached prediction",
tenant_id=tenant_id,
product=request.product_name)
inventory_product_id=request.inventory_product_id)
return self._create_forecast_response_from_cache(cached_prediction)
# Step 2: Get model with validation
model_data = await self._get_latest_model_with_fallback(tenant_id, request.product_name)
model_data = await self._get_latest_model_with_fallback(tenant_id, request.inventory_product_id)
if not model_data:
raise ValueError(f"No valid model available for product: {request.product_name}")
raise ValueError(f"No valid model available for product: {request.inventory_product_id}")
# Step 3: Prepare features with fallbacks
features = await self._prepare_forecast_features_with_fallbacks(tenant_id, request)
@@ -244,7 +244,7 @@ class EnhancedForecastingService:
forecast_data = {
"tenant_id": tenant_id,
"product_name": request.product_name,
"inventory_product_id": request.inventory_product_id,
"location": request.location,
"forecast_date": forecast_datetime,
"predicted_demand": adjusted_prediction['prediction'],
@@ -271,7 +271,7 @@ class EnhancedForecastingService:
# Step 7: Cache the prediction
await repos['cache'].cache_prediction(
tenant_id=tenant_id,
product_name=request.product_name,
inventory_product_id=request.inventory_product_id,
location=request.location,
forecast_date=forecast_datetime,
predicted_demand=adjusted_prediction['prediction'],
@@ -296,14 +296,14 @@ class EnhancedForecastingService:
logger.error("Error generating enhanced forecast",
error=str(e),
tenant_id=tenant_id,
product=request.product_name,
inventory_product_id=request.inventory_product_id,
processing_time=processing_time)
raise
async def get_forecast_history(
self,
tenant_id: str,
product_name: Optional[str] = None,
inventory_product_id: Optional[str] = None,
start_date: Optional[date] = None,
end_date: Optional[date] = None
) -> List[Dict[str, Any]]:
@@ -314,7 +314,7 @@ class EnhancedForecastingService:
if start_date and end_date:
forecasts = await repos['forecast'].get_forecasts_by_date_range(
tenant_id, start_date, end_date, product_name
tenant_id, start_date, end_date, inventory_product_id
)
else:
# Get recent forecasts (last 30 days)
@@ -374,7 +374,7 @@ class EnhancedForecastingService:
self,
tenant_id: str,
batch_name: str,
products: List[str],
inventory_product_ids: List[str],
forecast_days: int = 7
) -> Dict[str, Any]:
"""Create batch prediction job using repository"""
@@ -386,7 +386,7 @@ class EnhancedForecastingService:
batch_data = {
"tenant_id": tenant_id,
"batch_name": batch_name,
"total_products": len(products),
"total_products": len(inventory_product_ids),
"forecast_days": forecast_days,
"status": "pending"
}
@@ -396,12 +396,12 @@ class EnhancedForecastingService:
logger.info("Batch prediction created",
batch_id=batch.id,
tenant_id=tenant_id,
total_products=len(products))
total_products=len(inventory_product_ids))
return {
"batch_id": str(batch.id),
"status": batch.status,
"total_products": len(products),
"total_products": len(inventory_product_ids),
"created_at": batch.requested_at.isoformat()
}
@@ -423,7 +423,7 @@ class EnhancedForecastingService:
"forecast_id": forecast.id,
"alert_type": "high_demand",
"severity": "high" if prediction['prediction'] > 200 else "medium",
"message": f"High demand predicted for {forecast.product_name}: {prediction['prediction']:.1f} units"
"message": f"High demand predicted for inventory product {forecast.inventory_product_id}: {prediction['prediction']:.1f} units"
})
# Check for low demand alert
@@ -433,7 +433,7 @@ class EnhancedForecastingService:
"forecast_id": forecast.id,
"alert_type": "low_demand",
"severity": "low",
"message": f"Low demand predicted for {forecast.product_name}: {prediction['prediction']:.1f} units"
"message": f"Low demand predicted for inventory product {forecast.inventory_product_id}: {prediction['prediction']:.1f} units"
})
# Check for stockout risk (very low prediction with narrow confidence interval)
@@ -444,7 +444,7 @@ class EnhancedForecastingService:
"forecast_id": forecast.id,
"alert_type": "stockout_risk",
"severity": "critical",
"message": f"Stockout risk for {forecast.product_name}: predicted {prediction['prediction']:.1f} units with high confidence"
"message": f"Stockout risk for inventory product {forecast.inventory_product_id}: predicted {prediction['prediction']:.1f} units with high confidence"
})
# Create alerts
@@ -462,7 +462,7 @@ class EnhancedForecastingService:
return ForecastResponse(
id=str(cache_entry.id),
tenant_id=str(cache_entry.tenant_id),
product_name=cache_entry.product_name,
inventory_product_id=cache_entry.inventory_product_id,
location=cache_entry.location,
forecast_date=cache_entry.forecast_date,
predicted_demand=cache_entry.predicted_demand,
@@ -486,7 +486,7 @@ class EnhancedForecastingService:
return ForecastResponse(
id=str(forecast.id),
tenant_id=str(forecast.tenant_id),
product_name=forecast.product_name,
inventory_product_id=forecast.inventory_product_id,
location=forecast.location,
forecast_date=forecast.forecast_date,
predicted_demand=forecast.predicted_demand,
@@ -514,7 +514,7 @@ class EnhancedForecastingService:
return {
"id": str(forecast.id),
"tenant_id": str(forecast.tenant_id),
"product_name": forecast.product_name,
"inventory_product_id": forecast.inventory_product_id,
"location": forecast.location,
"forecast_date": forecast.forecast_date.isoformat(),
"predicted_demand": forecast.predicted_demand,
@@ -527,17 +527,17 @@ class EnhancedForecastingService:
}
# Additional helper methods from original service
async def _get_latest_model_with_fallback(self, tenant_id: str, product_name: str) -> Optional[Dict[str, Any]]:
async def _get_latest_model_with_fallback(self, tenant_id: str, inventory_product_id: str) -> Optional[Dict[str, Any]]:
"""Get the latest trained model with fallback strategies"""
try:
model_data = await self.model_client.get_best_model_for_forecasting(
tenant_id=tenant_id,
product_name=product_name
inventory_product_id=inventory_product_id
)
if model_data:
logger.info("Found specific model for product",
product=product_name,
inventory_product_id=inventory_product_id,
model_id=model_data.get('model_id'))
return model_data

View File

@@ -62,7 +62,7 @@ class ModelClient:
async def get_best_model_for_forecasting(
self,
tenant_id: str,
product_name: Optional[str] = None
inventory_product_id: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""
Get the best model for forecasting based on performance metrics
@@ -71,7 +71,7 @@ class ModelClient:
# Get latest model
latest_model = await self.clients.training.get_active_model_for_product(
tenant_id=tenant_id,
product_name=product_name
inventory_product_id=inventory_product_id
)
if not latest_model:
@@ -137,7 +137,7 @@ class ModelClient:
logger.info("Found fallback model for tenant",
tenant_id=tenant_id,
model_id=best_model.get('id', 'unknown'),
product=best_model.get('product_name', 'unknown'))
inventory_product_id=best_model.get('inventory_product_id', 'unknown'))
return best_model
logger.warning("No fallback models available for tenant", tenant_id=tenant_id)

View File

@@ -38,7 +38,7 @@ class PredictionService:
async def validate_prediction_request(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Validate prediction request"""
try:
required_fields = ["product_name", "model_id", "features"]
required_fields = ["inventory_product_id", "model_id", "features"]
missing_fields = [field for field in required_fields if field not in request]
if missing_fields: