-
-
The live React dashboard: 51.9% divergence across 1,344 entities, and the coverage gate suppressing our own largest rule.
-
Atlanta records this school active, the federal directory records it closed. Same address. Four models on four clouds voted genuine.
-
Every claim carries its source URL, fetch time and sha256. The two contradicting records side by side, checkable by anyone.
Right now, the City of Atlanta is publishing a list of 681 licensed child care facilities. Places where you would leave your kid.
That data was last checked on October 21st, 2021.
The dataset says so itself. Every row carries its own provenance:
SOURCE https://families.decal.ga.gov/provider/data
SOURCEDATE 1634774400000 -> 2021-10-21
That is the Georgia state child care licensing registry, snapshotted four years and ten months ago and republished as current ever since. Georgia DECAL's own provider API is auth-gated and returns 401, so this dataset is the only public view of that registry.
Nobody had looked since. So we built the thing that looks.
Inspiration
In four years and ten months, facilities closed. Moved. Lost a licence. A parent searching Atlanta's official open data today is reading 2021 and has no way to know it.
This is the exact failure that runs through child welfare, which is where we started. A child in foster care exists simultaneously inside five or six institutions: the child welfare agency, the family court, whichever school district they are enrolled in, Medicaid, the placement provider. None of these systems exchange data reliably. The agency's record is treated as the authoritative account of that child's life, and it is frequently wrong.
The consequence is not administrative. It is that a child arrives at a new school with no transcript and sits out for weeks, repeats a class they already passed, or misses a prescription because nobody knew about it.
There is one number nobody has: how wrong is the record, actually?
What it does
Throughline is a record-integrity layer. It reconciles what one institution asserts about an entity against independent authorities, and reports typed divergence with provenance and confidence on every field.
It is built for the person who has to act on a record and cannot tell whether it is current: a placement caseworker, a school registrar, a city data steward, a parent reading an official list.
Real child-welfare records are confidential by federal law, so no honest hackathon project can demo on them. We refused to fake them. Instead we pointed Throughline at Atlanta's own public institutional records, which exhibit exactly the same failure mode, and every number below is genuinely computed from live public APIs.
On a live run against five public sources, all unauthenticated and reproducible by anyone:
| Measure | Value |
|---|---|
| Entities resolved across 5 authorities | 1,344 |
| Claims ingested | 6,385 |
| Divergences detected | 813 |
| Asserted 4.8 years ago, served as current | 658 |
| Published coordinates conflicting with the federal geocode | 69 |
| Addresses the U.S. Census Bureau cannot resolve | 58 |
| ZIP codes two authorities disagree on | 14 |
| Addresses local and federal records disagree on | 10 |
| Records carrying a live identifier and an empty address | 3 |
| Schools one authority calls open and another does not | 1 |
| Divergence rate | 51.9% |
| Full pipeline, five live APIs, wall time | 6.4s |
Every figure is computed during a run. Nothing is hardcoded, a test in CI enforces that, and any single number walks back to its raw source record via GET /api/provenance/{claim_id}.
Press Run reconciliation on the live dashboard and watch it happen: https://throughline-web-gkay.onrender.com
The number we refused to report
Our first run produced 1,331 divergences, 656 of them "listed as OPEN in the state registry, absent from the city's current business licence roll."
That number was wrong, and it was wrong in our favour.
The City of Atlanta's published 2026 licence roll holds 506 records for the entire city, six of them child day care. A registry of 681 facilities cannot be refuted by a roll that small. Absence from it carries almost no information.
So Throughline now measures the corroborating authority's coverage before drawing any inference from absence. It found that roll corroborates 2 of 659 entities, 0.3%, against a required 25%, so the rule suppresses itself and says why: on the dashboard, in the API, and in the README. That is roughly 650 findings we could have reported and did not.
A tool about record integrity that inflated its own count using a source it never checked would be committing the exact failure it exists to detect. That gate is the part of this project we are proudest of.
One finding, end to end
Verifiable by anyone in two browser tabs:
| City of Atlanta GIS | NCES federal directory | |
|---|---|---|
| Name | Thomasville Heights Elementary Facility | Thomasville Heights Elementary School |
| Identifier | GADOE_ID 5067 (state) |
ncessch 130012000069 (federal) |
| Operational status | A, active |
2, closed |
| Address | 1820 Henry Thomas Dr SE, 30315 | 1820 Henry Thomas Dr SE, 30315 |
Same street address. Neither record carries the other's identifier, so nothing in either system connects them. Throughline matched them on normalized name and address, flagged a STATUS_CONFLICT, and all four models on the adjudication panel independently returned genuine.
What we do not claim. The city record says Facility and the federal record says School, so the building may well still be in active municipal use while the school programme closed. We do not claim Atlanta is unaware a school shut. We claim exactly what the code claims:
"Authorities disagree on whether this is operational. A person acting on either record alone would be acting on a contested fact."
Not a prediction. Not a judgement about which authority is right. Just the fact that they disagree, surfaced with both sources attached so a human can settle it.
How we built it
FIVE REAL PUBLIC SOURCES (no auth on any)
Atlanta Child Care Facilities 681 · Business Licenses 2026 506
Atlanta Public Schools 132 · NCES federal directory 88 · US Census Geocoder
-> CLAIM STORE append-only, provenance + sha256 per claim
-> ENTITY RESOLUTION no shared key: blocking + rapidfuzz + address normalization
-> DIVERGENCE ENGINE 8 deterministic rules
-> COVERAGE GATE can this authority support the inference at all?
-> ADJUDICATION PANEL 4 voters on 4 clouds, ambiguous tail only
-> TIMESCALEDB hypertables + continuous aggregate + compression
-> FastAPI + React dashboard + provenance API
RENDER WORKFLOW fans out one retrying probe per authority, then reconciles
Why Render Workflows rather than a cron entry: five third-party endpoints fail independently, so the right shape is one retrying task per authority that fans out and then reconciles, which a single scheduled script cannot express. Why TimescaleDB rather than plain Postgres: the divergence rate is only meaningful as a series, and a continuous aggregate keeps the chart fast as the series grows rather than as a rewrite later.
Python 3.11, FastAPI, httpx, rapidfuzz. React and TypeScript and Tailwind dashboard on a Render Static Site, plus a zero-dependency Jinja2 dashboard the API serves itself so the findings survive the static site being down. CI on GitHub Actions: ruff, ruff format, 69 tests, and an anti-fabrication guard.
This is not a wrapper around a model API. Delete all four models and Throughline still ingests five sources, resolves entities with no shared identifier, computes seven kinds of divergence, gates them on measured coverage, persists the series, and reports a rate. A test enforces that boundary: the engine modules are forbidden from importing the model layer.
The adjudication panel: four voters, four clouds
Models get one narrow job: judging whether an already-detected discrepancy is a genuine conflict or an artefact of formatting. They never count, never set severity, never decide a divergence exists.
| Seat | Model | Provider | Why it is there |
|---|---|---|---|
| 1 | gemini-3.6-flash |
Google AI Studio | Hosted frontier model, strong general judgement |
| 2 | gemma-4-31b-it |
Google AI Studio, open weights | An agency that cannot send record data to a third-party cloud can run this on its own hardware, so the on-premises path is not a downgrade to nothing |
| 3 | openai-gpt-oss-120b |
DigitalOcean Gradient, open weights | A different vendor on different infrastructure, so one provider's outage degrades the panel instead of ending it |
| 4 | llama3.3-70b |
Snowflake Cortex, warehouse-native | An agency whose records already live in Snowflake can adjudicate without the data crossing its own warehouse boundary |
Two voters can only agree or deadlock. Four produce a majority with a visible minority, and every vote is stored and displayed with its rationale, dissent included. A panel that hides disagreement is an average wearing a panel's clothes. A model that fails to answer is recorded as an error and never counted as agreement. We saw exactly that in production when DigitalOcean returned 402 on exhausted trial credits.
All four seats are live right now: https://throughline-api-yo1p.onrender.com/api/panel
Challenges we ran into
Absence is not evidence. Covered above. It cost us roughly 650 findings and it is the thing we are proudest of.
Entity resolution with no shared key. Five registries describe overlapping sets of real places and not one carries an identifier the others recognise. The city's school layer carries GADOE_ID, the federal directory carries ncessch, neither recognises the other. A false match invents a disagreement between two places that were never the same place. So: blocking on ZIP and name head, rapidfuzz with address weighted above name, and an explicit review band (72 to 88) where the system declines to merge rather than guess. GET /api/matches exposes what we nearly merged.
Atlanta is quadrant-addressed. NW versus NE is load-bearing, since the same street number exists in more than one quadrant. "929 CHARLES ALLEN DRIVE N. E." and "929 Charles Allen Dr NE" are the same building and must normalize identically.
A 200 is not a success, and our own guard proved it too aggressively. Government hosts answer rate-limited clients with challenge pages served as HTTP 200, so our connectors validate payload shape rather than the status line. Then the guard rejected a perfectly good 13-byte {"count":681} as a suspected block page. A WAF challenge is HTML, so size is only evidence when the body is not valid JSON. Parse first, then judge size. Six tests now cover both directions.
Deployed code fails differently. Our first Render Workflow run died on asyncio.run() cannot be called from a running event loop, because the SDK executor already runs inside a loop and awaits coroutine tasks natively. It retried four times before surfacing, which was independent evidence the retry policy worked. Later a panel seat that worked locally returned 404 in production, so we shipped /api/panel, which reports every seat's configuration without leaking a credential, and it found the cause in ninety seconds: one stray character in an account identifier.
Our own prose guard went blind. We ban em dashes in this repo and gate it in CI. The script ran over its own source and rewrote the two constants holding the character it searches for, so it reported a clean sweep over every file while two em dashes sat on the live dashboard. The sentinels are escape sequences now, the script excludes its own path, and it refuses to report any result if those constants are ever not the dashes themselves. A guard that has lost its own needle is the most durable kind of false green.
Accomplishments that we're proud of
- We found something real, in public data, about children, in the host city, sitting unmeasured for four years and ten months.
- Our tool suppressed its own headline number rather than overstate it.
- 69 tests, green CI, and a guard that fails rather than skips, sees untracked files, and includes a non-vacuity check so a bug that made it scan nothing would fail rather than pass quietly.
- Every claim on every screen is independently verifiable by a stranger, against public URLs, with no API key.
What we learned
That the hardest part of interoperability is not the pipes. It is knowing when your evidence does not support your conclusion. We spent more of the build on the coverage gate than on any single connector, and it is the part that makes the rest trustworthy.
Concretely: probabilistic entity resolution, USPS address normalization, the Census batch geocoder, ArcGIS FeatureServer layer discovery, TimescaleDB hypertables and continuous aggregates, Render's Workflows SDK, Snowflake keypair JWT auth, and structured-output prompting across four model families on four clouds.
What's next for Throughline
The product spec this was built from sequences two products. We built The Ledger, the audit. Next is The Relay: when a child's placement changes, automatically deliver the transcript, immunization record, IEP, and current court order to the receiving school before the child arrives.
More immediately: the 2025 licence roll for a genuine year-over-year series, and a scheduled Workflow so the divergence rate is tracked continuously and can be shown to fall.
Explicit non-goals
Enforced in the code, not just stated:
- No predictive risk scoring. Prediction from a broken record is the problem, not the fix.
- No case management. We never compete with the systems that feed us.
- No automated decision affecting a child's placement or a parent's rights. Throughline surfaces discrepancies. Humans decide. Always.
Verify anything here
Every source is public and unauthenticated. Pick any claim and check it yourself.
- Live dashboard: https://throughline-web-gkay.onrender.com
- Panel seats and endpoints: https://throughline-api-yo1p.onrender.com/api/panel
- Proof the hypertables are real: https://throughline-api-yo1p.onrender.com/api/storage
- What we nearly merged but declined to: https://throughline-api-yo1p.onrender.com/api/matches
- Code: https://github.com/StephenSook/throughline
A record that is wrong and confident is worse than no record at all. Atlanta has 681 of them, and now there is a number.
Built With
- asyncpg
- atlanta-open-data
- digitalocean
- digitalocean-gradient
- esri
- fastapi
- gemma
- github-actions
- google-gemini
- httpx
- jinja
- llama
- nces
- python
- rapidfuzz
- react
- render
- render-workflows
- snowflake
- snowflake-cortex
- tailwind
- tigerdata
- timescaledb
- typescript
- us-census-geocoder

Log in or sign up for Devpost to join the conversation.