Case study 03
Public-record business-data pipeline
How far can public records go on a laptop?
A config-driven local pipeline that combines public records into one business reference, with layered processing, provenance, record linkage, and transparent modeling experiments.
- Role
- Data Science Co-op
- Period
- 2026
- Status
- Working local prototype
01 — My contribution
I designed and built the pipeline end to end — ingestion, normalization, linkage, enrichment, and export — and the evaluation work around it.
02 — Problem and context
Public records describe businesses from many angles: registrations, liens, loans, licences, nonprofit filings, and government spending. Each source has its own identifiers, formats, and quirks, and none of them is a business directory on its own.
The question was how far public records can go toward a useful business reference, and what it takes to combine them reliably on modest hardware.
Layered processing with provenance
Raw
Immutable, dated snapshots
Staging
Typed, source-shaped
Normalized
One canonical schema
Identity
Deterministic, then probabilistic links
Golden record
Fields with provenance
Export + warehouse
Parquet, queryable views
03 — Approach and key decisions
Decision 01
Layered, immutable processing
Raw files are kept unchanged in dated snapshots. Each run moves through staging, normalization, identity, golden-record, and export layers, so every output can be traced to its source and rebuilt.
Decision 02
Configuration over code
Sources and column mappings live in configuration. Adding a source means a mapping and a small adapter, while every downstream step stays on one canonical schema.
Decision 03
Columnar tools that fit in memory
Polars and DuckDB over Parquet keep large joins and aggregations practical on a laptop, and streaming avoids decompressing large archives to disk.
Decision 04
Deterministic links first
Where records share an identifier they link on it directly. Only the remainder goes to probabilistic linkage, and records that stay ambiguous are kept as provisional entities rather than silently merged or dropped.
Decision 05
Address enrichment with caching
Addresses are geocoded through a public batch service with a distinct-address cache, so reruns and new sources only pay for addresses not seen before.
Decision 06
Provenance on every field
Each output field records which source supplied it, so coverage and disagreement can be measured per source.
Illustrative example — synthetic data
- RegistryCopper Kettle Tea Co88 Mesa Rd Ste 210Anchor record
- Lien filingCopper Kettle Tea Company15 Old Mill Ln (at filing time)Deterministic · carries registry ID
- Loan recordCopper Kettle Tea Co LLC88 Mesa RdProbabilistic · name and address agree
- LicenceThe Copper Kettle88 Mesa Rd Ste 210Review · name only partly agrees
The lien filing still shows an old address. Penalizing that disagreement as if it were a current address would reject a true match, which is why filing-time sources get their own address weights.
04 — Evaluation and evidence
Evaluation focused on whether links are right, whether fields agree with an independent reference, and whether the data is internally consistent.
- Known-correct pairs for linkage
- Some records carry the registry identifier of the business they refer to. Those give a set of known-correct matches to tune and test the linker against, without hand labelling.
- Why stale addresses matter
- Filings record the address at the time of filing, so a business that has since moved disagrees on street, city, or postal code. Address penalties tuned for current addresses wrongly reject those true matches. A separate weight profile for filing-time addresses recovered them without loosening the name requirements.
- Why duplicate registry entries matter
- Registries contain re-registrations and duplicate entities with identical names. They contaminate naive estimates of what a non-match looks like, and they create “wrong” links that are really duplicates. Measuring precision honestly meant auditing those cases separately.
- Coverage and field agreement
- Compared the output with a commercial reference dataset for coverage and agreement on overlapping fields, and examined which kinds of businesses each side is missing.
- Data-quality checks
- Automated checks for missing shares, dangling links between tables, and join keys that fail silently — for example identifiers exported with thousands separators.
05 — What shipped
A working local prototype that builds a business reference from public sources across several states, with a queryable local warehouse and documented, repeatable runs.
It is not deployed and is not a replacement for a commercial dataset.
- Status: Local prototype
Pipeline and record linkage
Layered processing, deterministic and probabilistic linkage, geocoding, provenance.
- Status: Research
Transparent public-data score
An additive score with reason codes, built from public evidence and calibrated per state to a reference scale on held-out records. Filing-history features — how recently and how often a business files — mattered most where annual reports are not required.
- Status: Research
Industry classifier
Naive Bayes on business-name tokens with abstention, so it predicts only when confident. Self-training on confident predictions extended coverage; passes were compared at matched precision on a fixed holdout.
06 — Limitations and lessons
Limitations
- A local prototype, not a production system or a vendor replacement.
- Agreement with a vendor's score is not the same as predicting real outcomes; the score has not been validated against outcomes.
- Registries and commercial datasets cover different populations. Sole proprietors, branch locations, and unregistered businesses are often absent from registries.
- Linkage precision is bounded by duplicate entities in the source registries themselves.
Lessons
- When a standard estimation method produces strange weights, check the labels before the method.
- Compare classifier passes at matched precision, not matched thresholds.
- A join rate near zero usually means a formatting problem, not a different identifier space.
07 — Tools
- Python
- Polars
- DuckDB
- Parquet
- pytest
- rapidfuzz
- YAML
- Census Geocoder