Spaces:
Sleeping
Sleeping
File size: 6,344 Bytes
c817825 223c705 c817825 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | """
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
@asynccontextmanager
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
# =============================================================================
@app.get("/health", response_model=HealthResponse, tags=["System"])
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",
)
@app.get("/", tags=["System"])
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/*"],
)
|