Skip to main content

Geocode-first record matching — the thing that imploded, and why it won't again

Mailwoman started as the answer to a record-matching problem, not a parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. one. The original job was unglamorous: a pile of clinics receiving federal and stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. funding, arriving as SQLite dumps, CSV exports, and hand-keyed spreadsheets, and a question — which of these registered entities already got money, which are eligible and didn't, and which are the same clinic typed three different ways? NormalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input., geocode, drop it into QGIS, see who's covered, build the lead list.

The pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. imploded on the matching. PeliasPeliasAn open-source geocoder, Mailwoman's spiritual predecessor. and libpostallibpostalAn open-source C address parser used by Pelias. Mailwoman's rule-based v0 and neural classifier supersede it. weren't good enough at the parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates., which is what spawned mailwoman's v0 TypeScript rewrite and then the neural pivot. But the parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. was only ever the on-ramp. The thing we never shipped was the matcher — the part that decides two messy records are the same real-world entity. This page is the strategy for finally building it, and the one idea that makes it tractable now when it wasn't then.

What it is

The matcher is a calibrated, geocode-firstgeocode-firstThe matcher's core design principle: resolve addresses to geographic coordinates first, then compare the resolved places — not the raw address strings. Two records at the same coordinates match even if one says '123 Main St' and the other says '123 MAIN STREET.' entity-resolution layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6.: it resolves every record to a coordinate and a hierarchy first, then links records by where they are and who they are — not by how their address strings happen to be spelled.

Every clause earns its place:

  • Entity resolutionrecord matchingThe process of determining whether two database records refer to the same real-world entity. Mailwoman's matcher uses a geocode-first approach (match the resolved place, not the address string) with Fellegi-Sunter probabilistic scoring.. The classical problem — blockingblockingThe first stage of entity resolution: generate candidate record pairs using cheap, high-recall keys (geo cell, canonical address, phone) instead of comparing every record to every other (O(n²)). The matcher only scores pairs that survive blocking., pairwise matching, clusteringclusteringThe final stage of entity resolution: resolve non-transitive pairwise match decisions (A↔B, B↔C, but not A↔C) into canonical entities via union-find with path compression. Each cluster of records becomes one resolved entity. into canonical entities. Not a new field; a very old, very well-theorized one (Fellegi & Sunter, 1969).
  • Geocode-firstgeocode-firstThe matcher's core design principle: resolve addresses to geographic coordinates first, then compare the resolved places — not the raw address strings. Two records at the same coordinates match even if one says '123 Main St' and the other says '123 MAIN STREET.'. The address becomes a point before it becomes a match featurefeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them.. This is the whole bet, and it's the reason this version won't implode.
  • Calibrated. Every link carries a probability, every threshold a stated precisionprecisionOf the spans the model labeled as a given tag, the fraction it got right. High precision means few false positives. Paired with recall to compute F1./recallrecallOf the spans whose gold label is a given tag, the fraction the model found. High recall means few misses. Paired with precision to compute F1. trade, and the ambiguous middle is abstained to review rather than guessed. The same discipline that runs the geocoder.

Why it imploded — and what changed under it

The v0 matcher was string-first: classify the address into components, slot them into templates, compare the strings. Strings are the wrong key for addresses, and there's a single fact that proves it. Jyllandsgade 15 and Jyllandsgade 75 differ by one character — Levenshtein similarity 0.963 — and they are 650 metres apart (Isaj et al., SSTD 2019). String similarity has the wrong topology: it pulls different buildings together and pushes spellings of the same building apart. A matcher built on it spends its life drowning in false pairs and missing real ones, and ours did.

Two decision surfaces — string-first vs geocode-first — for P(match) over string similarity × geographic distance. String-first is a vertical wall blind to geography; geocode-first is a basin carved by distance, with the two canonical traps annotated.

Both surfaces are P(match) over the same record-pair space — string similarity (x) against geographic distance (y) — scored by the same Fellegi-Sunter modelFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization. (below) with the real per-level Bayes factors (NAME_LEVELS, DEFAULT_DISTANCE_LEVELS); the only difference is which evidence each modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' may see. String-first sees the name alone — a vertical wall at high similarity, blind to where the records are. It fuses namesakes 1500 km apart (① Springfield General IL vs MA) and splits a same-building pair whose string drifted (② 123 Main St vs 123 Main Street Apt 2). Geocode-firstgeocode-firstThe matcher's core design principle: resolve addresses to geographic coordinates first, then compare the resolved places — not the raw address strings. Two records at the same coordinates match even if one says '123 Main St' and the other says '123 MAIN STREET.' also sees distance — the wall becomes a basin you climb out of only near a shared place, and it inverts both verdicts the right way. The geography levels carry the heaviest evidence by design (±9.45 bits at the same-building grain vs ±6.32 for an exact name), which is why the boundary bends with distance rather than with spelling. (Prior λ is illustrative here to keep the boundary visible; production runs λ=1e-4 with phone and spatial-key corroboration this two-axis slice omits. Generator: scripts/record-matcher/viz/geocode-first-surface.ts.)

