/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * @file Build the sub-venue designator lexicon (#35) — the vocabulary a corpus slice and, eventually, * the span proposer read to recognize `Terminal 5`, `North Terminal`, `Concourse B`, `ターミナル1` as * venue-INTERIOR structure. This is the assembly: the record schema lives in `sub-venue/table.ts`, the * implementation in its siblings, and the curation decisions in `sub-venue-promotions.ts`. * * Reads the fetch outputs (`mailwoman corpus fetch wikidata-subvenue`, a JSONL of * `@mailwoman/osm/sdk`'s `SubVenueSourceRow`s per region, and the Overture slice of `poi.db` via * `overture-subvenue.ts`) and emits one committed JSON table. * * ── Determinism ────────────────────────────────────────────────────────────────────────────────── * {@link buildSubVenueLexicon} is a PURE function of its inputs with a stable sort on every array, so * a regenerate against the same fetch outputs is byte-identical. No timestamp is emitted for the same * reason `taxonomy.json` carries none — a clock in the artifact makes every regenerate a diff. * Vintages live in `sources[]`, taken from the fetch manifests. * * ── Where the stages live ──────────────────────────────────────────────────────────────────────── * Each stage carries the measurements that shaped it; the order they run in is * {@link buildSubVenueLexicon}'s own docstring, and it is required. * * - `sub-venue/table.ts` — the emitted record schema plus the shipped seed vocabulary. * - `sub-venue/surfaces.ts` — phrase normalization, the phrase → record index, and the name-match * operator that gates the harvest. * - `sub-venue/wikidata.ts` — the designator-label SPARQL payload turned into surfaces. * - `sub-venue/head-nouns.ts` — the addressed form derived from an encyclopaedic label. * - `sub-venue/harvest.ts` — the harvestable row shape, its JSONL reader, and the attestation pass. * * ── What `curated: false` means, and how a surface stops being it ──────────────────────────────── * Every machine-derived surface lands `curated: false`, and {@link SubVenueLexiconTable} consumers * that gate parsing MUST filter to `curated: true`. A surface becomes curated ONLY by matching a * {@link SubVenuePromotion} in `sub-venue-promotions.ts` — a per-designator, per-LOCALE decision * carrying the census that backs it. Promotion is per-locale because the same token is a designator in * one language and a disaster in another: `hall` is `Halle 2` at Frankfurt and `Village Hall` at 3,205 * British bus stops. */ import { type SubVenueHarvestRow } from "#tools/sub/venue/harvest"; import { type SubVenuePromotion } from "#tools/sub/venue/promotions"; import { type SubVenueLexiconSource, type SubVenueLexiconTable, type SubVenueSurface } from "#tools/sub/venue/table"; export * from "#tools/sub/venue/harvest"; export * from "#tools/sub/venue/head-nouns"; export * from "#tools/sub/venue/surfaces"; export * from "#tools/sub/venue/table"; export * from "#tools/sub/venue/wikidata"; /** * Apply the curation decisions to a surface list, IN PLACE on a copy. * * A promotion binds `(designatorID, phrase, locale)`. A surface matches when it names the same record with the same * phrase and its language is the locale's language OR the untagged `und` — the default `name` tag carries no language, * and a German extract's untagged `Halle 2` is German. * * REGION is the subtle half. A surface attested in an extract carries that extract's region and matches only its own * locale. A surface with `region: ""` is region-FREE — a Wikidata label or a derived head noun — and a promotion * reaches it only when no REJECTION exists for the same designator, phrase and language anywhere else. That guard is * not decoration: `pier` is promoted for en-GB and rejected for en-US, and without it the en-GB decision would curate * the region-free English surface and hand `Pier 1 Imports` the promotion en-US was refused. Where no rejection * competes — `terminal` in `es`, `ターミナル` in `ja` — the region-free surface is the whole point, since a language's * designator does not stop at a border. * * Rejections mark nothing themselves. They exist in `promotions[]` as the record of a decision taken, so the next * reader meets en-GB `hall`'s 3,204 bus stops before re-proposing it, not after. */ export declare function applyPromotions(surfaces: readonly SubVenueSurface[], promotions: readonly SubVenuePromotion[]): SubVenueSurface[]; /** * One harvestable input: rows plus the stamp they carry into the table. */ export interface SubVenueHarvest { rows: readonly SubVenueHarvestRow[]; source?: string; region?: string; } /** * Everything {@link buildSubVenueLexicon} needs, already parsed. Keeping the builder off the filesystem is what makes it * deterministic and testable without fixtures on disk. */ export interface BuildSubVenueLexiconInput { /** * The raw `designator-labels.json` SPARQL envelope, or `null` to build the seed-only table. */ wikidata: unknown | null; /** * Every harvestable source, in the order they should contribute. Order matters only for the surface INDEX: a source * can match a phrase an earlier source introduced, never a later one. */ harvests: readonly SubVenueHarvest[]; /** * Provenance rows, copied off the fetch manifests by the caller. */ sources: readonly SubVenueLexiconSource[]; /** * Curation decisions. Defaults to the committed {@link SUBVENUE_PROMOTIONS}; pass an empty array to build the * pre-curation table (which is what the promotion census itself is taken against). */ promotions?: readonly SubVenuePromotion[]; } /** * Build the lexicon table. PURE and deterministic — same inputs, byte-identical output. * * Order of operations is required in three places: * * 1. Seed surfaces are inserted before anything else, so `terminal` indexes to the `terminal` designator rather than to * whichever Wikidata alias sorts first. * 2. Head nouns are derived AFTER Wikidata and BEFORE the harvests, because `ターミナル` has to exist as a surface before a * Japanese extract can be searched for it. That ordering is the entire reason the Japan harvest finds anything — see * `PROVENANCE.md`. * 3. Promotions are applied LAST, over the union, so a decision can promote a surface whichever source produced it. */ export declare function buildSubVenueLexicon(input: BuildSubVenueLexiconInput): SubVenueLexiconTable; /** * Serialize the table the way the committed artifact stores it: pretty-printed, trailing newline. Run `oxfmt` over the * result before committing — repo law is that committed JSON is oxfmt-clean, which `JSON.stringify` cannot reproduce. */ export declare function serializeSubVenueLexicon(table: SubVenueLexiconTable): string; /** * One OSM extract to harvest: the JSONL path plus the ISO country its rows describe. */ export interface SubVenueExtractInput { path: string; region: string; } export interface GenerateSubVenueLexiconOptions { /** * Directory holding the `mailwoman corpus fetch wikidata-subvenue` output. Omit to build the seed-only table. */ wikidataDir?: string; /** * OSM extract JSONLs, one per region. */ extracts?: readonly SubVenueExtractInput[]; /** * Already-read Overture rows (`readOvertureSubVenues`), grouped by the caller. Kept as a parameter rather than a path * so this function stays free of a 3.9 GB database dependency — the CLI opens `poi.db`, this assembles. */ overtureRows?: readonly (SubVenueHarvestRow & { country: string; })[]; /** * `poi.db`'s layer vintage, for `sources[]`. Only read when `overtureRows` is non-empty. */ overtureVintage?: string; /** * Where the table is written. */ outPath: string; } /** * Read the fetch outputs, build the table, and write it. * * The IO half only — every decision lives in {@link buildSubVenueLexicon}, which is pure. Run `oxfmt` over `outPath` * afterwards; repo law is that committed JSON is oxfmt-clean. */ export declare function generateSubVenueLexicon(options: GenerateSubVenueLexiconOptions): Promise; //# sourceMappingURL=lexicon.d.ts.map