# ================================================================ # services/production/app/main.py # ================================================================ """ Production Service - FastAPI Application Production planning and batch management service """ import time from fastapi import FastAPI, Request from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from app.services.production_alert_service import ProductionAlertService from app.services.production_scheduler import ProductionScheduler from app.services.production_notification_service import ProductionNotificationService from shared.service_base import StandardFastAPIService # Import standardized routers from app.api import ( internal_demo, production_batches, production_schedules, production_operations, production_dashboard, analytics, quality_templates, equipment, orchestrator, # NEW: Orchestrator integration endpoint production_orders_operations, # Tenant deletion endpoints audit, ml_insights, # ML insights endpoint batch ) from app.api.internal_alert_trigger import router as internal_alert_trigger_router class ProductionService(StandardFastAPIService): """Production Service with standardized setup""" expected_migration_version = "001_initial_schema" async def on_startup(self, app): """Custom startup logic including migration verification""" await self.verify_migrations() await super().on_startup(app) async def verify_migrations(self): """Verify database schema matches the latest migrations.""" try: async with self.database_manager.get_session() as session: result = await session.execute(text("SELECT version_num FROM alembic_version")) version = result.scalar() if version != self.expected_migration_version: self.logger.error(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") raise RuntimeError(f"Migration version mismatch: expected {self.expected_migration_version}, got {version}") self.logger.info(f"Migration verification successful: {version}") except Exception as e: self.logger.error(f"Migration verification failed: {e}") raise def __init__(self): # Define expected database tables for health checks production_expected_tables = [ 'production_batches', 'production_schedules', 'production_capacity', 'quality_check_templates', 'quality_checks', 'equipment' ] self.alert_service = None self.notification_service = None self.rabbitmq_client = None self.event_publisher = None # REMOVED: scheduler_service (replaced by Orchestrator Service) # Create custom checks for services async def check_alert_service(): """Check production alert service health""" try: return bool(self.alert_service) if self.alert_service else False except Exception as e: self.logger.error("Alert service health check failed", error=str(e)) return False super().__init__( service_name=settings.SERVICE_NAME, app_name=settings.APP_NAME, description=settings.DESCRIPTION, version=settings.VERSION, api_prefix="", # Empty because RouteBuilder already includes /api/v1 database_manager=database_manager, expected_tables=production_expected_tables, custom_health_checks={ "alert_service": check_alert_service }, enable_messaging=True # Enable messaging support ) async def _setup_messaging(self): """Setup messaging for production service using unified messaging""" from shared.messaging import UnifiedEventPublisher, RabbitMQClient try: self.rabbitmq_client = RabbitMQClient(settings.RABBITMQ_URL, service_name="production-service") await self.rabbitmq_client.connect() # Create unified event publisher self.event_publisher = UnifiedEventPublisher(self.rabbitmq_client, "production-service") self.logger.info("Production service unified messaging setup completed") except Exception as e: self.logger.error("Failed to setup production unified messaging", error=str(e)) raise async def _cleanup_messaging(self): """Cleanup messaging for production service""" try: if self.rabbitmq_client: await self.rabbitmq_client.disconnect() self.logger.info("Production service messaging cleanup completed") except Exception as e: self.logger.error("Error during production messaging cleanup", error=str(e)) async def on_startup(self, app: FastAPI): """Custom startup logic for production service""" # Initialize messaging await self._setup_messaging() # Initialize alert service with EventPublisher and database manager self.alert_service = ProductionAlertService(self.event_publisher, self.database_manager) await self.alert_service.start() self.logger.info("Production alert service started") # Initialize notification service with EventPublisher self.notification_service = ProductionNotificationService(self.event_publisher) self.logger.info("Production notification service initialized") # Initialize production scheduler with alert service and database manager self.production_scheduler = ProductionScheduler(self.alert_service, self.database_manager) await self.production_scheduler.start() self.logger.info("Production scheduler started") # Store services in app state app.state.alert_service = self.alert_service app.state.production_alert_service = self.alert_service # Also store with this name for internal trigger app.state.notification_service = self.notification_service # Notification service for state change events app.state.production_scheduler = self.production_scheduler # Store scheduler for manual triggering async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for production service""" # Stop production scheduler if hasattr(self, 'production_scheduler') and self.production_scheduler: await self.production_scheduler.stop() self.logger.info("Production scheduler stopped") # Stop alert service if self.alert_service: await self.alert_service.stop() self.logger.info("Alert service stopped") # Cleanup messaging await self._cleanup_messaging() def get_service_features(self): """Return production-specific features""" return [ "production_planning", "batch_management", "production_scheduling", "orchestrator_integration", # NEW: Orchestrator-driven scheduling "quality_control", "equipment_management", "capacity_planning", "alert_notifications" ] def setup_custom_middleware(self): """Setup custom middleware for production service""" @self.app.middleware("http") async def logging_middleware(request: Request, call_next): """Add request logging middleware""" start_time = time.time() response = await call_next(request) process_time = time.time() - start_time self.logger.info("HTTP request processed", method=request.method, url=str(request.url), status_code=response.status_code, process_time=round(process_time, 4)) return response # Create service instance service = ProductionService() # Create FastAPI app with standardized setup app = service.create_app() # Setup standard endpoints service.setup_standard_endpoints() # Setup custom middleware service.setup_custom_middleware() # Include standardized routers # NOTE: Register more specific routes before generic parameterized routes # IMPORTANT: Register audit router FIRST to avoid route matching conflicts service.add_router(audit.router) service.add_router(batch.router) service.add_router(orchestrator.router) # NEW: Orchestrator integration endpoint service.add_router(production_orders_operations.router) # Tenant deletion endpoints service.add_router(quality_templates.router) # Register first to avoid route conflicts service.add_router(equipment.router) service.add_router(production_batches.router) service.add_router(production_schedules.router) service.add_router(production_operations.router) service.add_router(production_dashboard.router) service.add_router(analytics.router) service.add_router(internal_demo.router, tags=["internal-demo"]) service.add_router(ml_insights.router) # ML insights endpoint service.add_router(ml_insights.internal_router) # Internal ML insights endpoint for demo cloning service.add_router(internal_alert_trigger_router) # Internal alert trigger for demo cloning # REMOVED: test_production_scheduler endpoint # Production scheduling is now triggered by the Orchestrator Service if __name__ == "__main__": import uvicorn uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=settings.DEBUG )