Skip to main content

POI layer runbook

Operational knowledge from the first poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db builds (2026-07-18/19, the exotic-POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. arc, PRs #1180–#1185). Read this before touching gazetteer build poi, the poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. executor, or the 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. publish path. The design rationale lives in the spec named in the front matter; this page is the "what bites you" record.

poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db is 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. #1. The schema every 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. database embeds — the layer_manifest / layer_coverage contract, the spine keys, tierstierInternal 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 meaning-of-zero rule this build follows — is specified in the spatial-layer contract. Read that first if you are authoring a new 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. rather than rebuilding poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db.

Build

node mailwoman/out/cli.js gazetteer build poi --countries US,CA,MX,FR \
--out $MAILWOMAN_DATA_ROOT/poi/poi-full.db

Two phasesphaseA milestone in the implementation plan (Foundation, Corpus, Training, Integration, and forward-looking phases). Distinct from stage (runtime pipeline) and tier (model vocabulary).: DuckDB ingest (Overture Places S3 → per-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. parquetParquetThe open columnar file format the corpus is written and streamed in. The training pipeline reads shards row-by-row from Parquet. under <data-root>/overture/<release>/) then the SQLite build (staged bulk load → clustered materialize → FTS → poi_name_key index → 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. manifest + 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. → seal). --limit N caps rows for smoke builds. --skipIngest reuses existing parquetParquetThe open columnar file format the corpus is written and streamed in. The training pipeline reads shards row-by-row from Parquet..

Overture Places schema gotchas

  • There is no top-level country column in the Places theme (the addresses theme has one; Places does not). 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. lives at addresses[1].country (1-based DuckDB list index). The builder probes DESCRIBE output and picks the right expression (chooseCountryExpression); rows with no address struct are excluded from 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. slices under the addresses-based filter — this is the accepted v1 limitation.
  • The category property is mid-transition. The old categories property is slated for removal in Overture's September 2026 release; the new taxonomy property (taxonomy.primary) replaces it. The builder probes for both and prefers taxonomy (chooseCategoryColumn). When bumping the pinned release, re-check the probe output in the build log — it prints which property it chose.
  • --limit samples are geographically clustered, not representative. LIMIT takes the first rows the scan yields; the first 50k US rows in the 2026-05-20.0 release all landed in Hawaii. Fine for smoke verification, useless for 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. judgments. Never grade 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. or the query board on a limited build.
  • brand.names.primary is deliberately NOT extracted — the QID (brand.wikidata) is the join key and the row's own name carries the display form. Re-adding it is a schema + builder change, not a flag.

Key decisions (do not silently re-decide)

  • res-9 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. 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. ((h3_cell, category_id, neg_rank, rowid_key) WITHOUT ROWID PK): matches ADDRESS_H3_RESOLUTION = 9 in @mailwoman/address-id so the POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer.↔address join is key-equality, and makes a neighborhood query one contiguous B-tree range — the byte-range/httpvfs serving profile. 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. cells sit at res 6.
  • 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 is decorated at read time, not build time (deviation from the spec's original build-time-PIP wording, pre-declared in plan 3). The read-time half plus gersID on results landed 2026-07-19 (mailwoman/poi-executor.ts's reverseGeocode dep, wired lazily in runtime-pipeline.ts from a WOFReverseGeocoder) — see the runtime-flag register.
  • ODbL never ships. Infrastructure categories (source: "mailwoman-infra" in the taxonomy) abstain with requires_build_local_layer; Overture's base theme is ODbL too (Overture does not launder OSMOpenStreetMap (OSM). A community-curated global map database (ODbL-licensed) with addr:* tagged features and place hierarchies. A secondary corpus source and a source of street names.'s license), so there is no permissive source to "just use." We ship the builder.

Verify

A build isn't verified until a live query returns ranked results:

node mailwoman/out/cli.js poi "coffee near <city-in-coverage>" --db <built.db>

The CLI needs 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. to turn anchor text into coordinates (--resolve-db / --candidate-db, defaulting to the standard shardshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. paths) — without one, every anchored query abstains anchor_required, which looks like a data bug and isn't. An empty results list with a resolved anchor means the sample lacks 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. there (see the LIMIT note); an anchor_required abstain means 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. isn't wired.

