@mailwoman/codex 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,516 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Per-locale LEVEL semantics — the fix for "Flr 1 means something different depending on where you
7
+ * are" (#1100, the secondary-address epic's second data deliverable). {@link "./us/unit-designator.ts"},
8
+ * {@link "./us/floor-designator.ts"}, and {@link "./au/level-designator.ts"} each standardize ONE
9
+ * address system's floor/level VOCABULARY — which surface tokens exist ("FL", "L 3", "Ground
10
+ * Floor"). This module standardizes the cross-locale ORDINAL SEMANTICS those tokens carry: the fact
11
+ * that "1st floor" names the SAME physical storey as "ground floor" in the United States
12
+ * (`firstNumberedIsGround: true`) but ONE STOREY ABOVE it in France, Germany, and most of
13
+ * continental Europe (`firstNumberedIsGround: false`), where ground already has its own name (RDC,
14
+ * EG, PLANTA BAJA, …) and claims ordinal 0 on its own.
15
+ *
16
+ * This is inherently a MULTI-LOCALE module — unlike `us/`, `au/`, `fr/`, … it doesn't belong to one
17
+ * address system, so it lives at the codex root and is exported only from the root barrel
18
+ * (mirroring `address-system-conventions.ts` and `postcode-systems.ts`, the other cross-system root
19
+ * modules). It does not get its own `@mailwoman/codex/<x>` subpath.
20
+ *
21
+ * Two tables do the work:
22
+ *
23
+ * 1. {@link LEVEL_DESIGNATORS_BY_FAMILY} — the designator LEXICON per LANGUAGE family (the surface
24
+ * vocabulary: FL/FLOOR/LVL/LEVEL, ÉTAGE/ÉT, OG/OBERGESCHOSS, …). Keyed by a bare language tag
25
+ * ("en", "fr", "de", …) because the WORDS don't vary by country — American and British English
26
+ * both say "floor", "basement", "penthouse".
27
+ * 2. {@link LEVEL_ORDINAL_CONVENTIONS} — the ORDINAL CONVENTION per full locale ("en-US", "en-GB",
28
+ * …), because the NUMBERING varies by country even within one language: American and Canadian
29
+ * buildings both call the ground floor "the 1st floor"; British buildings, like the rest of the
30
+ * IMDF/continental-European convention locales, do not.
31
+ *
32
+ * {@link levelToOrdinal} composes both: look up the designator's KIND in the locale's language
33
+ * family (ground / basement / numbered / fractional / special / fixed), then — for numbered
34
+ * designators only — apply the locale's numbering convention. IMDF (Apple's Indoor Mapping Data
35
+ * Format) is the schema precedent for encoding a level as a signed integer `ordinal` where ground is
36
+ * always 0; this table supplies the locale-aware mapping from a raw (designator, number) pair into
37
+ * that same ordinal space.
38
+ *
39
+ * Data is convention encoded from common postal/building usage, not a single postal authority
40
+ * publication — level-numbering conventions are cultural, not regulatory, so no Pub-28-style single
41
+ * source exists for most of these locales. Retrieved/encoded 2026-07-13, epic #1100.
42
+ *
43
+ * Deliberately-excluded ambiguities (handled by an explicit, documented rounding rule below, never a
44
+ * silent guess):
45
+ *
46
+ * - **Spanish PRINCIPAL / ENTRESUELO**: pre-metric Spanish buildings run BAJO (0) → ENTRESUELO
47
+ * (~0.5) → PRINCIPAL (1) → PISO 1/2 (2), but the exact offset varies by city and building age.
48
+ * ENTRESUELO's true position (0.5) isn't representable as an integer ordinal; it floors to 0
49
+ * (grouped with ground) — a documented approximation, not an empirical claim. PRINCIPAL is a
50
+ * fixed, always-ordinal-1 designator (it names a specific floor by convention, not by a number the
51
+ * caller supplies).
52
+ * - **English LOWER GROUND / UPPER GROUND** (UK mixed-use buildings): sit at roughly -0.5 and +0.5
53
+ * relative to ground. Both round DOWN (floor): LOWER GROUND → -1 (grouped with the first basement
54
+ * level), UPPER GROUND → 0 (grouped with ground). Convention choices, not measurements.
55
+ * - **PENTHOUSE / ROOF / ATTIC / DACHGESCHOSS / ÁTICO / ATTICO**: named by relationship to the TOP of
56
+ * a SPECIFIC building, not by a fixed distance from ground — there is no locale-independent integer
57
+ * to assign. {@link levelToOrdinal} returns `undefined` for this designator kind rather than
58
+ * guessing.
59
+ * - **Nordic ground-floor vocabulary**: Danish STUEN/STUEETAGE is a well-attested standard term
60
+ * (the "st." you see on Danish addresses). Norwegian has no equally standard, universally-agreed
61
+ * single word for "ground floor" distinct from "1. etasje" in everyday use; GATEPLAN is included
62
+ * here for structural parity with the other Nordic tables but is a lower-confidence, regional
63
+ * inclusion — flagged in-line, not asserted as authoritative.
64
+ *
65
+ * @see {@link https://register.apple.com/resources/imdf/Level/ IMDF Level — `ordinal` (Apple Indoor Mapping Data Format)}
66
+ */
67
+ /**
68
+ * English (American, British, Canadian, Australian, …) floor/level vocabulary. This is the GENERIC English lexicon for
69
+ * the ordinal-semantics table; it doesn't replace the more detailed per-system lexicons in
70
+ * {@link "./us/floor-designator.ts"} (USPS Pub-28 C2) or {@link "./au/level-designator.ts"} (AS 4590.1 / AMAS) — those
71
+ * drive span-proposer/synthesis vocabulary for their own address system. This table exists to answer a narrower
72
+ * question for ANY English-speaking locale: given a designator + number, what ordinal does it name.
73
+ */
74
+ export const EN_LEVEL_DESIGNATORS = [
75
+ {
76
+ code: "FLOOR",
77
+ name: "Floor",
78
+ variants: ["FLOOR", "FL", "FLR", "LEVEL", "LVL"],
79
+ kind: "numbered",
80
+ requiresNumber: true,
81
+ },
82
+ { code: "BASEMENT", name: "Basement", variants: ["BASEMENT", "BSMT", "B"], kind: "basement", requiresNumber: true },
83
+ { code: "PENTHOUSE", name: "Penthouse", variants: ["PENTHOUSE", "PH"], kind: "special", requiresNumber: false },
84
+ {
85
+ code: "GROUND",
86
+ name: "Ground",
87
+ variants: ["GROUND", "G", "GROUND FLOOR", "GF"],
88
+ kind: "ground",
89
+ requiresNumber: false,
90
+ },
91
+ {
92
+ code: "LOWER GROUND",
93
+ name: "Lower Ground",
94
+ variants: ["LOWER GROUND", "LG"],
95
+ kind: "fractionalBelowGround",
96
+ requiresNumber: false,
97
+ },
98
+ {
99
+ code: "UPPER GROUND",
100
+ name: "Upper Ground",
101
+ variants: ["UPPER GROUND", "UG"],
102
+ kind: "fractionalAboveGround",
103
+ requiresNumber: false,
104
+ },
105
+ {
106
+ code: "MEZZANINE",
107
+ name: "Mezzanine",
108
+ variants: ["MEZZANINE", "MEZZ", "M"],
109
+ kind: "fractionalAboveGround",
110
+ requiresNumber: false,
111
+ },
112
+ { code: "ROOF", name: "Roof", variants: ["ROOF", "RF"], kind: "special", requiresNumber: false },
113
+ ];
114
+ /** French (France, and — for the vocabulary, not the numbering convention — Francophone Canada) floor/level vocabulary. */
115
+ export const FR_LEVEL_DESIGNATORS = [
116
+ {
117
+ code: "ÉTAGE",
118
+ name: "Étage (Floor)",
119
+ variants: ["ÉTAGE", "ETAGE", "ÉT", "ET"],
120
+ kind: "numbered",
121
+ requiresNumber: true,
122
+ },
123
+ {
124
+ code: "RDC",
125
+ name: "Rez-de-chaussée (Ground floor)",
126
+ variants: ["RDC", "REZ-DE-CHAUSSÉE", "REZ-DE-CHAUSSEE", "REZ DE CHAUSSEE"],
127
+ kind: "ground",
128
+ requiresNumber: false,
129
+ },
130
+ {
131
+ code: "SOUS-SOL",
132
+ name: "Sous-sol (Basement)",
133
+ variants: ["SOUS-SOL", "SOUS SOL", "SS"],
134
+ kind: "basement",
135
+ requiresNumber: true,
136
+ },
137
+ {
138
+ code: "ENTRESOL",
139
+ name: "Entresol (Mezzanine)",
140
+ variants: ["ENTRESOL"],
141
+ kind: "fractionalAboveGround",
142
+ requiresNumber: false,
143
+ },
144
+ ];
145
+ /** German floor/level vocabulary (the -geschoss family). */
146
+ export const DE_LEVEL_DESIGNATORS = [
147
+ {
148
+ code: "OBERGESCHOSS",
149
+ name: "Obergeschoss (Upper floor)",
150
+ variants: ["OBERGESCHOSS", "OG"],
151
+ kind: "numbered",
152
+ requiresNumber: true,
153
+ },
154
+ {
155
+ code: "ERDGESCHOSS",
156
+ name: "Erdgeschoss (Ground floor)",
157
+ variants: ["ERDGESCHOSS", "EG"],
158
+ kind: "ground",
159
+ requiresNumber: false,
160
+ },
161
+ {
162
+ code: "UNTERGESCHOSS",
163
+ name: "Untergeschoss (Basement)",
164
+ variants: ["UNTERGESCHOSS", "UG"],
165
+ kind: "basement",
166
+ requiresNumber: true,
167
+ },
168
+ {
169
+ code: "DACHGESCHOSS",
170
+ name: "Dachgeschoss (Attic/roof floor)",
171
+ variants: ["DACHGESCHOSS", "DG"],
172
+ kind: "special",
173
+ requiresNumber: false,
174
+ },
175
+ {
176
+ code: "ZWISCHENGESCHOSS",
177
+ name: "Zwischengeschoss (Mezzanine)",
178
+ variants: ["ZWISCHENGESCHOSS", "ZG"],
179
+ kind: "fractionalAboveGround",
180
+ requiresNumber: false,
181
+ },
182
+ ];
183
+ /**
184
+ * Spanish floor/level vocabulary. PRINCIPAL and ENTRESUELO offsets vary by city and building age (see the module
185
+ * header) — encoded here as a single convention, not an empirical universal.
186
+ */
187
+ export const ES_LEVEL_DESIGNATORS = [
188
+ { code: "PLANTA", name: "Planta/Piso (Floor)", variants: ["PLANTA", "PISO"], kind: "numbered", requiresNumber: true },
189
+ {
190
+ code: "PLANTA BAJA",
191
+ name: "Planta Baja (Ground floor)",
192
+ variants: ["PLANTA BAJA", "BAJO", "PB"],
193
+ kind: "ground",
194
+ requiresNumber: false,
195
+ },
196
+ {
197
+ code: "ENTRESUELO",
198
+ name: "Entresuelo (Mezzanine)",
199
+ variants: ["ENTRESUELO"],
200
+ kind: "fractionalAboveGround",
201
+ requiresNumber: false,
202
+ },
203
+ {
204
+ code: "PRINCIPAL",
205
+ name: "Principal",
206
+ variants: ["PRINCIPAL"],
207
+ kind: "fixedOrdinal",
208
+ requiresNumber: false,
209
+ fixedOrdinal: 1,
210
+ },
211
+ {
212
+ code: "SÓTANO",
213
+ name: "Sótano (Basement)",
214
+ variants: ["SÓTANO", "SOTANO"],
215
+ kind: "basement",
216
+ requiresNumber: true,
217
+ },
218
+ {
219
+ code: "ÁTICO",
220
+ name: "Ático (Attic/penthouse)",
221
+ variants: ["ÁTICO", "ATICO"],
222
+ kind: "special",
223
+ requiresNumber: false,
224
+ },
225
+ ];
226
+ /** Italian floor/level vocabulary. */
227
+ export const IT_LEVEL_DESIGNATORS = [
228
+ { code: "PIANO", name: "Piano (Floor)", variants: ["PIANO"], kind: "numbered", requiresNumber: true },
229
+ {
230
+ code: "PIANO TERRA",
231
+ name: "Piano Terra (Ground floor)",
232
+ variants: ["PIANO TERRA", "PT"],
233
+ kind: "ground",
234
+ requiresNumber: false,
235
+ },
236
+ {
237
+ code: "SEMINTERRATO",
238
+ name: "Seminterrato (Semi-basement)",
239
+ variants: ["SEMINTERRATO"],
240
+ kind: "fractionalBelowGround",
241
+ requiresNumber: false,
242
+ },
243
+ {
244
+ code: "ATTICO",
245
+ name: "Attico (Attic/penthouse)",
246
+ variants: ["ATTICO"],
247
+ kind: "special",
248
+ requiresNumber: false,
249
+ },
250
+ ];
251
+ /** Portuguese floor/level vocabulary. */
252
+ export const PT_LEVEL_DESIGNATORS = [
253
+ { code: "ANDAR", name: "Andar (Floor)", variants: ["ANDAR"], kind: "numbered", requiresNumber: true },
254
+ {
255
+ code: "RÉS-DO-CHÃO",
256
+ name: "Rés-do-chão (Ground floor)",
257
+ variants: ["RÉS-DO-CHÃO", "RES-DO-CHAO", "RC"],
258
+ kind: "ground",
259
+ requiresNumber: false,
260
+ },
261
+ { code: "CAVE", name: "Cave (Basement)", variants: ["CAVE"], kind: "basement", requiresNumber: true },
262
+ ];
263
+ /** Dutch floor/level vocabulary. */
264
+ export const NL_LEVEL_DESIGNATORS = [
265
+ {
266
+ code: "VERDIEPING",
267
+ name: "Verdieping (Floor)",
268
+ variants: ["VERDIEPING", "VERD"],
269
+ kind: "numbered",
270
+ requiresNumber: true,
271
+ },
272
+ {
273
+ code: "BEGANE GROND",
274
+ name: "Begane Grond (Ground floor)",
275
+ variants: ["BEGANE GROND", "BG"],
276
+ kind: "ground",
277
+ requiresNumber: false,
278
+ },
279
+ { code: "KELDER", name: "Kelder (Basement)", variants: ["KELDER"], kind: "basement", requiresNumber: true },
280
+ ];
281
+ /**
282
+ * Japanese (and generic CJK numeral+letter) floor/level vocabulary. Japanese addresses write the numbered floor as a
283
+ * trailing "F" suffix on the number ("2F", "地下1F"/"B1F") or the kanji "階" ("2階"); there is no distinct bare word for
284
+ * "ground floor" the way RDC/EG/PLANTA BAJA exist in Europe — "1F"/"1階" already IS ground (handled by the `"numbered"`
285
+ * kind + the ja-JP `firstNumberedIsGround: true` convention, not a separate `"ground"` row). "B" (and "地下", literally
286
+ * "underground") name the basement count the same way English "B1" does.
287
+ */
288
+ export const JA_LEVEL_DESIGNATORS = [
289
+ { code: "F", name: "階 (Floor)", variants: ["F", "階"], kind: "numbered", requiresNumber: true },
290
+ { code: "B", name: "地下 (Basement)", variants: ["B", "地下"], kind: "basement", requiresNumber: true },
291
+ { code: "RF", name: "屋上 (Rooftop)", variants: ["RF", "屋上", "ROOFTOP"], kind: "special", requiresNumber: false },
292
+ ];
293
+ /** Swedish floor/level vocabulary. */
294
+ export const SV_LEVEL_DESIGNATORS = [
295
+ { code: "VÅNING", name: "Våning (Floor)", variants: ["VÅNING", "VANING"], kind: "numbered", requiresNumber: true },
296
+ {
297
+ code: "BOTTENVÅNING",
298
+ name: "Bottenvåning (Ground floor)",
299
+ variants: ["BOTTENVÅNING", "BOTTENVANING", "BV"],
300
+ kind: "ground",
301
+ requiresNumber: false,
302
+ },
303
+ {
304
+ code: "KÄLLARE",
305
+ name: "Källare (Basement)",
306
+ variants: ["KÄLLARE", "KALLARE"],
307
+ kind: "basement",
308
+ requiresNumber: true,
309
+ },
310
+ ];
311
+ /**
312
+ * Norwegian floor/level vocabulary. GATEPLAN ("street level") is a lower-confidence, regional inclusion for the
313
+ * ground-floor row — see the module header's Nordic-vocabulary caveat.
314
+ */
315
+ export const NO_LEVEL_DESIGNATORS = [
316
+ { code: "ETASJE", name: "Etasje (Floor)", variants: ["ETASJE"], kind: "numbered", requiresNumber: true },
317
+ {
318
+ code: "GATEPLAN",
319
+ name: "Gateplan (Street/ground level)",
320
+ variants: ["GATEPLAN"],
321
+ kind: "ground",
322
+ requiresNumber: false,
323
+ },
324
+ { code: "KJELLER", name: "Kjeller (Basement)", variants: ["KJELLER"], kind: "basement", requiresNumber: true },
325
+ ];
326
+ /** Danish floor/level vocabulary. STUEN/STUEETAGE ("st.") is the standard ground-floor term seen on Danish addresses. */
327
+ export const DA_LEVEL_DESIGNATORS = [
328
+ { code: "ETAGE", name: "Etage (Floor)", variants: ["ETAGE"], kind: "numbered", requiresNumber: true },
329
+ {
330
+ code: "STUEN",
331
+ name: "Stuen/Stueetage (Ground floor)",
332
+ variants: ["STUEN", "STUEETAGE", "ST"],
333
+ kind: "ground",
334
+ requiresNumber: false,
335
+ },
336
+ {
337
+ code: "KÆLDER",
338
+ name: "Kælder (Basement)",
339
+ variants: ["KÆLDER", "KAELDER"],
340
+ kind: "basement",
341
+ requiresNumber: true,
342
+ },
343
+ ];
344
+ /** Every language family's level-designator lexicon, keyed by bare language tag. */
345
+ export const LEVEL_DESIGNATORS_BY_FAMILY = {
346
+ en: EN_LEVEL_DESIGNATORS,
347
+ fr: FR_LEVEL_DESIGNATORS,
348
+ de: DE_LEVEL_DESIGNATORS,
349
+ es: ES_LEVEL_DESIGNATORS,
350
+ it: IT_LEVEL_DESIGNATORS,
351
+ pt: PT_LEVEL_DESIGNATORS,
352
+ nl: NL_LEVEL_DESIGNATORS,
353
+ ja: JA_LEVEL_DESIGNATORS,
354
+ sv: SV_LEVEL_DESIGNATORS,
355
+ no: NO_LEVEL_DESIGNATORS,
356
+ da: DA_LEVEL_DESIGNATORS,
357
+ };
358
+ /**
359
+ * Per-family inverse lookup (lowercase variant → its designator row), built once at module load. Structural integrity
360
+ * check runs here: every row must carry at least one non-blank variant, and no variant may repeat WITHIN a family
361
+ * (case-insensitive) — a malformed table throws loudly at import time rather than silently producing an ambiguous
362
+ * lexicon. Collisions ACROSS families are expected and fine (that's the entire point of this module: "UG" is Upper
363
+ * Ground in English but Untergeschoss in German — same token, different family, different meaning).
364
+ */
365
+ const LEVEL_DESIGNATOR_LOOKUP_BY_FAMILY = (() => {
366
+ const byFamily = new Map();
367
+ for (const family of Object.keys(LEVEL_DESIGNATORS_BY_FAMILY)) {
368
+ const rows = LEVEL_DESIGNATORS_BY_FAMILY[family];
369
+ const lookup = new Map();
370
+ for (const row of rows) {
371
+ if (row.variants.length === 0) {
372
+ throw new Error(`[codex/level-semantics] family "${family}" designator "${row.code}" has no variants`);
373
+ }
374
+ for (const variant of row.variants) {
375
+ if (!variant || !variant.trim()) {
376
+ throw new Error(`[codex/level-semantics] family "${family}" designator "${row.code}" has an empty or blank variant`);
377
+ }
378
+ const key = variant.toLowerCase();
379
+ const existing = lookup.get(key);
380
+ if (existing) {
381
+ throw new Error(`[codex/level-semantics] family "${family}" has a duplicate variant "${variant}" (designators "${existing.code}" and "${row.code}")`);
382
+ }
383
+ lookup.set(key, row);
384
+ }
385
+ }
386
+ byFamily.set(family, lookup);
387
+ }
388
+ return byFamily;
389
+ })();
390
+ const LEVEL_LOCALE_FAMILIES = new Set(Object.keys(LEVEL_DESIGNATORS_BY_FAMILY));
391
+ /** BCP-47-ish language tags that fold into the Norwegian family (Bokmål, Nynorsk, and the deprecated macrolanguage tag). */
392
+ const NORWEGIAN_LANGUAGE_TAGS = new Set(["no", "nb", "nn"]);
393
+ /**
394
+ * Split `locale` into `{ language, region }`, lowercasing the language and uppercasing the region. Tolerant of a bare
395
+ * language tag (no region).
396
+ */
397
+ function splitLocaleTag(locale) {
398
+ const [language, region] = locale.split("-");
399
+ return { language: (language ?? "").toLowerCase(), region: region?.toUpperCase() };
400
+ }
401
+ /** Resolve a BCP-47-ish locale tag to its {@link LevelLocaleFamily}, or `undefined` if this module has no lexicon for it. */
402
+ function localeFamily(locale) {
403
+ const { language } = splitLocaleTag(locale);
404
+ const family = NORWEGIAN_LANGUAGE_TAGS.has(language) ? "no" : language;
405
+ return LEVEL_LOCALE_FAMILIES.has(family) ? family : undefined;
406
+ }
407
+ /**
408
+ * Per-FULL-LOCALE ordinal convention overrides. Keyed by full locale (not bare language family) because English splits
409
+ * by country even though the vocabulary doesn't: American and Canadian buildings number the ground floor "1"; British
410
+ * buildings do not. "CA" (English or French) buckets with the US/Japan convention per real-world North American
411
+ * building-code practice, though individual Quebec buildings can and do vary.
412
+ */
413
+ export const LEVEL_ORDINAL_CONVENTIONS = {
414
+ "en-US": { firstNumberedIsGround: true },
415
+ "en-CA": { firstNumberedIsGround: true },
416
+ "en-GB": { firstNumberedIsGround: false },
417
+ "fr-CA": { firstNumberedIsGround: true },
418
+ "ja-JP": { firstNumberedIsGround: true },
419
+ };
420
+ /**
421
+ * Default convention per language family, used when {@link levelToOrdinal} is given a bare-language locale ("fr" with no
422
+ * country) or a country this table doesn't specifically override. Every entry here follows the
423
+ * continental-European/IMDF convention (ground is its own designator; numbered floors start at 1 for the storey above)
424
+ * except Japanese, which follows the US/CA convention. English has NO family-wide default — American/Canadian and
425
+ * British buildings disagree, so a bare "en" locale intentionally resolves to `undefined` rather than guessing.
426
+ */
427
+ const FAMILY_DEFAULT_ORDINAL_CONVENTION = {
428
+ fr: { firstNumberedIsGround: false },
429
+ de: { firstNumberedIsGround: false },
430
+ es: { firstNumberedIsGround: false },
431
+ it: { firstNumberedIsGround: false },
432
+ pt: { firstNumberedIsGround: false },
433
+ nl: { firstNumberedIsGround: false },
434
+ sv: { firstNumberedIsGround: false },
435
+ no: { firstNumberedIsGround: false },
436
+ da: { firstNumberedIsGround: false },
437
+ ja: { firstNumberedIsGround: true },
438
+ };
439
+ /**
440
+ * Resolve the ordinal convention for `locale`: an exact full-locale override, else the language family's default, else
441
+ * `undefined`.
442
+ */
443
+ function resolveOrdinalConvention(locale) {
444
+ const { language, region } = splitLocaleTag(locale);
445
+ const normalized = region ? `${language}-${region}` : language;
446
+ if (LEVEL_ORDINAL_CONVENTIONS[normalized]) {
447
+ return LEVEL_ORDINAL_CONVENTIONS[normalized];
448
+ }
449
+ const family = localeFamily(locale);
450
+ return family ? FAMILY_DEFAULT_ORDINAL_CONVENTION[family] : undefined;
451
+ }
452
+ /**
453
+ * Look up a level designator (by canonical code, abbreviation, or any recognized variant) within a locale's language
454
+ * family. Case-insensitive. Returns `undefined` when the locale's family is unknown to this module, or the token isn't
455
+ * a recognized designator in that family.
456
+ */
457
+ export function lookupLevelDesignator(designator, locale) {
458
+ if (!designator || typeof designator !== "string")
459
+ return undefined;
460
+ const family = localeFamily(locale);
461
+ if (!family)
462
+ return undefined;
463
+ return LEVEL_DESIGNATOR_LOOKUP_BY_FAMILY.get(family)?.get(designator.trim().toLowerCase());
464
+ }
465
+ /** True when `input` is a recognized level designator (case-insensitive) in `locale`'s language family. */
466
+ export function isLevelDesignatorToken(input, locale) {
467
+ return typeof input === "string" && lookupLevelDesignator(input, locale) !== undefined;
468
+ }
469
+ /**
470
+ * Map a (designator, number) pair to an IMDF-style signed integer ordinal, given the semantics of `locale`. Ground is
471
+ * always 0. Returns `undefined` when:
472
+ *
473
+ * - `locale`'s language family has no lexicon in this module,
474
+ * - `designator` isn't a recognized token in that family,
475
+ * - The designator is `"special"` (penthouse/roof/attic — no locale-independent ordinal exists), or
476
+ * - The designator is `"numbered"` but either `number` is missing or the locale has no resolvable ordinal convention (a
477
+ * bare "en" locale, for example).
478
+ *
479
+ * @example
480
+ * levelToOrdinal("FL", 1, "en-US") // → 0 (US: 1st floor IS ground)
481
+ * levelToOrdinal("étage", 1, "fr-FR") // → 1 (FR: 1st étage is one storey above ground)
482
+ * levelToOrdinal("EG", undefined, "de-DE") // → 0 (ground, number ignored)
483
+ * levelToOrdinal("B", 1, "en-US") // → -1 (basement 1)
484
+ * levelToOrdinal("F", 1, "ja-JP") // → 0 (JP: 1F IS ground)
485
+ * levelToOrdinal("B", 1, "ja-JP") // → -1 (JP: B1F)
486
+ */
487
+ export function levelToOrdinal(designator, number, locale) {
488
+ const row = lookupLevelDesignator(designator, locale);
489
+ if (!row)
490
+ return undefined;
491
+ switch (row.kind) {
492
+ case "ground":
493
+ return 0;
494
+ case "fractionalAboveGround":
495
+ return 0;
496
+ case "fractionalBelowGround":
497
+ return -1;
498
+ case "special":
499
+ return undefined;
500
+ case "fixedOrdinal":
501
+ return row.fixedOrdinal;
502
+ case "basement":
503
+ return -Math.abs(number ?? 1);
504
+ case "numbered": {
505
+ if (number === undefined)
506
+ return undefined;
507
+ const convention = resolveOrdinalConvention(locale);
508
+ if (!convention)
509
+ return undefined;
510
+ return convention.firstNumberedIsGround ? number - 1 : number;
511
+ }
512
+ default:
513
+ return undefined;
514
+ }
515
+ }
516
+ //# sourceMappingURL=level-semantics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"level-semantics.js","sourceRoot":"","sources":["../level-semantics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Build the COUNTRY-SURFACE LEXICON for the country-lexicon soft-feed channel (#1104). This is the
7
+ * third atlas channel, a sibling of the postcode anchor (#239/#240) and the gazetteer anchor
8
+ * (#464): a per-token multi-hot clue the neural GRAMMAR conditions on but never obeys. Country is a
9
+ * CLOSED, ENUMERABLE class (~250 surfaces) — atlas, not grammar — so a dictionary phrase-lookup
10
+ * recovers the WOF-admin / resolver hierarchy case ("United States of America, Wyoming, <locality>")
11
+ * the learned tagger reads as a leading STREET. Pelias handled the same class the same way
12
+ * (`WhosOnFirstClassifier extends PhraseClassifier`); this is the model-first analogue.
13
+ *
14
+ * WHY A DEDICATED LEXICON (not just the gazetteer's `country` slot): the gazetteer already carries
15
+ * these surfaces in slot 0, and the shipped model already consumes them — yet the WOF-admin case
16
+ * still fails (model-card #1104: golden country recall 82.0% vs 88.6%). The country bit is one of a
17
+ * 5-hot vector sharing ONE learned projection with region/po_box/cedex/homograph, and it is ZEROED
18
+ * adjacent to a postcode by `suppress_gazetteer_near_postcode` (exactly where a trailing "…12345
19
+ * USA" sits). A dedicated channel de-entangles the country signal (its own projection + confidence
20
+ * weight) and is immune to that suppression. See
21
+ * docs/superpowers/plans/2026-07-14-country-lexicon-channel.md.
22
+ *
23
+ * The matcher REUSES the gazetteer's phrase-scan (longest-first n-gram over whitespace words,
24
+ * case-insensitive `entries` + uppercase-exact `code_entries`, char→piece projection) — one tested
25
+ * algorithm, two vocabularies. Only the vocabulary + the emitted feature differ. The emitted
26
+ * feature is 2-dim per piece: `[country_surface, country_ambiguous]`.
27
+ *
28
+ * - `country_surface` (bit 1): the piece is part of a recognized country surface phrase.
29
+ * - `country_ambiguous` (bit 2): the SURFACE is a homograph (also a US region) or a common-word
30
+ * name ("Georgia", "America", "England", "IN") — a SOFT version of Pelias's hard blacklist. The
31
+ * model learns to trust `surface & !ambiguous` (unambiguous long/code forms) strongly and
32
+ * `surface & ambiguous` weakly, using context — model-first, never a hard drop, so recall on
33
+ * "Republic of Georgia" is preserved.
34
+ *
35
+ * Source of truth: `@mailwoman/codex` (COUNTRY_SURFACE_FORMS + ISO2_TO_NAME) — the SAME data the
36
+ * corpus-python bridge `country-surfaces.json` is generated from (export-country-surfaces.ts), so
37
+ * the channel and the corpus shard synthesizer cannot diverge on what a country surface IS.
38
+ *
39
+ * Output: data/gazetteer/country-surface-lexicon-v1.json (small, committed, provenance-tracked).
40
+ * Regenerate: `node codex/tools/build-country-surface-lexicon.ts`
41
+ */
42
+ export {};
43
+ //# sourceMappingURL=build-country-surface-lexicon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-country-surface-lexicon.d.ts","sourceRoot":"","sources":["../../tools/build-country-surface-lexicon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG"}