# ================================================================ # services/forecasting/app/schemas/forecasts.py # ================================================================ """ Forecast schemas for request/response validation """ from pydantic import BaseModel, Field, validator from datetime import datetime, date from typing import Optional, List, Dict, Any from enum import Enum class BusinessType(str, Enum): INDIVIDUAL = "individual" CENTRAL_WORKSHOP = "central_workshop" class ForecastRequest(BaseModel): """Request schema for generating forecasts""" inventory_product_id: str = Field(..., description="Inventory product UUID reference") # product_name: str = Field(..., description="Product name") # DEPRECATED - use inventory_product_id forecast_date: date = Field(..., description="Starting date for forecast") forecast_days: int = Field(1, ge=1, le=30, description="Number of days to forecast") location: str = Field(..., description="Location identifier") # Optional parameters - internally handled confidence_level: float = Field(0.8, ge=0.5, le=0.95, description="Confidence level") @validator('forecast_date') def validate_forecast_date(cls, v): if v < date.today(): raise ValueError("Forecast date cannot be in the past") return v class BatchForecastRequest(BaseModel): """Request schema for batch forecasting""" tenant_id: str = Field(..., description="Tenant ID") batch_name: str = Field(..., description="Batch name for tracking") inventory_product_ids: List[str] = Field(..., description="List of inventory product IDs") forecast_days: int = Field(7, ge=1, le=30, description="Number of days to forecast") class ForecastResponse(BaseModel): """Response schema for forecast results""" id: str tenant_id: str inventory_product_id: str # Reference to inventory service # product_name: str # Can be fetched from inventory service if needed for display location: str forecast_date: datetime # Predictions predicted_demand: float confidence_lower: float confidence_upper: float confidence_level: float # Model info model_id: str model_version: str algorithm: str # Context business_type: str is_holiday: bool is_weekend: bool day_of_week: int # External factors weather_temperature: Optional[float] weather_precipitation: Optional[float] weather_description: Optional[str] traffic_volume: Optional[int] # Metadata created_at: datetime processing_time_ms: Optional[int] features_used: Optional[Dict[str, Any]] class BatchForecastResponse(BaseModel): """Response schema for batch forecast requests""" id: str tenant_id: str batch_name: str status: str total_products: int completed_products: int failed_products: int # Timing requested_at: datetime completed_at: Optional[datetime] processing_time_ms: Optional[int] # Results 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") # ================================================================ # SCENARIO SIMULATION SCHEMAS - PROFESSIONAL/ENTERPRISE ONLY # ================================================================ class ScenarioType(str, Enum): """Types of scenarios available for simulation""" WEATHER = "weather" # Weather impact (heatwave, cold snap, rain, etc.) COMPETITION = "competition" # New competitor opening nearby EVENT = "event" # Local event (festival, sports, concert, etc.) PRICING = "pricing" # Price changes PROMOTION = "promotion" # Promotional campaigns HOLIDAY = "holiday" # Holiday periods SUPPLY_DISRUPTION = "supply_disruption" # Supply chain issues CUSTOM = "custom" # Custom user-defined scenario class WeatherScenario(BaseModel): """Weather scenario parameters""" temperature_change: Optional[float] = Field(None, ge=-30, le=30, description="Temperature change in °C") precipitation_change: Optional[float] = Field(None, ge=0, le=100, description="Precipitation change in mm") weather_type: Optional[str] = Field(None, description="Weather type (heatwave, cold_snap, rainy, etc.)") class CompetitionScenario(BaseModel): """Competition scenario parameters""" new_competitors: int = Field(1, ge=1, le=10, description="Number of new competitors") distance_km: float = Field(0.5, ge=0.1, le=10, description="Distance from location in km") estimated_market_share_loss: float = Field(0.1, ge=0, le=0.5, description="Estimated market share loss (0-50%)") class EventScenario(BaseModel): """Event scenario parameters""" event_type: str = Field(..., description="Type of event (festival, sports, concert, etc.)") expected_attendance: int = Field(..., ge=0, description="Expected attendance") distance_km: float = Field(0.5, ge=0, le=50, description="Distance from location in km") duration_days: int = Field(1, ge=1, le=30, description="Duration in days") class PricingScenario(BaseModel): """Pricing scenario parameters""" price_change_percent: float = Field(..., ge=-50, le=100, description="Price change percentage") affected_products: Optional[List[str]] = Field(None, description="List of affected product IDs") class PromotionScenario(BaseModel): """Promotion scenario parameters""" discount_percent: float = Field(..., ge=0, le=75, description="Discount percentage") promotion_type: str = Field(..., description="Type of promotion (bogo, discount, bundle, etc.)") expected_traffic_increase: float = Field(0.2, ge=0, le=2, description="Expected traffic increase (0-200%)") class ScenarioSimulationRequest(BaseModel): """Request schema for scenario simulation - PROFESSIONAL/ENTERPRISE ONLY""" scenario_name: str = Field(..., min_length=3, max_length=200, description="Name for this scenario") scenario_type: ScenarioType = Field(..., description="Type of scenario to simulate") inventory_product_ids: List[str] = Field(..., min_items=1, description="Products to simulate") start_date: date = Field(..., description="Simulation start date") duration_days: int = Field(7, ge=1, le=30, description="Simulation duration in days") # Scenario-specific parameters (one should be provided based on scenario_type) weather_params: Optional[WeatherScenario] = None competition_params: Optional[CompetitionScenario] = None event_params: Optional[EventScenario] = None pricing_params: Optional[PricingScenario] = None promotion_params: Optional[PromotionScenario] = None # Custom scenario parameters custom_multipliers: Optional[Dict[str, float]] = Field( None, description="Custom multipliers for baseline forecast (e.g., {'demand': 1.2, 'traffic': 0.8})" ) # Comparison settings include_baseline: bool = Field(True, description="Include baseline forecast for comparison") @validator('start_date') def validate_start_date(cls, v): if v < date.today(): raise ValueError("Simulation start date cannot be in the past") return v class ScenarioImpact(BaseModel): """Impact of scenario on a specific product""" inventory_product_id: str baseline_demand: float simulated_demand: float demand_change_percent: float confidence_range: tuple[float, float] impact_factors: Dict[str, Any] # Breakdown of what drove the change class ScenarioSimulationResponse(BaseModel): """Response schema for scenario simulation""" id: str = Field(..., description="Simulation ID") tenant_id: str scenario_name: str scenario_type: ScenarioType # Simulation parameters start_date: date end_date: date duration_days: int # Results baseline_forecasts: Optional[List[ForecastResponse]] = Field( None, description="Baseline forecasts (if requested)" ) scenario_forecasts: List[ForecastResponse] = Field(..., description="Forecasts with scenario applied") # Impact summary total_baseline_demand: float total_scenario_demand: float overall_impact_percent: float product_impacts: List[ScenarioImpact] # Insights and recommendations insights: List[str] = Field(..., description="AI-generated insights about the scenario") recommendations: List[str] = Field(..., description="Actionable recommendations") risk_level: str = Field(..., description="Risk level: low, medium, high") # Metadata created_at: datetime processing_time_ms: int class Config: json_schema_extra = { "example": { "id": "scenario_123", "tenant_id": "tenant_456", "scenario_name": "Summer Heatwave Impact", "scenario_type": "weather", "overall_impact_percent": 15.5, "insights": [ "Cold beverages expected to increase by 45%", "Bread products may decrease by 8% due to reduced appetite", "Ice cream demand projected to surge by 120%" ], "recommendations": [ "Increase cold beverage inventory by 40%", "Reduce bread production by 10%", "Stock additional ice cream varieties" ], "risk_level": "medium" } } class ScenarioComparisonRequest(BaseModel): """Request to compare multiple scenarios""" scenario_ids: List[str] = Field(..., min_items=2, max_items=5, description="Scenario IDs to compare") class ScenarioComparisonResponse(BaseModel): """Response comparing multiple scenarios""" scenarios: List[ScenarioSimulationResponse] comparison_matrix: Dict[str, Dict[str, Any]] best_case_scenario_id: str worst_case_scenario_id: str recommended_action: str