What changed is that mailwoman is now a geocoder with calibrated confidence. Every record can become a rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few metres — the highest tier of the geocode cascade. Sourced from address-point and situs data. coordinate (± calibrated metres) plus a Who's-on-First hierarchy plus structured components, each with a confidence. That hands you matching keys with the right topology:

  • the coordinate / situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. point / H3 cellH3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id. — the same building gets the same key, regardless of how the streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. was abbreviated;
  • the WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. ancestry — spelling-invariant blockingblockingThe first stage of entity resolution: generate candidate record pairs using cheap, high-recall keys (geo cell, canonical address, phone) instead of comparing every record to every other (O(n²)). The matcher only scores pairs that survive blocking. by localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. and regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., because you're keying on resolved IDs, not text;
  • structured components with confidence — you compare field-by-field, and you know which fields to trust.

You never match on the formatted string. You match on the resolved place, and the lossy-template problem disappears with the string it lived in.

Entity resolution is three problems, not one

The reason ER "implodes" is that people treat it as one giant problem. It's three, and naming them is the architecture (Binette & Steorts, 2022; Papadakis et al., 2021):

  1. Block — generate candidate pairs cheaply, because comparing all pairs is O(n²) and a million records is a trillion comparisons. This is where geography does its heaviest lifting.
  2. Match — score a candidate pair: are these the same entity? This is the calibrated decision layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6..
  3. Cluster — pairwise scores are not transitive (AB at 0.6 and BC at 0.6 does not make A~C a match), so a separate stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). closes pairs into canonical entities. Skip it and your "entities" quietly fracture or fuse (Dedupe; the non-transitivity is essential, not a footnote).

Upstream of all three is canonicalize: parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. the name, sanitize the org name, geocode the address, normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input. the phone and email. That's mostly salvage — the old isp-nexus contact/organization/postal bones, re-pointed at mailwoman's geocoder.

The foundation: Fellegi-Sunter, without labels

The matcher's core is the classical probabilistic-linkage modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.', and it's the right core for one decisive reason: it trains without labeled data. The Expectation-Maximizationexpectation-maximizationAn iterative algorithm that estimates model parameters when some variables are unobserved. In Mailwoman's matcher, EM learns the Fellegi-Sunter m and u parameters from unlabeled data — no training labels needed. estimate of its parametersparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. breaks the chicken-and-egg paradox — you need known matches to learn the modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.', but finding matches is the whole problem — by iterating predict which pairs matchre-estimate from those predictions, exploiting that true matches agree on most fields and non-matches don't (Winkler 1988; Splink).

That property is the answer to why the first attempt died. Messy clinic CSVs come with no ground truthground truthThe correct answer for an example, used as the standard a prediction is graded against. Mailwoman's ground truth is the hand-labeled golden set; its quality caps achievable accuracy.. A supervised matcher has nothing to learn from on day one. Fellegi-SunterFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization. gives you a calibrated matcher anyway, and its scores are interpretable by construction: a total match weightparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. is a prior plus a sum of per-field log-Bayes-factors (M = M_prior + Σ log₂(mᵢ/uᵢ)), so every decision is attributable to the fields that drove it. You can read why two records linked. The match-weightparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. threshold is the single precisionprecisionOf the spans the model labeled as a given tag, the fraction it got right. High precision means few false positives. Paired with recall to compute F1./recallrecallOf the spans whose gold label is a given tag, the fraction the model found. High recall means few misses. Paired with precision to compute F1. knob, with a clerical-review band in the middle for the ambiguous — the abstain zone, the same move the geocoder makes when its radius blows up.

Geography is the blocking key — the bet, and its tax

