"""Pydantic schemas for CMHC rental market data.""" from decimal import Decimal from enum import Enum from pydantic import BaseModel, Field class BedroomType(str, Enum): """CMHC bedroom type categories.""" BACHELOR = "Bachelor" ONE_BED = "1 Bedroom" TWO_BED = "2 Bedroom" THREE_BED_PLUS = "3 Bedroom+" TOTAL = "Total" class ReliabilityCode(str, Enum): """CMHC data reliability codes. Based on coefficient of variation (CV). """ EXCELLENT = "a" # CV <= 2.5% GOOD = "b" # 2.5% < CV <= 5% FAIR = "c" # 5% < CV <= 10% POOR = "d" # CV > 10% SUPPRESSED = "**" # Sample too small class CMHCRentalRecord(BaseModel): """Schema for a single CMHC rental survey record. Represents rental data for one zone and bedroom type in one survey year. """ survey_year: int = Field(ge=1990, description="Survey year (October snapshot)") zone_code: str = Field(max_length=10, description="CMHC zone identifier") zone_name: str = Field(max_length=100, description="Zone name") bedroom_type: BedroomType = Field(description="Bedroom category") universe: int | None = Field( default=None, ge=0, description="Total rental units in zone" ) vacancy_rate: Decimal | None = Field( default=None, ge=0, le=100, description="Vacancy rate (%)" ) vacancy_rate_reliability: ReliabilityCode | None = Field(default=None) availability_rate: Decimal | None = Field( default=None, ge=0, le=100, description="Availability rate (%)" ) average_rent: Decimal | None = Field( default=None, ge=0, description="Average monthly rent ($)" ) average_rent_reliability: ReliabilityCode | None = Field(default=None) median_rent: Decimal | None = Field( default=None, ge=0, description="Median monthly rent ($)" ) rent_change_pct: Decimal | None = Field( default=None, description="YoY rent change (%)" ) turnover_rate: Decimal | None = Field( default=None, ge=0, le=100, description="Unit turnover rate (%)" ) model_config = {"str_strip_whitespace": True} class CMHCAnnualSurvey(BaseModel): """Schema for a complete CMHC annual survey for Toronto. Contains all zone and bedroom type combinations for one survey year. """ survey_year: int records: list[CMHCRentalRecord] @property def zone_count(self) -> int: """Number of unique zones in survey.""" return len({r.zone_code for r in self.records})