Files
bakery-ia/services/inventory/app/schemas/food_safety.py
2025-08-21 20:28:14 +02:00

283 lines
10 KiB
Python

# ================================================================
# 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