Postcode anchor through the resolver eval (2026-06-03)
The postcode anchoranchor inferenceA technique where structured knowledge (postcode locations, gazetteer place names) is injected into the model as soft input features — not as deterministic overrides. The model still decides the final labels, but the anchor signal biases it toward correct admin tags. (#240) is wired into the 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. real-point 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. evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. as a neural+anchor
row (--postcode-anchor). The neural 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. 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. supply the admin/place identity as before;
the coordinate is taken from the postcode anchoranchor inferenceA technique where structured knowledge (postcode locations, gazetteer place names) is injected into the model as soft input features — not as deterministic overrides. The model still decides the final labels, but the anchor signal biases it toward correct admin tags.'s own centroid. So the row isolates exactly what the
anchor sharpens: where, not which place. The tables below are emitted verbatim by
scripts/eval/oa-resolver-eval.ts (evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. figures are never hand-typed).
The anchor now carries a position-aware confidence: a digit-only code that shares its comma-segmentsegmentA punctuation-bounded chunk of the normalized input — the comma-separated parts of 'Portland, OR' — used to give downstream stages structural context.
with a streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels. word reads as a house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales., not 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. (house numbershouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales. sit beside 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.,
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. beside the citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy.), so its confidence is scaled down. 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. vocabularyvocabularyThe fixed set of tokens a tokenizer can produce. Mailwoman's SentencePiece vocabulary is tens of thousands of subword pieces, with byte fallback for anything outside it. is the full USPS
Pub-28 suffix table from @mailwoman/codex/us — minus the abbreviations that collide with a stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. code
(KY is both Key and Kentucky, PR both Prairie and Puerto Rico), since those appear in 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
own City, ST ZIP segmentsegmentA punctuation-bounded chunk of the normalized input — the comma-separated parts of 'Portland, OR' — used to give downstream stages structural context.. The evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. then picks the highest-confidence spanspanA 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. above a trust floor, with no
"take the last spanspanA 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." crutch: a real trailing 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. out-ranks a leading house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales. on its own merit,
and a spanspanA 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. the prior flags as a house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales. falls back to 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. coordinate rather than placing
the address at a far-away same-shaped ZIP.
German — the resolver carries admin only, no German postcodes
| parser | locality-match | region-match | resolved | coord p50 km | coord p90 km | p99 km |
| **neural** | 77.4% | 43.8% | 99.3% | 9.9 | 66.8 | 318.2 |
| v0 (Pelias) | 79.3% | 99.3% | 99.3% | 7.0 | 16.9 | 106.8 |
| **neural+anchor** | 77.4% | 43.8% | 99.3% | 1.3 | 5.7 | 13.5 |
The anchor drops coord p50 from 9.9 km to 1.3 km (p90 66.8 → 5.7, p99 318 → 13.5), admin-match unchanged. The German parser is out of distribution (localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. 77%), so 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. lands on a coarse admin centroid; the postcode anchoranchor inferenceA technique where structured knowledge (postcode locations, gazetteer place names) is injected into the model as soft input features — not as deterministic overrides. The model still decides the final labels, but the anchor signal biases it toward correct admin tags. — a regex plus a 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., no 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.' in the loop — carries the coordinate to 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 own point. It also beats the PeliasPeliasAn open-source geocoder, Mailwoman's spiritual predecessor. parser (v0) on coordinate by a wide margin (1.3 vs 7.0 km).
(The regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality.-match figures here come from the codex-aware matcher: 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. returns 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 English
exonym Saxony while OA's expected is the German Sachsen, so the evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error. folds both through
@mailwoman/codex/de's lookupGermanState to one ISO 3166-2:DE code before comparing. Before that fix
both parsers read ~0% on Saxony purely from the language mismatch. The remaining neural gap is real and
concentrated in the Berlin 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.: per-stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality., neural emits a resolvable regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. for Saxony 87.5% of
the time but for Berlin only 0.1% — Berlin, Berlin collapses regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. into localitylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy. and the OOD parser
drops the duplicate. v0 reaches 99.3% because it emits the regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. in both.)
US — the resolver already loads postalcode-us.db
| parser | locality-match | region-match | resolved | coord p50 km | coord p90 km | p99 km |
| **neural** | 97.3% | 99.9% | 100.0% | 3.3 | 11.0 | 277.4 |
| v0 (Pelias) | 95.3% | 99.4% | 99.7% | 3.3 | 11.7 | 368.6 |
| **neural+anchor** | 97.3% | 99.9% | 100.0% | 2.8 | 11.2 | 25.7 |
Even where 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. already resolves ZIPs, the anchor helps on the tail: p50 tightens 3.3 → 2.8 km
and the p99 collapses from 277 km to 25.7 km. The tail is the handful of addresses whose neural 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.
resolves to the wrong admin centroid; 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. is a more reliable coordinate than a mis-resolved
citylocalityThe city / town / settlement component of an address: a populated place sitting between region and neighbourhood in the hierarchy., so swapping it in caps the error at ZIP granularity. (Getting here meant closing the house-number
trap: a leading 5-digit that is a valid ZIP in another stateregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. — 32147 Bio Station Lane, Polson, MT 59860
— must not win over the real trailing ZIP. The position prior plus the trust floor handle it; before
them, that trap put the US p99 at 889 km.)
Takeaway
The fusion is a clean win in both directions: it supplies 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.-level coordinates for the localeslocaleThe combination of language and country an address comes from. en-US and fr-FR are the locales Mailwoman ships weights for. whose 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. 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.'s shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. do not carry (DE/ES/IT/NL/FR), and it tightens the tail even where they do (US), because a real 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. out-resolves a mis-parsed admin centroid. It is 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. 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. the evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error.'s own note promised, between the admin-centroid 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. and the future streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.-level (TIGERTIGERThe US Census Topologically Integrated Geographic Encoding and Referencing database. Used as a corpus source for street-segment 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. — and the position-aware confidence means it runs on the anchor's own judgment, not an evalevalRunning the model against a held-out golden dataset and computing per-component F1, exact-match, calibration, and resolved-coordinate error.-side crutch.
Reproduce:
node scripts/eval/oa-resolver-eval.ts \
--eval data/eval/external/openaddresses-de-sample.jsonl --limit 1500 --default-country DE \
--model <onnx> --tokenizer <tok> --model-card <card> \
--wof $MAILWOMAN_DATA_ROOT/wof/admin-global-priority.db \
--postcode-shards <us.db>,<intl.db> --postcode-anchor