Download main.py from adithaf7/datax-outlet-intelligence-api: direct link, hf CLI and curl.
- Browser
- Download file 4.46 kB
-
https://huggingface.co/spaces/adithaf7/datax-outlet-intelligence-api/resolve/main/main.py
- Command line
-
hf download hf://spaces/adithaf7/datax-outlet-intelligence-api/main.py
-
curl -L -o main.py https://huggingface.co/spaces/adithaf7/datax-outlet-intelligence-api/resolve/main/main.py
4.46 kB
| """FastAPI entry-point for the Outlet Intelligence web app. | |
| Run locally: | |
| cd webapp/backend | |
| pip install -r requirements.txt | |
| cp .env.example .env # set GEMINI_API_KEY | |
| python -m uvicorn main:app --reload --port 8000 | |
| The app loads gold parquets / audit CSVs into memory once at startup and | |
| exposes the data through JSON endpoints. CORS is open in local dev so the | |
| Next.js frontend on :3000 can call it directly. | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| # Ensure routers/ and services/ are importable as top-level packages | |
| # whether the app is launched as `main:app` from webapp/backend/ or as | |
| # `backend.main:app` from webapp/. | |
| _BACKEND_DIR = Path(__file__).resolve().parent | |
| if str(_BACKEND_DIR) not in sys.path: | |
| sys.path.insert(0, str(_BACKEND_DIR)) | |
| from dotenv import load_dotenv # noqa: E402 | |
| from fastapi import FastAPI # noqa: E402 | |
| from fastapi.middleware.cors import CORSMiddleware # noqa: E402 | |
| # Always load the .env that lives next to this file, regardless of CWD. | |
| load_dotenv(_BACKEND_DIR / ".env") | |
| from routers import outlets as outlets_router # noqa: E402 | |
| from routers import dashboards as dash_router # noqa: E402 | |
| from routers import xai as xai_router # noqa: E402 | |
| from services.data_loader import get_cache # noqa: E402 | |
| async def lifespan(app: FastAPI): | |
| import os | |
| cache = get_cache() | |
| print(f"[startup] Repo root: {cache.repo_root}") | |
| print(f"[startup] outlets table: {cache.outlets_table.shape}") | |
| print(f"[startup] predictions: {cache.predictions.shape}") | |
| print(f"[startup] dormancy: {cache.dormancy.shape}") | |
| key = os.environ.get("GEMINI_API_KEY") | |
| if key: | |
| print(f"[startup] GEMINI_API_KEY loaded ({len(key)} chars) " | |
| f"using model {os.environ.get('GEMINI_MODEL', 'gemini-2.5-flash')}") | |
| else: | |
| print("[startup] WARN: GEMINI_API_KEY not set — /api/xai endpoints will 503") | |
| yield | |
| app = FastAPI( | |
| title="Outlet Intelligence API", | |
| version="0.1.0", | |
| description="Beverage outlet potential, budget allocation, and XAI narratives.", | |
| lifespan=lifespan, | |
| ) | |
| # CORS — local dev (localhost / 127.0.0.1 any port) plus deployed origins: | |
| # Vercel previews/production (*.vercel.app), the HF Space's own origin so | |
| # the FastAPI Swagger UI works in-place, and any extra origins listed in | |
| # the CORS_EXTRA_ORIGINS env var (comma-separated). The regex is intentionally | |
| # strict so we don't accept arbitrary HTTP origins from the internet. | |
| import os as _os # noqa: E402 | |
| _extra = [o.strip() for o in _os.environ.get("CORS_EXTRA_ORIGINS", "").split(",") if o.strip()] | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origin_regex=( | |
| r"https?://(localhost|127\.0\.0\.1)(:\d+)?|" | |
| r"https://[a-zA-Z0-9-]+\.vercel\.app|" | |
| r"https://[a-zA-Z0-9-]+-[a-zA-Z0-9-]+\.vercel\.app|" | |
| r"https://huggingface\.co|" | |
| r"https://[a-zA-Z0-9-]+\.hf\.space" | |
| ), | |
| allow_origins=_extra, | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| app.include_router(outlets_router.router) | |
| app.include_router(dash_router.router) | |
| app.include_router(xai_router.router) | |
| def root(): | |
| return { | |
| "service": "Outlet Intelligence API", | |
| "endpoints": [ | |
| "GET /health", | |
| "GET /api/summary", | |
| "GET /api/outlets", | |
| "GET /api/outlets/filters", | |
| "GET /api/outlets/{outlet_id}", | |
| "POST /api/xai/explain/{outlet_id}", | |
| "GET /api/budget/distributors", | |
| "GET /api/budget/channels", | |
| "GET /api/budget/outlets", | |
| "GET /api/cooler-roi/top100", | |
| "GET /api/cooler-roi/summary", | |
| "GET /api/dormancy/top", | |
| "GET /api/dormancy/bands", | |
| "GET /api/scorecard", | |
| "GET /api/territories", | |
| "GET /api/forensics", | |
| "GET /api/shap/global", | |
| ], | |
| } | |
| def health(): | |
| """Liveness probe used by the frontend on first paint.""" | |
| import os | |
| cache = get_cache() | |
| return { | |
| "status": "ok", | |
| "outlets_loaded": int(len(cache.outlets_table)), | |
| "predictions_loaded": int(len(cache.predictions)), | |
| "gemini_configured": bool(os.environ.get("GEMINI_API_KEY")), | |
| } | |