# ============================================================================= # QWHPI — Quebec Weekly Housing Price Index # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # File : api/app/schemas.py # Purpose : Pydantic response schemas (monthly v2.1) — uncertainty and # reliability are always present, never hidden. # ============================================================================= """API response schemas — QHPI monthly.""" from __future__ import annotations from pydantic import BaseModel AUTHOR = "Simon-Pierre Boucher" CONTACT = "contact@spboucher.ai" class Credit(BaseModel): author: str = AUTHOR contact: str = CONTACT class Observation(BaseModel): period: str index: float | None index_smoothed: float representative_value: float | None transactions: int effective_sample_size: float | None monthly_pct: float | None three_month_pct: float | None six_month_pct: float | None yoy_pct: float | None lower_95: float upper_95: float reliability_grade: str shrinkage_weight: float | None is_partial_month: bool class SeriesResponse(Credit): geography: str geography_name: str geography_level: str property_type: str frequency: str = "monthly" base_period: str = "2021 average = 100" model_version: str data_vintage: str total_observations: int offset: int = 0 limit: int | None = None observations: list[Observation] class LatestResponse(Credit): geography: str geography_name: str property_type: str frequency: str = "monthly" period: str latest_index: float latest_index_raw: float | None representative_value: float | None monthly_change: float | None three_month_change: float | None yoy_change: float | None transactions: int effective_sample_size: float | None reliability: str lower_95: float upper_95: float is_partial_month: bool model_version: str data_vintage: str class GeographyNode(BaseModel): geography_id: str geography_name: str geography_level: str property_types: list[str] class GeographiesResponse(Credit): data_vintage: str published_series: list[GeographyNode] coverage_matrix_url: str = "/v1/geographies/coverage" class LiquidityPoint(BaseModel): period: str transactions: int class LiquidityResponse(Credit): geography: str property_type: str median_monthly_transactions: float observations: list[LiquidityPoint] class MapCell(BaseModel): geography_id: str geography_name: str value: float | None reliability_grade: str transactions: int class MapResponse(Credit): metric: str level: str property_type: str period: str cells: list[MapCell] class VintageObservation(BaseModel): period: str first_release_index_smoothed: float current_index_smoothed: float first_release_vintage: str current_vintage: str revision_pct: float class MetaResponse(Credit): name: str = "QHPI — Quebec Housing Price Index (monthly)" model_version: str data_vintage: str frequency: str = "monthly" base_period: str = "2021 average = 100" methodology: str = ( "Robust monthly architecture: province paths by rolling-time-dummy " "hedonic (13-month Huber-IRLS windows, mean splice — revision-free); " "liquid cells (≥40 tx/month) estimated directly by local robust " "time-dummy with state-space smoothing; thin cells shrunk toward " "their parent path via a heteroskedastic local-level Kalman model. " "Validated against repeat sales, stratified matched-cell medians, " "downsampling and composition-shock simulations." ) methodology_url: str = "/methodology" seasonal_adjustment: str = "NSA (no stable monthly seasonality detected)" series_count: int periods: int latest_complete_period: str