Skip to content
FLANERY
← Flanery Solutions

A Kansas nonprofit · 2026 · Nonprofit · outreach operations

Every public high school in Kansas, and who to call at each one

They booked speaking engagements one school at a time, by searching for a phone number. The directory now holds 328 bookable venues with a named contact for each, rebuilt monthly from public records — and it refuses to collect anything a school chose not to publish.

This client hasn’t been asked yet whether they’re happy to be named, so they aren’t. Everything else here is exactly as it was built. If you want a reference before hiring me, I’ll ask them.

Built with

Python · Next.js · TypeScript · Playwright · Cron

Where it stands

  • 328 bookable venues across roughly 275 districts.
  • A named counselor for 95.4% of them. Phone number for 100%.
  • Refreshes monthly on its own; no manual re-collection.
  • 24 invariant tests, each mapped to a numbered clause in the spec.

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.

If any of this sounds like your operation, the first call is free.

Ask for a call