Fixture-level tests cannot catch source-schema drift or scale behavior — the first live builds caught three bugs (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. column, the CLI 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. gap, and a full-scale OOM) that 800+ green tests missed. End every poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db-touching change with a live smoke build, and treat the first FULL-scale build as its own gate: the 2026-07-19 four-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. run died at Node's default ~4 GB heap inside DuckDB's buffer-copy path because the parquetParquetThe open columnar file format the corpus is written and streamed in. The training pipeline reads shards row-by-row from Parquet. reader materialized instead of streamed. Row readers over DuckDB must use the chunked/streaming API; memory must stay flat with row count.

Two more full-scale-only failures, both fixed 2026-07-19 and worth knowing the class of: the DuckDB parquetParquetThe open columnar file format the corpus is written and streamed in. The training pipeline reads shards row-by-row from Parquet. reader must STREAM (chunked fetches; a materializing reader OOM'd the ~4 GB JS heap at 13.68M rows — peak RSS is ~2.4 GB streamed), and any Kysely bulk .values(...) insert must be CHUNKED (writeLayerCoverage's single INSERT for 159,702 res-6 cells exceeded SQLite's bound-variable limit; batchesbatch sizeHow many examples the model processes before each parameter update. Larger batches give smoother gradients but cost more memory; gradient accumulation simulates a big batch on a small GPU. of 5,000 rows now). The class: anything linear in row count that fixture scale cannot exercise.

Reference numbers from the first complete 4-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. build (release 2026-05-20.0, confidence ≥ 0.85): 13,681,698 rows (US 11.52M / CA 794k / FR 721k / MX 644k), 1,936 categories, 159,702 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. cells, 3.7 GB sealed. Continental probe: "coffee near Springfield IL" → 11 ranked results, nearest 743 m.

A crashed build leaves a partial, unsealed .db behind (the builder direct-writes and seals at the end, per the candidate-builder precedent). The next run's rmSync clears it, but never point a reader — or the publish path — at an unsealed file: no manifest row is the tell (readLayerManifest throws).

Publish (demo)

Decision of record (2026-07-19): publish the FULL multi-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. poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db to R2 for byte-range access — no separate demo slice artifact. Demo config: category queries only (no FTS name search over httpvfs — multi-hop traversal is the slow path), ring budget capped ~3–4 (the CLI default is 12). Contingency if the shipped file or probe latency surprises: a clearly-labeled regional slice, as fallback only.

Publish flow (canonical POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. path, 2026-07-20). The poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer..db goes up via the demo-assets uploader, not 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. path: 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). a hardlink dir mirroring the R2 layout poi/<version>/poi.db (a hardlink avoids re-copying the 3.7 GB file), then set -a; . ./.env; set +a; python3 scripts/publish-demo-assets-to-r2.py --src <staged-dir> (source the repo .env first — the R2 credentials live there). Then bump the demo version constant (POI_LAYER_VERSION in docs/src/shared/resources.tsx). Note: gazetteer publish is admin-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.-shaped — it uploads as candidate.db — and is not the poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. path; using it would land the file at the wrong key. The docs tester (understanding/exotic-poi/try-it.mdx) is intent-only until the 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 published.

Flag state

poiQueryKind is default-ON since 2026-07-20 (boolean | { poiDatabasePath }) — the promotion gategated promotionA release discipline where a model ships to production only if it passes pre-registered metrics (e.g. 'DE locality ≥ 70%, no tag regresses > 2pp'). Failing models can be uploaded as experiments but not promoted to default. passed: 0/4,507 golden misroutes, 6/6 demo presets byte-identical, POIpoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. board v1.1 92.2% (see 2026-07-20-poi-promotion-battery.md and the runtime-flag register). After #1206/#1208/#1209 (leaf fan-out, indexed brand fetch, full ~2,110-category snapshot) the board stands at 50/51 = 98.0% against the rebuilt poi/2026-07-20a/poi.dbnm-04 the sole holdout (a ring-budget case, not a 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. gap), both hard floors green (see the 2026-07-20 standing update in 2026-07-19-poi-query-board-v1-baseline.md). undefined/true is intent/abstain-only (no db); pass false to restore the pre-flag, byte-identical address-only classification. forceFullPipeline does not bypass the poipoint of interest (POI). A named place that is not strictly an address — landmark, transit stop, venue, amenity, or franchise. Mailwoman tags these as venue and resolves them through the gazetteer. branch (routing branch, not a fast-path — decided, documented on PipelineOpts).