Before
The organisation books motivational speaking assemblies at high schools. Finding who to contact was the job: search the school, find a site, hunt for a directory page, hope it was current, write the number down somewhere.
Kansas has hundreds of public high schools. Doing that once per school is a week of work that has to be repeated every year, because staff turn over and nobody tells you.
What I built
An eleven-stage pipeline that assembles the directory from public records, and a small application to use it.
The pipeline pulls from the state education department’s own data exports, the high school activities association’s published PDF, a schools vendor’s public JSON, and a licensing roster. It merges those channels, resolves the same school appearing under different names, and assigns a confidence tier to every individual field rather than to the record as a whole — so a school can have a verified phone number and an uncertain counselor name, and the interface says so.
Email addresses are validated by checking the domain actually accepts mail, not by pattern-matching what an address usually looks like.
On top sits a search interface, a page per school showing where each fact came from, an outreach tracker, and a review queue holding the entries the data could not settle on its own.
It costs nothing to run. No paid APIs, no keys, all public endpoints.
The part I would want you to read
Anything that collects contact information can be built two ways, and the difference is entirely in what it does when it hits a wall.
The spec sets a rule: never defeat a deliberate protection. If a district
obfuscates its staff email addresses, sets a directory behind a vendor’s privacy
toggle, or disallows a path in robots.txt, that district has answered the
question. The pipeline records "none published" and moves on.
robots.txt is evaluated as a gate before any request, not as something to
interpret per-crawler afterwards. Six districts are excluded from collection
entirely on that basis, and the coverage numbers above are lower because of
it. That is the correct trade and it is written into the spec rather than left
to whoever runs it next.
The same instinct shows up in the tests. There are 24 of them, each one tied to a numbered clause in the specification, and each verified by mutation testing — deliberately breaking the code to confirm the test actually catches it. A test that passes whether or not the code works is worse than no test, because it tells you something false.
After
The directory is shipped and refreshes itself monthly. Coverage is reported honestly, including where it is incomplete: a named counselor for 95.4% of venues, a usable mailing address for 65.9%, a phone number for 100%.
Nothing sends automatically. The system assembles and tracks; a person still decides who to contact and what to say. That was deliberate — the failure mode of automated outreach is not a wasted email, it is a nonprofit’s reputation in a community it needs.