2025-08-21 20:28:14 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# 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
|
2025-11-07 17:35:38 +00:00
|
|
|
from sqlalchemy.orm import deferred
|
2025-08-21 20:28:14 +02:00
|
|
|
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"""
|
2025-09-21 07:45:19 +02:00
|
|
|
PENDING = "PENDING"
|
|
|
|
|
IN_PROGRESS = "IN_PROGRESS"
|
|
|
|
|
COMPLETED = "COMPLETED"
|
|
|
|
|
CANCELLED = "CANCELLED"
|
|
|
|
|
ON_HOLD = "ON_HOLD"
|
|
|
|
|
QUALITY_CHECK = "QUALITY_CHECK"
|
|
|
|
|
FAILED = "FAILED"
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProductionPriority(str, enum.Enum):
|
|
|
|
|
"""Production priority levels"""
|
2025-09-21 07:45:19 +02:00
|
|
|
LOW = "LOW"
|
|
|
|
|
MEDIUM = "MEDIUM"
|
|
|
|
|
HIGH = "HIGH"
|
|
|
|
|
URGENT = "URGENT"
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
|
2025-09-23 19:24:22 +02:00
|
|
|
class EquipmentStatus(str, enum.Enum):
|
|
|
|
|
"""Equipment status enumeration"""
|
|
|
|
|
OPERATIONAL = "operational"
|
|
|
|
|
MAINTENANCE = "maintenance"
|
|
|
|
|
DOWN = "down"
|
|
|
|
|
WARNING = "warning"
|
|
|
|
|
|
|
|
|
|
|
2025-09-24 16:42:23 +02:00
|
|
|
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"
|
|
|
|
|
|
|
|
|
|
|
2025-09-23 19:24:22 +02:00
|
|
|
class EquipmentType(str, enum.Enum):
|
|
|
|
|
"""Equipment type enumeration"""
|
|
|
|
|
OVEN = "oven"
|
|
|
|
|
MIXER = "mixer"
|
|
|
|
|
PROOFER = "proofer"
|
|
|
|
|
FREEZER = "freezer"
|
|
|
|
|
PACKAGING = "packaging"
|
|
|
|
|
OTHER = "other"
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# Status and priority
|
|
|
|
|
status = Column(SQLEnum(ProductionStatus), nullable=False, default=ProductionStatus.PENDING, index=True)
|
|
|
|
|
priority = Column(SQLEnum(ProductionPriority), nullable=False, default=ProductionPriority.MEDIUM)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
|
|
|
|
# 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
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
# 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)
|
2025-10-24 13:05:04 +02:00
|
|
|
waste_defect_type = Column(String(100), nullable=True) # Type of defect causing waste (burnt, misshapen, underproofed, temperature_issues, expired)
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
# 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)
|
2025-10-24 13:05:04 +02:00
|
|
|
is_ai_assisted = Column(Boolean, default=False) # Whether batch used AI forecasting/optimization
|
2025-08-21 20:28:14 +02:00
|
|
|
|
|
|
|
|
# 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)
|
feat: Complete JTBD-aligned bakery dashboard redesign
Implements comprehensive dashboard redesign based on Jobs To Be Done methodology
focused on answering: "What requires my attention right now?"
## Backend Implementation
### Dashboard Service (NEW)
- Health status calculation (green/yellow/red traffic light)
- Action queue prioritization (critical/important/normal)
- Orchestration summary with narrative format
- Production timeline transformation
- Insights calculation and consequence prediction
### API Endpoints (NEW)
- GET /dashboard/health-status - Overall bakery health indicator
- GET /dashboard/orchestration-summary - What system did automatically
- GET /dashboard/action-queue - Prioritized tasks requiring attention
- GET /dashboard/production-timeline - Today's production schedule
- GET /dashboard/insights - Key metrics (savings, inventory, waste, deliveries)
### Enhanced Models
- PurchaseOrder: Added reasoning, consequence, reasoning_data fields
- ProductionBatch: Added reasoning, reasoning_data fields
- Enables transparency into automation decisions
## Frontend Implementation
### API Hooks (NEW)
- useBakeryHealthStatus() - Real-time health monitoring
- useOrchestrationSummary() - System transparency
- useActionQueue() - Prioritized action management
- useProductionTimeline() - Production tracking
- useInsights() - Glanceable metrics
### Dashboard Components (NEW)
- HealthStatusCard: Traffic light indicator with checklist
- ActionQueueCard: Prioritized actions with reasoning/consequences
- OrchestrationSummaryCard: Narrative of what system did
- ProductionTimelineCard: Chronological production view
- InsightsGrid: 2x2 grid of key metrics
### Main Dashboard Page (REPLACED)
- Complete rewrite with mobile-first design
- All sections integrated with error handling
- Real-time refresh and quick action links
- Old dashboard backed up as DashboardPage.legacy.tsx
## Key Features
### Automation-First
- Shows what orchestrator did overnight
- Builds trust through transparency
- Explains reasoning for all automated decisions
### Action-Oriented
- Prioritizes tasks over information display
- Clear consequences for each action
- Large touch-friendly buttons
### Progressive Disclosure
- Shows 20% of info that matters 80% of time
- Expandable details when needed
- No overwhelming metrics
### Mobile-First
- One-handed operation
- Large touch targets (min 44px)
- Responsive grid layouts
### Trust-Building
- Narrative format ("I planned your day")
- Reasoning inputs transparency
- Clear status indicators
## User Segments Supported
1. Solo Bakery Owner (Primary)
- Simple health indicator
- Action checklist (max 3-5 items)
- Mobile-optimized
2. Multi-Location Owner
- Multi-tenant support (existing)
- Comparison capabilities
- Delegation ready
3. Enterprise/Central Bakery (Future)
- Network topology support
- Advanced analytics ready
## JTBD Analysis Delivered
Main Job: "Help me quickly understand bakery status and know what needs my intervention"
Emotional Jobs Addressed:
- Feel in control despite automation
- Reduce daily anxiety
- Feel competent with technology
- Trust system as safety net
Social Jobs Addressed:
- Demonstrate professional management
- Avoid being bottleneck
- Show sustainability
## Technical Stack
Backend: Python, FastAPI, SQLAlchemy, PostgreSQL
Frontend: React, TypeScript, TanStack Query, Tailwind CSS
Architecture: Microservices with circuit breakers
## Breaking Changes
- Complete dashboard page rewrite (old version backed up)
- New API endpoints require orchestrator service deployment
- Database migrations needed for reasoning fields
## Migration Required
Run migrations to add new model fields:
- purchase_orders: reasoning, consequence, reasoning_data
- production_batches: reasoning, reasoning_data
## Documentation
See DASHBOARD_REDESIGN_SUMMARY.md for complete implementation details,
JTBD analysis, success metrics, and deployment guide.
BREAKING CHANGE: Dashboard page completely redesigned with new data structures
2025-11-07 17:10:17 +00:00
|
|
|
|
|
|
|
|
# JTBD Dashboard: Reasoning and context for user transparency
|
2025-11-07 17:35:38 +00:00
|
|
|
# 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
|
feat: Complete JTBD-aligned bakery dashboard redesign
Implements comprehensive dashboard redesign based on Jobs To Be Done methodology
focused on answering: "What requires my attention right now?"
## Backend Implementation
### Dashboard Service (NEW)
- Health status calculation (green/yellow/red traffic light)
- Action queue prioritization (critical/important/normal)
- Orchestration summary with narrative format
- Production timeline transformation
- Insights calculation and consequence prediction
### API Endpoints (NEW)
- GET /dashboard/health-status - Overall bakery health indicator
- GET /dashboard/orchestration-summary - What system did automatically
- GET /dashboard/action-queue - Prioritized tasks requiring attention
- GET /dashboard/production-timeline - Today's production schedule
- GET /dashboard/insights - Key metrics (savings, inventory, waste, deliveries)
### Enhanced Models
- PurchaseOrder: Added reasoning, consequence, reasoning_data fields
- ProductionBatch: Added reasoning, reasoning_data fields
- Enables transparency into automation decisions
## Frontend Implementation
### API Hooks (NEW)
- useBakeryHealthStatus() - Real-time health monitoring
- useOrchestrationSummary() - System transparency
- useActionQueue() - Prioritized action management
- useProductionTimeline() - Production tracking
- useInsights() - Glanceable metrics
### Dashboard Components (NEW)
- HealthStatusCard: Traffic light indicator with checklist
- ActionQueueCard: Prioritized actions with reasoning/consequences
- OrchestrationSummaryCard: Narrative of what system did
- ProductionTimelineCard: Chronological production view
- InsightsGrid: 2x2 grid of key metrics
### Main Dashboard Page (REPLACED)
- Complete rewrite with mobile-first design
- All sections integrated with error handling
- Real-time refresh and quick action links
- Old dashboard backed up as DashboardPage.legacy.tsx
## Key Features
### Automation-First
- Shows what orchestrator did overnight
- Builds trust through transparency
- Explains reasoning for all automated decisions
### Action-Oriented
- Prioritizes tasks over information display
- Clear consequences for each action
- Large touch-friendly buttons
### Progressive Disclosure
- Shows 20% of info that matters 80% of time
- Expandable details when needed
- No overwhelming metrics
### Mobile-First
- One-handed operation
- Large touch targets (min 44px)
- Responsive grid layouts
### Trust-Building
- Narrative format ("I planned your day")
- Reasoning inputs transparency
- Clear status indicators
## User Segments Supported
1. Solo Bakery Owner (Primary)
- Simple health indicator
- Action checklist (max 3-5 items)
- Mobile-optimized
2. Multi-Location Owner
- Multi-tenant support (existing)
- Comparison capabilities
- Delegation ready
3. Enterprise/Central Bakery (Future)
- Network topology support
- Advanced analytics ready
## JTBD Analysis Delivered
Main Job: "Help me quickly understand bakery status and know what needs my intervention"
Emotional Jobs Addressed:
- Feel in control despite automation
- Reduce daily anxiety
- Feel competent with technology
- Trust system as safety net
Social Jobs Addressed:
- Demonstrate professional management
- Avoid being bottleneck
- Show sustainability
## Technical Stack
Backend: Python, FastAPI, SQLAlchemy, PostgreSQL
Frontend: React, TypeScript, TanStack Query, Tailwind CSS
Architecture: Microservices with circuit breakers
## Breaking Changes
- Complete dashboard page rewrite (old version backed up)
- New API endpoints require orchestrator service deployment
- Database migrations needed for reasoning fields
## Migration Required
Run migrations to add new model fields:
- purchase_orders: reasoning, consequence, reasoning_data
- production_batches: reasoning, reasoning_data
## Documentation
See DASHBOARD_REDESIGN_SUMMARY.md for complete implementation details,
JTBD analysis, success metrics, and deployment guide.
BREAKING CHANGE: Dashboard page completely redesigned with new data structures
2025-11-07 17:10:17 +00:00
|
|
|
# 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"
|
|
|
|
|
# }
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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,
|
2025-10-24 13:05:04 +02:00
|
|
|
"waste_defect_type": self.waste_defect_type,
|
2025-08-21 20:28:14 +02:00
|
|
|
"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,
|
2025-10-24 13:05:04 +02:00
|
|
|
"is_ai_assisted": self.is_ai_assisted,
|
2025-08-21 20:28:14 +02:00
|
|
|
"production_notes": self.production_notes,
|
|
|
|
|
"quality_notes": self.quality_notes,
|
|
|
|
|
"delay_reason": self.delay_reason,
|
|
|
|
|
"cancellation_reason": self.cancellation_reason,
|
feat: Complete JTBD-aligned bakery dashboard redesign
Implements comprehensive dashboard redesign based on Jobs To Be Done methodology
focused on answering: "What requires my attention right now?"
## Backend Implementation
### Dashboard Service (NEW)
- Health status calculation (green/yellow/red traffic light)
- Action queue prioritization (critical/important/normal)
- Orchestration summary with narrative format
- Production timeline transformation
- Insights calculation and consequence prediction
### API Endpoints (NEW)
- GET /dashboard/health-status - Overall bakery health indicator
- GET /dashboard/orchestration-summary - What system did automatically
- GET /dashboard/action-queue - Prioritized tasks requiring attention
- GET /dashboard/production-timeline - Today's production schedule
- GET /dashboard/insights - Key metrics (savings, inventory, waste, deliveries)
### Enhanced Models
- PurchaseOrder: Added reasoning, consequence, reasoning_data fields
- ProductionBatch: Added reasoning, reasoning_data fields
- Enables transparency into automation decisions
## Frontend Implementation
### API Hooks (NEW)
- useBakeryHealthStatus() - Real-time health monitoring
- useOrchestrationSummary() - System transparency
- useActionQueue() - Prioritized action management
- useProductionTimeline() - Production tracking
- useInsights() - Glanceable metrics
### Dashboard Components (NEW)
- HealthStatusCard: Traffic light indicator with checklist
- ActionQueueCard: Prioritized actions with reasoning/consequences
- OrchestrationSummaryCard: Narrative of what system did
- ProductionTimelineCard: Chronological production view
- InsightsGrid: 2x2 grid of key metrics
### Main Dashboard Page (REPLACED)
- Complete rewrite with mobile-first design
- All sections integrated with error handling
- Real-time refresh and quick action links
- Old dashboard backed up as DashboardPage.legacy.tsx
## Key Features
### Automation-First
- Shows what orchestrator did overnight
- Builds trust through transparency
- Explains reasoning for all automated decisions
### Action-Oriented
- Prioritizes tasks over information display
- Clear consequences for each action
- Large touch-friendly buttons
### Progressive Disclosure
- Shows 20% of info that matters 80% of time
- Expandable details when needed
- No overwhelming metrics
### Mobile-First
- One-handed operation
- Large touch targets (min 44px)
- Responsive grid layouts
### Trust-Building
- Narrative format ("I planned your day")
- Reasoning inputs transparency
- Clear status indicators
## User Segments Supported
1. Solo Bakery Owner (Primary)
- Simple health indicator
- Action checklist (max 3-5 items)
- Mobile-optimized
2. Multi-Location Owner
- Multi-tenant support (existing)
- Comparison capabilities
- Delegation ready
3. Enterprise/Central Bakery (Future)
- Network topology support
- Advanced analytics ready
## JTBD Analysis Delivered
Main Job: "Help me quickly understand bakery status and know what needs my intervention"
Emotional Jobs Addressed:
- Feel in control despite automation
- Reduce daily anxiety
- Feel competent with technology
- Trust system as safety net
Social Jobs Addressed:
- Demonstrate professional management
- Avoid being bottleneck
- Show sustainability
## Technical Stack
Backend: Python, FastAPI, SQLAlchemy, PostgreSQL
Frontend: React, TypeScript, TanStack Query, Tailwind CSS
Architecture: Microservices with circuit breakers
## Breaking Changes
- Complete dashboard page rewrite (old version backed up)
- New API endpoints require orchestrator service deployment
- Database migrations needed for reasoning fields
## Migration Required
Run migrations to add new model fields:
- purchase_orders: reasoning, consequence, reasoning_data
- production_batches: reasoning, reasoning_data
## Documentation
See DASHBOARD_REDESIGN_SUMMARY.md for complete implementation details,
JTBD analysis, success metrics, and deployment guide.
BREAKING CHANGE: Dashboard page completely redesigned with new data structures
2025-11-07 17:10:17 +00:00
|
|
|
"reasoning": self.reasoning,
|
|
|
|
|
"reasoning_data": self.reasoning_data,
|
2025-08-21 20:28:14 +02:00
|
|
|
"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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-09-23 19:24:22 +02:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
class QualityCheck(Base):
|
2025-09-23 19:24:22 +02:00
|
|
|
"""Quality check model for tracking production quality metrics with stage support"""
|
2025-08-21 20:28:14 +02:00
|
|
|
__tablename__ = "quality_checks"
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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
|
2025-09-23 19:24:22 +02:00
|
|
|
template_id = Column(UUID(as_uuid=True), nullable=True, index=True) # FK to QualityCheckTemplate
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# Check information
|
|
|
|
|
check_type = Column(String(50), nullable=False) # visual, weight, temperature, etc.
|
2025-09-23 19:24:22 +02:00
|
|
|
process_stage = Column(SQLEnum(ProcessStage), nullable=True, index=True) # Stage when check was performed
|
2025-08-21 20:28:14 +02:00
|
|
|
check_time = Column(DateTime(timezone=True), nullable=False)
|
|
|
|
|
checker_id = Column(String(100), nullable=True) # Staff member who performed check
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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)
|
2025-09-23 19:24:22 +02:00
|
|
|
stage_specific_data = Column(JSON, nullable=True) # Stage-specific measurements
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# Results
|
|
|
|
|
within_tolerance = Column(Boolean, nullable=True)
|
|
|
|
|
corrective_action_needed = Column(Boolean, default=False)
|
|
|
|
|
corrective_actions = Column(JSON, nullable=True)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
|
|
|
|
# Template-based results
|
|
|
|
|
template_results = Column(JSON, nullable=True) # Results from template-based checks
|
|
|
|
|
criteria_scores = Column(JSON, nullable=True) # Individual criteria scores
|
|
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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)
|
2025-09-23 19:24:22 +02:00
|
|
|
|
2025-08-21 20:28:14 +02:00
|
|
|
# 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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-09-23 19:24:22 +02:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|