# ============================================================================= # QWHPI — Quebec Weekly Housing Price Index # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # File : api/app/routers/geographies.py # Purpose : Geography hierarchy + coverage matrix endpoints. # ============================================================================= """Geography and coverage endpoints.""" from __future__ import annotations import polars as pl from fastapi import APIRouter, Query, Request, Response from app.deps import csv_response, etag_for from app.schemas import GeographiesResponse, GeographyNode from app.services import data router = APIRouter() @router.get("/geographies", response_model=GeographiesResponse) def geographies(request: Request, response: Response): """Published series hierarchy (province → regions → municipalities).""" if etag_for(request, response): return Response(status_code=304) c = data.canonical() nodes = ( c.group_by(["geography_id", "geography_name", "geography_level"]) .agg(pl.col("property_type").unique().sort().alias("types")) .sort(["geography_level", "geography_id"]) ) return GeographiesResponse( data_vintage=data.data_vintage(), published_series=[ GeographyNode(geography_id=r["geography_id"], geography_name=r["geography_name"], geography_level=r["geography_level"], property_types=r["types"]) for r in nodes.iter_rows(named=True) ], ) @router.get("/geographies/coverage") def coverage_matrix( request: Request, response: Response, status: str | None = Query(None, pattern="^(published|conditional|not_published)$"), format: str = Query("json", pattern="^(json|csv)$"), ): """Full municipality × type coverage matrix with publication status.""" df = data.coverage() if status: df = df.filter(pl.col("coverage_status") == status) if format == "csv": return csv_response(df) if etag_for(request, response, str(status)): return Response(status_code=304) return { "coverage": df.rename({"propertyType": "property_type"}).to_dicts(), "author": "Simon-Pierre Boucher", "contact": "contact@spboucher.ai", }