Case study C
Taking a method that had worked once and proving it transferred — by splitting the research across parallel passes, then building a second pass whose only job was to disagree with the first. It moved the headline total by one. Underneath that one, four things were wrong.
A method that has worked once has not been proven. It might just have suited that particular dataset. So when two adjacent datasets came up — smaller, different landscape, five source authorities I hadn't worked with — I treated it as a test of the approach rather than a fresh build.
I was handed a starting list of 205 organisations and asked to verify it. The tempting reading of that task is “fill in the missing details”. The correct reading is “find out whether these 205 are the right 205”, and those are almost unrelated jobs.
A list you are given is a claim, not a fact — including when the organisation that published it is the one that governs the thing being listed.
Three commitments, made before any research started, and written into a specification document so I couldn't quietly relax them later when they got expensive.
The specification also carried a section I'd recommend to anyone doing repeat work: “Traps that cost real time last build”. Writing down what went wrong the previous time is the cheapest engineering there is, and I had to relearn none of it.
205 became 206. A net change of one, which is the least interesting number on this page. Underneath it: one entity added, three removed, and a source I stopped trusting.
| Change | What the list said | What was true |
|---|---|---|
| Added | Absent entirely | A real, active programme with staff, schedule and unbroken history — simply never listed. Flagged with a caution: its most recent season predates the current one. |
| Removed | On the governing body's own current list, alongside sixteen that checked out | Sponsors nothing of the kind. The authority's rules allow entry to its event without sponsoring the activity — so that page proves entry, not existence. |
| Removed | Present in the official standings | Zero fixtures, zero wins, zero losses, and nothing on its own site. Listed but never actually participated. |
| Removed | Announced it would start, and the announcement is still being repeated | Two years on: no page, no staff, no mention in its own menu. An announced activity is not an activity. |
| Kept | A reference source said three would not start until 2028–29 | All three already active, with live staff pages and published contacts. Three of five rows in that table were wrong the same way. |
The finding I'd lead with in any interview: a governing body's own current list was wrong, and not through sloppiness. Its page answered “who entered our event” while I was reading it as “who runs this programme”. Authoritative sources are authoritative about their own question, which is not always yours.
The recount caught something structural too. A column carrying the same name in both this dataset and the older one was pointing at two different kinds of page — in 175 of 206 rows it landed on the specific programme's page rather than the parent directory the column was defined to hold. All 206 were repointed, and a link was only accepted when the page both identified itself as a directory and listed several different activities. That test caught six more that looked fine at a glance.
The trap list from the specification, generalised. Every one of these was paid for once already:
| Looks like | Actually is |
|---|---|
| An unscrapeable site | A stale domain that still answers 200. The most expensive failure mode there is, because no health check flags it. Check the domain before blaming the parser. |
| A record with no contact published | A detail page one click away that carries a field the index page omits. |
| A page showing no contact details at all | A JSON feed behind the same platform returning the full records. |
| An obfuscated placeholder where an address should be | The real value, hex-encoded in a data attribute, XOR'd against its own first byte. |
| An empty table | Values held in data attributes and never rendered. |
| “No records listed” | A firewall challenge — a 202 with a tiny body, or a 500 with a suspiciously consistent one. Requeue it; never record it as an absence. |
The last row is the same lesson as the previous build, in a new costume: a system that cannot tell “blocked” from “empty” will confidently report absences that don't exist.
It worked — but a recount at the end can only tell you that something is wrong, not where it entered. By then every grouping had been built the same way for weeks. If the recount had disagreed badly, I'd have had no way to narrow it down except redoing the lot.
A sampled recount running alongside the build — a handful of records per grouping, checked independently as each pass finishes — would have cost almost nothing and would have caught a systematic error while it was still one grouping wide.
I worked out that one reference source was wrong in a consistent direction, and I wrote that down — in a summary document, in a sentence, for a human to read and remember. Which means it's already half-lost.
I discovered something reusable about a source and stored it somewhere only a person can find.
That belongs on the row: which source confirmed this, and how much that source has historically been worth. Then the next build inherits the judgement instead of re-earning it.
Exactly one row in the file was deduced rather than confirmed by looking, and I disclosed it properly — in the summary. But a written note doesn't survive the file being copied, filtered or handed on, and that's the row where a future reader most needs to know.
It should have been a typed confirmation method column on every row: read directly, inferred from two signals, or unconfirmed. I already record where a value came from; I should equally record how sure I am, in a form that can be filtered.
Writing the “traps that cost real time last build” section before starting. It took maybe an hour, it read like admin, and it meant six expensive failure modes cost nothing the second time. Every repeat project should open with the list of ways the last one hurt.