Appearance
F-022: Household Income Data
Status: Done · Priority: P2 · Updated: Mar 6, 2026
Summary
Display household and individual income data on the platform. Census tract choropleth map showing median household income, 2016→2021 income change visualization, and FSA-level income trends from CRA tax data. Listing detail pages show the census tract's median household income for the listing's location.
Requirements
- [x] Census tract boundary shapefile import + GeoJSON conversion (2021 + 2016)
- [x] Census tract income data import (2021 + 2016 census)
- [x] Choropleth map page (
/income-map) with 2021 income + 2016→2021 change modes - [x] Per-listing income display via nearest census tract lookup
- [x] CRA individual tax data by FSA (2015–2021, annual)
- [x] FSA income trends page (
/income-map/trends) with sparklines - [x] Historical income snapshots table (
income_snapshots) - [x] API endpoints for income data + trends + comparisons
- [x] Bilingual (FR/EN)
- [x] PostGIS extension enabled
- [x] Income distribution brackets (StatCan 98-10-0055, 19 brackets, 2016+2021)
- [x] Tabbed income page (Census Map, Distribution, Rent Trends)
- [x] recharts visualizations (bar charts, line charts)
- [x] income_distributions + rent_snapshots tables
- [x] API: /income/distribution, /income/rent-trends
- [x] CMHC rental market data — auto-downloaded from StatCan Table 34-10-0133-01 (5,880 records, 205 geographies, 1987-2025)
- [x] Dedicated
/rent-datapage with city selector, comparison mode (up to 5 cities), line chart, stats cards, sortable city table - [x]
csv-parsedependency added to database package for CMHC CSV parser - [x] Nav header updated with "Rent Data" link
ISQ Quebec income by MRC— dropped (removed ISQ parser, MRC tab, mrc-income API endpoint, xlsx dependency)- [ ] Affordability heatmap page (rent vs income by area — separate future feature)
Data Sources
In Database
| Source | Type | Geography | Years | Records |
|---|---|---|---|---|
| Census 2021 (98-10-0058-01) | Household income | Census tract | 2021 | 986 CTs |
| Census 2016 (Census Profile) | Household income | Census tract | 2016 | 951 CTs |
| CRA Individual Tax Stats | Individual income | FSA | 2015–2021 | 1,945 (278 FSAs × 7 years) |
| CT Boundaries 2021 | Shapefile→GeoJSON | Census tract | 2021 | 1,004 CTs |
| CT Boundaries 2016 | Shapefile→GeoJSON | Census tract | 2016 | downloaded, not imported |
Income Type Clarification
Census data = household income. Total income of all persons in a household combined. Range: $23k (student areas like Quartier Latin) to $360k (Westmount). Montreal CMA median: ~$76k.
CRA data = individual income. Average total income per individual tax filer. Range: $7.5k (tiny rural FSAs) to $400k. Not directly comparable to census household income.
Both are clearly labeled in the UI to avoid confusion.
Auto-Downloaded Data
CMHC Rental Market Survey (StatCan Table 34-10-0133-01)
CMHC rent data is now automatically downloaded from Statistics Canada's open data API during pnpm db:seed. No manual download required.
- Source: StatCan Table 34-10-0133-01
- Records: 5,880 across 205 Canadian geographies
- Years: 1987–2025
- Parser:
packages/database/src/parsers/parse-cmhc-rent.ts(usescsv-parse) - Storage:
rent_snapshotstable
ISQ Quebec Income by MRC — Dropped
ISQ MRC data has been removed from the project. The ISQ parser, MRC tab on /income-map, /income/mrc-income API endpoint, and xlsx dependency have all been deleted.
Schema
census_tracts table
1,004 Montreal CMA census tracts with GeoJSON boundaries and 2021 income data.
| Column | Type | Notes |
|---|---|---|
| ct_uid | text | unique, StatCan CT identifier (e.g., "4620001.00") |
| cma_uid | text | "462" for Montreal |
| boundary | jsonb | GeoJSON Polygon/MultiPolygon |
| latitude/longitude | numeric | centroid |
| median_income | integer | household, before tax (2021) |
| median_income_after_tax | integer | household (2021) |
| total_households | integer | (2021) |
| census_year | integer | default 2021 |
income_snapshots table
Historical income data across all sources and geographies.
| Column | Type | Notes |
|---|---|---|
| geo_type | text | 'ct', 'fsa', 'cma', 'mrc' |
| geo_code | text | CT UID, FSA code, etc. |
| year | integer | data year |
| source | text | 'census', 'cra', 'isq' |
| median_income | integer | household median (census only) |
| average_income | integer | household avg (census) or individual avg (CRA) |
| total_households | integer | census only |
| total_filers | integer | CRA only |
Unique index on (geo_type, geo_code, year, source).
postal_areas income columns
median_income, median_income_after_tax, average_income — currently set to CMA-level fallback ($76k). Not used for listing display (nearest CT is used instead).
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /census-tracts | All CTs with boundaries + income (24h cache) |
| GET | /census-tracts/:ctuid | Single CT detail |
| GET | /income/snapshots | Income snapshots, filterable by geo_type, source, year |
| GET | /income/fsa-trends | CRA FSA income trends 2015–2021 |
| GET | /income/ct-comparison | 2016 vs 2021 CT income change (923 matched CTs) |
| GET | /listings/:id | Now includes areaMedianIncome via nearest CT centroid |
Frontend Pages
/income-map — Choropleth Map
- Mode toggle: 2021 Income (red→yellow→green by median household income) | 2016→2021 Change (red→green by % change, 923 CTs matched)
- Hover: tooltip with CT name + income (or change %)
- Click: popup with median income, after-tax, households, change %
- Legend adapts to mode
- Link to FSA Trends page
/income-map/trends — FSA Income Trends
- Table of ~278 Montreal FSAs with 2015–2021 average individual income
- SVG sparkline per FSA showing 7-year trend
- Sort by growth %, income level, or FSA code
- Search/filter by FSA
- Clear note: "individual tax filer income, not household"
Listing Detail — Area Income Card
- Shows "Area Median Household Income: $X/yr" in sidebar
- Uses nearest census tract by centroid distance (haversine, <5km)
- Real variation: $46k–$109k+ across listings
Data Pipeline
Download Script
packages/database/scripts/download-census-data.sh — downloads:
- CT boundaries 2021 + 2016 (shapefiles from StatCan)
- Census income 2021 (Table 98-10-0058-01 CSV)
- Census Profile 2016 (full profile CSV, 165MB)
- CRA Individual Tax Stats by FSA (2015–2021, 7 CSV files)
- Instructions for manual CMHC + ISQ downloads
Parsers
packages/database/src/parsers/parse-census-income.ts— 2021 census CT incomepackages/database/src/parsers/parse-census-2016-income.ts— 2016 Census Profile CT incomepackages/database/src/parsers/parse-cra-fsa-income.ts— CRA FSA annual data (auto-detects dollars vs thousands format)
Boundary Conversion
packages/database/scripts/convert-ct-boundaries.ts — shapefile to GeoJSON, proj4 reprojection EPSG:3347→4326, filtered to Montreal CMA (ctUid starts with "462").
Seed
pnpm db:seed loads census tracts + all income snapshots (2,882 total records).
Discussion Notes
Mar 5, 2026
- Researched 6 data sources for Montreal income data
- Best primary: StatCan census CT income (household) + CRA tax stats (individual, annual)
- CT boundaries available 2021 + 2016 from StatCan
- CMHC rental market data requires manual portal export (1998–2025 available)
- Decided: CT-level for map visualization, nearest-CT for per-listing income
Mar 6, 2026
- Fixed listing income display: changed from $76k CMA fallback (useless) to nearest census tract lookup (real variation $46k–$109k)
- Downloaded all auto-downloadable data: 2016 CT boundaries, 2016 Census Profile, CRA FSA 2015–2021
- Built parsers for 2016 census + CRA FSA data, loaded 2,882 income snapshots into DB
- Added income_snapshots table for multi-source historical data
- Built 2016→2021 income change choropleth mode (923 CTs matched, avg ~20% growth)
- Built FSA income trends page with sparklines (278 FSAs × 7 years)
- Installed PostGIS extension for future spatial queries
- Clarified: census = household income, CRA = individual income. Both labeled clearly.
- Simplified listing detail: shows "Area Median Household Income: $X/yr" instead of affordability ratio
- Affordability heatmap (rent vs income) deferred to separate future feature
Mar 6, 2026 (cont.)
- CMHC data switched from manual portal download to auto-download from StatCan Table 34-10-0133-01. 5,880 records across 205 Canadian geographies, 1987-2025. Added
csv-parsedependency to database package. - ISQ/MRC removed entirely: deleted ISQ parser, removed MRC tab from income-map, removed
/income/mrc-incomeAPI endpoint, removedxlsxdependency, removed MRC translations. - Built dedicated
/rent-datapage with city selector, comparison mode (up to 5 cities), line chart, stats cards, sortable city table. Component:services/web/components/rent/rent-data-index.tsx. - Updated
/income/rent-trendsAPI to support optionalgeo_codequery parameter for filtering by geography. - Added "Rent Data" link to header navigation.