# Work Breakdown Structure & Sprint Plan **Project**: Toronto Housing Dashboard (Portfolio Phase 1) **Version**: 4.1 **Date**: January 2026 --- ## Document Context | Attribute | Value | |-----------|-------| | **Parent Documents** | `portfolio_project_plan_v5.md`, `toronto_housing_dashboard_spec_v5.md` | | **Content Source** | `bio_content_v2.md` | | **Role** | Executable sprint plan for Phase 1 delivery | --- ## Milestones | Milestone | Deliverable | Target Sprint | |-----------|-------------|---------------| | **Launch 1** | Bio Landing Page | Sprint 2 | | **Launch 2** | Toronto Housing Dashboard | Sprint 6 | --- ## WBS Structure ``` 1.0 Launch 1: Bio Landing Page ├── 1.1 Project Bootstrap ├── 1.2 Infrastructure ├── 1.3 Application Foundation ├── 1.4 Bio Page └── 1.5 Deployment 2.0 Launch 2: Toronto Housing Dashboard ├── 2.1 Data Acquisition ├── 2.2 Data Processing ├── 2.3 Database Layer ├── 2.4 dbt Transformation ├── 2.5 Visualization ├── 2.6 Documentation └── 2.7 Operations ``` --- ## Launch 1: Bio Landing Page ### 1.1 Project Bootstrap | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 1.1.1 | Git repository initialization | — | Low | Low | | 1.1.2 | Create `.gitignore` | 1.1.1 | Low | Low | | 1.1.3 | Create `pyproject.toml` | 1.1.1 | Low | Low | | 1.1.4 | Create `.python-version` (3.11+) | 1.1.1 | Low | Low | | 1.1.5 | Create `.env.example` | 1.1.1 | Low | Low | | 1.1.6 | Create `README.md` (initial) | 1.1.1 | Low | Low | | 1.1.7 | Create `CLAUDE.md` | 1.1.1 | Low | Low | | 1.1.8 | Create `Makefile` with all targets | 1.1.3 | Low | Medium | ### 1.2 Infrastructure | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 1.2.1 | Python env setup (pyenv, venv, deps) | 1.1.3, 1.1.4 | Low | Low | | 1.2.2 | Create `.pre-commit-config.yaml` | 1.2.1 | Low | Low | | 1.2.3 | Install pre-commit hooks | 1.2.2 | Low | Low | | 1.2.4 | Create `docker-compose.yml` (PostgreSQL + PostGIS) | 1.1.5 | Low | Low | | 1.2.5 | Create `scripts/` directory structure | 1.1.1 | Low | Low | | 1.2.6 | Create `scripts/docker/up.sh` | 1.2.5 | Low | Low | | 1.2.7 | Create `scripts/docker/down.sh` | 1.2.5 | Low | Low | | 1.2.8 | Create `scripts/docker/logs.sh` | 1.2.5 | Low | Low | | 1.2.9 | Create `scripts/docker/rebuild.sh` | 1.2.5 | Low | Low | | 1.2.10 | Create `scripts/db/init.sh` (PostGIS extension) | 1.2.5 | Low | Low | | 1.2.11 | Create `scripts/dev/setup.sh` | 1.2.5 | Low | Low | | 1.2.12 | Verify Docker + PostGIS working | 1.2.4, 1.2.10 | Low | Low | ### 1.3 Application Foundation | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 1.3.1 | Create `portfolio_app/` directory structure (full tree) | 1.2.1 | Low | Low | | 1.3.2 | Create `portfolio_app/__init__.py` | 1.3.1 | Low | Low | | 1.3.3 | Create `portfolio_app/config.py` (Pydantic BaseSettings) | 1.3.1 | Low | Medium | | 1.3.4 | Create `portfolio_app/errors/__init__.py` | 1.3.1 | Low | Low | | 1.3.5 | Create `portfolio_app/errors/exceptions.py` | 1.3.4 | Low | Low | | 1.3.6 | Create `portfolio_app/errors/handlers.py` | 1.3.5 | Low | Medium | | 1.3.7 | Create `portfolio_app/app.py` (Dash + Pages routing) | 1.3.3 | Low | Medium | | 1.3.8 | Configure dash-mantine-components theme | 1.3.7 | Low | Low | | 1.3.9 | Create `portfolio_app/assets/` directory | 1.3.1 | Low | Low | | 1.3.10 | Create `portfolio_app/assets/styles.css` | 1.3.9 | Low | Medium | | 1.3.11 | Create `portfolio_app/assets/variables.css` | 1.3.9 | Low | Low | | 1.3.12 | Add `portfolio_app/assets/favicon.ico` | 1.3.9 | Low | Low | | 1.3.13 | Create `portfolio_app/assets/images/` directory | 1.3.9 | Low | Low | | 1.3.14 | Create `tests/` directory structure | 1.2.1 | Low | Low | | 1.3.15 | Create `tests/__init__.py` | 1.3.14 | Low | Low | | 1.3.16 | Create `tests/conftest.py` | 1.3.14 | Low | Medium | | 1.3.17 | Configure pytest in `pyproject.toml` | 1.1.3, 1.3.14 | Low | Low | ### 1.4 Bio Page | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 1.4.1 | Create `portfolio_app/components/__init__.py` | 1.3.1 | Low | Low | | 1.4.2 | Create `portfolio_app/components/navbar.py` | 1.4.1, 1.3.8 | Low | Low | | 1.4.3 | Create `portfolio_app/components/footer.py` | 1.4.1, 1.3.8 | Low | Low | | 1.4.4 | Create `portfolio_app/components/cards.py` | 1.4.1, 1.3.8 | Low | Low | | 1.4.5 | Create `portfolio_app/pages/__init__.py` | 1.3.1 | Low | Low | | 1.4.6 | Create `portfolio_app/pages/home.py` (layout) | 1.4.5, 1.4.2, 1.4.3 | Low | Low | | 1.4.7 | Integrate bio content from `bio_content_v2.md` | 1.4.6 | Low | Low | | 1.4.8 | Replace social link placeholders with real URLs | 1.4.7 | Low | Low | | 1.4.9 | Implement project cards (deployed/in-dev logic) | 1.4.4, 1.4.6 | Low | Low | | 1.4.10 | Test bio page renders locally | 1.4.9 | Low | Low | ### 1.5 Deployment | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 1.5.1 | Install PostgreSQL + PostGIS on VPS | — | Low | Low | | 1.5.2 | Configure firewall (ufw: SSH, HTTP, HTTPS) | 1.5.1 | Low | Low | | 1.5.3 | Create application database user | 1.5.1 | Low | Low | | 1.5.4 | Create Gunicorn systemd service file | 1.4.10 | Low | Low | | 1.5.5 | Configure Nginx reverse proxy | 1.5.4 | Low | Low | | 1.5.6 | Configure SSL (certbot) | 1.5.5 | Low | Low | | 1.5.7 | Create `scripts/deploy/deploy.sh` | 1.2.5 | Low | Low | | 1.5.8 | Create `scripts/deploy/health-check.sh` | 1.2.5 | Low | Low | | 1.5.9 | Deploy bio page | 1.5.6, 1.5.7 | Low | Low | | 1.5.10 | Verify HTTPS access | 1.5.9 | Low | Low | --- ## Launch 2: Toronto Housing Dashboard ### 2.1 Data Acquisition | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.1.1 | Define TRREB year scope + download PDFs | — | Low | Low | | 2.1.2 | **HUMAN**: Digitize TRREB district boundaries (QGIS) | 2.1.1 | High | High | | 2.1.3 | Register for CMHC portal | — | Low | Low | | 2.1.4 | Export CMHC Toronto rental CSVs | 2.1.3 | Low | Low | | 2.1.5 | Extract CMHC zone boundaries (R cmhc package) | 2.1.3 | Low | Medium | | 2.1.6 | Download neighbourhoods GeoJSON (158 boundaries) | — | Low | Low | | 2.1.7 | Download Neighbourhood Profiles 2021 (xlsx) | — | Low | Low | | 2.1.8 | Validate CRS alignment (all geo files WGS84) | 2.1.2, 2.1.5, 2.1.6 | Low | Medium | | 2.1.9 | Research Tier 1 policy events (10—20 events) | — | Mid | Medium | | 2.1.10 | Create `data/toronto/reference/policy_events.csv` | 2.1.9 | Low | Low | | 2.1.11 | Create `data/` directory structure | 1.3.1 | Low | Low | | 2.1.12 | Organize raw files into `data/toronto/raw/` | 2.1.11 | Low | Low | | 2.1.13 | Test TRREB parser across year boundaries | 2.2.3 | Low | Medium | ### 2.2 Data Processing | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.2.1 | Create `portfolio_app/toronto/__init__.py` | 1.3.1 | Low | Low | | 2.2.2 | Create `portfolio_app/toronto/parsers/__init__.py` | 2.2.1 | Low | Low | | 2.2.3 | Build TRREB PDF parser (`parsers/trreb.py`) | 2.2.2, 2.1.1 | Mid | High | | 2.2.4 | TRREB data cleaning/normalization | 2.2.3 | Low | Medium | | 2.2.5 | TRREB parser unit tests | 2.2.4 | Low | Low | | 2.2.6 | Build CMHC CSV processor (`parsers/cmhc.py`) | 2.2.2, 2.1.4 | Low | Low | | 2.2.7 | CMHC reliability code handling | 2.2.6 | Low | Low | | 2.2.8 | CMHC processor unit tests | 2.2.7 | Low | Low | | 2.2.9 | Build Neighbourhood Profiles parser | 2.2.1, 2.1.7 | Low | Low | | 2.2.10 | Policy events CSV loader | 2.2.1, 2.1.10 | Low | Low | ### 2.3 Database Layer | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.3.1 | Create `portfolio_app/toronto/schemas/__init__.py` | 2.2.1 | Low | Low | | 2.3.2 | Create TRREB Pydantic schemas (`schemas/trreb.py`) | 2.3.1 | Low | Medium | | 2.3.3 | Create CMHC Pydantic schemas (`schemas/cmhc.py`) | 2.3.1 | Low | Medium | | 2.3.4 | Create enrichment Pydantic schemas (`schemas/enrichment.py`) | 2.3.1 | Low | Low | | 2.3.5 | Create policy event Pydantic schema (`schemas/policy_event.py`) | 2.3.1 | Low | Low | | 2.3.6 | Create `portfolio_app/toronto/models/__init__.py` | 2.2.1 | Low | Low | | 2.3.7 | Create SQLAlchemy base (`models/base.py`) | 2.3.6, 1.3.3 | Low | Medium | | 2.3.8 | Create dimension models (`models/dimensions.py`) | 2.3.7 | Low | Medium | | 2.3.9 | Create fact models (`models/facts.py`) | 2.3.8 | Low | Medium | | 2.3.10 | Create `portfolio_app/toronto/loaders/__init__.py` | 2.2.1 | Low | Low | | 2.3.11 | Create dimension loaders (`loaders/database.py`) | 2.3.10, 2.3.8 | Low | Medium | | 2.3.12 | Create fact loaders | 2.3.11, 2.3.9, 2.2.4, 2.2.7 | Mid | Medium | | 2.3.13 | Loader integration tests | 2.3.12 | Low | Medium | | 2.3.14 | Create SQL views for dashboard queries | 2.3.12 | Low | Medium | ### 2.4 dbt Transformation | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.4.1 | Create `dbt/` directory structure | 1.3.1 | Low | Low | | 2.4.2 | Create `dbt/dbt_project.yml` | 2.4.1 | Low | Low | | 2.4.3 | Create `dbt/profiles.yml` | 2.4.1, 1.3.3 | Low | Low | | 2.4.4 | Create `scripts/dbt/run.sh` | 1.2.5 | Low | Low | | 2.4.5 | Create `scripts/dbt/test.sh` | 1.2.5 | Low | Low | | 2.4.6 | Create `scripts/dbt/docs.sh` | 1.2.5 | Low | Low | | 2.4.7 | Create `scripts/dbt/fresh.sh` | 1.2.5 | Low | Low | | 2.4.8 | Create staging models (`stg_trreb__monthly`, `stg_cmhc__rental`) | 2.4.3, 2.3.12 | Low | Medium | | 2.4.9 | Create intermediate models | 2.4.8 | Low | Medium | | 2.4.10 | Create mart models | 2.4.9 | Low | Medium | | 2.4.11 | Create dbt schema tests (unique, not_null, relationships) | 2.4.10 | Low | Medium | | 2.4.12 | Create custom dbt tests (anomaly detection) | 2.4.11 | Low | Medium | | 2.4.13 | Create dbt documentation (schema.yml) | 2.4.10 | Low | Low | ### 2.5 Visualization | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.5.1 | Create `portfolio_app/figures/__init__.py` | 1.3.1 | Low | Low | | 2.5.2 | Build choropleth factory (`figures/choropleth.py`) | 2.5.1, 2.1.8 | Mid | Medium | | 2.5.3 | Build time series factory (`figures/time_series.py`) | 2.5.1 | Low | Medium | | 2.5.4 | Build YoY change chart factory (`figures/statistical.py`) | 2.5.1 | Low | Medium | | 2.5.5 | Build seasonality decomposition chart | 2.5.4 | Low | Medium | | 2.5.6 | Build district correlation matrix chart | 2.5.4 | Low | Medium | | 2.5.7 | Create `portfolio_app/pages/toronto/__init__.py` | 1.4.5 | Low | Low | | 2.5.8 | Create `portfolio_app/pages/toronto/dashboard.py` (layout only) | 2.5.7, 1.4.2, 1.4.3 | Mid | High | | 2.5.9 | Implement purchase/rental mode toggle | 2.5.8 | Low | Low | | 2.5.10 | Implement monthly time slider | 2.5.8 | Low | Medium | | 2.5.11 | Implement annual time selector (CMHC) | 2.5.8 | Low | Low | | 2.5.12 | Implement layer toggles (districts/zones/neighbourhoods) | 2.5.8 | Low | Medium | | 2.5.13 | Create `portfolio_app/pages/toronto/callbacks/__init__.py` | 2.5.7 | Low | Low | | 2.5.14 | Create `callbacks/map_callbacks.py` | 2.5.13, 2.5.2 | Mid | Medium | | 2.5.15 | Create `callbacks/filter_callbacks.py` | 2.5.13 | Low | Medium | | 2.5.16 | Create `callbacks/timeseries_callbacks.py` | 2.5.13, 2.5.3 | Low | Medium | | 2.5.17 | Implement district/zone tooltips | 2.5.14 | Low | Low | | 2.5.18 | Implement neighbourhood overlay | 2.5.14, 2.1.6 | Low | Medium | | 2.5.19 | Implement enrichment layer toggle | 2.5.18 | Low | Medium | | 2.5.20 | Implement policy event markers on time series | 2.5.16, 2.2.10 | Low | Medium | | 2.5.21 | Implement "district contains neighbourhoods" tooltip | 2.5.17 | Low | Low | | 2.5.22 | Test dashboard renders with sample data | 2.5.20 | Low | Medium | ### 2.6 Documentation | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.6.1 | Create `docs/` directory | 1.3.1 | Low | Low | | 2.6.2 | Write `docs/methodology.md` (geographic limitations) | 2.5.22 | Low | Medium | | 2.6.3 | Write `docs/data_sources.md` (citations) | 2.5.22 | Low | Low | | 2.6.4 | Write `docs/user_guide.md` | 2.5.22 | Low | Low | | 2.6.5 | Update `README.md` (final) | 2.6.2, 2.6.3 | Low | Low | | 2.6.6 | Update `CLAUDE.md` (final) | 2.6.5 | Low | Low | ### 2.7 Operations | ID | Task | Depends On | Effort | Complexity | |----|------|------------|--------|------------| | 2.7.1 | Create `scripts/db/backup.sh` | 1.2.5 | Low | Low | | 2.7.2 | Create `scripts/db/restore.sh` | 1.2.5 | Low | Low | | 2.7.3 | Create `scripts/db/reset.sh` (dev only) | 1.2.5 | Low | Low | | 2.7.4 | Create `scripts/deploy/rollback.sh` | 1.2.5 | Low | Medium | | 2.7.5 | Implement backup retention policy | 2.7.1 | Low | Low | | 2.7.6 | Add `/health` endpoint | 2.5.8 | Low | Low | | 2.7.7 | Configure uptime monitoring (external) | 2.7.6 | Low | Low | | 2.7.8 | Deploy Toronto dashboard | 1.5.9, 2.5.22 | Low | Low | | 2.7.9 | Verify production deployment | 2.7.8 | Low | Low | --- ## L3 Task Details ### 1.1 Project Bootstrap #### 1.1.1 Git repository initialization | Attribute | Value | |-----------|-------| | **What** | Initialize git repo with main branch | | **How** | `git init`, initial commit | | **Inputs** | — | | **Outputs** | `.git/` directory | | **Why** | Version control foundation | #### 1.1.2 Create `.gitignore` | Attribute | Value | |-----------|-------| | **What** | Git ignore rules per project plan | | **How** | Create file with patterns for: `.env`, `data/*/processed/`, `reports/`, `backups/`, `notebooks/*.html`, `__pycache__/`, `.venv/` | | **Inputs** | Project plan → Directory Rules | | **Outputs** | `.gitignore` | #### 1.1.3 Create `pyproject.toml` | Attribute | Value | |-----------|-------| | **What** | Python packaging config | | **How** | Define project metadata, dependencies, tool configs (ruff, mypy, pytest) | | **Inputs** | Tech stack versions from project plan | | **Outputs** | `pyproject.toml` | | **Dependencies** | PostgreSQL 16.x, Pydantic ≥2.0, SQLAlchemy ≥2.0, dbt-postgres ≥1.7, Pandas ≥2.1, GeoPandas ≥0.14, Dash ≥2.14, dash-mantine-components (latest), pytest ≥7.0 | #### 1.1.4 Create `.python-version` | Attribute | Value | |-----------|-------| | **What** | pyenv version file | | **How** | Single line: `3.11` or specific patch version | | **Outputs** | `.python-version` | #### 1.1.5 Create `.env.example` | Attribute | Value | |-----------|-------| | **What** | Environment variable template | | **How** | Template with: DATABASE_URL, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DASH_DEBUG, SECRET_KEY, LOG_LEVEL | | **Inputs** | Project plan → Environment Setup | | **Outputs** | `.env.example` | #### 1.1.6 Create `README.md` (initial) | Attribute | Value | |-----------|-------| | **What** | Project overview stub | | **How** | Title, brief description, "Setup coming soon" | | **Outputs** | `README.md` | #### 1.1.7 Create `CLAUDE.md` | Attribute | Value | |-----------|-------| | **What** | AI assistant context file | | **How** | Project context, architecture decisions, patterns, conventions | | **Inputs** | Project plan → Code Architecture | | **Outputs** | `CLAUDE.md` | | **Why** | Claude Code effectiveness from day 1 | #### 1.1.8 Create `Makefile` | Attribute | Value | |-----------|-------| | **What** | All make targets from project plan | | **How** | Implement targets: setup, venv, clean, docker-up/down/logs/rebuild, db-init/backup/restore/reset, run, run-prod, dbt-run/test/docs/fresh, test, test-cov, lint, format, typecheck, ci, deploy, rollback | | **Inputs** | Project plan → Makefile Targets | | **Outputs** | `Makefile` | ### 1.2 Infrastructure #### 1.2.4 Create `docker-compose.yml` | Attribute | Value | |-----------|-------| | **What** | Docker Compose V2 for PostgreSQL 16 + PostGIS | | **How** | Service definition, volume mounts, port 5432, env vars from `.env` | | **Inputs** | `.env.example` | | **Outputs** | `docker-compose.yml` | | **Note** | No `version` field (Docker Compose V2) | #### 1.2.5 Create `scripts/` directory structure | Attribute | Value | |-----------|-------| | **What** | Full scripts tree per project plan | | **How** | `mkdir -p scripts/{db,docker,deploy,dbt,dev}` | | **Outputs** | `scripts/db/`, `scripts/docker/`, `scripts/deploy/`, `scripts/dbt/`, `scripts/dev/` | #### 1.2.10 Create `scripts/db/init.sh` | Attribute | Value | |-----------|-------| | **What** | Database initialization with PostGIS | | **How** | `CREATE DATABASE`, `CREATE EXTENSION postgis`, schema creation | | **Standard** | `set -euo pipefail`, usage comment, idempotent | | **Outputs** | `scripts/db/init.sh` | ### 1.3 Application Foundation #### 1.3.1 Create `portfolio_app/` directory structure | Attribute | Value | |-----------|-------| | **What** | Full application tree per project plan | | **Directories** | `portfolio_app/`, `portfolio_app/assets/`, `portfolio_app/assets/images/`, `portfolio_app/pages/`, `portfolio_app/pages/toronto/`, `portfolio_app/pages/toronto/callbacks/`, `portfolio_app/components/`, `portfolio_app/figures/`, `portfolio_app/toronto/`, `portfolio_app/toronto/parsers/`, `portfolio_app/toronto/loaders/`, `portfolio_app/toronto/schemas/`, `portfolio_app/toronto/models/`, `portfolio_app/toronto/transforms/`, `portfolio_app/errors/` | | **Pattern** | Callbacks in `pages/{dashboard}/callbacks/` per project plan | #### 1.3.3 Create `config.py` | Attribute | Value | |-----------|-------| | **What** | Pydantic BaseSettings for config | | **How** | Settings class loading from `.env` | | **Fields** | DATABASE_URL, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DASH_DEBUG, SECRET_KEY, LOG_LEVEL | #### 1.3.5 Create `exceptions.py` | Attribute | Value | |-----------|-------| | **What** | Exception hierarchy per project plan | | **Classes** | `PortfolioError` (base), `ParseError`, `ValidationError`, `LoadError` | #### 1.3.6 Create `handlers.py` | Attribute | Value | |-----------|-------| | **What** | Error handling decorators | | **How** | Decorators for: logging/re-raise, retry logic, transaction boundaries, timing | | **Pattern** | Infrastructure concerns only; domain logic uses explicit handling | #### 1.3.7 Create `app.py` | Attribute | Value | |-----------|-------| | **What** | Dash app factory with Pages routing | | **How** | `Dash(__name__, use_pages=True)`, MantineProvider wrapper | | **Imports** | External: absolute; Internal: relative (dot notation) | #### 1.3.16 Create `conftest.py` | Attribute | Value | |-----------|-------| | **What** | pytest fixtures | | **How** | Test database fixture, sample data fixtures, app client fixture | ### 1.4 Bio Page #### 1.4.7 Integrate bio content | Attribute | Value | |-----------|-------| | **What** | Content from `bio_content_v2.md` | | **Sections** | Headline, Professional Summary, Tech Stack, Side Project, Availability | | **Layout** | Hero → Summary → Tech Stack → Project Cards → Social Links → Availability | #### 1.4.8 Replace social link placeholders | Attribute | Value | |-----------|-------| | **What** | Replace `[USERNAME]` in LinkedIn/GitHub URLs | | **Source** | `bio_content_v2.md` → Social Links | | **Acceptance** | No placeholder text in production | #### 1.4.9 Implement project cards | Attribute | Value | |-----------|-------| | **What** | Dynamic project card display | | **Logic** | Show deployed projects with links; show "In Development" for in-progress; hide or grey out planned | | **Source** | `bio_content_v2.md` → Portfolio Projects Section | ### 2.1 Data Acquisition #### 2.1.1 Define TRREB year scope + download PDFs | Attribute | Value | |-----------|-------| | **What** | Decide which years to parse for V1, download PDFs | | **Decision** | 2020—present for V1 (manageable scope, consistent PDF format). Expand to 2007+ in future if needed. | | **Output** | `data/toronto/raw/trreb/market_watch_YYYY_MM.pdf` | | **Note** | PDF format may vary pre-2018; test before committing to older years | #### 2.1.2 Digitize TRREB district boundaries | Attribute | Value | |-----------|-------| | **What** | GeoJSON with ~35 district polygons | | **Tool** | QGIS | | **Process** | Import PDF as raster → create vector layer → trace polygons → add attributes (district_code, district_name, area_type) → export GeoJSON (WGS84/EPSG:4326) | | **Input** | TRREB Toronto.pdf map | | **Output** | `data/toronto/raw/geo/trreb_districts.geojson` | | **Effort** | High | | **Complexity** | High | | **Note** | HUMAN TASK — not automatable | #### 2.1.5 Extract CMHC zone boundaries | Attribute | Value | |-----------|-------| | **What** | GeoJSON with ~20 zone polygons | | **Tool** | R with cmhc and sf packages | | **Process** | `get_cmhc_geography(geography_type="ZONE", cma="Toronto")` → `st_write()` to GeoJSON | | **Output** | `data/toronto/raw/geo/cmhc_zones.geojson` | #### 2.1.9 Research Tier 1 policy events | Attribute | Value | |-----------|-------| | **What** | Federal/provincial policy events with dates, descriptions, expected direction | | **Sources** | Bank of Canada, OSFI, Ontario Legislature | | **Schema** | event_date, effective_date, level, category, title, description, expected_direction, source_url, confidence | | **Acceptance** | Minimum 10 events, maximum 20 | | **Examples** | BoC rate decisions, OSFI B-20, Ontario Fair Housing Plan, foreign buyer tax | #### 2.1.13 Test TRREB parser across year boundaries | Attribute | Value | |-----------|-------| | **What** | Verify parser handles PDFs from different years | | **Test Cases** | 2020 Q1, 2022 Q1, 2024 Q1 (minimum) | | **Check For** | Table structure changes, column naming variations, page number shifts | | **Output** | Documented format variations, parser fallbacks if needed | ### 2.2 Data Processing #### 2.2.3 Build TRREB PDF parser | Attribute | Value | |-----------|-------| | **What** | Extract summary tables from TRREB PDFs | | **Tool** | pdfplumber or camelot-py | | **Location** | Pages 3-4 (Summary by Area) | | **Fields** | report_date, area_code, area_name, area_type, sales, dollar_volume, avg_price, median_price, new_listings, active_listings, avg_sp_lp, avg_dom | | **Output** | `portfolio_app/toronto/parsers/trreb.py` | #### 2.2.7 CMHC reliability code handling | Attribute | Value | |-----------|-------| | **What** | Parse reliability codes, handle suppression | | **Codes** | a (excellent), b (good), c (fair), d (poor/caution), ** (suppressed → NULL) | | **Implementation** | Pydantic validators, enum type | ### 2.3 Database Layer #### 2.3.8 Create dimension models | Attribute | Value | |-----------|-------| | **What** | SQLAlchemy 2.0 models for dimensions | | **Tables** | `dim_time`, `dim_trreb_district`, `dim_cmhc_zone`, `dim_neighbourhood`, `dim_policy_event` | | **Geometry** | PostGIS geometry columns for districts, zones, neighbourhoods | | **Note** | `dim_neighbourhood` has no FK to facts in V1 | #### 2.3.9 Create fact models | Attribute | Value | |-----------|-------| | **What** | SQLAlchemy 2.0 models for facts | | **Tables** | `fact_purchases`, `fact_rentals` | | **FKs** | fact_purchases → dim_time, dim_trreb_district; fact_rentals → dim_time, dim_cmhc_zone | ### 2.4 dbt Transformation #### 2.4.8 Create staging models | Attribute | Value | |-----------|-------| | **What** | 1:1 source mapping, cleaned and typed | | **Models** | `stg_trreb__monthly`, `stg_cmhc__rental` | | **Naming** | `stg_{source}__{entity}` | #### 2.4.11 Create dbt schema tests | Attribute | Value | |-----------|-------| | **What** | Data quality tests | | **Tests** | `unique` (PKs), `not_null` (required), `accepted_values` (reliability codes, area_type), `relationships` (FK integrity) | #### 2.4.12 Create custom dbt tests | Attribute | Value | |-----------|-------| | **What** | Anomaly detection rules | | **Rules** | Price MoM change >30% → flag; missing districts → fail; duplicate records → fail | ### 2.5 Visualization #### 2.5.2 Build choropleth factory | Attribute | Value | |-----------|-------| | **What** | Reusable choropleth_mapbox figure generator | | **Inputs** | GeoDataFrame, metric column, color config | | **Output** | Plotly figure | | **Location** | `portfolio_app/figures/choropleth.py` | #### 2.5.4—2.5.6 Statistical chart factories | Attribute | Value | |-----------|-------| | **What** | Statistical analysis visualizations | | **Charts** | YoY change with variance bands, seasonality decomposition, district correlation matrix | | **Location** | `portfolio_app/figures/statistical.py` | | **Why** | Required skill demonstration per project plan | #### 2.5.8 Create dashboard layout | Attribute | Value | |-----------|-------| | **What** | Toronto dashboard page structure | | **File** | `portfolio_app/pages/toronto/dashboard.py` | | **Pattern** | Layout only — no callbacks in this file | | **Components** | Navbar, choropleth map, time controls, layer toggles, time series panel, statistics panel, footer | #### 2.5.13—2.5.16 Create callbacks | Attribute | Value | |-----------|-------| | **What** | Dashboard interaction logic | | **Location** | `portfolio_app/pages/toronto/callbacks/` | | **Files** | `__init__.py`, `map_callbacks.py`, `filter_callbacks.py`, `timeseries_callbacks.py` | | **Pattern** | Separate from layout per project plan callback separation pattern | | **Registration** | Import callback modules in `callbacks/__init__.py`; import that package in `dashboard.py`. Dash Pages auto-discovers callbacks when module is imported. | #### 2.5.22 Test dashboard renders with sample data | Attribute | Value | |-----------|-------| | **What** | Verify dashboard works end-to-end | | **Sample Data** | Use output from task 2.3.12 (fact loaders). Run loaders with subset of parsed data before this task. | | **Verify** | Choropleth renders, time controls work, tooltips display, no console errors | --- ## Sprint Plan ### Sprint 1: Project Bootstrap + Start TRREB Digitization **Goal**: Dev environment working, repo initialized, TRREB digitization started | Task ID | Task | Effort | |---------|------|--------| | 1.1.1 | Git repo init | Low | | 1.1.2 | .gitignore | Low | | 1.1.3 | pyproject.toml | Low | | 1.1.4 | .python-version | Low | | 1.1.5 | .env.example | Low | | 1.1.6 | README.md (initial) | Low | | 1.1.7 | CLAUDE.md | Low | | 1.1.8 | Makefile | Low | | 1.2.1 | Python env setup | Low | | 1.2.2 | .pre-commit-config.yaml | Low | | 1.2.3 | Install pre-commit | Low | | 1.2.4 | docker-compose.yml | Low | | 1.2.5 | scripts/ directory structure | Low | | 1.2.6—1.2.9 | Docker scripts | Low | | 1.2.10 | scripts/db/init.sh | Low | | 1.2.11 | scripts/dev/setup.sh | Low | | 1.2.12 | Verify Docker + PostGIS | Low | | 1.3.1 | portfolio_app/ directory structure | Low | | 1.3.2—1.3.6 | App foundation files | Low | | 1.3.14—1.3.17 | Test infrastructure | Low | | 2.1.1 | Download TRREB PDFs | Low | | 2.1.2 | **START** TRREB boundaries (HUMAN) | High | | 2.1.9 | **START** Policy events research | Mid | --- ### Sprint 2: Bio Page + Data Acquisition **Goal**: Bio live, all raw data downloaded | Task ID | Task | Effort | |---------|------|--------| | 1.3.7 | app.py with Pages | Low | | 1.3.8 | Theme config | Low | | 1.3.9—1.3.13 | Assets directory + files | Low | | 1.4.1—1.4.4 | Components | Low | | 1.4.5—1.4.10 | Bio page | Low | | 1.5.1—1.5.3 | VPS setup | Low | | 1.5.4—1.5.6 | Gunicorn/Nginx/SSL | Low | | 1.5.7—1.5.8 | Deploy scripts | Low | | 1.5.9—1.5.10 | Deploy + verify | Low | | 2.1.2 | **CONTINUE** TRREB boundaries | High | | 2.1.3—2.1.4 | CMHC registration + export | Low | | 2.1.5 | CMHC zone boundaries (R) | Low | | 2.1.6 | Neighbourhoods GeoJSON | Low | | 2.1.7 | Neighbourhood Profiles download | Low | | 2.1.9 | **CONTINUE** Policy events research | Mid | | 2.1.10 | policy_events.csv | Low | | 2.1.11—2.1.12 | data/ directory + organize | Low | **Milestone**: **Launch 1 — Bio Live** --- ### Sprint 3: Parsers + Schemas + Models **Goal**: ETL pipeline working, database layer complete | Task ID | Task | Effort | |---------|------|--------| | 2.1.2 | **COMPLETE** TRREB boundaries | High | | 2.1.8 | CRS validation | Low | | 2.2.1—2.2.2 | Toronto module init | Low | | 2.2.3—2.2.5 | TRREB parser + tests | Mid | | 2.2.6—2.2.8 | CMHC processor + tests | Low | | 2.2.9 | Neighbourhood Profiles parser | Low | | 2.2.10 | Policy events loader | Low | | 2.3.1—2.3.5 | Pydantic schemas | Low | | 2.3.6—2.3.9 | SQLAlchemy models | Low | --- ### Sprint 4: Loaders + dbt **Goal**: Data loaded, transformation layer ready | Task ID | Task | Effort | |---------|------|--------| | 2.3.10—2.3.13 | Loaders + tests | Mid | | 2.3.14 | SQL views | Low | | 2.4.1—2.4.7 | dbt setup + scripts | Low | | 2.4.8—2.4.10 | dbt models | Low | | 2.4.11—2.4.12 | dbt tests | Low | | 2.4.13 | dbt documentation | Low | | 2.7.1—2.7.3 | DB backup/restore scripts | Low | --- ### Sprint 5: Visualization **Goal**: Dashboard functional | Task ID | Task | Effort | |---------|------|--------| | 2.5.1—2.5.6 | Figure factories | Mid | | 2.5.7—2.5.12 | Dashboard layout + controls | Mid | | 2.5.13—2.5.16 | Callbacks | Mid | | 2.5.17—2.5.21 | Tooltips + overlays + markers | Low | | 2.5.22 | Test dashboard | Low | --- ### Sprint 6: Polish + Launch 2 **Goal**: Dashboard deployed | Task ID | Task | Effort | |---------|------|--------| | 2.6.1—2.6.6 | Documentation | Low | | 2.7.4—2.7.5 | Rollback script + retention | Low | | 2.7.6—2.7.7 | Health endpoint + monitoring | Low | | 2.7.8—2.7.9 | Deploy + verify | Low | **Milestone**: **Launch 2 — Toronto Dashboard Live** --- ### Sprint 7: Buffer **Goal**: Contingency for slippage, bug fixes | Task ID | Task | Effort | |---------|------|--------| | — | Overflow from previous sprints | Varies | | — | Bug fixes | Varies | | — | UX polish | Low | --- ## Sprint Summary | Sprint | Focus | Key Risk | Milestone | |--------|-------|----------|-----------| | 1 | Bootstrap + start boundaries | — | — | | 2 | Bio + data acquisition | TRREB digitization | Launch 1 | | 3 | Parsers + DB layer | PDF parser, boundaries | — | | 4 | Loaders + dbt | — | — | | 5 | Visualization | Choropleth complexity | — | | 6 | Polish + deploy | — | Launch 2 | | 7 | Buffer | — | — | --- ## Dependency Graph ### Launch 1 Critical Path ``` 1.1.1 → 1.1.3 → 1.2.1 → 1.3.1 → 1.3.7 → 1.4.6 → 1.4.10 → 1.5.9 → 1.5.10 ``` ### Launch 2 Critical Path ``` 2.1.2 (TRREB boundaries) ─┬→ 2.1.8 (CRS) → 2.5.2 (choropleth) → 2.5.8 (layout) → 2.5.22 (test) → 2.7.8 (deploy) │ 2.1.1 → 2.2.3 (parser) → 2.2.4 → 2.3.12 (loaders) → 2.4.8 (dbt) ─┘ ``` ### Parallel Tracks (can run simultaneously) | Track | Tasks | Can Start | |-------|-------|-----------| | **A: TRREB Boundaries** | 2.1.1 → 2.1.2 | Sprint 1 | | **B: TRREB Parser** | 2.2.3—2.2.5 | Sprint 2 (after PDFs) | | **C: CMHC** | 2.1.3—2.1.5 → 2.2.6—2.2.8 | Sprint 2 | | **D: Enrichment** | 2.1.6—2.1.7 → 2.2.9 | Sprint 2 | | **E: Policy Events** | 2.1.9—2.1.10 → 2.2.10 | Sprint 1—2 | | **F: Schemas/Models** | 2.3.1—2.3.9 | Sprint 3 (after parsers) | | **G: dbt** | 2.4.* | Sprint 4 (after loaders) | | **H: Ops Scripts** | 2.7.1—2.7.5 | Sprint 4 | --- ## Risk Register | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | TRREB digitization slips | Medium | High | Start Sprint 1; timebox; accept lower precision initially | | PDF parser breaks on older years | Medium | Medium | Test multiple years early; build fallbacks | | PostGIS geometry issues | Low | Medium | Validate CRS before load (2.1.8) | | Choropleth performance | Low | Medium | Pre-aggregate; simplify geometries | | Policy events research takes too long | Medium | Low | Cap at 10 events minimum; expand post-launch | --- ## Acceptance Criteria ### Launch 1 - [ ] Bio page accessible via HTTPS - [ ] All content from `bio_content_v2.md` rendered - [ ] No placeholder text ([USERNAME]) visible - [ ] Mobile responsive - [ ] Social links functional ### Launch 2 - [ ] Choropleth renders TRREB districts - [ ] Choropleth renders CMHC zones - [ ] Purchase/rental mode toggle works - [ ] Time navigation works (monthly for TRREB, annual for CMHC) - [ ] Policy event markers visible on time series - [ ] Neighbourhood overlay toggleable - [ ] Methodology documentation published - [ ] Data sources cited - [ ] Health endpoint responds --- ## Effort Legend | Level | Meaning | |-------|---------| | **Low** | Straightforward; minimal iteration expected | | **Mid** | Requires debugging or multi-step coordination | | **High** | Complex logic, external tools, or human intervention required | --- *Document Version: 4.1* *Created: January 2026*