Undercount.

Municipal 311 deduplication. Converts how many people complained into how many problems exist. Toronto 311 open data, 2025.

Intake

Not available on this deployment.

This page runs free-text intake: embed, classify into the city's taxonomy, geocode, and run the hybrid duplicate query. This page runs the live system: Postgres 17 with PostGIS and pgvector, and an embedding model. Neither is configured on this deployment.

The three pages that carry the argument, Ward coverage, Concentration and Evidence, are fully live here. Every number on them is read from out/evidence.json, which was produced by running sql/10_evidence.sql against all 500,269 rows of the published file, and each result is stored beside the exact statement that produced it.

Worked examples, real runs, captured

These are recordings, not simulations. Each is an actual run of the pipeline against the real database and the real hybrid query, captured on 2026-09-12 and rendered here verbatim. Nothing on this page is computed at page load and no number is invented. Run the system yourself with the commands below and you get the same three answers.

1. A duplicate, caught

Resident typed
“dead animal on the grass at the corner, been there a few days and it smells”
Location given
Henley Cres & Watercliffe Rd · 2025-10-09
Decision
DUPLICATE
Rationale
Recommend linking to #17: 0.74 text similarity, 0 m away, 0.6 days apart. Combined 0.84, at or above the 0.35 link threshold.
Classified as
Pick up Dead Wildlife → Municipal Licensing & Standards / Toronto Animal Services, looked up from the city's own mapping
Classifier confidence
0.90 floor 0.55
Geocoded to
43.730843, -79.574644 via centreline
Duplicate candidates returned by the single hybrid query, with the three raw scores that produced the decision.
IDExisting report Text similarityMetres Days apartCombined Checks
17 synth theres a ded animal on the grass it smell bad and its been ther for days 0.7422 0.0 0.61 0.8387 all three pass

2. Below the confidence floor, sent to a human

Resident typed
“my streetlight has been out for two years”
Location given
Queen St W & Shaw St · 2025-10-09
Decision
UNCLASSIFIED : needs human triage
Rationale
Needs human triage. The best candidate is Park Lighting Maintenance at confidence 0.30, below the 0.55 floor. No routing was issued and no duplicate check was run - the duplicate query filters on service type, and there is no reliable one. The ranked candidates are below; a clerk picks one or rejects them all.
Classifier confidence
0.30 floor 0.55
Candidates offered for triage. None cleared the floor, so no division was assigned and nothing was dispatched.
RankService request type Would route toConfidenceReasoning
1Park Lighting Maintenance Parks and Recreation → Parks 0.30 Streetlight out is closest to park lighting maintenance but may not be correct as this might be a road streetlight.

3. Two problems in one report

Resident typed
“theres a pothole here and also the streetlight is out”
Location given
Queen St W & Shaw St · 2025-10-09
Decision
NEW
Rationale
No open request of this type within 150 m and 14 days. Logged as a new issue.
Classified as
Road Pothole / Road Damage → Transportation Services / Road Operations, looked up from the city's own mapping
Classifier confidence
0.80 floor 0.55
Second issue found
the streetlight is out surfaced, not split into a second ticket
Geocoded to
43.644701, -79.416273 via centreline

Units: cosine similarity and distance (dimensionless); metres (geodesic, PostGIS geography); days; classifier confidence 0–1. Captured by scripts/capture_worked_examples.pyout/worked_examples.json.

Running the live system

It is a local Postgres cluster and one command:

git clone https://github.com/Greatodu/undercount.git && cd undercount
make db      # local Postgres 17 + PostGIS + pgvector
make part1   # load 500,269 rows, cluster, write out/evidence.json
make part2   # taxonomy, centreline, seed the demo rows   [needs OPENAI_API_KEY]
make part4   # 150-pair eval; writes out/thresholds.json
make link    # apply the tuned rule across the seeded queue
make serve   # http://localhost:8311

Nothing is faked here to make the page work. A duplicate check without an embedding model and a spatial index is not a duplicate check, and a page that pretended otherwise would be the exact failure this project is about.