Initial commit - production deployment
This commit is contained in:
440
services/procurement/app/schemas/replenishment.py
Normal file
440
services/procurement/app/schemas/replenishment.py
Normal file
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
Pydantic schemas for replenishment planning.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Replenishment Plan Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ReplenishmentPlanItemBase(BaseModel):
|
||||
"""Base schema for replenishment plan item"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
unit_of_measure: str
|
||||
|
||||
base_quantity: Decimal
|
||||
safety_stock_quantity: Decimal
|
||||
shelf_life_adjusted_quantity: Decimal
|
||||
final_order_quantity: Decimal
|
||||
|
||||
order_date: date
|
||||
delivery_date: date
|
||||
required_by_date: date
|
||||
|
||||
lead_time_days: int
|
||||
is_urgent: bool
|
||||
urgency_reason: Optional[str] = None
|
||||
waste_risk: str
|
||||
stockout_risk: str
|
||||
|
||||
supplier_id: Optional[UUID] = None
|
||||
|
||||
safety_stock_calculation: Optional[Dict[str, Any]] = None
|
||||
shelf_life_adjustment: Optional[Dict[str, Any]] = None
|
||||
inventory_projection: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class ReplenishmentPlanItemCreate(ReplenishmentPlanItemBase):
|
||||
"""Schema for creating replenishment plan item"""
|
||||
replenishment_plan_id: UUID
|
||||
|
||||
|
||||
class ReplenishmentPlanItemResponse(ReplenishmentPlanItemBase):
|
||||
"""Schema for replenishment plan item response"""
|
||||
id: UUID
|
||||
replenishment_plan_id: UUID
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReplenishmentPlanBase(BaseModel):
|
||||
"""Base schema for replenishment plan"""
|
||||
planning_date: date
|
||||
projection_horizon_days: int = 7
|
||||
|
||||
forecast_id: Optional[UUID] = None
|
||||
production_schedule_id: Optional[UUID] = None
|
||||
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
|
||||
|
||||
class ReplenishmentPlanCreate(ReplenishmentPlanBase):
|
||||
"""Schema for creating replenishment plan"""
|
||||
tenant_id: UUID
|
||||
items: List[Dict[str, Any]] = []
|
||||
|
||||
|
||||
class ReplenishmentPlanResponse(ReplenishmentPlanBase):
|
||||
"""Schema for replenishment plan response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
status: str
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
executed_at: Optional[datetime] = None
|
||||
|
||||
items: List[ReplenishmentPlanItemResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReplenishmentPlanSummary(BaseModel):
|
||||
"""Summary schema for list views"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
planning_date: date
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
status: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Inventory Projection Schemas
|
||||
# ============================================================================
|
||||
|
||||
class InventoryProjectionBase(BaseModel):
|
||||
"""Base schema for inventory projection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
projection_date: date
|
||||
|
||||
starting_stock: Decimal
|
||||
forecasted_consumption: Decimal
|
||||
scheduled_receipts: Decimal
|
||||
projected_ending_stock: Decimal
|
||||
|
||||
is_stockout: bool
|
||||
coverage_gap: Decimal
|
||||
|
||||
|
||||
class InventoryProjectionCreate(InventoryProjectionBase):
|
||||
"""Schema for creating inventory projection"""
|
||||
tenant_id: UUID
|
||||
replenishment_plan_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class InventoryProjectionResponse(InventoryProjectionBase):
|
||||
"""Schema for inventory projection response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
replenishment_plan_id: Optional[UUID] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class IngredientProjectionSummary(BaseModel):
|
||||
"""Summary of projections for one ingredient"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
projection_horizon_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
stockout_days: int
|
||||
stockout_risk: str
|
||||
daily_projections: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Allocation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierAllocationBase(BaseModel):
|
||||
"""Base schema for supplier allocation"""
|
||||
supplier_id: UUID
|
||||
supplier_name: str
|
||||
|
||||
allocation_type: str
|
||||
allocated_quantity: Decimal
|
||||
allocation_percentage: Decimal
|
||||
|
||||
unit_price: Decimal
|
||||
total_cost: Decimal
|
||||
lead_time_days: int
|
||||
|
||||
supplier_score: Decimal
|
||||
score_breakdown: Optional[Dict[str, float]] = None
|
||||
allocation_reason: Optional[str] = None
|
||||
|
||||
|
||||
class SupplierAllocationCreate(SupplierAllocationBase):
|
||||
"""Schema for creating supplier allocation"""
|
||||
replenishment_plan_item_id: Optional[UUID] = None
|
||||
requirement_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class SupplierAllocationResponse(SupplierAllocationBase):
|
||||
"""Schema for supplier allocation response"""
|
||||
id: UUID
|
||||
replenishment_plan_item_id: Optional[UUID] = None
|
||||
requirement_id: Optional[UUID] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Selection Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierSelectionRequest(BaseModel):
|
||||
"""Request to select suppliers for an ingredient"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
supplier_options: List[Dict[str, Any]]
|
||||
|
||||
|
||||
class SupplierSelectionResult(BaseModel):
|
||||
"""Result of supplier selection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
allocations: List[Dict[str, Any]]
|
||||
total_cost: Decimal
|
||||
weighted_lead_time: float
|
||||
risk_score: float
|
||||
diversification_applied: bool
|
||||
selection_strategy: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Replenishment Planning Request Schemas
|
||||
# ============================================================================
|
||||
|
||||
class IngredientRequirementInput(BaseModel):
|
||||
"""Input for a single ingredient requirement"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
required_by_date: date
|
||||
|
||||
supplier_id: Optional[UUID] = None
|
||||
lead_time_days: int = 3
|
||||
shelf_life_days: Optional[int] = None
|
||||
is_perishable: bool = False
|
||||
category: str = 'dry'
|
||||
unit_of_measure: str = 'kg'
|
||||
|
||||
current_stock: Decimal = Decimal('0')
|
||||
daily_consumption_rate: float = 0.0
|
||||
demand_std_dev: float = 0.0
|
||||
|
||||
|
||||
class GenerateReplenishmentPlanRequest(BaseModel):
|
||||
"""Request to generate replenishment plan"""
|
||||
tenant_id: UUID
|
||||
requirements: List[IngredientRequirementInput]
|
||||
forecast_id: Optional[UUID] = None
|
||||
production_schedule_id: Optional[UUID] = None
|
||||
|
||||
projection_horizon_days: int = 7
|
||||
service_level: float = 0.95
|
||||
buffer_days: int = 1
|
||||
|
||||
|
||||
class GenerateReplenishmentPlanResponse(BaseModel):
|
||||
"""Response from generating replenishment plan"""
|
||||
plan_id: UUID
|
||||
tenant_id: UUID
|
||||
planning_date: date
|
||||
projection_horizon_days: int
|
||||
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
|
||||
created_at: datetime
|
||||
|
||||
items: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MOQ Aggregation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class MOQAggregationRequest(BaseModel):
|
||||
"""Request for MOQ aggregation"""
|
||||
requirements: List[Dict[str, Any]]
|
||||
supplier_constraints: Dict[str, Dict[str, Any]]
|
||||
|
||||
|
||||
class MOQAggregationResponse(BaseModel):
|
||||
"""Response from MOQ aggregation"""
|
||||
aggregated_orders: List[Dict[str, Any]]
|
||||
efficiency_metrics: Dict[str, Any]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Safety Stock Calculation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SafetyStockRequest(BaseModel):
|
||||
"""Request for safety stock calculation"""
|
||||
ingredient_id: UUID
|
||||
daily_demands: List[float]
|
||||
lead_time_days: int
|
||||
service_level: float = 0.95
|
||||
|
||||
|
||||
class SafetyStockResponse(BaseModel):
|
||||
"""Response from safety stock calculation"""
|
||||
safety_stock_quantity: Decimal
|
||||
service_level: float
|
||||
z_score: float
|
||||
demand_std_dev: float
|
||||
lead_time_days: int
|
||||
calculation_method: str
|
||||
confidence: str
|
||||
reasoning: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Inventory Projection Request Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ProjectInventoryRequest(BaseModel):
|
||||
"""Request to project inventory"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
daily_demand: List[Dict[str, Any]]
|
||||
scheduled_receipts: List[Dict[str, Any]] = []
|
||||
projection_horizon_days: int = 7
|
||||
|
||||
|
||||
class ProjectInventoryResponse(BaseModel):
|
||||
"""Response from inventory projection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
projection_horizon_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
stockout_days: int
|
||||
stockout_risk: str
|
||||
daily_projections: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Selection History Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierSelectionHistoryBase(BaseModel):
|
||||
"""Base schema for supplier selection history"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
selected_supplier_id: UUID
|
||||
selected_supplier_name: str
|
||||
|
||||
selection_date: date
|
||||
quantity: Decimal
|
||||
unit_price: Decimal
|
||||
total_cost: Decimal
|
||||
|
||||
lead_time_days: int
|
||||
quality_score: Optional[Decimal] = None
|
||||
delivery_performance: Optional[Decimal] = None
|
||||
|
||||
selection_strategy: str
|
||||
was_primary_choice: bool = True
|
||||
|
||||
|
||||
class SupplierSelectionHistoryCreate(SupplierSelectionHistoryBase):
|
||||
"""Schema for creating supplier selection history"""
|
||||
tenant_id: UUID
|
||||
|
||||
|
||||
class SupplierSelectionHistoryResponse(SupplierSelectionHistoryBase):
|
||||
"""Schema for supplier selection history response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Analytics Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ReplenishmentAnalytics(BaseModel):
|
||||
"""Analytics for replenishment planning"""
|
||||
total_plans: int
|
||||
total_items_planned: int
|
||||
total_estimated_value: Decimal
|
||||
|
||||
urgent_items_percentage: float
|
||||
high_risk_items_percentage: float
|
||||
|
||||
average_lead_time_days: float
|
||||
average_safety_stock_percentage: float
|
||||
|
||||
stockout_prevention_rate: float
|
||||
moq_optimization_savings: Decimal
|
||||
|
||||
supplier_diversification_rate: float
|
||||
average_suppliers_per_ingredient: float
|
||||
|
||||
|
||||
class InventoryProjectionAnalytics(BaseModel):
|
||||
"""Analytics for inventory projections"""
|
||||
total_ingredients: int
|
||||
stockout_ingredients: int
|
||||
stockout_percentage: float
|
||||
|
||||
risk_breakdown: Dict[str, int]
|
||||
|
||||
total_stockout_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
|
||||
projection_horizon_days: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Validators
|
||||
# ============================================================================
|
||||
|
||||
@validator('required_quantity', 'current_stock', 'allocated_quantity',
|
||||
'safety_stock_quantity', 'base_quantity', 'final_order_quantity')
|
||||
def validate_positive_quantity(cls, v):
|
||||
"""Validate that quantities are positive"""
|
||||
if v < 0:
|
||||
raise ValueError('Quantity must be non-negative')
|
||||
return v
|
||||
|
||||
|
||||
@validator('service_level')
|
||||
def validate_service_level(cls, v):
|
||||
"""Validate service level is between 0 and 1"""
|
||||
if not 0 <= v <= 1:
|
||||
raise ValueError('Service level must be between 0 and 1')
|
||||
return v
|
||||
Reference in New Issue
Block a user