Using geographic proximity as the primary block is documented, production-proven practice. Spatial cell-joins (geohash/H3H3Uber's hexagonal hierarchical geospatial indexing system. Mailwoman uses H3 cells at resolution 9 (~0.03 km²) for geo-first blocking in the matcher and for stable address primary keys in @mailwoman/address-id./S2) or distance-bounded quadtrees group co-located records so only plausible pairs are ever scored. Grab collapsed ~30 trillion candidate pairs to a tractable set with a single level-6 geohash join (Gao & Widdows, 2020); Geo-ER blocks on name-sim ≥ 0.6 AND distance ≤ 2 km, keeping the distance bound deliberately loose so large entities whose registered coordinates wander — parks, campuses, airports — still survive into scoring (Balsebre et al., WWW 2022).

Distance then re-enters at the scoring stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone)., two ways, and both help:

  • Bucketed, as ordinary Fellegi-SunterFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization. comparison levels — "within X m" / "within Y m" / "farther", each with its own learned m/u (Splink's DistanceInKMAtThresholds). Simplest to calibrate; the proven default.
  • Continuous, as a numeric featurefeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them. — measurably stronger (Geo-ER beats text-only matching by 2.7–7.2% F1, with the largest gains where the address text is sparse, and it generalizes across regionsregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. far better than a text modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.').

Our bet — the part nobody has published — is doing this on a parser and geocoder we own and calibrate, so the matcher can condition on the geocode's own stated uncertainty. And there is a tax, which we pay openly. Geocoder error is large, heavy-tailed, and non-Gaussian, with a hard urban→rural gradientgradientThe direction and rate at which the loss would change if each parameter were nudged. Training follows the gradient downhill to reduce error. Huge gradients are tamed by gradient clipping. (streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-interpolationinterpolationA geocoding technique that estimates a coordinate along a street segment based on the house number range. Used as the middle tier of Mailwoman's geocode cascade when exact address-point data is unavailable. median ~38 m urban, ~201 m rural; Cayo & Talbot, Zimmerman et al.). Two rules fall out:

  1. Distance buckets are density-aware. A 50 m "same building" bucket is right downtown and far too tight in the countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.. Mailwoman knows the density context; the matcher uses it.
  2. Geocode quality weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. the evidence. Two records sharing a rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few metres — the highest tier of the geocode cascade. Sourced from address-point and situs data. point is strong agreement; two sharing an interpolated streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. centroid is weak; two sharing a PO-box or multi-unitunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise. coordinate is barely location agreement at all. This is not a new idea we have to invent — NAACCR's cancer-registry geocoder already propagates exactly this (interpolationinterpolationA geocoding technique that estimates a coordinate along a street segment based on the house number range. Used as the middle tier of Mailwoman's geocode cascade when exact address-point data is unavailable. type, featurefeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them.-match geography, PO-box/rural-route flags, a Match/Review/Non-Match triage). It maps cleanly onto our situssitusThe physical site address of a property, as opposed to the owner's mailing address. Parcel records often carry both; the divergence is a real-world data-quality challenge. → interp → admin tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. cascade and conformal radii. We have the quality class already; the matcher just has to spend it.

Calibrate the thresholds against mailwoman's own measured error distribution, not the literature's older-geocoder constants. Our rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few metres — the highest tier of the geocode cascade. Sourced from address-point and situs data. tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous. is tighter; the shape of the lesson holds, the numbers are ours to measure.

Where a model earns its place

