Initial commit - production deployment
This commit is contained in:
302
services/forecasting/app/schemas/forecasts.py
Normal file
302
services/forecasting/app/schemas/forecasts.py
Normal file
@@ -0,0 +1,302 @@
|
||||
# ================================================================
|
||||
# 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
|
||||
from uuid import UUID
|
||||
|
||||
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('inventory_product_id')
|
||||
def validate_inventory_product_id(cls, v):
|
||||
"""Validate that inventory_product_id is a valid UUID"""
|
||||
try:
|
||||
UUID(v)
|
||||
except (ValueError, AttributeError):
|
||||
raise ValueError(f"inventory_product_id must be a valid UUID, got: {v}")
|
||||
return v
|
||||
|
||||
@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: Optional[str] = None # Optional, can be from path parameter
|
||||
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")
|
||||
|
||||
@validator('tenant_id')
|
||||
def validate_tenant_id(cls, v):
|
||||
"""Validate that tenant_id is a valid UUID if provided"""
|
||||
if v is not None:
|
||||
try:
|
||||
UUID(v)
|
||||
except (ValueError, AttributeError):
|
||||
raise ValueError(f"tenant_id must be a valid UUID, got: {v}")
|
||||
return v
|
||||
|
||||
@validator('inventory_product_ids')
|
||||
def validate_inventory_product_ids(cls, v):
|
||||
"""Validate that all inventory_product_ids are valid UUIDs"""
|
||||
for product_id in v:
|
||||
try:
|
||||
UUID(product_id)
|
||||
except (ValueError, AttributeError):
|
||||
raise ValueError(f"All inventory_product_ids must be valid UUIDs, got invalid: {product_id}")
|
||||
return v
|
||||
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user