@mailwoman/codex 7.2.0 → 7.3.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.
Files changed (66) hide show
  1. package/address-system-conventions.ts +68 -0
  2. package/au/delivery-service.ts +179 -0
  3. package/au/index.ts +15 -0
  4. package/au/level-designator.ts +209 -0
  5. package/au/postcode.ts +51 -0
  6. package/au/state.ts +35 -0
  7. package/ca/index.ts +12 -0
  8. package/ca/postal-code.ts +121 -0
  9. package/ca/province.ts +99 -0
  10. package/ca/street-type.ts +167 -0
  11. package/country/codes.ts +534 -0
  12. package/country/country.ts +125 -0
  13. package/country/index.ts +15 -0
  14. package/country/names.ts +274 -0
  15. package/country/official-languages.ts +397 -0
  16. package/country/reference-data.ts +267 -0
  17. package/country/reference.ts +47 -0
  18. package/country/subdivision.ts +93 -0
  19. package/de/bundesland.ts +102 -0
  20. package/de/index.ts +12 -0
  21. package/de/postleitzahl.ts +91 -0
  22. package/de/street-type.ts +83 -0
  23. package/fr/cedex.ts +56 -0
  24. package/fr/code-postal.ts +105 -0
  25. package/fr/departement.ts +142 -0
  26. package/fr/index.ts +14 -0
  27. package/fr/region.ts +93 -0
  28. package/fr/voie.ts +98 -0
  29. package/gb/country.ts +74 -0
  30. package/gb/index.ts +14 -0
  31. package/gb/postcode-area.ts +107 -0
  32. package/gb/postcode.ts +109 -0
  33. package/gb/street-type.ts +90 -0
  34. package/index.ts +38 -0
  35. package/jp/address-unit.ts +87 -0
  36. package/jp/index.ts +13 -0
  37. package/jp/postal-code.ts +93 -0
  38. package/jp/prefecture.ts +173 -0
  39. package/level-semantics.ts +623 -0
  40. package/nz/delivery-service.ts +211 -0
  41. package/nz/index.ts +12 -0
  42. package/nz/postcode.ts +42 -0
  43. package/out/country/index.d.ts +1 -0
  44. package/out/country/index.d.ts.map +1 -1
  45. package/out/country/index.js +1 -0
  46. package/out/country/index.js.map +1 -1
  47. package/out/country/subdivision.d.ts +44 -0
  48. package/out/country/subdivision.d.ts.map +1 -0
  49. package/out/country/subdivision.js +76 -0
  50. package/out/country/subdivision.js.map +1 -0
  51. package/package.json +81 -37
  52. package/postcode-systems.ts +68 -0
  53. package/tools/build-country-surface-lexicon.ts +166 -0
  54. package/tools/export-country-surfaces.ts +46 -0
  55. package/tools/generate-country-reference.ts +153 -0
  56. package/tools/generate-official-languages.ts +188 -0
  57. package/tools/index.ts +12 -0
  58. package/us/floor-designator.ts +119 -0
  59. package/us/index.ts +19 -0
  60. package/us/military-address.ts +199 -0
  61. package/us/po-box.ts +82 -0
  62. package/us/state.ts +156 -0
  63. package/us/street-directional.ts +220 -0
  64. package/us/street-suffix.ts +345 -0
  65. package/us/unit-designator.ts +223 -0
  66. package/us/zipcode.ts +212 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * USPS Publication 28, Appendix C2 — Secondary Unit Designators.
