Files
l3ocho 62d1a52eed
Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
refactor: multi-dashboard structural migration
- Rename dbt project from toronto_housing to portfolio
- Restructure dbt models into domain subdirectories:
  - shared/ for cross-domain dimensions (dim_time)
  - staging/toronto/, intermediate/toronto/, marts/toronto/
- Update SQLAlchemy models for raw_toronto schema
- Add explicit cross-schema FK relationships for FactRentals
- Namespace figure factories under figures/toronto/
- Namespace notebooks under notebooks/toronto/
- Update Makefile with domain-specific targets and env loading
- Update all documentation for multi-dashboard structure

This enables adding new dashboard projects (e.g., /football, /energy)
without structural conflicts or naming collisions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 19:08:20 -05:00

149 lines
5.7 KiB
Python

"""SQLAlchemy models for fact tables."""
from sqlalchemy import ForeignKey, Index, Integer, Numeric, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base
from .dimensions import RAW_TORONTO_SCHEMA
class BridgeCMHCNeighbourhood(Base):
"""Bridge table for CMHC zone to neighbourhood mapping with area weights.
Enables disaggregation of CMHC zone-level rental data to neighbourhood level
using area-based proportional weights computed via PostGIS.
"""
__tablename__ = "bridge_cmhc_neighbourhood"
__table_args__ = (
Index("ix_bridge_cmhc_zone", "cmhc_zone_code"),
Index("ix_bridge_neighbourhood", "neighbourhood_id"),
{"schema": RAW_TORONTO_SCHEMA},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
cmhc_zone_code: Mapped[str] = mapped_column(String(10), nullable=False)
neighbourhood_id: Mapped[int] = mapped_column(Integer, nullable=False)
weight: Mapped[float] = mapped_column(
Numeric(5, 4), nullable=False
) # 0.0000 to 1.0000
class FactCensus(Base):
"""Census statistics by neighbourhood and year.
Grain: One row per neighbourhood per census year.
"""
__tablename__ = "fact_census"
__table_args__ = (
Index("ix_fact_census_neighbourhood_year", "neighbourhood_id", "census_year"),
{"schema": RAW_TORONTO_SCHEMA},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
neighbourhood_id: Mapped[int] = mapped_column(Integer, nullable=False)
census_year: Mapped[int] = mapped_column(Integer, nullable=False)
population: Mapped[int | None] = mapped_column(Integer, nullable=True)
population_density: Mapped[float | None] = mapped_column(
Numeric(10, 2), nullable=True
)
median_household_income: Mapped[float | None] = mapped_column(
Numeric(12, 2), nullable=True
)
average_household_income: Mapped[float | None] = mapped_column(
Numeric(12, 2), nullable=True
)
unemployment_rate: Mapped[float | None] = mapped_column(
Numeric(5, 2), nullable=True
)
pct_bachelors_or_higher: Mapped[float | None] = mapped_column(
Numeric(5, 2), nullable=True
)
pct_owner_occupied: Mapped[float | None] = mapped_column(
Numeric(5, 2), nullable=True
)
pct_renter_occupied: Mapped[float | None] = mapped_column(
Numeric(5, 2), nullable=True
)
median_age: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True)
average_dwelling_value: Mapped[float | None] = mapped_column(
Numeric(12, 2), nullable=True
)
class FactCrime(Base):
"""Crime statistics by neighbourhood and year.
Grain: One row per neighbourhood per year per crime type.
"""
__tablename__ = "fact_crime"
__table_args__ = (
Index("ix_fact_crime_neighbourhood_year", "neighbourhood_id", "year"),
Index("ix_fact_crime_type", "crime_type"),
{"schema": RAW_TORONTO_SCHEMA},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
neighbourhood_id: Mapped[int] = mapped_column(Integer, nullable=False)
year: Mapped[int] = mapped_column(Integer, nullable=False)
crime_type: Mapped[str] = mapped_column(String(50), nullable=False)
count: Mapped[int] = mapped_column(Integer, nullable=False)
rate_per_100k: Mapped[float | None] = mapped_column(Numeric(10, 2), nullable=True)
class FactAmenities(Base):
"""Amenity counts by neighbourhood.
Grain: One row per neighbourhood per amenity type per year.
"""
__tablename__ = "fact_amenities"
__table_args__ = (
Index("ix_fact_amenities_neighbourhood_year", "neighbourhood_id", "year"),
Index("ix_fact_amenities_type", "amenity_type"),
{"schema": RAW_TORONTO_SCHEMA},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
neighbourhood_id: Mapped[int] = mapped_column(Integer, nullable=False)
amenity_type: Mapped[str] = mapped_column(String(50), nullable=False)
count: Mapped[int] = mapped_column(Integer, nullable=False)
year: Mapped[int] = mapped_column(Integer, nullable=False)
class FactRentals(Base):
"""Fact table for CMHC rental market data.
Grain: One row per zone per bedroom type per survey year.
"""
__tablename__ = "fact_rentals"
__table_args__ = {"schema": RAW_TORONTO_SCHEMA}
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
date_key: Mapped[int] = mapped_column(
Integer, ForeignKey("public.dim_time.date_key"), nullable=False
)
zone_key: Mapped[int] = mapped_column(
Integer,
ForeignKey(f"{RAW_TORONTO_SCHEMA}.dim_cmhc_zone.zone_key"),
nullable=False,
)
bedroom_type: Mapped[str] = mapped_column(String(20), nullable=False)
universe: Mapped[int | None] = mapped_column(Integer, nullable=True)
avg_rent: Mapped[float | None] = mapped_column(Numeric(10, 2), nullable=True)
median_rent: Mapped[float | None] = mapped_column(Numeric(10, 2), nullable=True)
vacancy_rate: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True)
availability_rate: Mapped[float | None] = mapped_column(
Numeric(5, 2), nullable=True
)
turnover_rate: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True)
rent_change_pct: Mapped[float | None] = mapped_column(Numeric(5, 2), nullable=True)
reliability_code: Mapped[str | None] = mapped_column(String(2), nullable=True)
# Relationships - explicit foreign_keys needed for cross-schema joins
time = relationship("DimTime", foreign_keys=[date_key], backref="rentals")
zone = relationship("DimCMHCZone", foreign_keys=[zone_key], backref="rentals")