Imporve the predicciones page

This commit is contained in:
Urtzi Alfaro
2025-09-20 22:11:05 +02:00
parent abe7cf2444
commit 38d314e28d
14 changed files with 1659 additions and 364 deletions

View File

@@ -11,8 +11,8 @@ import uuid
from app.services.forecasting_service import EnhancedForecastingService
from app.schemas.forecasts import (
ForecastRequest, ForecastResponse, BatchForecastRequest,
BatchForecastResponse
ForecastRequest, ForecastResponse, BatchForecastRequest,
BatchForecastResponse, MultiDayForecastResponse
)
from shared.auth.decorators import (
get_current_user_dep,
@@ -66,7 +66,7 @@ async def create_enhanced_single_forecast(
forecast_id=forecast.id)
return forecast
except ValueError as e:
if metrics:
metrics.increment_counter("enhanced_forecast_validation_errors_total")
@@ -89,6 +89,70 @@ async def create_enhanced_single_forecast(
)
@router.post("/tenants/{tenant_id}/forecasts/multi-day", response_model=MultiDayForecastResponse)
@track_execution_time("enhanced_multi_day_forecast_duration_seconds", "forecasting-service")
async def create_enhanced_multi_day_forecast(
request: ForecastRequest,
tenant_id: str = Path(..., description="Tenant ID"),
request_obj: Request = None,
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
):
"""Generate multiple daily forecasts for the specified period using enhanced repository pattern"""
metrics = get_metrics_collector(request_obj)
try:
logger.info("Generating enhanced multi-day forecast",
tenant_id=tenant_id,
inventory_product_id=request.inventory_product_id,
forecast_days=request.forecast_days,
forecast_date=request.forecast_date.isoformat())
# Record metrics
if metrics:
metrics.increment_counter("enhanced_multi_day_forecasts_total")
# Validate forecast_days parameter
if request.forecast_days <= 0 or request.forecast_days > 30:
raise ValueError("forecast_days must be between 1 and 30")
# Generate multi-day forecast using enhanced service
forecast_result = await enhanced_forecasting_service.generate_multi_day_forecast(
tenant_id=tenant_id,
request=request
)
if metrics:
metrics.increment_counter("enhanced_multi_day_forecasts_success_total")
logger.info("Enhanced multi-day forecast generated successfully",
tenant_id=tenant_id,
inventory_product_id=request.inventory_product_id,
forecast_days=len(forecast_result.get("forecasts", [])))
return MultiDayForecastResponse(**forecast_result)
except ValueError as e:
if metrics:
metrics.increment_counter("enhanced_multi_day_forecast_validation_errors_total")
logger.error("Enhanced multi-day forecast validation error",
error=str(e),
tenant_id=tenant_id)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_multi_day_forecasts_errors_total")
logger.error("Enhanced multi-day forecast generation failed",
error=str(e),
tenant_id=tenant_id)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Enhanced multi-day forecast generation failed"
)
@router.post("/tenants/{tenant_id}/forecasts/batch", response_model=BatchForecastResponse)
@track_execution_time("enhanced_batch_forecast_duration_seconds", "forecasting-service")
async def create_enhanced_batch_forecast(

View File

@@ -95,4 +95,15 @@ class BatchForecastResponse(BaseModel):
forecasts: Optional[List[ForecastResponse]]
error_message: Optional[str]
class MultiDayForecastResponse(BaseModel):
"""Response schema for multi-day forecast results"""
tenant_id: str = Field(..., description="Tenant ID")
inventory_product_id: str = Field(..., description="Inventory product ID")
forecast_start_date: date = Field(..., description="Start date of forecast period")
forecast_days: int = Field(..., description="Number of forecasted days")
forecasts: List[ForecastResponse] = Field(..., description="Daily forecasts")
total_predicted_demand: float = Field(..., description="Total demand across all days")
average_confidence_level: float = Field(..., description="Average confidence across all days")
processing_time_ms: int = Field(..., description="Total processing time")

View File

@@ -345,7 +345,101 @@ class EnhancedForecastingService:
inventory_product_id=request.inventory_product_id,
processing_time=processing_time)
raise
async def generate_multi_day_forecast(
self,
tenant_id: str,
request: ForecastRequest
) -> Dict[str, Any]:
"""
Generate multiple daily forecasts for the specified period.
"""
start_time = datetime.utcnow()
forecasts = []
try:
logger.info("Generating multi-day forecast",
tenant_id=tenant_id,
inventory_product_id=request.inventory_product_id,
forecast_days=request.forecast_days,
start_date=request.forecast_date.isoformat())
# Generate a forecast for each day
for day_offset in range(request.forecast_days):
# Calculate the forecast date for this day
current_date = request.forecast_date
if isinstance(current_date, str):
from dateutil.parser import parse
current_date = parse(current_date).date()
if day_offset > 0:
from datetime import timedelta
current_date = current_date + timedelta(days=day_offset)
# Create a new request for this specific day
daily_request = ForecastRequest(
inventory_product_id=request.inventory_product_id,
forecast_date=current_date,
forecast_days=1, # Single day for each iteration
location=request.location,
confidence_level=request.confidence_level
)
# Generate forecast for this day
daily_forecast = await self.generate_forecast(tenant_id, daily_request)
forecasts.append(daily_forecast)
# Calculate summary statistics
total_demand = sum(f.predicted_demand for f in forecasts)
avg_confidence = sum(f.confidence_level for f in forecasts) / len(forecasts)
processing_time = int((datetime.utcnow() - start_time).total_seconds() * 1000)
# Convert forecasts to dictionary format for the response
forecast_dicts = []
for forecast in forecasts:
forecast_dicts.append({
"id": forecast.id,
"tenant_id": forecast.tenant_id,
"inventory_product_id": forecast.inventory_product_id,
"location": forecast.location,
"forecast_date": forecast.forecast_date.isoformat() if hasattr(forecast.forecast_date, 'isoformat') else str(forecast.forecast_date),
"predicted_demand": forecast.predicted_demand,
"confidence_lower": forecast.confidence_lower,
"confidence_upper": forecast.confidence_upper,
"confidence_level": forecast.confidence_level,
"model_id": forecast.model_id,
"model_version": forecast.model_version,
"algorithm": forecast.algorithm,
"business_type": forecast.business_type,
"is_holiday": forecast.is_holiday,
"is_weekend": forecast.is_weekend,
"day_of_week": forecast.day_of_week,
"weather_temperature": forecast.weather_temperature,
"weather_precipitation": forecast.weather_precipitation,
"weather_description": forecast.weather_description,
"traffic_volume": forecast.traffic_volume,
"created_at": forecast.created_at.isoformat() if hasattr(forecast.created_at, 'isoformat') else str(forecast.created_at),
"processing_time_ms": forecast.processing_time_ms,
"features_used": forecast.features_used
})
return {
"tenant_id": tenant_id,
"inventory_product_id": request.inventory_product_id,
"forecast_start_date": request.forecast_date.isoformat() if hasattr(request.forecast_date, 'isoformat') else str(request.forecast_date),
"forecast_days": request.forecast_days,
"forecasts": forecast_dicts,
"total_predicted_demand": total_demand,
"average_confidence_level": avg_confidence,
"processing_time_ms": processing_time
}
except Exception as e:
logger.error("Multi-day forecast generation failed",
tenant_id=tenant_id,
error=str(e))
raise
async def get_forecast_history(
self,
tenant_id: str,