# ================================================================ # services/forecasting/app/main.py # ================================================================ """ Forecasting Service Main Application Demand prediction and forecasting service for bakery operations """ from fastapi import FastAPI from sqlalchemy import text from app.core.config import settings from app.core.database import database_manager from app.services.forecasting_alert_service import ForecastingAlertService from shared.service_base import StandardFastAPIService # Import API routers from app.api import forecasts, forecasting_operations, analytics, scenario_operations, audit, ml_insights, validation, historical_validation, webhooks, performance_monitoring, retraining, enterprise_forecasting, internal_demo class ForecastingService(StandardFastAPIService): """Forecasting Service with standardized setup""" expected_migration_version = "00003" 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 forecasting_expected_tables = [ 'forecasts', 'prediction_batches', 'model_performance_metrics', 'prediction_cache', 'validation_runs', 'sales_data_updates' ] self.alert_service = None self.rabbitmq_client = None self.event_publisher = None # Create custom checks for alert service async def alert_service_check(): """Custom health check for forecasting alert service""" return await self.alert_service.health_check() if self.alert_service else False # Define custom metrics for forecasting service forecasting_custom_metrics = { "forecasts_generated_total": { "type": "counter", "description": "Total forecasts generated" }, "predictions_served_total": { "type": "counter", "description": "Total predictions served" }, "prediction_errors_total": { "type": "counter", "description": "Total prediction errors" }, "forecast_processing_time_seconds": { "type": "histogram", "description": "Time to process forecast request" }, "prediction_processing_time_seconds": { "type": "histogram", "description": "Time to process prediction request" }, "model_cache_hits_total": { "type": "counter", "description": "Total model cache hits" }, "model_cache_misses_total": { "type": "counter", "description": "Total model cache misses" } } super().__init__( service_name="forecasting-service", app_name="Bakery Forecasting Service", description="AI-powered demand prediction and forecasting service for bakery operations", version="1.0.0", log_level=settings.LOG_LEVEL, cors_origins=settings.CORS_ORIGINS_LIST, api_prefix="", # Empty because RouteBuilder already includes /api/v1 database_manager=database_manager, expected_tables=forecasting_expected_tables, custom_health_checks={"alert_service": alert_service_check}, enable_messaging=True, custom_metrics=forecasting_custom_metrics ) async def _setup_messaging(self): """Setup messaging for forecasting service using unified messaging""" from shared.messaging import UnifiedEventPublisher, RabbitMQClient try: self.rabbitmq_client = RabbitMQClient(settings.RABBITMQ_URL, service_name="forecasting-service") await self.rabbitmq_client.connect() # Create unified event publisher self.event_publisher = UnifiedEventPublisher(self.rabbitmq_client, "forecasting-service") self.logger.info("Forecasting service unified messaging setup completed") except Exception as e: self.logger.error("Failed to setup forecasting unified messaging", error=str(e)) raise async def _cleanup_messaging(self): """Cleanup messaging for forecasting service""" try: if self.rabbitmq_client: await self.rabbitmq_client.disconnect() self.logger.info("Forecasting service messaging cleanup completed") except Exception as e: self.logger.error("Error during forecasting messaging cleanup", error=str(e)) async def on_startup(self, app: FastAPI): """Custom startup logic for forecasting service""" await super().on_startup(app) # Initialize forecasting alert service with EventPublisher if self.event_publisher: self.alert_service = ForecastingAlertService(self.event_publisher) await self.alert_service.start() self.logger.info("Forecasting alert service initialized") else: self.logger.error("Event publisher not initialized, alert service unavailable") async def on_shutdown(self, app: FastAPI): """Custom shutdown logic for forecasting service""" # Cleanup alert service if self.alert_service: await self.alert_service.stop() self.logger.info("Alert service cleanup completed") def get_service_features(self): """Return forecasting-specific features""" return [ "demand_prediction", "ai_forecasting", "model_performance_tracking", "prediction_caching", "alert_notifications", "messaging_integration" ] def setup_custom_endpoints(self): """Setup custom endpoints for forecasting service""" @self.app.get("/alert-metrics") async def get_alert_metrics(): """Alert service metrics endpoint""" if self.alert_service: return self.alert_service.get_metrics() return {"error": "Alert service not initialized"} # Create service instance service = ForecastingService() # Create FastAPI app with standardized setup app = service.create_app( docs_url="/docs", redoc_url="/redoc" ) # Setup standard endpoints service.setup_standard_endpoints() # Setup custom endpoints service.setup_custom_endpoints() # Include API routers # IMPORTANT: Register audit router FIRST to avoid route matching conflicts service.add_router(audit.router) service.add_router(forecasts.router) service.add_router(forecasting_operations.router) service.add_router(analytics.router) service.add_router(scenario_operations.router) service.add_router(internal_demo.router, tags=["internal-demo"]) service.add_router(ml_insights.router) # ML insights endpoint service.add_router(validation.router) # Validation endpoint service.add_router(historical_validation.router) # Historical validation endpoint service.add_router(webhooks.router) # Webhooks endpoint service.add_router(performance_monitoring.router) # Performance monitoring endpoint service.add_router(retraining.router) # Retraining endpoint service.add_router(enterprise_forecasting.router) # Enterprise forecasting endpoint if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)