Suggested house counts from Toronto Open Data - #23
Open
AbeerDas wants to merge 3 commits into
Open
Conversation
Counts the addresses between a route's two endpoints and offers the number
as a suggestion in the route detail panel. The manual house_count stays
authoritative until someone presses Use.
How it works: each endpoint (a street address or an intersection — both
carry coordinates) snaps to the nearest known address point on the route's
street, and the addresses between those two house numbers are counted,
filtered to the route's side. Computed on read, so nothing is stored and
there is no freshness state to manage.
Side matching uses Toronto's own L/R centreline flag as the source of truth
and only decides which compass direction each group corresponds to, from the
group's mean offset relative to the other group. Comparing the groups to each
other rather than to the start->end chord matters: a route whose endpoints are
two houses on the same side puts the chord along that side, so an absolute
test collapses. Curved streets (Glen Manor Dr) and diagonals (Kingston Rd)
both fall out of this correctly.
House numbering alternates by side, so a one-sided route returning both odd
and even numbers means the side or range logic is wrong. That check ships as
`parityConsistent` on the response and is asserted in the tests — it is the
only correctness signal available until real counts exist to compare against.
PostGIS turned out to be unnecessary. Notable data details: ADDRESS_NUMBER is
text ("2962B") so LO_NUM is the range key; MAINT_STAGE 'RESERVED' rows are
allocated but unbuilt and are dropped at load; the street match is exact
first, because Toronto's direction-suffixed siblings (Glen Manor Dr E/W) are
separate streets with their own numbering.
Also corrects the seeded route geography, which used hand-invented
coordinates that did not sit on the real streets, and two routes whose side
was impossible for the street's orientation (a north-south street has east
and west sides).
Reference data loads via `pnpm load-addresses`.
Contains information licensed under the Open Government Licence – Toronto.
AbeerDas
added a commit
that referenced
this pull request
Jul 29, 2026
The July review locked four changes that superseded earlier decisions, and 6e7b46f implemented all four plus GET /api/overview. Only design_decisions.md was updated at the time; the spec, the data model, and the finance flow still described the model we replaced. Those are what the design engineers read, so the drift was live. finances_flow.md needed the most work because it used "freeze" to mean what closing does, in seven places including a state diagram — and the new model makes freeze a separate per-cell state. The two are now distinguished explicitly: closing detaches every cell in an issue from live calculation and writes nothing, freezing snapshots one cell and is reversible, paying locks one cell against everything. Adds Frozen to the calculation state machine with the override -> frozen -> calculated precedence, a coverage state machine for substitutes (3d), and the two new flows as 4j and 4k. Narrows 4g to genuine reallocation and points coverage at substitutes instead. Section letters 4a-4i are unchanged on purpose: eight service files cite "finance flow 4c/4g/4h/4i" in comments, and renumbering would churn code that was just reviewed for no behavioural gain. api_spec.md gains the freeze/unfreeze/substitute actions, a section for GET /api/overview with its full response type, the required startDate on year creation, the amount precedence, and the 409 rules per action. data_model.md gains frozenAmount/frozenAt/substituteCaptainId on CaptainPayout, FinancialYear.startDate, and the corrected PayCadence enum. House-count spec drift is deliberately untouched — #23 resolves it coherently and fixing it here would only conflict. open_items.md records that the numeric-as-string premise behind the coercion helpers did not reproduce against the live project, which returns numeric as JSON numbers. Their code is left exactly as merged for @kenzysoror to confirm.
Picks up the merged backend (#12) and the dependency resolution done one level up, so this merge had no conflicts of its own. Reformats lib/services/house-count.ts for the same Prettier rule change main brought in — cosmetic, one union type re-wrapped. Migrations interleave correctly: 20260728 (Toronto address points) before 20260729 (finance reconciliation), so no reordering was needed.
5 tasks
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.
Summary
Stacked on #17. Adds a house-count suggestion for routes, computed from the City of Toronto's address register — a suggestion the user accepts, never a silent overwrite.
house_count_overridein the schema was reserved for exactly this ("only relevant once auto-calc exists").Approach
Each route endpoint (a street address or an intersection — both carry coordinates) snaps to the nearest known address on that street; everything between the two resulting house numbers is counted, filtered to the route's side. Computed on read — nothing is stored, so there's no freshness state to manage.
Side matching uses Toronto's own L/R centreline flag as ground truth and only decides which compass direction each side corresponds to, from the group's mean offset relative to the other group (not against an absolute chord — a route whose endpoints are two houses on the same side would otherwise collapse the sign test). This correctly handles both diagonal streets (Kingston Rd) and curved ones (Glen Manor Dr, which bends around a ravine).
PostGIS turned out unnecessary — the address data already carries centreline side, so it's a house-number range plus a bounding box.
What's here
supabase/migrations/20260728000000_toronto_address_points.sql— reference table, RLS + explicitservice_rolegrant (new tables aren't auto-exposed)scripts/load-toronto-addresses.ts(pnpm load-addresses) — streams the ~525k-row CSV viaCOPY, filters unbuilt (RESERVED) lots, scoped to Beach Metro's area by default (--bbox/--allto widen)lib/services/house-count.ts— the pure matching/geometry core (no DB, no I/O — fully unit-testable)lib/services/routes.ts—suggestRouteHouseCount, exact-street-match first (Toronto splits direction suffixes like "Glen Manor Dr W" into separate streets with their own numbering)GET /api/routes/{id}/suggested-house-counthouseCountand clearshouseCountOverride— writing only the former is broken whenever an override exists, see the seed route 7 regression test) + collapsed address list + required OGL–Toronto attributionVerification
tests/house-count.test.ts) — parity self-check, diagonal streets, curvature, digitization-flip guard, dedup, symmetrypnpm load-addresseshasn't runsidethat's geometrically impossible for that street's orientation (a north-south street only has east/west sides)pnpm format:check && pnpm typecheck && pnpm lint && pnpm testall greenOpen item
No ground truth yet. Everything above is self-consistency (parity, additivity, symmetry) — real counts for 15–20 routes from Melinda would let us state an actual error bar. Until then the number stays a suggestion.
Contains information licensed under the Open Government Licence – Toronto.