Some checks failed
CI / lint-and-test (pull_request) Has been cancelled
- 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>
149 lines
5.7 KiB
Python
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")
|