Case 004
HPD Lookup
New York City publishes every housing violation as open data, which makes it public without making it readable. This is the piece that translates it — a zero-dependency package, an embeddable widget, and a parser we pulled out of our own civic-tech site because it was the part worth sharing.
§00 · The brief
The lookup is the easy half
Several good tools will already show you the violations on a New York City building. HPD publishes the whole dataset, and the gap was never in finding the records, it’s that the records themselves are close to unreadable. Results are in all-caps prose with a legal citation fused to the front of the sentence. The status codes beside them read something like NOV CERTIFIED LATE, which means little to users looking for answers.
§ 27-2005 ADM CODE REPAIR THE BROKEN OR DEFECTIVE PLASTERED SURFACES AND PAINT IN A UNIFORM COLOR AT EAST AND WEST WALLS IN THE 2ND ROOM FROM NORTH LOCATED AT APT 4B, 1ST STORY
NOV SENT OUT
A tenant working out whether their landlord is on the hook for the mold in the bathroom has to decode all of that first. Many tools that display this data have written some version of a parser to clean it up, but none of them had published the parser on its own. So we did.
@howellandgibbs/hpd-lookup looks up NYC housing violations by address or BBL and returns them in plain English. There are zero runtime dependencies, both ESM and CJS builds with TypeScript types, and it runs in Node, Deno, Bun, and the browser. It’s MIT licensed, and it ships an embeddable <hpd-lookup> web component for anyone who wants the whole interface rather than the data.
The tool was extracted from our own site, then audited against the live dataset — which is how we learned the parser we had been using was quietly wrong about 48,700 records.
§01 · Extraction
Pulling a part out of a working thing
The parser started inside Tenant Triage NYC, our free guide for tenants dealing with unresponsive landlords. The building lookup there needed to translate violations, so we wrote something that worked well enough on the page it was built for. Once it was extracted, however, it was clear there were gaps to fill and bugs to fix.
Rather than port the original code and trust it, every status was re-audited from the live Socrata dataset. HPD emits 23 distinct status codes; the original mapping only accounted for 12, and two of those keys never appear in real data at all. HPD’s own spacing is inconsistent: FIRST NO ACCESS TO RE- INSPECT VIOLATION carries a space after the hyphen where the second-attempt version does not, and because the original relied on the unspaced version, roughly 325,000 records had been falling through due to a faulty heuristic.
The worst issue came from Holly misreading the statuses in the first place; INVALID CERTIFICATION and FALSE CERTIFICATION both sound like the violation went away. They actually mean the landlord claimed the work was done and HPD disagreed, so the violation is still open. The old heuristic matched on the words “dismiss” and “invalid,” so it reported the first as dismissed, which was wrong across about 48,700 records. It landed on the right answer for the second only because neither word happens to appear in it. Being right by luck and wrong by the same mechanism was a great argument for mapping codes explicitly rather than pattern-matching the text.
Maps to: Engineering and infrastructure·Systems and file organization
§02 · The parser
Never truncate on a guess
Every violation description opens with a legal citation, and there is no consistent format for one. Matching every variant the city has ever emitted wasn’t scalable, so the parser reads the description word by word and cuts at the first HPD action verb; repair, abate, exterminate, and about sixty others. Violations are instructions to a landlord, so a verb is nearly always there. When no verb appears, it looks at the first substantive word after the citation material, and when neither rule fires it returns the description whole.
Whether a violation is open matters more than anything else on the record, and the status text turns out to be an unreliable guide to it: VIOLATION WILL BE REINSPECTED splits roughly 57/43 between closed and open across the dataset, so no amount of reading the string will tell a tenant which it is. Whether it is open or closed comes from HPD’s own status flag instead, and any codes the parser does not recognize come back marked as unrecognized, so a developer can hedge the wording rather than present a guess as fact.
The parser changes were measured rather than reasoned about. Holly ran the old and new implementations across several thousand live records, counted what changed, and read by hand every case where the output got shorter to ensure no content went missing. Three real bugs surfaced that way: verbs attached to a citation by a colon were being eaten, two-letter HPD shorthand was being read as prose, and some stopping points were landing inside a run of citations. Across 6,996 records the fixes changed 569 outputs; 526 recovering a lost verb, 43 shedding citation debris, and zero regressions.
§03 · The widget
A component that lives in someone else’s page
Most organizations that would want this — a tenant union, a legal aid provider — want an address box on their site, not a data structure. So the package ships a <hpd-lookup> custom element behind a separate entry point, which keeps the core package free of any DOM code for folks importing it in Node. It contains no custom add-ons, no third-party plugins, and no external frameworks or heavy libraries, because it’s important it is both lightweight and self-contained.
Anyone who embeds the component can apply custom CSS properties, and our demo page shows that. The same element is rendered twice against two real brand guides: there’s no forked stylesheet, no build flag, nothing reaching inside the shadow root.
Theming a housing tool also has a floor. Our written palette has no semantic colors, so an immediately-hazardous violation rendered at the same visual weight as a routine one. Severity has to survive a theme, or the theme makes the tool actively worse. The showcase uses a red pulled from our own brand imagery and verified at 4.65:1 on the peach ground.
An accessibility audit found the kind of bug testing did not: clicking “Look up” disabled the button while the request was in flight, and since the button had focus at that moment, the browser dropped focus to the document body, which stranded keyboard users mid-task on every single lookup. It wasn’t caught after 91 passing tests because nothing was asserting where focus lands. It is fixed, two tests verify the fix, and the test count is now at 93.
Maps to: Product design and direction·Engineering and infrastructure
§04 · Scope
What it deliberately doesn’t do
There is no hosted API. The package calls the city’s endpoints directly from wherever it runs, so there is no service for us to keep up. Since this is a free civic tool, we wanted to ensure it will still work in three years. There is no caching layer, either; just a hook for wrapping requests with your own.
There is also no legal interpretation. Plain-English labels are a translation, not a legal opinion, and the documentation says so in the places where someone might be about to rely on it. The two upstream sources are named, their rate limits and freshness caveats are documented, and the dataset ID is pinned in source rather than the dataset name, because the city keeps the IDs stable and the titles are largely up for interpretation.
The tests show the same: 93 of them were performed, none touching the network, with parser fixtures captured from real records covering every status code the city emits.
§05 · Where it is
Published, and free to build on
Version 1.0.0 is on npm as @howellandgibbs/hpd-lookup, the source is at github.com/howellandgibbs/hpd-lookup under an MIT license, and the demo runs at hpd-lookup.howellandgibbs.com.
Documentation was treated as part of the deliverable: the tool comes with architecture notes explaining how the citation stripping and status translation actually work and what the package doesn’t do, a change log, and a contributing guide.
The loop is closed: Tenant Triage NYC now runs on the package it produced. Moving the site onto the audited version fixed the certification statuses it had been reading backwards, and vendoring the build as a single file kept the guide’s no-build-step setup intact. The tool that motivated the extraction is the package’s first consumer, which is the honest test of whether pulling something out was worth doing.
§06 · A look
Selected screens


Sitting on public data that nobody can read, or a component that has to live inside someone else’s brand? Tell us about it.