The classical core is the v1. Modelsneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' are selective levers bolted on where the evidence says they pay — never the foundation:

  • Org-name embeddingsembeddingA vector of numbers representing a token (or other item) so that similar items sit near each other in vector space. The first thing the model does is turn each token into an embedding. for fuzzy blockingblockingThe first stage of entity resolution: generate candidate record pairs using cheap, high-recall keys (geo cell, canonical address, phone) instead of comparing every record to every other (O(n²)). The matcher only scores pairs that survive blocking. and similarity. Regex can't see Comcast Cable Communications LLCXfinity (a DBA) or survive OCR noise; an embeddingembeddingA vector of numbers representing a token (or other item) so that similar items sit near each other in vector space. The first thing the model does is turn each token into an embedding. + ANN index can. EmbeddingembeddingA vector of numbers representing a token (or other item) so that similar items sit near each other in vector space. The first thing the model does is turn each token into an embedding./ANN blockingblockingThe first stage of entity resolution: generate candidate record pairs using cheap, high-recall keys (geo cell, canonical address, phone) instead of comparing every record to every other (O(n²)). The matcher only scores pairs that survive blocking. earns its keep specifically on noisy, textual fields — not as a blanket replacement (DeepBlocker).
  • A gradientgradientThe direction and rate at which the loss would change if each parameter were nudged. Training follows the gradient downhill to reduce error. Huge gradients are tamed by gradient clipping.-boosted matcher over the comparison vector once labelscomponent tagOne of the 33 labels in Mailwoman's address schema — street, locality, region, postcode, house_number, unit, po_box, country, venue, intersection, and others. Each parsed span carries exactly one component tag. exist. A tree over {distance, name-sim, street-sim} already clears ~90% matched-class F1 in production (Grab) — a strong, interpretable upgrade from hand-weightsparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values., not a neural moonshot. This one is shipped, and on by default: a pure-Node gradientgradientThe direction and rate at which the loss would change if each parameter were nudged. Training follows the gradient downhill to reduce error. Huge gradients are tamed by gradient clipping.-boosted tree over the agreement pattern plus the over-merge interaction (co-located × name-disagree), trained on the NPPES NPI-truth set. It beat the Fellegi-SunterFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization. core by ~5pp dedup F1 held-out within a stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. and by ~+22pp on statesregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. it never trained on (TX→CA, TX→NY), cutting the co-located over-merge — so it earns the default slot, shipped with a calibrated link threshold (its logitlogitA raw, unnormalized per-label score the model outputs before softmax. Priors and biases are added in logit space, then softmax turns logits into probabilities. isn't in FS-weightparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. unitsunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise.). The modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.' is NPPES/US-trained; for a very different domain, A/B it or drop back to the hand-weighted core with resolveEntities({ learnedScorer: false }). One sharp caveat we measured: the GBTGBT (Gradient Boosted Trees). A non-linear machine learning model that combines many weak decision trees into a strong predictor. Mailwoman uses a GBT as an optional learned scorer for single-dataset dedup, improving F1 by 5–7 percentage points over the Fellegi-Sunter baseline. is calibrated for dedup — where "same address, different name" means distinct co-located providers, so it's trained to reject that. Cross-dataset link discovery is the opposite objective — that same pattern is the prototypical signal of one facility under different operational names across sources — so the cross-dataset and reconciliation flows pin the FS core (learnedScorer: false), where recallrecallOf the spans whose gold label is a given tag, the fraction the model found. High recall means few misses. Paired with precision to compute F1., not over-merge precisionprecisionOf the spans the model labeled as a given tag, the fraction it got right. High precision means few false positives. Paired with recall to compute F1., is what matters. Same modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.', opposite objective: pick the scorer by the question you're asking.
  • An LLM in the gray zone, never on the easy 95%. Framed as in-context clusteringclusteringThe final stage of entity resolution: resolve non-transitive pairwise match decisions (A↔B, B↔C, but not A↔C) into canonical entities via union-find with path compression. Each cluster of records becomes one resolved entity. rather than all-pairs classification, an LLM cuts API calls ~5× and improves quality (LLM-CER, 2025). Spend it only on the clerical-review band the calibrated scorer abstained on.
  • An LLM for ingest. The messy-data reality — "it came as a SQLite dump" — is where an LLM is cheaply excellent: read a header row plus a few samples, infer the column→field mapping, cache it. One call per source schema, not per row. The most painful onboarding step, deleted.

Four rules, carried over

The four rules the geocoder already lives by govern the matcher too:

  • Configuration dominates the modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'. Across ~40,000 pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. configs, F1 spansspanA contiguous range of characters or tokens in the input string, tagged with an address component type (street, locality, postcode, etc.). Parsed addresses are represented as collections of spans, possibly nested in a tree. nearly the full [0,1] range, and cheap sampling-based auto-tuning recovers >95% of an exhaustive search in under 100 trials (AutoER, 2025). So we build a tunable matcher and a small tuner — we do not chase one perfect modelneural classifierThe machine learning model at the core of Mailwoman's parser — a transformer encoder (~30M parameters) trained from scratch to do BIO token classification over addresses. It learns the 'grammar' of address formats; the gazetteer supplies the 'atlas.'.
  • Calibrate, then abstain. A link is a probability; the ambiguous middle goes to review, not to a coin flip.
  • Grade the pipelinestaged pipelineMailwoman's runtime architecture: a sequence of pure-function stages (normalize → query-shape → locale-gate → kind-classifier → phrase-grouper → classifier → decoder) connected by typed handoffs. Each stage is published as its own npm package. against truth, never a stagestageOne of the dataflow stages in the runtime pipeline (normalize, locale gate, kind classify, phrase group, token classify, sequence correct, reconcile, resolve). Distinct from tier (model vocabulary) and phase (plan milestone). in isolation. The reconcile regression taught this the hard way. Match quality is measured on linked entities versus reality, not on a scorer's pairwise F1.
  • No silent caps. When blockingblockingThe first stage of entity resolution: generate candidate record pairs using cheap, high-recall keys (geo cell, canonical address, phone) instead of comparing every record to every other (O(n²)). The matcher only scores pairs that survive blocking. drops a regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. or a tiertierInternal versioning of which label classes the model emits. Tier 1 is the coarse components (country, region, locality, postcode); Tier 2 adds venue, street, house_number; Tier 3 (future) would add attention, po_box, and POI venue subtyping. Historically called 'Stage 1/2/3' before the runtime-pipeline naming made that ambiguous., say so. A matcher that silently never compares two records reads as "no match found" — the most dangerous lie an ER system can tell.

