# ================================================================ # services/inventory/app/schemas/food_safety.py # ================================================================ """ Food safety schemas for Inventory Service """ from datetime import datetime from decimal import Decimal from typing import List, Optional, Dict, Any from uuid import UUID from pydantic import BaseModel, Field, validator # ===== Food Safety Compliance Schemas ===== class FoodSafetyComplianceBase(BaseModel): ingredient_id: UUID standard: str compliance_status: str = Field(default="pending_review") certification_number: Optional[str] = None certifying_body: Optional[str] = None certification_date: Optional[datetime] = None expiration_date: Optional[datetime] = None requirements: Optional[Dict[str, Any]] = None compliance_notes: Optional[str] = None documentation_url: Optional[str] = None last_audit_date: Optional[datetime] = None next_audit_date: Optional[datetime] = None auditor_name: Optional[str] = None audit_score: Optional[float] = Field(None, ge=0, le=100) risk_level: str = Field(default="medium") risk_factors: Optional[List[str]] = None mitigation_measures: Optional[List[str]] = None requires_monitoring: bool = Field(default=True) monitoring_frequency_days: Optional[int] = Field(None, gt=0) class FoodSafetyComplianceCreate(FoodSafetyComplianceBase): tenant_id: UUID class FoodSafetyComplianceUpdate(BaseModel): compliance_status: Optional[str] = None certification_number: Optional[str] = None certifying_body: Optional[str] = None certification_date: Optional[datetime] = None expiration_date: Optional[datetime] = None requirements: Optional[Dict[str, Any]] = None compliance_notes: Optional[str] = None documentation_url: Optional[str] = None last_audit_date: Optional[datetime] = None next_audit_date: Optional[datetime] = None auditor_name: Optional[str] = None audit_score: Optional[float] = Field(None, ge=0, le=100) risk_level: Optional[str] = None risk_factors: Optional[List[str]] = None mitigation_measures: Optional[List[str]] = None requires_monitoring: Optional[bool] = None monitoring_frequency_days: Optional[int] = Field(None, gt=0) class FoodSafetyComplianceResponse(FoodSafetyComplianceBase): id: UUID tenant_id: UUID is_active: bool created_at: datetime updated_at: datetime created_by: Optional[UUID] = None updated_by: Optional[UUID] = None class Config: from_attributes = True # ===== Temperature Monitoring Schemas ===== class TemperatureLogBase(BaseModel): storage_location: str = Field(..., min_length=1, max_length=100) warehouse_zone: Optional[str] = Field(None, max_length=50) equipment_id: Optional[str] = Field(None, max_length=100) temperature_celsius: float humidity_percentage: Optional[float] = Field(None, ge=0, le=100) target_temperature_min: Optional[float] = None target_temperature_max: Optional[float] = None measurement_method: str = Field(default="manual") device_id: Optional[str] = Field(None, max_length=100) calibration_date: Optional[datetime] = None class TemperatureLogCreate(TemperatureLogBase): tenant_id: UUID class TemperatureLogResponse(TemperatureLogBase): id: UUID tenant_id: UUID is_within_range: bool alert_triggered: bool deviation_minutes: Optional[int] = None recorded_at: datetime created_at: datetime recorded_by: Optional[UUID] = None class Config: from_attributes = True # ===== Food Safety Alert Schemas ===== class FoodSafetyAlertBase(BaseModel): alert_type: str severity: str = Field(default="medium") risk_level: str = Field(default="medium") source_entity_type: str source_entity_id: UUID ingredient_id: Optional[UUID] = None stock_id: Optional[UUID] = None title: str = Field(..., min_length=1, max_length=200) description: str = Field(..., min_length=1) detailed_message: Optional[str] = None regulatory_requirement: Optional[str] = Field(None, max_length=100) compliance_standard: Optional[str] = None regulatory_action_required: bool = Field(default=False) trigger_condition: Optional[str] = Field(None, max_length=200) threshold_value: Optional[Decimal] = None actual_value: Optional[Decimal] = None alert_data: Optional[Dict[str, Any]] = None environmental_factors: Optional[Dict[str, Any]] = None affected_products: Optional[List[UUID]] = None public_health_risk: bool = Field(default=False) business_impact: Optional[str] = None estimated_loss: Optional[Decimal] = Field(None, ge=0) class FoodSafetyAlertCreate(FoodSafetyAlertBase): tenant_id: UUID alert_code: str = Field(..., min_length=1, max_length=50) class FoodSafetyAlertUpdate(BaseModel): status: Optional[str] = None alert_state: Optional[str] = None immediate_actions_taken: Optional[List[str]] = None investigation_notes: Optional[str] = None resolution_action: Optional[str] = Field(None, max_length=200) resolution_notes: Optional[str] = None corrective_actions: Optional[List[str]] = None preventive_measures: Optional[List[str]] = None assigned_to: Optional[UUID] = None assigned_role: Optional[str] = Field(None, max_length=50) escalated_to: Optional[UUID] = None escalation_deadline: Optional[datetime] = None documentation: Optional[Dict[str, Any]] = None class FoodSafetyAlertResponse(FoodSafetyAlertBase): id: UUID tenant_id: UUID alert_code: str status: str alert_state: str immediate_actions_taken: Optional[List[str]] = None investigation_notes: Optional[str] = None resolution_action: Optional[str] = None resolution_notes: Optional[str] = None corrective_actions: Optional[List[str]] = None preventive_measures: Optional[List[str]] = None first_occurred_at: datetime last_occurred_at: datetime acknowledged_at: Optional[datetime] = None resolved_at: Optional[datetime] = None escalation_deadline: Optional[datetime] = None occurrence_count: int is_recurring: bool recurrence_pattern: Optional[str] = None assigned_to: Optional[UUID] = None assigned_role: Optional[str] = None escalated_to: Optional[UUID] = None escalation_level: int notification_sent: bool notification_methods: Optional[List[str]] = None notification_recipients: Optional[List[str]] = None regulatory_notification_required: bool regulatory_notification_sent: bool documentation: Optional[Dict[str, Any]] = None audit_trail: Optional[List[Dict[str, Any]]] = None external_reference: Optional[str] = None detection_time: Optional[datetime] = None response_time_minutes: Optional[int] = None resolution_time_minutes: Optional[int] = None alert_accuracy: Optional[bool] = None false_positive: bool feedback_notes: Optional[str] = None created_at: datetime updated_at: datetime created_by: Optional[UUID] = None updated_by: Optional[UUID] = None class Config: from_attributes = True # ===== Bulk Operations Schemas ===== class BulkTemperatureLogCreate(BaseModel): """Schema for bulk temperature logging""" tenant_id: UUID readings: List[TemperatureLogBase] = Field(..., min_items=1, max_items=100) class BulkComplianceUpdate(BaseModel): """Schema for bulk compliance updates""" tenant_id: UUID updates: List[Dict[str, Any]] = Field(..., min_items=1, max_items=50) # ===== Filter and Query Schemas ===== class FoodSafetyFilter(BaseModel): """Filtering options for food safety data""" compliance_standards: Optional[List[str]] = None compliance_statuses: Optional[List[str]] = None risk_levels: Optional[List[str]] = None alert_types: Optional[List[str]] = None severities: Optional[List[str]] = None date_from: Optional[datetime] = None date_to: Optional[datetime] = None assigned_to: Optional[UUID] = None include_resolved: bool = False regulatory_action_required: Optional[bool] = None class TemperatureMonitoringFilter(BaseModel): """Filtering options for temperature monitoring""" storage_locations: Optional[List[str]] = None equipment_ids: Optional[List[str]] = None date_from: Optional[datetime] = None date_to: Optional[datetime] = None violations_only: bool = False alerts_only: bool = False # ===== Analytics Schemas ===== class FoodSafetyMetrics(BaseModel): """Food safety performance metrics""" compliance_rate: Decimal = Field(..., ge=0, le=100) temperature_compliance_rate: Decimal = Field(..., ge=0, le=100) alert_response_time_avg: Optional[Decimal] = None alert_resolution_time_avg: Optional[Decimal] = None recurring_issues_count: int regulatory_violations: int certification_coverage: Decimal = Field(..., ge=0, le=100) audit_score_avg: Optional[Decimal] = Field(None, ge=0, le=100) risk_score: Decimal = Field(..., ge=0, le=10) class TemperatureAnalytics(BaseModel): """Temperature monitoring analytics""" total_readings: int violations_count: int violation_rate: Decimal = Field(..., ge=0, le=100) average_temperature: Decimal temperature_range: Dict[str, Decimal] longest_violation_hours: Optional[int] = None equipment_performance: List[Dict[str, Any]] location_performance: List[Dict[str, Any]] # ===== Notification Schemas ===== class AlertNotificationPreferences(BaseModel): """User preferences for alert notifications""" email_enabled: bool = True sms_enabled: bool = False whatsapp_enabled: bool = False dashboard_enabled: bool = True severity_threshold: str = Field(default="medium") # Only notify for this severity and above alert_types: Optional[List[str]] = None # Specific alert types to receive quiet_hours_start: Optional[str] = Field(None, pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$") quiet_hours_end: Optional[str] = Field(None, pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$") weekend_notifications: bool = True