Give every Toronto ward a brief, its statistics, and its incumbent - #82
Merged
Conversation
The 2021 Census numbers behind a ward brief are the sort of thing that gets hand-typed once and then quietly rots. So they are extracted instead: a small checked-in CSV per source, and a dependency-free generator that turns them into the display strings the pages want. scripts/data/README.md carries the provenance of every column down to its row in the City's spreadsheet, so a reviewer can check any figure without opening an XLSX. The extraction asserted each field against its source row label rather than trusting row offsets. Statistics come from Ward Profiles (25-Ward Model); the sitting councillor for each ward comes from Ward and Elected Councillor. Both are City of Toronto Open Data, over Statistics Canada's 2021 and 2016 Census. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A ward page opened with a name, a map and a list of candidates, which told a reader who was running and nothing at all about the place they would represent. Every ward now gets a short brief and eight statistics. The two halves are kept apart deliberately, the same way wardGeo.ts and candidates.ts already are: the figures are generated and must not be edited by hand, the briefs are prose and cannot be generated. wardProfiles.ts holds the briefs and combines the two. Every figure is shown against the citywide one, because a ward statistic read alone means very little — 45% of households renting is unremarkable until you know the city sits at 48%, and Ward 10's 88% of homes in towers is only legible beside a city average of 47%. The briefs stay on geography and the census, and leave the campaign alone. Anything that turns over mid-campaign would go stale, cannot be verified twenty-five times over, and on a page built to compare candidates risks reading as favouring one of them. Neighbourhood names are the City's own, assigned by which ward contains each neighbourhood's centroid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Incumbency is a fact about the seat; candidacy is a fact about the roster, and the roster changes daily. So the two are read from different places and joined at render time: who sits comes from the City's elected-councillor data, and whether they are standing again is answered by looking for them among the ward's registered candidates who have not withdrawn. That is why this does not use the hand-kept `tag: "Incumbent"` in candidates.ts. Only 13 of 25 wards carry one, so twelve wards would have shown nothing whether or not their councillor was running — silence that reads as an answer. Deriving it covers all 25 and cannot fall behind the roster. Twenty-one wards name someone today. The four that do not are all correct: Ward 19's councillor is registered for mayor, Ward 11's is registered in Ward 14, and Wards 4 and 14 have no sitting councillor on their own ballot. Names are matched with nameKey, so the Clerk's spelling and the City's need only agree up to case, accents and punctuation. The section sits at the bottom of the page, after the ballot a reader came for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Greptile SummaryThe PR adds an “About this ward” section to Toronto’s 25 ward pages, combining generated Census statistics, hand-written briefs, and incumbent information derived from the current candidate roster.
Confidence Score: 5/5The PR appears safe to merge with no concrete blocking or independently actionable non-blocking defects identified. The new profile data remains Toronto-specific, ward tokens are normalized through the existing roster, incumbent links use the same name normalization as candidate routes, and the shared component remains backward-compatible through its optional prop.
|
| Filename | Overview |
|---|---|
| scripts/gen-ward-profiles.mjs | Generates validated, display-ready statistics and councillor maps from the checked-in CSV extracts. |
| src/app/toronto/vote/2026/wardProfiles.ts | Combines ward briefs, generated statistics, and current candidate data into optional ward profiles without an identified correctness defect. |
| src/app/toronto/vote/2026/wards/[ward]/page.tsx | Supplies the resolved, canonically padded ward token and council roster to the new profile accessor. |
| src/components/elections/WardDetail.tsx | Adds the optional profile section after ballot content without changing behavior for callers that omit it. |
| src/components/elections/WardProfile.tsx | Renders the brief, incumbent status, comparative statistics, and source attribution with semantic section and definition-list markup. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Census[Ward Census CSV] --> Generator[gen-ward-profiles.mjs]
Councillors[Councillor CSV] --> Generator
Generator --> Stats[wardStats.ts]
Generator --> Seats[wardCouncillors.ts]
Briefs[wardProfiles.ts briefs] --> Profile[wardProfile]
Stats --> Profile
Seats --> Profile
Roster[Current ward candidate roster] --> Profile
Profile --> Page[Toronto ward page]
Page --> Section[About this ward section]
Reviews (1): Last reviewed commit: "Name a ward's incumbent when they are ru..." | Re-trigger Greptile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ward pages opened with a name, a map and a list of candidates — who is running, and nothing about the place they would represent. This adds the missing half.
Every ward now gets a short brief, eight Census statistics, and the name of the sitting councillor when they are registered to run again. The section sits at the bottom of the page, after the ballot a reader came for.
Where the numbers come from
Nothing here was typed from memory. Two City of Toronto Open Data sets, over Statistics Canada's 2021 and 2016 Census:
Each is extracted into a small checked-in CSV under
scripts/data/, andscripts/gen-ward-profiles.mjsturns them into the display strings the pages use.scripts/data/README.mdgives the provenance of every column down to its row in the City's spreadsheet, so any figure can be checked without opening an XLSX. The extraction asserted each field against its source row label rather than trusting row offsets.Neighbourhood names in the briefs are the City's own, assigned by which ward contains each neighbourhood's centroid — all 158 assigned, no orphans.
Structure
The generated and hand-written halves are kept apart, the same way
wardGeo.tsandcandidates.tsalready are:wardStats.ts,wardCouncillors.tswardProfiles.tsWardProfile.tsxowns theWardStat/WardProfiletypes, asWardMapalready ownsWardGeo.WardDetailtakes an optionalprofileprop, so Hamilton and Brampton are untouched.Two decisions worth a look
Every figure is shown against the citywide one. A ward statistic read alone means very little: 45% of households renting is unremarkable until you know the city sits at 48%, and Ward 10's 88% of homes in towers is only legible beside a city average of 47%. The comparison was free — the source has a Toronto column.
Incumbency and candidacy are read from different places and joined at render time. Who sits comes from the City's data; whether they are standing again is answered by looking for them among the ward's registered candidates who have not withdrawn. This deliberately does not use the hand-kept
tag: "Incumbent"incandidates.ts— only 13 of 25 wards carry one, so twelve wards would have shown nothing whether or not their councillor was running, and silence there reads as an answer.Twenty-one wards name someone. The four that do not are all correct: Ward 19's councillor is registered for mayor, Ward 11's is registered in Ward 14, and Wards 4 and 14 have no sitting councillor on their own ballot.
Reviewer notes
/toronto/vote/2026/wards/13before merging.candidates.ts:470lists Dianne Saxe under ward"11"withtag: "Incumbent", but she is registered in Ward 14. The entry is currently inert — enrichment matches within a ward, so Ward 14 finds nothing for her and she renders as "Registered" (zero Incumbent badges on that page). But moving that entry to"14"without changing the tag would badge her incumbent for a seat she does not hold. Either fix the entry, or derive the roster badge fromWARD_COUNCILLORSso incumbency has one source instead of two. Out of scope here.wardCouncillors.tsafter any by-election and once 2026 results are declared;toronto-ward-census-2021.csvwhen the City publishes 2026 Census profiles.tscandeslintare clean; the four remaining eslint warnings are pre-existing inbills/andstate-of-the-nation/. Wards 01, 05, 13 and 25 were verified rendering against the dev server.🤖 Generated with Claude Code