Consolidated incremental migrations into single unified initial schema files for both procurement and production services. This simplifies database setup and eliminates migration chain complexity. Changes: - Procurement: Merged 3 migrations into 001_unified_initial_schema.py - Initial schema (20251015_1229) - Add supplier_price_list_id (20251030_0737) - Add JTBD reasoning fields (20251107) - Production: Merged 3 migrations into 001_unified_initial_schema.py - Initial schema (20251015_1231) - Add waste tracking fields (20251023_0900) - Add JTBD reasoning fields (20251107) All new fields (reasoning, consequence, reasoning_data, waste_defect_type, is_ai_assisted, supplier_price_list_id) are now included in the initial schemas from the start. Updated model files to use deferred() for reasoning fields to prevent breaking queries when running against existing databases.
597 lines
27 KiB
Python
597 lines
27 KiB
Python
# ================================================================
|
|
# services/production/app/models/production.py
|
|
# ================================================================
|
|
"""
|
|
Production models for the production service
|
|
"""
|
|
|
|
from sqlalchemy import Column, String, Integer, Float, DateTime, Boolean, Text, JSON, Enum as SQLEnum
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import deferred
|
|
from sqlalchemy.sql import func
|
|
from datetime import datetime, timezone
|
|
from typing import Dict, Any, Optional
|
|
import uuid
|
|
import enum
|
|
|
|
from shared.database.base import Base
|
|
|
|
|
|
class ProductionStatus(str, enum.Enum):
|
|
"""Production batch status enumeration"""
|
|
PENDING = "PENDING"
|
|
IN_PROGRESS = "IN_PROGRESS"
|
|
COMPLETED = "COMPLETED"
|
|
CANCELLED = "CANCELLED"
|
|
ON_HOLD = "ON_HOLD"
|
|
QUALITY_CHECK = "QUALITY_CHECK"
|
|
FAILED = "FAILED"
|
|
|
|
|
|
class ProductionPriority(str, enum.Enum):
|
|
"""Production priority levels"""
|
|
LOW = "LOW"
|
|
MEDIUM = "MEDIUM"
|
|
HIGH = "HIGH"
|
|
URGENT = "URGENT"
|
|
|
|
|
|
class EquipmentStatus(str, enum.Enum):
|
|
"""Equipment status enumeration"""
|
|
OPERATIONAL = "operational"
|
|
MAINTENANCE = "maintenance"
|
|
DOWN = "down"
|
|
WARNING = "warning"
|
|
|
|
|
|
class ProcessStage(str, enum.Enum):
|
|
"""Production process stages where quality checks can occur"""
|
|
MIXING = "mixing"
|
|
PROOFING = "proofing"
|
|
SHAPING = "shaping"
|
|
BAKING = "baking"
|
|
COOLING = "cooling"
|
|
PACKAGING = "packaging"
|
|
FINISHING = "finishing"
|
|
|
|
|
|
class EquipmentType(str, enum.Enum):
|
|
"""Equipment type enumeration"""
|
|
OVEN = "oven"
|
|
MIXER = "mixer"
|
|
PROOFER = "proofer"
|
|
FREEZER = "freezer"
|
|
PACKAGING = "packaging"
|
|
OTHER = "other"
|
|
|
|
|
|
class ProductionBatch(Base):
|
|
"""Production batch model for tracking individual production runs"""
|
|
__tablename__ = "production_batches"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
batch_number = Column(String(50), nullable=False, unique=True, index=True)
|
|
|
|
# Product and recipe information
|
|
product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to inventory/recipes
|
|
product_name = Column(String(255), nullable=False)
|
|
recipe_id = Column(UUID(as_uuid=True), nullable=True)
|
|
|
|
# Production planning
|
|
planned_start_time = Column(DateTime(timezone=True), nullable=False)
|
|
planned_end_time = Column(DateTime(timezone=True), nullable=False)
|
|
planned_quantity = Column(Float, nullable=False)
|
|
planned_duration_minutes = Column(Integer, nullable=False)
|
|
|
|
# Actual production tracking
|
|
actual_start_time = Column(DateTime(timezone=True), nullable=True)
|
|
actual_end_time = Column(DateTime(timezone=True), nullable=True)
|
|
actual_quantity = Column(Float, nullable=True)
|
|
actual_duration_minutes = Column(Integer, nullable=True)
|
|
|
|
# Status and priority
|
|
status = Column(SQLEnum(ProductionStatus), nullable=False, default=ProductionStatus.PENDING, index=True)
|
|
priority = Column(SQLEnum(ProductionPriority), nullable=False, default=ProductionPriority.MEDIUM)
|
|
|
|
# Process stage tracking
|
|
current_process_stage = Column(SQLEnum(ProcessStage), nullable=True, index=True)
|
|
process_stage_history = Column(JSON, nullable=True) # Track stage transitions with timestamps
|
|
pending_quality_checks = Column(JSON, nullable=True) # Required quality checks for current stage
|
|
completed_quality_checks = Column(JSON, nullable=True) # Completed quality checks by stage
|
|
|
|
# Cost tracking
|
|
estimated_cost = Column(Float, nullable=True)
|
|
actual_cost = Column(Float, nullable=True)
|
|
labor_cost = Column(Float, nullable=True)
|
|
material_cost = Column(Float, nullable=True)
|
|
overhead_cost = Column(Float, nullable=True)
|
|
|
|
# Quality metrics
|
|
yield_percentage = Column(Float, nullable=True) # actual/planned quantity
|
|
quality_score = Column(Float, nullable=True)
|
|
waste_quantity = Column(Float, nullable=True)
|
|
defect_quantity = Column(Float, nullable=True)
|
|
waste_defect_type = Column(String(100), nullable=True) # Type of defect causing waste (burnt, misshapen, underproofed, temperature_issues, expired)
|
|
|
|
# Equipment and resources
|
|
equipment_used = Column(JSON, nullable=True) # List of equipment IDs
|
|
staff_assigned = Column(JSON, nullable=True) # List of staff IDs
|
|
station_id = Column(String(50), nullable=True)
|
|
|
|
# Business context
|
|
order_id = Column(UUID(as_uuid=True), nullable=True) # Associated customer order
|
|
forecast_id = Column(UUID(as_uuid=True), nullable=True) # Associated demand forecast
|
|
is_rush_order = Column(Boolean, default=False)
|
|
is_special_recipe = Column(Boolean, default=False)
|
|
is_ai_assisted = Column(Boolean, default=False) # Whether batch used AI forecasting/optimization
|
|
|
|
# Notes and tracking
|
|
production_notes = Column(Text, nullable=True)
|
|
quality_notes = Column(Text, nullable=True)
|
|
delay_reason = Column(String(255), nullable=True)
|
|
cancellation_reason = Column(String(255), nullable=True)
|
|
|
|
# JTBD Dashboard: Reasoning and context for user transparency
|
|
# Deferred loading to prevent breaking queries when columns don't exist yet
|
|
reasoning = deferred(Column(Text, nullable=True)) # Why this batch was scheduled (e.g., "Based on wedding order #1234")
|
|
reasoning_data = deferred(Column(JSON, nullable=True)) # Structured reasoning data
|
|
# reasoning_data structure: {
|
|
# "trigger": "forecast" | "order" | "inventory" | "manual",
|
|
# "forecast_id": "uuid",
|
|
# "orders_fulfilled": [{"id": "uuid", "customer": "Maria's Bakery", "quantity": 100}],
|
|
# "demand_score": 0-100,
|
|
# "scheduling_priority_reason": "High demand + VIP customer"
|
|
# }
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
completed_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"batch_number": self.batch_number,
|
|
"product_id": str(self.product_id),
|
|
"product_name": self.product_name,
|
|
"recipe_id": str(self.recipe_id) if self.recipe_id else None,
|
|
"planned_start_time": self.planned_start_time.isoformat() if self.planned_start_time else None,
|
|
"planned_end_time": self.planned_end_time.isoformat() if self.planned_end_time else None,
|
|
"planned_quantity": self.planned_quantity,
|
|
"planned_duration_minutes": self.planned_duration_minutes,
|
|
"actual_start_time": self.actual_start_time.isoformat() if self.actual_start_time else None,
|
|
"actual_end_time": self.actual_end_time.isoformat() if self.actual_end_time else None,
|
|
"actual_quantity": self.actual_quantity,
|
|
"actual_duration_minutes": self.actual_duration_minutes,
|
|
"status": self.status.value if self.status else None,
|
|
"priority": self.priority.value if self.priority else None,
|
|
"estimated_cost": self.estimated_cost,
|
|
"actual_cost": self.actual_cost,
|
|
"labor_cost": self.labor_cost,
|
|
"material_cost": self.material_cost,
|
|
"overhead_cost": self.overhead_cost,
|
|
"yield_percentage": self.yield_percentage,
|
|
"quality_score": self.quality_score,
|
|
"waste_quantity": self.waste_quantity,
|
|
"defect_quantity": self.defect_quantity,
|
|
"waste_defect_type": self.waste_defect_type,
|
|
"equipment_used": self.equipment_used,
|
|
"staff_assigned": self.staff_assigned,
|
|
"station_id": self.station_id,
|
|
"order_id": str(self.order_id) if self.order_id else None,
|
|
"forecast_id": str(self.forecast_id) if self.forecast_id else None,
|
|
"is_rush_order": self.is_rush_order,
|
|
"is_special_recipe": self.is_special_recipe,
|
|
"is_ai_assisted": self.is_ai_assisted,
|
|
"production_notes": self.production_notes,
|
|
"quality_notes": self.quality_notes,
|
|
"delay_reason": self.delay_reason,
|
|
"cancellation_reason": self.cancellation_reason,
|
|
"reasoning": self.reasoning,
|
|
"reasoning_data": self.reasoning_data,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
|
}
|
|
|
|
|
|
class ProductionSchedule(Base):
|
|
"""Production schedule model for planning and tracking daily production"""
|
|
__tablename__ = "production_schedules"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Schedule information
|
|
schedule_date = Column(DateTime(timezone=True), nullable=False, index=True)
|
|
shift_start = Column(DateTime(timezone=True), nullable=False)
|
|
shift_end = Column(DateTime(timezone=True), nullable=False)
|
|
|
|
# Capacity planning
|
|
total_capacity_hours = Column(Float, nullable=False)
|
|
planned_capacity_hours = Column(Float, nullable=False)
|
|
actual_capacity_hours = Column(Float, nullable=True)
|
|
overtime_hours = Column(Float, nullable=True, default=0.0)
|
|
|
|
# Staff and equipment
|
|
staff_count = Column(Integer, nullable=False)
|
|
equipment_capacity = Column(JSON, nullable=True) # Equipment availability
|
|
station_assignments = Column(JSON, nullable=True) # Station schedules
|
|
|
|
# Production metrics
|
|
total_batches_planned = Column(Integer, nullable=False, default=0)
|
|
total_batches_completed = Column(Integer, nullable=True, default=0)
|
|
total_quantity_planned = Column(Float, nullable=False, default=0.0)
|
|
total_quantity_produced = Column(Float, nullable=True, default=0.0)
|
|
|
|
# Status tracking
|
|
is_finalized = Column(Boolean, default=False)
|
|
is_active = Column(Boolean, default=True)
|
|
|
|
# Performance metrics
|
|
efficiency_percentage = Column(Float, nullable=True)
|
|
utilization_percentage = Column(Float, nullable=True)
|
|
on_time_completion_rate = Column(Float, nullable=True)
|
|
|
|
# Notes and adjustments
|
|
schedule_notes = Column(Text, nullable=True)
|
|
schedule_adjustments = Column(JSON, nullable=True) # Track changes made
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
finalized_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"schedule_date": self.schedule_date.isoformat() if self.schedule_date else None,
|
|
"shift_start": self.shift_start.isoformat() if self.shift_start else None,
|
|
"shift_end": self.shift_end.isoformat() if self.shift_end else None,
|
|
"total_capacity_hours": self.total_capacity_hours,
|
|
"planned_capacity_hours": self.planned_capacity_hours,
|
|
"actual_capacity_hours": self.actual_capacity_hours,
|
|
"overtime_hours": self.overtime_hours,
|
|
"staff_count": self.staff_count,
|
|
"equipment_capacity": self.equipment_capacity,
|
|
"station_assignments": self.station_assignments,
|
|
"total_batches_planned": self.total_batches_planned,
|
|
"total_batches_completed": self.total_batches_completed,
|
|
"total_quantity_planned": self.total_quantity_planned,
|
|
"total_quantity_produced": self.total_quantity_produced,
|
|
"is_finalized": self.is_finalized,
|
|
"is_active": self.is_active,
|
|
"efficiency_percentage": self.efficiency_percentage,
|
|
"utilization_percentage": self.utilization_percentage,
|
|
"on_time_completion_rate": self.on_time_completion_rate,
|
|
"schedule_notes": self.schedule_notes,
|
|
"schedule_adjustments": self.schedule_adjustments,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
"finalized_at": self.finalized_at.isoformat() if self.finalized_at else None,
|
|
}
|
|
|
|
|
|
class ProductionCapacity(Base):
|
|
"""Production capacity model for tracking equipment and resource availability"""
|
|
__tablename__ = "production_capacity"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Capacity definition
|
|
resource_type = Column(String(50), nullable=False) # equipment, staff, station
|
|
resource_id = Column(String(100), nullable=False)
|
|
resource_name = Column(String(255), nullable=False)
|
|
|
|
# Time period
|
|
date = Column(DateTime(timezone=True), nullable=False, index=True)
|
|
start_time = Column(DateTime(timezone=True), nullable=False)
|
|
end_time = Column(DateTime(timezone=True), nullable=False)
|
|
|
|
# Capacity metrics
|
|
total_capacity_units = Column(Float, nullable=False) # Total available capacity
|
|
allocated_capacity_units = Column(Float, nullable=False, default=0.0)
|
|
remaining_capacity_units = Column(Float, nullable=False)
|
|
|
|
# Status
|
|
is_available = Column(Boolean, default=True)
|
|
is_maintenance = Column(Boolean, default=False)
|
|
is_reserved = Column(Boolean, default=False)
|
|
|
|
# Equipment specific
|
|
equipment_type = Column(String(100), nullable=True)
|
|
max_batch_size = Column(Float, nullable=True)
|
|
min_batch_size = Column(Float, nullable=True)
|
|
setup_time_minutes = Column(Integer, nullable=True)
|
|
cleanup_time_minutes = Column(Integer, nullable=True)
|
|
|
|
# Performance tracking
|
|
efficiency_rating = Column(Float, nullable=True)
|
|
maintenance_status = Column(String(50), nullable=True)
|
|
last_maintenance_date = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Notes
|
|
notes = Column(Text, nullable=True)
|
|
restrictions = Column(JSON, nullable=True) # Product type restrictions, etc.
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"resource_type": self.resource_type,
|
|
"resource_id": self.resource_id,
|
|
"resource_name": self.resource_name,
|
|
"date": self.date.isoformat() if self.date else None,
|
|
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
"end_time": self.end_time.isoformat() if self.end_time else None,
|
|
"total_capacity_units": self.total_capacity_units,
|
|
"allocated_capacity_units": self.allocated_capacity_units,
|
|
"remaining_capacity_units": self.remaining_capacity_units,
|
|
"is_available": self.is_available,
|
|
"is_maintenance": self.is_maintenance,
|
|
"is_reserved": self.is_reserved,
|
|
"equipment_type": self.equipment_type,
|
|
"max_batch_size": self.max_batch_size,
|
|
"min_batch_size": self.min_batch_size,
|
|
"setup_time_minutes": self.setup_time_minutes,
|
|
"cleanup_time_minutes": self.cleanup_time_minutes,
|
|
"efficiency_rating": self.efficiency_rating,
|
|
"maintenance_status": self.maintenance_status,
|
|
"last_maintenance_date": self.last_maintenance_date.isoformat() if self.last_maintenance_date else None,
|
|
"notes": self.notes,
|
|
"restrictions": self.restrictions,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
|
|
class QualityCheckTemplate(Base):
|
|
"""Quality check templates for tenant-specific quality standards"""
|
|
__tablename__ = "quality_check_templates"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Template identification
|
|
name = Column(String(255), nullable=False)
|
|
template_code = Column(String(100), nullable=True, index=True)
|
|
check_type = Column(String(50), nullable=False) # visual, measurement, temperature, weight, boolean
|
|
category = Column(String(100), nullable=True) # appearance, structure, texture, etc.
|
|
|
|
# Template configuration
|
|
description = Column(Text, nullable=True)
|
|
instructions = Column(Text, nullable=True)
|
|
parameters = Column(JSON, nullable=True) # Dynamic check parameters
|
|
thresholds = Column(JSON, nullable=True) # Pass/fail criteria
|
|
scoring_criteria = Column(JSON, nullable=True) # Scoring methodology
|
|
|
|
# Configurability settings
|
|
is_active = Column(Boolean, default=True)
|
|
is_required = Column(Boolean, default=False)
|
|
is_critical = Column(Boolean, default=False) # Critical failures block production
|
|
weight = Column(Float, default=1.0) # Weight in overall quality score
|
|
|
|
# Measurement specifications
|
|
min_value = Column(Float, nullable=True)
|
|
max_value = Column(Float, nullable=True)
|
|
target_value = Column(Float, nullable=True)
|
|
unit = Column(String(20), nullable=True)
|
|
tolerance_percentage = Column(Float, nullable=True)
|
|
|
|
# Process stage applicability
|
|
applicable_stages = Column(JSON, nullable=True) # List of ProcessStage values
|
|
|
|
# Metadata
|
|
created_by = Column(UUID(as_uuid=True), nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"name": self.name,
|
|
"template_code": self.template_code,
|
|
"check_type": self.check_type,
|
|
"category": self.category,
|
|
"description": self.description,
|
|
"instructions": self.instructions,
|
|
"parameters": self.parameters,
|
|
"thresholds": self.thresholds,
|
|
"scoring_criteria": self.scoring_criteria,
|
|
"is_active": self.is_active,
|
|
"is_required": self.is_required,
|
|
"is_critical": self.is_critical,
|
|
"weight": self.weight,
|
|
"min_value": self.min_value,
|
|
"max_value": self.max_value,
|
|
"target_value": self.target_value,
|
|
"unit": self.unit,
|
|
"tolerance_percentage": self.tolerance_percentage,
|
|
"applicable_stages": self.applicable_stages,
|
|
"created_by": str(self.created_by),
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
|
|
class QualityCheck(Base):
|
|
"""Quality check model for tracking production quality metrics with stage support"""
|
|
__tablename__ = "quality_checks"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
batch_id = Column(UUID(as_uuid=True), nullable=False, index=True) # FK to ProductionBatch
|
|
template_id = Column(UUID(as_uuid=True), nullable=True, index=True) # FK to QualityCheckTemplate
|
|
|
|
# Check information
|
|
check_type = Column(String(50), nullable=False) # visual, weight, temperature, etc.
|
|
process_stage = Column(SQLEnum(ProcessStage), nullable=True, index=True) # Stage when check was performed
|
|
check_time = Column(DateTime(timezone=True), nullable=False)
|
|
checker_id = Column(String(100), nullable=True) # Staff member who performed check
|
|
|
|
# Quality metrics
|
|
quality_score = Column(Float, nullable=False) # 1-10 scale
|
|
pass_fail = Column(Boolean, nullable=False)
|
|
defect_count = Column(Integer, nullable=False, default=0)
|
|
defect_types = Column(JSON, nullable=True) # List of defect categories
|
|
|
|
# Measurements
|
|
measured_weight = Column(Float, nullable=True)
|
|
measured_temperature = Column(Float, nullable=True)
|
|
measured_moisture = Column(Float, nullable=True)
|
|
measured_dimensions = Column(JSON, nullable=True)
|
|
stage_specific_data = Column(JSON, nullable=True) # Stage-specific measurements
|
|
|
|
# Standards comparison
|
|
target_weight = Column(Float, nullable=True)
|
|
target_temperature = Column(Float, nullable=True)
|
|
target_moisture = Column(Float, nullable=True)
|
|
tolerance_percentage = Column(Float, nullable=True)
|
|
|
|
# Results
|
|
within_tolerance = Column(Boolean, nullable=True)
|
|
corrective_action_needed = Column(Boolean, default=False)
|
|
corrective_actions = Column(JSON, nullable=True)
|
|
|
|
# Template-based results
|
|
template_results = Column(JSON, nullable=True) # Results from template-based checks
|
|
criteria_scores = Column(JSON, nullable=True) # Individual criteria scores
|
|
|
|
# Notes and documentation
|
|
check_notes = Column(Text, nullable=True)
|
|
photos_urls = Column(JSON, nullable=True) # URLs to quality check photos
|
|
certificate_url = Column(String(500), nullable=True)
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"batch_id": str(self.batch_id),
|
|
"check_type": self.check_type,
|
|
"check_time": self.check_time.isoformat() if self.check_time else None,
|
|
"checker_id": self.checker_id,
|
|
"quality_score": self.quality_score,
|
|
"pass_fail": self.pass_fail,
|
|
"defect_count": self.defect_count,
|
|
"defect_types": self.defect_types,
|
|
"measured_weight": self.measured_weight,
|
|
"measured_temperature": self.measured_temperature,
|
|
"measured_moisture": self.measured_moisture,
|
|
"measured_dimensions": self.measured_dimensions,
|
|
"target_weight": self.target_weight,
|
|
"target_temperature": self.target_temperature,
|
|
"target_moisture": self.target_moisture,
|
|
"tolerance_percentage": self.tolerance_percentage,
|
|
"within_tolerance": self.within_tolerance,
|
|
"corrective_action_needed": self.corrective_action_needed,
|
|
"corrective_actions": self.corrective_actions,
|
|
"check_notes": self.check_notes,
|
|
"photos_urls": self.photos_urls,
|
|
"certificate_url": self.certificate_url,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
|
|
class Equipment(Base):
|
|
"""Equipment model for tracking production equipment"""
|
|
__tablename__ = "equipment"
|
|
|
|
# Primary identification
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
|
|
|
# Equipment identification
|
|
name = Column(String(255), nullable=False)
|
|
type = Column(SQLEnum(EquipmentType), nullable=False)
|
|
model = Column(String(100), nullable=True)
|
|
serial_number = Column(String(100), nullable=True)
|
|
location = Column(String(255), nullable=True)
|
|
|
|
# Status tracking
|
|
status = Column(SQLEnum(EquipmentStatus), nullable=False, default=EquipmentStatus.OPERATIONAL)
|
|
|
|
# Dates
|
|
install_date = Column(DateTime(timezone=True), nullable=True)
|
|
last_maintenance_date = Column(DateTime(timezone=True), nullable=True)
|
|
next_maintenance_date = Column(DateTime(timezone=True), nullable=True)
|
|
maintenance_interval_days = Column(Integer, nullable=True) # Maintenance interval in days
|
|
|
|
# Performance metrics
|
|
efficiency_percentage = Column(Float, nullable=True) # Current efficiency
|
|
uptime_percentage = Column(Float, nullable=True) # Overall equipment effectiveness
|
|
energy_usage_kwh = Column(Float, nullable=True) # Current energy usage
|
|
|
|
# Specifications
|
|
power_kw = Column(Float, nullable=True) # Power in kilowatts
|
|
capacity = Column(Float, nullable=True) # Capacity (units depend on equipment type)
|
|
weight_kg = Column(Float, nullable=True) # Weight in kilograms
|
|
|
|
# Temperature monitoring
|
|
current_temperature = Column(Float, nullable=True) # Current temperature reading
|
|
target_temperature = Column(Float, nullable=True) # Target temperature
|
|
|
|
# Status
|
|
is_active = Column(Boolean, default=True)
|
|
|
|
# Notes
|
|
notes = Column(Text, nullable=True)
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert to dictionary following shared pattern"""
|
|
return {
|
|
"id": str(self.id),
|
|
"tenant_id": str(self.tenant_id),
|
|
"name": self.name,
|
|
"type": self.type.value if self.type else None,
|
|
"model": self.model,
|
|
"serial_number": self.serial_number,
|
|
"location": self.location,
|
|
"status": self.status.value if self.status else None,
|
|
"install_date": self.install_date.isoformat() if self.install_date else None,
|
|
"last_maintenance_date": self.last_maintenance_date.isoformat() if self.last_maintenance_date else None,
|
|
"next_maintenance_date": self.next_maintenance_date.isoformat() if self.next_maintenance_date else None,
|
|
"maintenance_interval_days": self.maintenance_interval_days,
|
|
"efficiency_percentage": self.efficiency_percentage,
|
|
"uptime_percentage": self.uptime_percentage,
|
|
"energy_usage_kwh": self.energy_usage_kwh,
|
|
"power_kw": self.power_kw,
|
|
"capacity": self.capacity,
|
|
"weight_kg": self.weight_kg,
|
|
"current_temperature": self.current_temperature,
|
|
"target_temperature": self.target_temperature,
|
|
"is_active": self.is_active,
|
|
"notes": self.notes,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
|
|
|