7
+ *
8
+ * The sibling of {@link ./street-suffix.ts}: where that table standardizes the trailing street
9
+ * _type_ (AVENUE → AVE), this one standardizes the _secondary unit_ designator that introduces an
10
+ * apartment / suite / floor / room (APARTMENT → APT, SUITE → STE). For each canonical designator
11
+ * the value lists recognized variants in USPS order; the first is the approved USPS abbreviation
12
+ * (what the post office prints).
13
+ *
14
+ * Used by `@mailwoman/corpus`'s synthesis layer (the `unit-{expand,abbreviate}` augmentations) to
15
+ * vary the designator in a `unit` component while preserving the identifier — the data-generation
16
+ * counterpart to the runtime `UnitDesignatorClassifier` (which matches the broader libpostal
17
+ * `unit_types` lexicon). Designators are LEADING ("Apt 4B"), unlike street suffixes which trail.
18
+ *
19
+ * `US_UNIT_DESIGNATOR_REQUIRES_RANGE` (added for #1100, the secondary-address epic; retrieved from
20
+ * Appendix C2 2026-07-13) is Pub-28's own "Requires a Secondary Number" column: APT, BLDG, DEPT,
21
+ * FL, HNGR, KEY, LOT, PIER, RM, SLIP, SPC, STOP, STE, TRLR, and UNIT must be followed by an
22
+ * identifier ("Apt 4B", never bare "Apt"); BSMT, FRNT, LBBY, LOWR, OFC, PH, REAR, SIDE, and UPPR
23
+ * may stand alone. This formalizes, as provenance-tracked reference data, the split that
24
+ * `corpus/src/shard-recipes/unit.ts` previously hand-rolled (and only partially covered) as
25
+ * in-file `ID_DESIGNATORS`/`STANDALONE_DESIGNATORS` arrays for synthesis weighting. A SEPARATE,
26
+ * not-yet-built deliverable of #1100 is the per-locale *level-semantics* table (étage/RDC, EG/OG/UG,
27
+ * planta/piso/bajo, piano/terra, 階/F/B1, …) — this module stays US/Pub-28 only.
28
+ *
29
+ * Data is verbatim USPS Pub-28 C2.
30
+ * @see {@link https://pe.usps.com/text/pub28/28apc_003.htm USPS Secondary Unit Designators}
31
+ */
32
+
33
+ /**
34
+ * Canonical USPS secondary unit designator → recognized variants. The first variant is the approved USPS abbreviation.
35
+ * Keys + values uppercase per the publication. The designators marked by USPS as "requires a secondary number" (APT,
36
+ * BLDG, FL, …) and the standalone ones (BSMT, LBBY, PH, …) are both included — synthesis treats them uniformly.
37
+ */
38
+ export const US_UNIT_DESIGNATOR_VARIANTS = {
39
+ APARTMENT: ["APT", "APRT", "APMT"],
40
+ BASEMENT: ["BSMT"],
41
+ BUILDING: ["BLDG", "BLD"],
42
+ DEPARTMENT: ["DEPT"],
43
+ FLOOR: ["FL", "FLR"],
44
+ FRONT: ["FRNT"],
45
+ HANGAR: ["HNGR"],
46
+ KEY: ["KEY"],
47
+ LOBBY: ["LBBY"],
48
+ LOT: ["LOT"],
49
+ LOWER: ["LOWR"],
50
+ OFFICE: ["OFC"],
51
+ PENTHOUSE: ["PH"],
52
+ PIER: ["PIER"],
53
+ REAR: ["REAR"],
54
+ ROOM: ["RM"],
55
+ SIDE: ["SIDE"],
56
+ SLIP: ["SLIP"],
57
+ SPACE: ["SPC"],
58
+ STOP: ["STOP"],
59
+ SUITE: ["STE", "SUIT"],
60
+ TRAILER: ["TRLR"],
61
+ UNIT: ["UNIT"],
62
+ UPPER: ["UPPR"],
63
+ } as const satisfies Record<string, readonly string[]>
64
+
65
+ /** Canonical USPS secondary unit designator (full word, uppercase per the publication). */
66
+ export type USUnitDesignator = keyof typeof US_UNIT_DESIGNATOR_VARIANTS
67
+
68
+ /**
69
+ * Inverse lookup: every variant abbreviation OR full canonical word → its canonical key, built once at module load,
70
+ * lowercase-keyed for case-insensitive matching (`apt` → `"APARTMENT"`, `ste` → `"SUITE"`, `suite` → `"SUITE"`).
71
+ */
72
+ export const US_UNIT_DESIGNATOR_LOOKUP: ReadonlyMap<string, USUnitDesignator> = (() => {
73
+ const out = new Map<string, USUnitDesignator>()
74
+
75
+ for (const canonical of Object.keys(US_UNIT_DESIGNATOR_VARIANTS) as USUnitDesignator[]) {
76
+ out.set(canonical.toLowerCase(), canonical)
77
+
78
+ for (const variant of US_UNIT_DESIGNATOR_VARIANTS[canonical]) {
79
+ // First canonical that claims a variant wins (matches the publication's ordering).
80
+ if (!out.has(variant.toLowerCase())) {
81
+ out.set(variant.toLowerCase(), canonical)
82
+ }
83
+ }
84
+ }
85
+
86
+ return out
87
+ })()
88
+
89
+ /** Approved USPS abbreviation per canonical (`APARTMENT → "APT"`, `SUITE → "STE"`). */
90
+ export const US_UNIT_DESIGNATOR_PREFERRED_ABBR: Readonly<Record<USUnitDesignator, string>> = Object.fromEntries(
91
+ (Object.keys(US_UNIT_DESIGNATOR_VARIANTS) as USUnitDesignator[]).map((k) => [k, US_UNIT_DESIGNATOR_VARIANTS[k][0]])
92
+ ) as Readonly<Record<USUnitDesignator, string>>
93
+
94
+ /**
95
+ * Canonical designators Appendix C2 marks as "Requires a Secondary Number" — the designator must be followed by an
96
+ * identifier ("Apt 4B", "Rm 12"), never appearing bare. The remaining designators (BASEMENT, FRONT, LOBBY, LOWER,
97
+ * OFFICE, PENTHOUSE, REAR, SIDE, UPPER) may stand alone with no trailing identifier. Verbatim from USPS Pub-28 C2; see
98
+ * the module header for provenance (#1100).
99
+ */
100
+ export const US_UNIT_DESIGNATOR_REQUIRES_RANGE: Readonly<Record<USUnitDesignator, boolean>> = {
101
+ APARTMENT: true,
102
+ BASEMENT: false,
103
+ BUILDING: true,
104
+ DEPARTMENT: true,
105
+ FLOOR: true,
106
+ FRONT: false,
107
+ HANGAR: true,
108
+ KEY: true,
109
+ LOBBY: false,
110
+ LOT: true,
111
+ LOWER: false,
112
+ OFFICE: false,
113
+ PENTHOUSE: false,
114
+ PIER: true,
115
+ REAR: false,
116
+ ROOM: true,
117
+ SIDE: false,
118
+ SLIP: true,
119
+ SPACE: true,
120
+ STOP: true,
121
+ SUITE: true,
122
+ TRAILER: true,
123
+ UNIT: true,
124
+ UPPER: false,
125
+ } as const satisfies Record<USUnitDesignator, boolean>
126
+
127
+ /**
128
+ * If the FIRST whitespace-separated word of `unit` is a known USPS designator variant, return the canonical key and the
129
+ * matched word. Returns null if the leading word isn't a known designator (e.g. a bare `"4B"` or `"#210"`).
130
+ * Leading-word-only — designators introduce the unit, unlike street suffixes which trail.
131
+ */
132
+ export function matchLeadingDesignator(unit: string): { canonical: USUnitDesignator; matched: string } | null {
133
+ const trimmed = unit.trim()
134
+
135
+ if (!trimmed) return null
136
+ const first = trimmed.split(/\s+/)[0]!
137
+ const canonical = US_UNIT_DESIGNATOR_LOOKUP.get(first.toLowerCase())
138
+
139
+ if (!canonical) return null
140
+
141
+ return { canonical, matched: first }
142
+ }
143
+
144
+ /** Result of {@link matchLeadingDesignatorWithRange}: the leading designator plus its optional secondary range. */
145
+ export interface UnitDesignatorRangeMatch {
146
+ /** The matched canonical designator, i.e. "APARTMENT", "SUITE". */
147
+ canonical: USUnitDesignator
148
+ /** The designator's own matched surface form, i.e. "Apt". */
149
+ matched: string
150
+ /**
151
+ * The secondary range/identifier token immediately following the designator, i.e. "4B" in "Apt 4B". Undefined when
152
+ * the designator appears standalone (e.g. bare "Basement"). This module does not validate the range's own shape —
153
+ * numeric, letter, or alphanumeric ranges are all USPS-valid.
154
+ */
155
+ range: string | undefined
156
+ /**
157
+ * Whether USPS Pub-28 Appendix C2 marks this designator as requiring a secondary range (see
158
+ * {@link US_UNIT_DESIGNATOR_REQUIRES_RANGE}). Informational only — not enforced by this matcher.
159
+ */
160
+ requiresRange: boolean
161
+ }
162
+
163
+ /**
164
+ * Like {@link matchLeadingDesignator}, but also captures the secondary range/identifier token immediately following the
165
+ * designator, if present ("Apt 4B" → designator "APARTMENT", range "4B"; "Basement" → range `undefined`). Mirrors
166
+ * `street-suffix`/`street-directional`'s designator+adjacent-token matchers.
167
+ */
168
+ export function matchLeadingDesignatorWithRange(unit: string): UnitDesignatorRangeMatch | null {
169
+ const trimmed = unit.trim()
170
+
171
+ if (!trimmed) return null
172
+ const parts = trimmed.split(/\s+/)
173
+ const first = parts[0]!
174
+ const canonical = US_UNIT_DESIGNATOR_LOOKUP.get(first.toLowerCase())
175
+
176
+ if (!canonical) return null
177
+
178
+ return {
179
+ canonical,
180
+ matched: first,
181
+ range: parts[1],
182
+ requiresRange: US_UNIT_DESIGNATOR_REQUIRES_RANGE[canonical],
183
+ }
184
+ }
185
+
186
+ /** Result of a successful USPS secondary-unit designator lookup. */
187
+ export interface UnitDesignatorMatch<D extends USUnitDesignator = USUnitDesignator> {
188
+ /** The matched canonical designator, i.e. "APARTMENT", "SUITE". */
189
+ designator: D
190
+ /** The approved USPS abbreviation, i.e. "APT", "STE". */
191
+ abbreviation: (typeof US_UNIT_DESIGNATOR_VARIANTS)[D][0]
192
+ }
193
+
194
+ /**
195
+ * Look up a USPS secondary unit designator (by canonical word, abbreviation, or any variant) and its approved
196
+ * abbreviation.
197
+ */
198
+ export function lookupUnitDesignator<D extends USUnitDesignator>(designator: D): UnitDesignatorMatch<D>
199
+ export function lookupUnitDesignator(input: string | null | undefined): UnitDesignatorMatch | null
200
+ export function lookupUnitDesignator(input: string | null | undefined): UnitDesignatorMatch | null {
201
+ if (!input || typeof input !== "string") return null
202
+ const designator = US_UNIT_DESIGNATOR_LOOKUP.get(input.trim().toLowerCase())
203
+
204
+ if (!designator) return null
205
+
206
+ return { designator, abbreviation: US_UNIT_DESIGNATOR_VARIANTS[designator][0] }
207
+ }
208
+
209
+ /**
210
+ * True when a token is any USPS secondary unit designator or abbreviation (case-insensitive) — `"Apt"`, `"STE"`,
211
+ * `"floor"`.
212
+ */
213
+ export function isUnitDesignatorToken(input: unknown): boolean {
214
+ return typeof input === "string" && US_UNIT_DESIGNATOR_LOOKUP.has(input.trim().toLowerCase())
215
+ }
216
+
217
+ /**
218
+ * Alias of {@link isUnitDesignatorToken} under Pub-28's own term ("secondary unit designator"). Added for #1100 so
219
+ * secondary-address call sites can spell the predicate after the publication's vocabulary.
220
+ */
221
+ export function isSecondaryUnitDesignatorToken(input: unknown): boolean {
222
+ return isUnitDesignatorToken(input)
223
+ }
package/us/zipcode.ts ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * US ZIP (Zone Improvement Plan) codes: branded string types, the shape patterns, the first-digit →
7
+ * state geographic prior, and a state-plus-ZIP plucker.
8
+ */
9
+
10
+ import type { Tagged } from "type-fest"
11
+
12
+ import { isUSStateAbbreviation, type USStateAbbreviation } from "./state.ts"
13
+
14
+ /**
15
+ * USPS-recognized ZIP code digits.
16
+ *
17
+ * @internal
18
+ */
19
+ export type ZipCodeDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
20
+
21
+ /**
22
+ * A ZIP (Zone Improvement Plan) Code is a five-digit code assigned by the USPS to a section of a street, a collection
23
+ * of streets, an establishment, structure, or group of post office boxes, for the delivery of mail.
24
+ *
25
+ * - The first 3 digits of the ZIP code represent a specific central mail processing facility, which can be used to
26
+ * identify the locality and region of the address, i.e. the city and state.
27
+ * - The last 2 digits of the ZIP code represent a specific post office or delivery area.
28
+ *
29
+ * ```txt
30
+ * 90210
31
+ * / |⎿__(Post Office)
32
+ * / \
33
+ * (State) \
34
+ * \
35
+ * (City)
36
+ * ```
37
+ *
38
+ * Note that ZIP codes are not areas, but rather a group of deliverable addresses, which can and do change over time.
39
+ *
40
+ * @category Delivery
41
+ * @category Postal
42
+ * @title ZIP Code
43
+ * @pattern ^\d{5}$
44
+ * @see {@linkcode ZipCodePlusFour} for the extended ZIP code format.
45
+ */
46
+ export type ZipCode = Tagged<string, "ZipCode">
47
+
48
+ /**
49
+ * The extended ZIP code format includes the five-digit ZIP code followed by a hyphen and four additional digits. This
50
+ * extended format is used to provide more precise location information.
51
+ *
52
+ * - The first 3 digits of the ZIP code represent a specific central mail processing facility,
53
+ * - The last 2 digits of the ZIP code represent a specific post office or delivery area.
54
+ * - The four additional digits represent a specific delivery route within the ZIP code area.
55
+ *
56
+ * ```txt
57
+ * 90210-1234
58
+ * \_/\ /\__/
59
+ * / | \
60
+ * (State, City)_ / | \_ (Delivery Route)
61
+ * |
62
+ * (Post Office)
63
+ * ```
64
+ *
65
+ * Note that ZIP codes are not areas, but rather a group of deliverable addresses, which can and do change over time.
66
+ *
67
+ * @category Delivery
68
+ * @category Postal
69
+ * @type string
70
+ * @title ZIP Code+4
71
+ * @pattern ^\d{5}-\d{4}$
72
+ * @see {@linkcode ZipCode} for the standard ZIP code format.
73
+ */
74
+ export type ZipCodePlusFour = Tagged<string, "ZipCodePlusFour">
75
+
76
+ /**
77
+ * Type utility to extract the state abbreviation from a ZIP code.
78
+ *
79
+ * @internal
80
+ */
81
+ export type ExtractStateFromZipCode<Zip extends ZipCode | ZipCodePlusFour> =
82
+ Zip extends `${infer StateCode}${infer _Rest}` ? StateCode : never
83
+
84
+ /**
85
+ * Record of US state abbreviations to their corresponding ZIP code prefix (the leading digit). A cheap geographic
86
+ * prior: a 5-digit code's first digit narrows it to a band of states, which the parser can weigh against the
87
+ * surrounding city/state tokens.
88
+ *
89
+ * @internal
90
+ * @see {@linkcode ZipCodePrefixAbbreviationMap} for the reverse mapping.
91
+ */
92
+ export const StateAbbreviationZipCodePrefixRecord = {
93
+ AL: 3,
94
+ AK: 9,
95
+ AZ: 8,
96
+ AR: 7,
97
+ CA: 9,
98
+ CO: 8,
99
+ CT: 0,
100
+ DE: 1,
101
+ DC: 2,
102
+ FL: 3,
103
+ GA: 3,
104
+ HI: 9,
105
+ ID: 8,
106
+ IL: 6,
107
+ IN: 4,
108
+ IA: 5,
109
+ KS: 6,
110
+ KY: 4,
111
+ LA: 7,
112
+ ME: 0,
113
+ MD: 2,
114
+ MA: 0,
115
+ MI: 4,
116
+ MN: 5,
117
+ MS: 3,
118
+ MO: 6,
119
+ MT: 5,
120
+ NE: 6,
121
+ NV: 8,
122
+ NH: 0,
123
+ NJ: 0,
124
+ NM: 8,
125
+ NY: 1,
126
+ NC: 2,
127
+ ND: 5,
128
+ OH: 4,
129
+ OK: 7,
130
+ OR: 9,
131
+ PA: 1,
132
+ RI: 0,
133
+ SC: 2,
134
+ SD: 5,
135
+ TN: 3,
136
+ TX: 7,
137
+ UT: 8,
138
+ VT: 0,
139
+ VA: 2,
140
+ WA: 9,
141
+ WV: 2,
142
+ WI: 5,
143
+ WY: 8,
144
+ PR: 0,
145
+ GU: 9,
146
+ VI: 0,
147
+ MP: 9,
148
+ AS: 9,
149
+ } as const satisfies Record<USStateAbbreviation, ZipCodeDigit>
150
+
151
+ /**
152
+ * Map of ZIP code prefixes to their corresponding US state abbreviations.
153
+ */
154
+ const ZipCodePrefixAbbreviationMap = new Map<ZipCodeDigit, USStateAbbreviation[]>()
155
+
156
+ for (const [state, prefix] of Object.entries(StateAbbreviationZipCodePrefixRecord) as [
157
+ USStateAbbreviation,
158
+ ZipCodeDigit,
159
+ ][]) {
160
+ const states = ZipCodePrefixAbbreviationMap.get(prefix) ?? []
161
+ ZipCodePrefixAbbreviationMap.set(prefix, [...states, state])
162
+ }
163
+
164
+ export { ZipCodePrefixAbbreviationMap }
165
+
166
+ /**
167
+ * Regex patterns for ZIP codes.
168
+ */
169
+ export const ZipCodePatterns = {
170
+ /**
171
+ * 5-digit, or 9-digit ZIP code or ZIP+4 code.
172
+ */
173
+ Standard: /^\d{5}(?:[-\s]\d{4})?$/,
174
+
175
+ /**
176
+ * Two-letter state abbreviation followed by a 5-digit ZIP code or ZIP+4 code.
177
+ */
178
+ StateAbbreviationWithZipCode: /^(?:([A-Za-z]{2})[ ,]*)?([0-9]{5}(?:[-\s][0-9]{4})?)$/,
179
+ } as const
180
+
181
+ /**
182
+ * Type-predicate to determine if a value is a valid ZIP code.
183
+ */
184
+ export function isZipCode(input: unknown): input is ZipCode | ZipCodePlusFour {
185
+ return typeof input === "string" && ZipCodePatterns.Standard.test(input)
186
+ }
187
+
188
+ export interface PluckedStateZIPCodeResult {
189
+ stateAbbreviation: USStateAbbreviation | null
190
+ zipCode: ZipCode | ZipCodePlusFour
191
+ }
192
+
193
+ /**
194
+ * Given an address string like `"NY"`, `"CA 94016"`, attempts to match the state abbreviation and postal code, if
195
+ * applicable.
196
+ *
197
+ * @see {@linkcode isUSStateAbbreviation} to validate the state abbreviation.
198
+ */
199
+ export function pluckStateZIPCode(input: unknown): PluckedStateZIPCodeResult | null {
200
+ if (!input || typeof input !== "string") return null
201
+
202
+ const [, stateAbbreviation, zipCode = null] = input.match(ZipCodePatterns.StateAbbreviationWithZipCode) || []
203
+
204
+ if (!zipCode) return null
205
+
206
+ const normalizedState = stateAbbreviation?.toUpperCase()
207
+
208
+ return {
209
+ stateAbbreviation: isUSStateAbbreviation(normalizedState) ? normalizedState : null,
210
+ zipCode: zipCode as ZipCode | ZipCodePlusFour,
211
+ }
212
+ }