Spaces:
Sleeping
Sleeping
| """ | |
| AQI Intelligence Engine — FastAPI Main Application | |
| The central entry point that: | |
| 1. Loads all AI models on startup | |
| 2. Registers all API routers | |
| 3. Serves both locally and on HuggingFace Spaces (Docker) | |
| """ | |
| import logging | |
| import sys | |
| import io | |
| # Fix Windows console encoding for emoji rendering | |
| if sys.platform.startswith("win"): | |
| try: | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') | |
| sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') | |
| except AttributeError: | |
| pass | |
| import time | |
| from contextlib import asynccontextmanager | |
| from datetime import datetime, timezone | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from config import SERVER_CONFIG, DEVICE | |
| class HealthResponse(BaseModel): | |
| status: str = "ok" | |
| models_loaded: bool = False | |
| device: str = "cpu" | |
| timestamp: datetime = Field(default_factory=datetime.utcnow) | |
| version: str = "2.0.0" | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| handlers=[logging.StreamHandler(sys.stdout)], | |
| ) | |
| logger = logging.getLogger("aqi_engine") | |
| # Track model loading state | |
| models_loaded = False | |
| startup_time = None | |
| async def lifespan(app: FastAPI): | |
| """Load AI models on startup, clean up on shutdown.""" | |
| global models_loaded, startup_time | |
| logger.info("=" * 60) | |
| logger.info("🚀 AQI Intelligence Engine — Starting Up") | |
| logger.info(f" Device: {DEVICE}") | |
| logger.info("=" * 60) | |
| import asyncio | |
| import anyio | |
| async def load_models_bg(): | |
| global models_loaded, startup_time | |
| bg_start = time.time() | |
| try: | |
| from services.forecast.service import load_model as load_forecast | |
| await anyio.to_thread.run_sync(load_forecast) | |
| logger.info("✅ Forecast model loaded successfully") | |
| except Exception as e: | |
| logger.warning(f"Forecast model loading deferred: {e}") | |
| # Heavy vision/segmentation models are loaded on-demand to prevent OOM crashes in container | |
| logger.info("💤 Vision & Segmentation models deferred to on-demand loading") | |
| elapsed = round(time.time() - bg_start, 2) | |
| models_loaded = True | |
| startup_time = elapsed | |
| logger.info(f"✅ Startup models loaded in {elapsed}s") | |
| # Start loading in background without blocking port binding | |
| asyncio.create_task(load_models_bg()) | |
| logger.info(f"📡 API docs: http://localhost:{SERVER_CONFIG['port']}/docs") | |
| logger.info("=" * 60) | |
| yield # Application runs here | |
| logger.info("Shutting down AQI Intelligence Engine...") | |
| # ============================================================================= | |
| # FastAPI Application | |
| # ============================================================================= | |
| app = FastAPI( | |
| title="AQI Intelligence Engine", | |
| description=( | |
| "AI-powered Urban Air Quality Intelligence Platform for Smart City Intervention. " | |
| "Provides AQI forecasting (TimesFM 2.5), satellite vision analysis (Florence-2 + " | |
| "Grounding DINO), segmentation (SAM2), geospatial analysis, hotspot detection, " | |
| "atmospheric dispersion modeling, source attribution, health risk assessment, " | |
| "and inspector route optimization." | |
| ), | |
| version="2.0.0", | |
| lifespan=lifespan, | |
| ) | |
| # ============================================================================= | |
| # CORS Middleware (allow Next.js frontend to call this API) | |
| # ============================================================================= | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # In production: restrict to your Next.js domain | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ============================================================================= | |
| # Register Routers | |
| # ============================================================================= | |
| from routers.services import services_router | |
| app.include_router(services_router, prefix="/api/v1") | |
| app.include_router(services_router, prefix="/api") | |
| app.include_router(services_router) | |
| # ============================================================================= | |
| # Mount Frontend Static Files & Serve index.html at Root | |
| # ============================================================================= | |
| from fastapi.staticfiles import StaticFiles | |
| from fastapi.responses import FileResponse | |
| import os | |
| # Create frontend directory if it doesn't exist yet | |
| os.makedirs("frontend", exist_ok=True) | |
| # Mount the static directory | |
| app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend") | |
| # ============================================================================= | |
| # Health Check | |
| # ============================================================================= | |
| async def health_check(): | |
| """Health check endpoint — verifies the server and models are running.""" | |
| return HealthResponse( | |
| status="ok", | |
| models_loaded=models_loaded, | |
| device=DEVICE, | |
| timestamp=datetime.now(timezone.utc), | |
| version="2.0.0", | |
| ) | |
| async def root(): | |
| """Root endpoint serving the interactive control center dashboard.""" | |
| index_path = os.path.join("frontend", "index.html") | |
| if os.path.exists(index_path): | |
| return FileResponse(index_path) | |
| return { | |
| "name": "AQI Intelligence Engine", | |
| "version": "2.0.0", | |
| "message": "Frontend dashboard not built yet. Visit /docs for APIs.", | |
| "docs": "/docs", | |
| "health": "/health" | |
| } | |
| # ============================================================================= | |
| # Run with: uvicorn main:app --reload --port 7860 | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| "main:app", | |
| host=SERVER_CONFIG["host"], | |
| port=SERVER_CONFIG["port"], | |
| reload=SERVER_CONFIG["reload"], | |
| workers=SERVER_CONFIG["workers"], | |
| reload_excludes=["models/*", "**/models/*", "*.json", "frontend/*"], | |
| ) | |