@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.
- package/address-system-conventions.ts +68 -0
- package/au/delivery-service.ts +179 -0
- package/au/index.ts +15 -0
- package/au/level-designator.ts +209 -0
- package/au/postcode.ts +51 -0
- package/au/state.ts +35 -0
- package/ca/index.ts +12 -0
- package/ca/postal-code.ts +121 -0
- package/ca/province.ts +99 -0
- package/ca/street-type.ts +167 -0
- package/country/codes.ts +534 -0
- package/country/country.ts +125 -0
- package/country/index.ts +15 -0
- package/country/names.ts +274 -0
- package/country/official-languages.ts +397 -0
- package/country/reference-data.ts +267 -0
- package/country/reference.ts +47 -0
- package/country/subdivision.ts +93 -0
- package/de/bundesland.ts +102 -0
- package/de/index.ts +12 -0
- package/de/postleitzahl.ts +91 -0
- package/de/street-type.ts +83 -0
- package/fr/cedex.ts +56 -0
- package/fr/code-postal.ts +105 -0
- package/fr/departement.ts +142 -0
- package/fr/index.ts +14 -0
- package/fr/region.ts +93 -0
- package/fr/voie.ts +98 -0
- package/gb/country.ts +74 -0
- package/gb/index.ts +14 -0
- package/gb/postcode-area.ts +107 -0
- package/gb/postcode.ts +109 -0
- package/gb/street-type.ts +90 -0
- package/index.ts +38 -0
- package/jp/address-unit.ts +87 -0
- package/jp/index.ts +13 -0
- package/jp/postal-code.ts +93 -0
- package/jp/prefecture.ts +173 -0
- package/level-semantics.ts +623 -0
- package/nz/delivery-service.ts +211 -0
- package/nz/index.ts +12 -0
- package/nz/postcode.ts +42 -0
- package/out/country/index.d.ts +1 -0
- package/out/country/index.d.ts.map +1 -1
- package/out/country/index.js +1 -0
- package/out/country/index.js.map +1 -1
- package/out/country/subdivision.d.ts +44 -0
- package/out/country/subdivision.d.ts.map +1 -0
- package/out/country/subdivision.js +76 -0
- package/out/country/subdivision.js.map +1 -0
- package/package.json +81 -37
- package/postcode-systems.ts +68 -0
- package/tools/build-country-surface-lexicon.ts +166 -0
- package/tools/export-country-surfaces.ts +46 -0
- package/tools/generate-country-reference.ts +153 -0
- package/tools/generate-official-languages.ts +188 -0
- package/tools/index.ts +12 -0
- package/us/floor-designator.ts +119 -0
- package/us/index.ts +19 -0
- package/us/military-address.ts +199 -0
- package/us/po-box.ts +82 -0
- package/us/state.ts +156 -0
- package/us/street-directional.ts +220 -0
- package/us/street-suffix.ts +345 -0
- package/us/unit-designator.ts +223 -0
- package/us/zipcode.ts +212 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* USPS street **directionals** — the 8 cardinal/intercardinal prefixes (N, S, E, W, NE, NW, SE,
|
|
7
|
+
* SW). The leading-directional counterpart to {@link ./street-suffix.ts} (trailing street type):
|
|
8
|
+
* "N Main St" splits into a `street_prefix` directional ("N"), a `street` name ("Main"), and a
|
|
9
|
+
* `street_suffix` type ("St").
|
|
10
|
+
* @see {@link https://pe.usps.com/text/pub28/28apc_002.htm USPS Pub 28 Appendix C1}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { matchCase } from "./street-suffix.ts"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The 8 directional abbreviations accepted by the USPS. The USPS prefers the abbreviation over the fully-spelled-out
|
|
17
|
+
* name.
|
|
18
|
+
*/
|
|
19
|
+
export const DirectionalAbbreviation = {
|
|
20
|
+
NORTH: "N",
|
|
21
|
+
EAST: "E",
|
|
22
|
+
SOUTH: "S",
|
|
23
|
+
WEST: "W",
|
|
24
|
+
NORTHEAST: "NE",
|
|
25
|
+
NORTHWEST: "NW",
|
|
26
|
+
SOUTHEAST: "SE",
|
|
27
|
+
SOUTHWEST: "SW",
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
export type DirectionalAbbreviation = (typeof DirectionalAbbreviation)[keyof typeof DirectionalAbbreviation]
|
|
31
|
+
|
|
32
|
+
/** The 8 directional names accepted by the USPS (intercardinals spaced, per the publication). */
|
|
33
|
+
export const DirectionalNames = [
|
|
34
|
+
"NORTH",
|
|
35
|
+
"EAST",
|
|
36
|
+
"SOUTH",
|
|
37
|
+
"WEST",
|
|
38
|
+
"NORTH EAST",
|
|
39
|
+
"NORTH WEST",
|
|
40
|
+
"SOUTH EAST",
|
|
41
|
+
"SOUTH WEST",
|
|
42
|
+
] as const satisfies readonly string[]
|
|
43
|
+
|
|
44
|
+
export type DirectionalName = (typeof DirectionalNames)[number]
|
|
45
|
+
|
|
46
|
+
export const DirectionalNameVariations = [
|
|
47
|
+
...DirectionalNames,
|
|
48
|
+
// Without spaces (the common US street form: "Northeast Main St")…
|
|
49
|
+
"NORTHEAST",
|
|
50
|
+
"NORTHWEST",
|
|
51
|
+
"SOUTHEAST",
|
|
52
|
+
"SOUTHWEST",
|
|
53
|
+
// Title-case…
|
|
54
|
+
"North",
|
|
55
|
+
"East",
|
|
56
|
+
"South",
|
|
57
|
+
"West",
|
|
58
|
+
"North East",
|
|
59
|
+
"North West",
|
|
60
|
+
"South East",
|
|
61
|
+
"South West",
|
|
62
|
+
"Northeast",
|
|
63
|
+
"Northwest",
|
|
64
|
+
"Southeast",
|
|
65
|
+
"Southwest",
|
|
66
|
+
// Lower-case…
|
|
67
|
+
"north east",
|
|
68
|
+
"north west",
|
|
69
|
+
"south east",
|
|
70
|
+
"south west",
|
|
71
|
+
"northeast",
|
|
72
|
+
"northwest",
|
|
73
|
+
"southeast",
|
|
74
|
+
"southwest",
|
|
75
|
+
] as const satisfies readonly string[]
|
|
76
|
+
|
|
77
|
+
export type DirectionalNameVariation = (typeof DirectionalNameVariations)[number]
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Abbreviation → full name.
|
|
81
|
+
*/
|
|
82
|
+
export const DirectionalAbbreviationRecord = {
|
|
83
|
+
N: "NORTH",
|
|
84
|
+
E: "EAST",
|
|
85
|
+
S: "SOUTH",
|
|
86
|
+
W: "WEST",
|
|
87
|
+
NE: "NORTH EAST",
|
|
88
|
+
NW: "NORTH WEST",
|
|
89
|
+
SE: "SOUTH EAST",
|
|
90
|
+
SW: "SOUTH WEST",
|
|
91
|
+
} as const satisfies Record<DirectionalAbbreviation, DirectionalName>
|
|
92
|
+
|
|
93
|
+
export type DirectionalAbbreviationRecord = typeof DirectionalAbbreviationRecord
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Name (and its variations) → abbreviation.
|
|
97
|
+
*/
|
|
98
|
+
export const DirectionAbbreviationRecord = {
|
|
99
|
+
NORTH: DirectionalAbbreviation.NORTH,
|
|
100
|
+
EAST: DirectionalAbbreviation.EAST,
|
|
101
|
+
SOUTH: DirectionalAbbreviation.SOUTH,
|
|
102
|
+
WEST: DirectionalAbbreviation.WEST,
|
|
103
|
+
NORTHEAST: DirectionalAbbreviation.NORTHEAST,
|
|
104
|
+
NORTHWEST: DirectionalAbbreviation.NORTHWEST,
|
|
105
|
+
SOUTHEAST: DirectionalAbbreviation.SOUTHEAST,
|
|
106
|
+
SOUTHWEST: DirectionalAbbreviation.SOUTHWEST,
|
|
107
|
+
"NORTH EAST": DirectionalAbbreviation.NORTHEAST,
|
|
108
|
+
"NORTH WEST": DirectionalAbbreviation.NORTHWEST,
|
|
109
|
+
"SOUTH EAST": DirectionalAbbreviation.SOUTHEAST,
|
|
110
|
+
"SOUTH WEST": DirectionalAbbreviation.SOUTHWEST,
|
|
111
|
+
} as const satisfies Partial<Record<string, DirectionalAbbreviation>>
|
|
112
|
+
|
|
113
|
+
export type DirectionAbbreviationRecord = typeof DirectionAbbreviationRecord
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Abbreviation (verbatim or normalized) → full name.
|
|
117
|
+
*/
|
|
118
|
+
export const AbbreviationToDirectional: ReadonlyMap<string, DirectionalName> = new Map(
|
|
119
|
+
Object.entries(DirectionalAbbreviationRecord)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Name (verbatim or normalized) → abbreviation.
|
|
124
|
+
*/
|
|
125
|
+
export const DirectionalToAbbreviationMap: ReadonlyMap<string, DirectionalAbbreviation> = new Map(
|
|
126
|
+
Object.entries(DirectionAbbreviationRecord)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Given a possible directional abbreviation, return the corresponding full name (or null).
|
|
131
|
+
*/
|
|
132
|
+
export function pluckDirectionalName(input: unknown): DirectionalName | null {
|
|
133
|
+
if (!input || typeof input !== "string") return null
|
|
134
|
+
|
|
135
|
+
return AbbreviationToDirectional.get(input) || AbbreviationToDirectional.get(input.trim().toUpperCase()) || null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Given a possible directional name, return the corresponding abbreviation (or null).
|
|
140
|
+
*/
|
|
141
|
+
export function lookupDirectionalAbbreviation(input: unknown): DirectionalAbbreviation | null {
|
|
142
|
+
if (!input || typeof input !== "string") return null
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
DirectionalToAbbreviationMap.get(input) ||
|
|
146
|
+
DirectionalToAbbreviationMap.get(input.trim().toUpperCase().replace(/\s+/g, " ")) ||
|
|
147
|
+
null
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Result of a directional lookup: the canonical full name + its preferred abbreviation.
|
|
153
|
+
*/
|
|
154
|
+
export interface DirectionalMatch {
|
|
155
|
+
/** The matched directional name, e.g. "NORTH", "NORTH EAST". */
|
|
156
|
+
directional: DirectionalName
|
|
157
|
+
/** The corresponding USPS abbreviation, e.g. "N", "NE". */
|
|
158
|
+
abbreviation: DirectionalAbbreviation
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Look up a directional by abbreviation OR name (any variation), returning both forms.
|
|
163
|
+
*/
|
|
164
|
+
export function lookupDirectional(input: unknown): DirectionalMatch | null {
|
|
165
|
+
if (!input || typeof input !== "string") return null
|
|
166
|
+
const abbreviation = lookupDirectionalAbbreviation(input)
|
|
167
|
+
|
|
168
|
+
if (abbreviation) return { directional: DirectionalAbbreviationRecord[abbreviation], abbreviation }
|
|
169
|
+
const directional = pluckDirectionalName(input)
|
|
170
|
+
|
|
171
|
+
if (directional) return { directional, abbreviation: DirectionalToAbbreviationMap.get(directional)! }
|
|
172
|
+
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Codex shard-facing helpers (mirror street-suffix's matchTrailingSuffix) ───────────────────────
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* If the FIRST whitespace-separated word of `street` is a known USPS directional (abbrev or name), return the canonical
|
|
180
|
+
* name, its abbreviation, and the matched surface word. Null otherwise. (The leading-end counterpart of
|
|
181
|
+
* {@link matchTrailingSuffix}; mirrors unit-designator's `matchLeadingDesignator`.) Single-word only — the spaced "NORTH
|
|
182
|
+
* EAST" form is normalized to its one-word variant in real US streets, which this matches via the lookup.
|
|
183
|
+
*/
|
|
184
|
+
export function matchLeadingDirectional(
|
|
185
|
+
street: string
|
|
186
|
+
): { canonical: DirectionalName; abbreviation: DirectionalAbbreviation; matched: string } | null {
|
|
187
|
+
const trimmed = street.trim()
|
|
188
|
+
|
|
189
|
+
if (!trimmed) return null
|
|
190
|
+
const first = trimmed.split(/\s+/)[0]!
|
|
191
|
+
const m = lookupDirectional(first)
|
|
192
|
+
|
|
193
|
+
if (!m) return null
|
|
194
|
+
|
|
195
|
+
return { canonical: m.directional, abbreviation: m.abbreviation, matched: first }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render a directional in the requested surface form, in `reference`'s case pattern:
|
|
200
|
+
*
|
|
201
|
+
* - `"abbr"` → the USPS abbreviation ("N", "NE").
|
|
202
|
+
* - `"full"` → the one-word spelled-out form ("North", "Northeast") — the common US street form, not the publication's
|
|
203
|
+
* spaced "NORTH EAST".
|
|
204
|
+
*/
|
|
205
|
+
export function renderDirectional(
|
|
206
|
+
match: { canonical: DirectionalName; abbreviation: DirectionalAbbreviation },
|
|
207
|
+
form: "abbr" | "full",
|
|
208
|
+
reference: string
|
|
209
|
+
): string {
|
|
210
|
+
const target = form === "abbr" ? match.abbreviation : match.canonical.replace(/\s+/g, "")
|
|
211
|
+
|
|
212
|
+
return matchCase(target, reference)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Case-insensitive check: is the token any USPS directional or abbreviation (`"N"`, `"north"`, `"NW"`)?
|
|
217
|
+
*/
|
|
218
|
+
export function isStreetDirectionalToken(input: unknown): boolean {
|
|
219
|
+
return lookupDirectional(typeof input === "string" ? input.trim() : input) !== null
|
|
220
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* USPS Publication 28, Appendix C — Postal Service Standard Suffix Abbreviations.
|
|
7
|
+
*
|
|
8
|
+
* For each canonical suffix the value lists every recognized variant in USPS-published order; the
|
|
9
|
+
* first variant is the preferred USPS abbreviation (e.g. `AVENUE → ["AVE", "AV", "AVEN", "AVENU",
|
|
10
|
+
* "AVN", "AVNUE"]` — `AVE` is what the post office prints).
|
|
11
|
+
*
|
|
12
|
+
* This module is the single home for the USPS suffix table. It carries both the synthesis-layer
|
|
13
|
+
* helpers (`US_STREET_SUFFIX_PREFERRED_ABBR`, `matchCase`, `matchTrailingSuffix` — used by
|
|
14
|
+
* `@mailwoman/corpus`) and the richer branded-type lookup (`StreetSuffix`, `lookupStreetSuffix`,
|
|
15
|
+
* `isStreetSuffix`) The data is verbatim USPS Pub-28; the two APIs share one underlying record.
|
|
16
|
+
* @see {@link https://pe.usps.com/text/pub28/28apc_002.htm USPS Street Suffix Abbreviations}
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Canonical USPS street suffix → list of recognized variants. The first variant in each list is the preferred USPS
|
|
21
|
+
* abbreviation. Keys + values are uppercase per the publication.
|
|
22
|
+
*/
|
|
23
|
+
export const US_STREET_SUFFIX_VARIANTS = {
|
|
24
|
+
ALLEY: ["ALY", "ALLEE", "ALLY"],
|
|
25
|
+
ANEX: ["ANX", "ANNEX", "ANNX"],
|
|
26
|
+
ARCADE: ["ARC"],
|
|
27
|
+
AVENUE: ["AVE", "AV", "AVEN", "AVENU", "AVN", "AVNUE"],
|
|
28
|
+
BAYOU: ["BYU", "BAYOO"],
|
|
29
|
+
BEACH: ["BCH"],
|
|
30
|
+
BEND: ["BND"],
|
|
31
|
+
BLUFF: ["BLF", "BLUF"],
|
|
32
|
+
BLUFFS: ["BLFS"],
|
|
33
|
+
BOTTOM: ["BTM", "BOT", "BOTTM"],
|
|
34
|
+
BOULEVARD: ["BLVD", "BOUL", "BOULV"],
|
|
35
|
+
BRANCH: ["BR", "BRNCH"],
|
|
36
|
+
BRIDGE: ["BRG", "BRDGE"],
|
|
37
|
+
BROOK: ["BRK"],
|
|
38
|
+
BROOKS: ["BRKS"],
|
|
39
|
+
BURG: ["BG"],
|
|
40
|
+
BURGS: ["BGS"],
|
|
41
|
+
BYPASS: ["BYP", "BYPA", "BYPAS", "BYPS"],
|
|
42
|
+
CAMP: ["CP", "CMP"],
|
|
43
|
+
CANYON: ["CYN", "CANYN", "CNYN"],
|
|
44
|
+
CAPE: ["CPE"],
|
|
45
|
+
CAUSEWAY: ["CSWY", "CAUSWA"],
|
|
46
|
+
CENTER: ["CTR", "CEN", "CENT", "CENTR", "CENTRE", "CNTER", "CNTR"],
|
|
47
|
+
CENTERS: ["CTRS"],
|
|
48
|
+
CIRCLE: ["CIR", "CIRC", "CIRCL", "CRCL", "CRCLE"],
|
|
49
|
+
CIRCLES: ["CIRS"],
|
|
50
|
+
CLIFF: ["CLF"],
|
|
51
|
+
CLIFFS: ["CLFS"],
|
|
52
|
+
CLUB: ["CLB"],
|
|
53
|
+
COMMON: ["CMN"],
|
|
54
|
+
COMMONS: ["CMNS"],
|
|
55
|
+
CORNER: ["COR"],
|
|
56
|
+
CORNERS: ["CORS"],
|
|
57
|
+
COURSE: ["CRSE"],
|
|
58
|
+
COURT: ["CT"],
|
|
59
|
+
COURTS: ["CTS"],
|
|
60
|
+
COVE: ["CV"],
|
|
61
|
+
COVES: ["CVS"],
|
|
62
|
+
CREEK: ["CRK"],
|
|
63
|
+
CRESCENT: ["CRES", "CRSENT", "CRSNT"],
|
|
64
|
+
CREST: ["CRST"],
|
|
65
|
+
CROSSING: ["XING", "CRSSNG"],
|
|
66
|
+
CROSSROAD: ["XRD"],
|
|
67
|
+
CROSSROADS: ["XRDS"],
|
|
68
|
+
CURVE: ["CURV"],
|
|
69
|
+
DALE: ["DL"],
|
|
70
|
+
DAM: ["DM"],
|
|
71
|
+
DIVIDE: ["DV", "DIV", "DVD"],
|
|
72
|
+
DRIVE: ["DR", "DRIV", "DRV"],
|
|
73
|
+
DRIVES: ["DRS"],
|
|
74
|
+
ESTATE: ["EST"],
|
|
75
|
+
ESTATES: ["ESTS"],
|
|
76
|
+
EXPRESSWAY: ["EXPY", "EXP", "EXPR", "EXPRESS", "EXPW"],
|
|
77
|
+
EXTENSION: ["EXT", "EXTN", "EXTNSN"],
|
|
78
|
+
EXTENSIONS: ["EXTS"],
|
|
79
|
+
FALL: ["FALL"],
|
|
80
|
+
FALLS: ["FLS"],
|
|
81
|
+
FERRY: ["FRY", "FRRY"],
|
|
82
|
+
FIELD: ["FLD"],
|
|
83
|
+
FIELDS: ["FLDS"],
|
|
84
|
+
FLAT: ["FLT"],
|
|
85
|
+
FLATS: ["FLTS"],
|
|
86
|
+
FORD: ["FRD"],
|
|
87
|
+
FORDS: ["FRDS"],
|
|
88
|
+
FOREST: ["FRST", "FORESTS"],
|
|
89
|
+
FORGE: ["FRG", "FORG"],
|
|
90
|
+
FORGES: ["FRGS"],
|
|
91
|
+
FORK: ["FRK"],
|
|
92
|
+
FORKS: ["FRKS"],
|
|
93
|
+
FORT: ["FT", "FRT"],
|
|
94
|
+
FREEWAY: ["FWY", "FREEWY", "FRWAY", "FRWY"],
|
|
95
|
+
GARDEN: ["GDN", "GARDN", "GRDEN", "GRDN"],
|
|
96
|
+
GARDENS: ["GDNS", "GRDNS"],
|
|
97
|
+
GATEWAY: ["GTWY", "GATEWY", "GATWAY", "GTWAY"],
|
|
98
|
+
GLEN: ["GLN"],
|
|
99
|
+
GLENS: ["GLNS"],
|
|
100
|
+
GREEN: ["GRN"],
|
|
101
|
+
GREENS: ["GRNS"],
|
|
102
|
+
GROVE: ["GRV", "GROV"],
|
|
103
|
+
GROVES: ["GRVS"],
|
|
104
|
+
HARBOR: ["HBR", "HARB", "HARBR", "HRBOR"],
|
|
105
|
+
HARBORS: ["HBRS"],
|
|
106
|
+
HAVEN: ["HVN"],
|
|
107
|
+
HEIGHTS: ["HTS", "HT"],
|
|
108
|
+
HIGHWAY: ["HWY", "HIGHWY", "HIWAY", "HIWY", "HWAY"],
|
|
109
|
+
HILL: ["HL"],
|
|
110
|
+
HILLS: ["HLS"],
|
|
111
|
+
HOLLOW: ["HOLW", "HLLW", "HOLLOWS", "HOLWS"],
|
|
112
|
+
INLET: ["INLT"],
|
|
113
|
+
ISLAND: ["IS", "ISLND"],
|
|
114
|
+
ISLANDS: ["ISS", "ISLNDS"],
|
|
115
|
+
ISLE: ["ISLE", "ISLES"],
|
|
116
|
+
JUNCTION: ["JCT", "JCTION", "JCTN", "JUNCTN", "JUNCTON"],
|
|
117
|
+
JUNCTIONS: ["JCTS", "JCTNS"],
|
|
118
|
+
KEY: ["KY"],
|
|
119
|
+
KEYS: ["KYS"],
|
|
120
|
+
KNOLL: ["KNL", "KNOL"],
|
|
121
|
+
KNOLLS: ["KNLS"],
|
|
122
|
+
LAKE: ["LK"],
|
|
123
|
+
LAKES: ["LKS"],
|
|
124
|
+
LAND: ["LAND"],
|
|
125
|
+
LANDING: ["LNDG", "LNDNG"],
|
|
126
|
+
LANE: ["LN"],
|
|
127
|
+
LIGHT: ["LGT"],
|
|
128
|
+
LIGHTS: ["LGTS"],
|
|
129
|
+
LOAF: ["LF"],
|
|
130
|
+
LOCK: ["LCK"],
|
|
131
|
+
LOCKS: ["LCKS"],
|
|
132
|
+
LODGE: ["LDG", "LDGE", "LODG"],
|
|
133
|
+
LOOP: ["LOOP", "LOOPS"],
|
|
134
|
+
MALL: ["MALL"],
|
|
135
|
+
MANOR: ["MNR"],
|
|
136
|
+
MANORS: ["MNRS"],
|
|
137
|
+
MEADOW: ["MDW"],
|
|
138
|
+
MEADOWS: ["MDWS", "MDW", "MEDOWS"],
|
|
139
|
+
MEWS: ["MEWS"],
|
|
140
|
+
MILL: ["ML"],
|
|
141
|
+
MILLS: ["MLS"],
|
|
142
|
+
MISSION: ["MSN", "MISSN", "MSSN"],
|
|
143
|
+
MOTORWAY: ["MTWY"],
|
|
144
|
+
MOUNT: ["MT", "MNT"],
|
|
145
|
+
MOUNTAIN: ["MTN", "MNTAIN", "MNTN", "MOUNTIN", "MTIN"],
|
|
146
|
+
MOUNTAINS: ["MTNS", "MNTNS"],
|
|
147
|
+
NECK: ["NCK"],
|
|
148
|
+
ORCHARD: ["ORCH", "ORCHRD"],
|
|
149
|
+
OVAL: ["OVAL", "OVL"],
|
|
150
|
+
OVERPASS: ["OPAS"],
|
|
151
|
+
PARK: ["PARK", "PRK", "PARKS"],
|
|
152
|
+
PARKWAY: ["PKWY", "PARKWY", "PKWAY", "PKY"],
|
|
153
|
+
PARKWAYS: ["PKWY", "PKWYS"],
|
|
154
|
+
PASS: ["PASS"],
|
|
155
|
+
PASSAGE: ["PSGE"],
|
|
156
|
+
PATH: ["PATH", "PATHS"],
|
|
157
|
+
PIKE: ["PIKE", "PIKES"],
|
|
158
|
+
PINE: ["PNE"],
|
|
159
|
+
PINES: ["PNES"],
|
|
160
|
+
PLACE: ["PL"],
|
|
161
|
+
PLAIN: ["PLN"],
|
|
162
|
+
PLAINS: ["PLNS"],
|
|
163
|
+
PLAZA: ["PLZ", "PLZA"],
|
|
164
|
+
POINT: ["PT"],
|
|
165
|
+
POINTS: ["PTS"],
|
|
166
|
+
PORT: ["PRT"],
|
|
167
|
+
PORTS: ["PRTS"],
|
|
168
|
+
PRAIRIE: ["PR", "PRR"],
|
|
169
|
+
RADIAL: ["RADL", "RAD", "RADIEL"],
|
|
170
|
+
RAMP: ["RAMP"],
|
|
171
|
+
RANCH: ["RNCH", "RANCHES", "RNCHS"],
|
|
172
|
+
RAPID: ["RPD"],
|
|
173
|
+
RAPIDS: ["RPDS"],
|
|
174
|
+
REST: ["RST"],
|
|
175
|
+
RIDGE: ["RDG", "RDGE"],
|
|
176
|
+
RIDGES: ["RDGS"],
|
|
177
|
+
RIVER: ["RIV", "RVR", "RIVR"],
|
|
178
|
+
ROAD: ["RD"],
|
|
179
|
+
ROADS: ["RDS"],
|
|
180
|
+
ROUTE: ["RTE"],
|
|
181
|
+
ROW: ["ROW"],
|
|
182
|
+
RUE: ["RUE"],
|
|
183
|
+
RUN: ["RUN"],
|
|
184
|
+
SHOAL: ["SHL"],
|
|
185
|
+
SHOALS: ["SHLS"],
|
|
186
|
+
SHORE: ["SHR", "SHOAR"],
|
|
187
|
+
SHORES: ["SHRS", "SHOARS"],
|
|
188
|
+
SKYWAY: ["SKWY"],
|
|
189
|
+
SPRING: ["SPG", "SPNG", "SPRNG"],
|
|
190
|
+
SPRINGS: ["SPGS", "SPNGS", "SPRNGS"],
|
|
191
|
+
SPUR: ["SPUR"],
|
|
192
|
+
SPURS: ["SPUR"],
|
|
193
|
+
SQUARE: ["SQ", "SQR", "SQRE", "SQU"],
|
|
194
|
+
SQUARES: ["SQS", "SQRS"],
|
|
195
|
+
STATION: ["STA", "STATN", "STN"],
|
|
196
|
+
STRAVENUE: ["STRA", "STRAV", "STRAVEN", "STRAVN", "STRVN", "STRVNUE"],
|
|
197
|
+
STREAM: ["STRM", "STREME"],
|
|
198
|
+
STREET: ["ST", "STRT", "STR"],
|
|
199
|
+
STREETS: ["STS"],
|
|
200
|
+
SUMMIT: ["SMT", "SUMIT", "SUMITT"],
|
|
201
|
+
TERRACE: ["TER", "TERR"],
|
|
202
|
+
THROUGHWAY: ["TRWY"],
|
|
203
|
+
TRACE: ["TRCE", "TRACES"],
|
|
204
|
+
TRACK: ["TRAK", "TRACKS", "TRK", "TRKS"],
|
|
205
|
+
TRAFFICWAY: ["TRFY"],
|
|
206
|
+
TRAIL: ["TRL", "TRAILS", "TRLS"],
|
|
207
|
+
TRAILER: ["TRLR", "TRLRS"],
|
|
208
|
+
TUNNEL: ["TUNL", "TUNEL", "TUNLS", "TUNNELS", "TUNNL"],
|
|
209
|
+
TURNPIKE: ["TPKE", "TRNPK", "TURNPK"],
|
|
210
|
+
UNDERPASS: ["UPAS"],
|
|
211
|
+
UNION: ["UN"],
|
|
212
|
+
UNIONS: ["UNS"],
|
|
213
|
+
VALLEY: ["VLY", "VALLY", "VLLY"],
|
|
214
|
+
VALLEYS: ["VLYS"],
|
|
215
|
+
VIADUCT: ["VIA", "VDCT", "VIADCT"],
|
|
216
|
+
VIEW: ["VW"],
|
|
217
|
+
VIEWS: ["VWS"],
|
|
218
|
+
VILLAGE: ["VLG", "VILL", "VILLAG", "VILLG", "VILLIAGE"],
|
|
219
|
+
VILLAGES: ["VLGS"],
|
|
220
|
+
VILLE: ["VL"],
|
|
221
|
+
VISTA: ["VIS", "VIST", "VST", "VSTA"],
|
|
222
|
+
WALK: ["WALK"],
|
|
223
|
+
WALKS: ["WALK"],
|
|
224
|
+
WALL: ["WALL"],
|
|
225
|
+
WAY: ["WAY", "WY"],
|
|
226
|
+
WAYS: ["WAYS"],
|
|
227
|
+
WELL: ["WL"],
|
|
228
|
+
WELLS: ["WLS"],
|
|
229
|
+
} as const satisfies Record<string, readonly string[]>
|
|
230
|
+
|
|
231
|
+
/** Canonical USPS suffix (full word, uppercase per the publication). */
|
|
232
|
+
export type USStreetSuffix = keyof typeof US_STREET_SUFFIX_VARIANTS
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Inverse lookup: every variant abbreviation OR full canonical word → its canonical key. Built once at module load,
|
|
236
|
+
* lowercase-keyed for case-insensitive matching (`street` → `"STREET"`, `st` → `"STREET"`, `strt` → `"STREET"`, …).
|
|
237
|
+
*/
|
|
238
|
+
export const US_STREET_SUFFIX_LOOKUP: ReadonlyMap<string, USStreetSuffix> = (() => {
|
|
239
|
+
const out = new Map<string, USStreetSuffix>()
|
|
240
|
+
|
|
241
|
+
for (const canonical of Object.keys(US_STREET_SUFFIX_VARIANTS) as USStreetSuffix[]) {
|
|
242
|
+
out.set(canonical.toLowerCase(), canonical)
|
|
243
|
+
|
|
244
|
+
for (const variant of US_STREET_SUFFIX_VARIANTS[canonical]) {
|
|
245
|
+
// Don't overwrite — first canonical that claims a variant wins (matches USPS Pub-28's
|
|
246
|
+
// ordering). E.g. "WALK" and "WALKS" both list "WALK" as a variant; "WALK" wins because it
|
|
247
|
+
// sorts first in `Object.keys`.
|
|
248
|
+
if (!out.has(variant.toLowerCase())) {
|
|
249
|
+
out.set(variant.toLowerCase(), canonical)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return out
|
|
255
|
+
})()
|
|
256
|
+
|
|
257
|
+
/** Preferred USPS abbreviation per canonical (`AVENUE → "AVE"`, `STREET → "ST"`). */
|
|
258
|
+
export const US_STREET_SUFFIX_PREFERRED_ABBR: Readonly<Record<USStreetSuffix, string>> = Object.fromEntries(
|
|
259
|
+
(Object.keys(US_STREET_SUFFIX_VARIANTS) as USStreetSuffix[]).map((k) => [k, US_STREET_SUFFIX_VARIANTS[k][0]])
|
|
260
|
+
) as Readonly<Record<USStreetSuffix, string>>
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Apply `target`'s letters in the same case-pattern as `reference`. Three patterns covered:
|
|
264
|
+
*
|
|
265
|
+
* - All-uppercase reference (`"AVE"`) → uppercase target (`"AVENUE"`).
|
|
266
|
+
* - All-lowercase reference (`"ave"`) → lowercase target (`"avenue"`).
|
|
267
|
+
* - Anything else (`"Ave"`, `"aVe"`) → title-case target (`"Avenue"`).
|
|
268
|
+
*/
|
|
269
|
+
export function matchCase(target: string, reference: string): string {
|
|
270
|
+
if (!reference) return target
|
|
271
|
+
|
|
272
|
+
if (reference === reference.toUpperCase()) return target.toUpperCase()
|
|
273
|
+
|
|
274
|
+
if (reference === reference.toLowerCase()) return target.toLowerCase()
|
|
275
|
+
|
|
276
|
+
return target.charAt(0).toUpperCase() + target.slice(1).toLowerCase()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* If the last whitespace-separated word of `street` is a known USPS suffix variant, return the canonical key and the
|
|
281
|
+
* matched word. Returns null if the trailing word isn't a known suffix.
|
|
282
|
+
*/
|
|
283
|
+
export function matchTrailingSuffix(street: string): { canonical: USStreetSuffix; matched: string } | null {
|
|
284
|
+
const trimmed = street.trim()
|
|
285
|
+
|
|
286
|
+
if (!trimmed) return null
|
|
287
|
+
const parts = trimmed.split(/\s+/)
|
|
288
|
+
const last = parts[parts.length - 1]!
|
|
289
|
+
const canonical = US_STREET_SUFFIX_LOOKUP.get(last.toLowerCase())
|
|
290
|
+
|
|
291
|
+
if (!canonical) return null
|
|
292
|
+
|
|
293
|
+
return { canonical, matched: last }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The USPS suffix record, under its original isp-nexus name. Aliases {@link US_STREET_SUFFIX_VARIANTS}.
|
|
298
|
+
*/
|
|
299
|
+
export const StreetSuffixAbbreviationRecord = US_STREET_SUFFIX_VARIANTS
|
|
300
|
+
export type StreetSuffixAbbreviationRecord = typeof US_STREET_SUFFIX_VARIANTS
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* A canonical USPS street suffix, i.e. "STREET", "AVENUE", "BOULEVARD". Aliases {@link USStreetSuffix}.
|
|
304
|
+
*/
|
|
305
|
+
export type StreetSuffix = USStreetSuffix
|
|
306
|
+
|
|
307
|
+
/** A standardized USPS street suffix abbreviation (the preferred form), i.e. "ST", "AVE", "BLVD". */
|
|
308
|
+
export type USPSStandardSuffixAbbreviation = StreetSuffixAbbreviationRecord[StreetSuffix][0]
|
|
309
|
+
|
|
310
|
+
/** Any USPS-recognized suffix variant or abbreviation. */
|
|
311
|
+
export type StreetSuffixAbbreviation = StreetSuffixAbbreviationRecord[StreetSuffix][number]
|
|
312
|
+
|
|
313
|
+
/** Result of a successful USPS street suffix lookup. */
|
|
314
|
+
export interface StreetSuffixMatch<S extends StreetSuffix = StreetSuffix> {
|
|
315
|
+
/** The matched canonical USPS street suffix, i.e. "STREET", "AVENUE". */
|
|
316
|
+
suffix: S
|
|
317
|
+
/** The preferred USPS street suffix abbreviation, i.e. "ST", "AVE". */
|
|
318
|
+
abbreviation: StreetSuffixAbbreviationRecord[S][0]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Look up a USPS street suffix (by canonical word, abbreviation, or any variant) and its preferred abbreviation.
|
|
323
|
+
*/
|
|
324
|
+
export function lookupStreetSuffix<S extends StreetSuffix>(suffix: S): StreetSuffixMatch<S>
|
|
325
|
+
export function lookupStreetSuffix(input: string | null | undefined): StreetSuffixMatch | null
|
|
326
|
+
export function lookupStreetSuffix(input: string | null | undefined): StreetSuffixMatch | null {
|
|
327
|
+
if (!input || typeof input !== "string") return null
|
|
328
|
+
const suffix = US_STREET_SUFFIX_LOOKUP.get(input.trim().toLowerCase())
|
|
329
|
+
|
|
330
|
+
if (!suffix) return null
|
|
331
|
+
|
|
332
|
+
return { suffix, abbreviation: US_STREET_SUFFIX_VARIANTS[suffix][0] }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Type-predicate: is the input a canonical USPS street suffix (uppercase full word, e.g. "STREET")? */
|
|
336
|
+
export function isStreetSuffix(input: unknown): input is StreetSuffix {
|
|
337
|
+
return typeof input === "string" && Object.hasOwn(US_STREET_SUFFIX_VARIANTS, input)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* True when a token is any USPS street suffix or abbreviation (case-insensitive) — `"St"`, `"BLVD"`, `"trail"`.
|
|
342
|
+
*/
|
|
343
|
+
export function isStreetSuffixToken(input: unknown): boolean {
|
|
344
|
+
return typeof input === "string" && US_STREET_SUFFIX_LOOKUP.has(input.trim().toLowerCase())
|
|
345
|
+
}
|