The address-frequency lever needs a corpus

Two records at the same address might be the same clinic — or two of the forty providers who share a hospital campus. The matcher's strongest precisionprecisionOf the spans the model labeled as a given tag, the fraction it got right. High precision means few false positives. Paired with recall to compute F1. lever is inverse-address-frequency: down-weightparameterA single learned number inside a model — one weight or bias. Mailwoman's encoder has roughly 30 million of them; training is the search for good values. a shared address by how many distinct entities sit on it, so a lonely address is strong evidence of identity and a crowded one is nearly none. It's on by default.

But rarity is only meaningful against a population. The lever counts how often each address appears across the records you hand it — so when you dedupe a whole dataset, the input is the corpuscorpusThe BIO-labeled training data used to train Mailwoman's neural classifier. Assembled from real sources (OpenAddresses, National Address Database) and synthetic shards (boundary stress, order variants, negative space). Managed by @mailwoman/corpus. and it just works. Hand it a thin sample and there's nothing to be rare against: every address looks unique, the signal flattens, and the lever degrades to baseline with no warning. The fix is cheap — the frequency table is a parseaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates.-free string scan, no geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. — so when you're matching a slice, build the table over the full source and pass it in. The one thing you can't do is conjure rarity from a single record with nothing to compare it to.

The layers

WorkspaceJob
@mailwoman/formatterThe inverse of the parser: components → idiomatic localized string + a canonical keycanonical keyA deterministic, normalized string representation of an address produced by @mailwoman/formatter. Lowercase, abbreviation-expanded, punctuation-stripped — so '123 Main St' and '123 MAIN STREET' produce the same key. Used for blocking in the matcher..
@mailwoman/recordThe record schema (person / organization / address) + per-field normalizers.
@mailwoman/matchBlock (geo-first) → score (Fellegi-SunterFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization.) → cluster (centroid-linkage).
@mailwoman/address-idA resolved address → a stable state.cell.hash primary key — the deterministic join.
the applicationIngest → normalizenormalizeStage 1 of the runtime pipeline: deterministic input preprocessing (Unicode NFC, punctuation normalization, whitespace collapse). Returns a NormalizedInput with an offsetMap that maps normalized positions back to the raw input. → geocode → match → cluster → export to GeoJSON / QGIS / leads.

The formatter is the small foundation — it's also the piece this whole threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. started as, before we understood it was one component of a matcher. The record layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. is salvage. The match layerlayerOne transformer block — attention plus a feed-forward network, with normalization and residual connections — applied to every position. Stacking layers lets the model build up richer representations; Mailwoman's encoder has 6. is the part that imploded last time, now standing on coordinates instead of strings. The address-id is the cheap certainty beside the probabilistic match: when two records resolve to the same place and share a canonical address, they collapse on a deterministic key — no scoring, no threshold — and the matcher spends its effort only on the messy rest.

Why you can't find the comparable project

The literature has spatial entity resolutionrecord matchingThe process of determining whether two database records refer to the same real-world entity. Mailwoman's matcher uses a geocode-first approach (match the resolved place, not the address string) with Fellegi-Sunter probabilistic scoring., and it has calibrated probabilistic linkage, and it has neural address parsingaddress parsingThe process of decomposing a free-text postal address string into structured components — house number, street name, locality, region, postcode, and country — so a geocoder can resolve them to coordinates. — as three separate fields with three separate citation graphs. What it does not have is one system that owns the parser, owns the geocoder, calibrates both, and then feeds that calibrated location, uncertainty and all, into a Fellegi-SunterFellegi-SunterA probabilistic record linkage model that computes match probability from agreement-level log-likelihood ratios: log₂(m/u) where m is the probability of agreement given a true match and u is the probability of agreement by chance. Mailwoman learns m and u label-free via expectation-maximization. matcher as a quality-weighted featurefeatureAn input signal a model conditions on. Beyond the raw tokens, Mailwoman feeds soft features — gazetteer-membership channels and the postcode anchor — that inform predictions without overriding them.. That's either a moat or a warning, and the only way to learn which is to build it and grade it against real clinic data. That's the work.

See also