SPB Git

spb/qwhpi Public

QHPI — Quebec Housing Price Index: quality-adjusted, hierarchically pooled housing price indexes.

Python 63.9% TypeScript 25.4% CSS 5.5% TeX 3.5% SQL 0.8% Makefile 0.5% Dockerfile 0.5%
2.3 KB · 68 lines python
Raw Blame History
1# =============================================================================2# QWHPI — Quebec Weekly Housing Price Index3# Author  : Simon-Pierre Boucher4# Contact : contact@spboucher.ai5# File    : api/app/main.py6# Purpose : FastAPI application — routers, CORS, rate limiting, OpenAPI.7# =============================================================================8"""QWHPI API service entry point.910Run locally:  uvicorn app.main:app --reload --port 8080  (from api/)11Docs: /docs (OpenAPI). Every response credits the author and exposes12uncertainty (CIs, reliability grades) — the API never hides it.13"""1415from __future__ import annotations1617import time18from collections import defaultdict, deque1920from fastapi import FastAPI, Request21from fastapi.middleware.cors import CORSMiddleware22from fastapi.responses import JSONResponse2324from app.routers import geographies, index, liquidity, maps, meta, reports2526RATE_LIMIT = 120          # requests27RATE_WINDOW = 60.0        # seconds28_hits: dict[str, deque] = defaultdict(deque)2930app = FastAPI(31    title="QWHPI API",32    version="0.1.0",33    description=(34        "Quebec Weekly Housing Price Index — quality-adjusted, hierarchically "35        "pooled weekly indexes for Quebec geographies and property types.\n\n"36        "**Author: Simon-Pierre Boucher — contact@spboucher.ai**"37    ),38    contact={"name": "Simon-Pierre Boucher", "email": "contact@spboucher.ai"},39)4041app.add_middleware(42    CORSMiddleware,43    allow_origins=["*"],44    allow_methods=["GET"],45    allow_headers=["*"],46)474849@app.middleware("http")50async def rate_limiter(request: Request, call_next):51    ip = request.client.host if request.client else "anon"52    now = time.monotonic()53    q = _hits[ip]54    while q and now - q[0] > RATE_WINDOW:55        q.popleft()56    if len(q) >= RATE_LIMIT:57        return JSONResponse({"detail": "rate limit exceeded"}, status_code=429)58    q.append(now)59    return await call_next(request)606162app.include_router(index.router, prefix="/v1", tags=["index"])63app.include_router(reports.router, prefix="/v1", tags=["stats & reports"])64app.include_router(geographies.router, prefix="/v1", tags=["geographies"])65app.include_router(liquidity.router, prefix="/v1", tags=["liquidity"])66app.include_router(maps.router, prefix="/v1", tags=["maps"])67app.include_router(meta.router, tags=["meta"])68