The locality resolver works in four languages, and the parser only speaks two
Date: 2026-06-04 Scope: coordinate-first localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. resolution across DE, FR, GB, NL — metric, results, design, and the limits.
We started this stretch trying to teach a neural parser to read German. We're ending it with a resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. that places Dutch addresses correctly without the parser understanding a word of Dutch. That inversion is the whole story, so let's tell it in order.
Where this came from
The German parser was stuck. A from-scratch self-conditioned retrain (PR3 Pilot A) came back at 25.6% localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon.-match against v0.7.2's 77.4% — 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.' learned German streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. order and then dropped the trailing citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., the same end-of-string collapse that killed the v0.8.0 order-shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row.. We chased it into the decoderdecoderIn a transformer encoder-decoder model, the part that produces output sequences. Mailwoman's classifier is encoder-only (no decoder); the 'CRF decoder' is a different thing — a structured-prediction layer that picks the best label sequence from the encoder's outputs. too: a postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.-boundary trim that produced string-perfect parsesaddress 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. (auen Vogtl → Plauen Vogtl) and moved the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. zero.
That zero was the tell. 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 right and the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. still missed, because the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. name-matched the parsed localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. against Who's On FirstWOF (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., and OpenAddressesOpenAddresses (OA). A global open aggregation of address points collected from many official sources. A primary source of component-supervised training data outside proprietary registries.' gold carries regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. suffixes 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. doesn't store (Plauen Vogtl vs 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.'s Plauen). We'd been polishing the wrong surface. German was never a parser problem; it was a resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. problem, and the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. was matching on names when it should have been matching on coordinates. That's the reframe the whole multi-localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. program is built on.
The metric: point-in-polygon containment
A name-match metric is the thing that misled us, so we replaced it with one that can't. A localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. is resolved correctly if the real OpenAddressesOpenAddresses (OA). A global open aggregation of address points collected from many official sources. A primary source of component-supervised training data outside proprietary registries. per-address point lies inside the polygon of 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. localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. picked — ST_Within(gold_point, resolved_polygon). Containment, not centroid distance (distance is gameable in a dense metro), and scored against the genuine OA point, never the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. centroid the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. itself consumed.
Running that metric on the existing resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. first was the cheap move that paid off twice: it confirmed German was a real gap (77.1% containment agreed with the 77.4% name-match, killing the comfortable "it's just a name artifact" hypothesis), and it became the honest yardstick for everything that followed.
What we built
Coordinate-first candidate generation, soft-scored. We precompute a postcode → containing + nearby WOF localities table — point-in-polygon each postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. centroid against the localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. polygons, offline, from 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. source GeoJSON. At resolve time the parser's job shrinks to one reliable thing: extract the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.. The resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. looks it up, gets the coordinate candidate the name-match could never generate for an under-indexed small town, and soft-scores the union against the parsed name:
Score = 0.6·S_pc + 0.3·S_name + 0.1·S_pop
with exact-name tiering on top, so an unambiguous citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. (Berlin, exact + huge population) stays ahead of the fine-grained Ortsteil its postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. centroid happens to land in, while a small town the name-match never finds is carried entirely by its postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.. The parser is never touched, and there's no Elasticsearch anywhere.
Results
PIP-containment, n=3000 per localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for., coordinate-first on:
| localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. | sample | PIP-containment | name-match baseline |
|---|---|---|---|
| DE | Berlin + Saxony OA | 92.6% | 77.1% |
| FR | national BANBAN (Base Adresse Nationale). France's authoritative open national address register — the highest-quality training source for French addresses, with full component structure. OA (from 24.7M points) | 84.0% | 83.5% |
| NL | national BAG OA (from 9.1M points) | 94.9% | 97.0% |
| GB | — | conflict-validated; ~66% 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. coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. | — |
German went from a stuck 77% to 92.6% — Saxony alone moved 54.3% → 89.3% (+35pp) once the small Saxon towns the FTS missed got generated from their postcodespostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.; Berlin held at 95.9%.
France lands at 84.0%, and the gap from DE is honest rather than alarming: this is the whole 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., BANBAN (Base Adresse Nationale). France's authoritative open national address register — the highest-quality training source for French addresses, with full component structure.'s full 25 million points including the long rural-commune tail where 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.'s localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. polygons thin out. DE's 92.6% was two dense regionsregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.. On comparable density FR sits near DE.
The Netherlands is the result that proves the thesis. 94.9% — and 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 out-of-distribution on Dutch. v0.7.2 was trained on US and French addresses; it has never seen Dignahoeve 71, 1187LM Amstelveen. A name-matching or BM25 resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. fed an un-parsed Dutch string would crater. Ours didn't move, because coordinate-first resolves off the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one., and a postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. is language-agnostic — a regex finds 1187LM whether or not 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.' understands 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. around it. NL's near-complete 99.6% postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.→localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. does the rest. The architecture's whole point is that the parser can be wrong about the language and the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. is still right about the place.
The part a search box can't do
PeliasPeliasAn open-source geocoder, Mailwoman's spiritual predecessor., Nominatim, Airmail — the retrieval geocoders — absorb a wrong field by ranking. Hand them 10 Main St, 90210, Los Angeles and BM25 returns Beverly Hills if a Main StreetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. exists there. They have no signal that says "these two fields disagree." 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.-then-resolve 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. does: the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.-derived localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. and the name-derived localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. are independent, and when they point to places far apart, that's a transposed or wrong-for-the-citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one., caught instead of laundered.
We surface it as postcode_city_mismatch on the resolved node when the chosen citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. sits more than 50 km from the postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one.'s anchor localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. — generous enough to ignore city-statedual-role placeA place that is two placetypes at once — Berlin as both a city and a state, Washington DC as city and district. Resolved using the coincident-roles relation plus hierarchy completion. Ortsteile (~15 km) and abutting border towns (a few km), tight enough to catch a wrong citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy.. Across DE + FR + GB + NL the conflict evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. runs 92% 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. (12/13), 100% specificity (10/10): every wrong-for-the-citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. flags, no correct or abutting address false-flags. 80331 Berlin, 75001 Lyon, 1026 Rotterdam, SW1A 1AA Edinburgh — all raised. The lone miss is a GB postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. that simply isn't in the table, not a logic failure. That conflict signal is the concrete differentiator over a retrieval system: we don't just resolve the address, we tell you when it can't be trusted.
One asset, read-only, from source
All of it ships as a single self-contained postcode-locality-intl.db — DE/ES/FR/GB/IT/NL in one 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.-filtered table, with a provenance/license meta row, journal_mode=DELETE (no sidecar), integrity-checked and VACUUM'd. Built from 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. source GeoJSON, never a prebuilt dump, with every source repo commit pinned in the build manifest. You could hand someone the file and they'd have coordinate-first resolution for four working localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for..
What it doesn't do yet
The limits are real and worth naming plainly. GB resolves out of the box, but 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.'s GB localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. is only ~66% — a third of UK unitunitA subdivision of a building — apartment, suite, floor — that refines a street address. Mailwoman's unit component; a designator plus identifier forms a subpremise. postcodespostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. fall outside any 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. localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. polygon and get no coordinate candidate; closing that needs a finer or non-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. British source. ES and IT are in the gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. but 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. is orphan-heavy there (29% / 42% postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present.), so they're data-limited until a richer source lands. And there's a few-point city-statedual-role placeA place that is two placetypes at once — Berlin as both a city and a state, Washington DC as city and district. Resolved using the coincident-roles relation plus hierarchy completion. residual in Berlin / Paris / London where a postcodepostcodeThe country-specific postal code (US ZIP, French code postal, etc.). Mailwoman handles postcode parsing entirely by rule classifier — a regex problem, not an ML one. centroid lands in an unnamed Ortsteil — cosmetic, on the table for a later tune. None of these are architectural; they're all the upstream gazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. running out of data, which is exactly where you'd want the remaining work to live.
Where this leaves the parser
Untouched, and that was the point. Every localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. above was added without retraining 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.', without an anchor-conditioning 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. baked into it, without an Elasticsearch cluster. The parser stays a clean universal tokentokenOne word or subword in the tokenized input. For the neural classifier, tokens come from SentencePiece (subword units); for the rule classifiers, tokens are whitespace- and punctuation-separated words. tagger; the resolverresolverThe component that converts parsed address components (locality, region, postcode) into coordinates by looking them up in the gazetteer. The resolver ranks candidates by name match, population, and proximity, and returns the best-matching place with its centroid or polygon. carries the localelocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for.-specific intelligence and the falsehood detection. German started this as a parser crisis and ended it as four working localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. and a shippable asset — by moving the work to where it belonged.