@mailwoman/codex 9.4.0 → 10.0.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 (198) hide show
  1. package/README.md +221 -75
  2. package/lib/address/format.ts +144 -0
  3. package/lib/address/key.ts +134 -0
  4. package/lib/address/layout.ts +189 -0
  5. package/lib/address/layouts/generated.ts +1576 -0
  6. package/lib/address/layouts/index.ts +213 -0
  7. package/lib/address/render.ts +176 -0
  8. package/lib/{address-system-conventions.ts → address/system-conventions.ts} +6 -5
  9. package/lib/component.ts +96 -0
  10. package/lib/country/index.ts +3 -2
  11. package/lib/country/{reference.ts → reference/index.ts} +1 -1
  12. package/lib/country/region-languages.ts +46 -0
  13. package/lib/es/co-official-languages.ts +62 -0
  14. package/lib/es/index.ts +1 -0
  15. package/lib/gb/index.ts +2 -2
  16. package/lib/index.ts +4 -2
  17. package/lib/placetype-map.ts +157 -0
  18. package/lib/postcode/shapes.json +80 -0
  19. package/lib/postcode/shapes.ts +56 -0
  20. package/lib/{postcode-systems.ts → postcode/systems.ts} +8 -1
  21. package/lib/us/index.ts +2 -2
  22. package/lib/us/po-box.ts +37 -10
  23. package/lib/us/{street-directional.ts → street/directional.ts} +1 -1
  24. package/lib/us/{street-suffix.ts → street/suffix.ts} +1 -1
  25. package/out/address/format.d.ts +87 -0
  26. package/out/address/format.d.ts.map +1 -0
  27. package/out/address/format.js +85 -0
  28. package/out/address/format.js.map +1 -0
  29. package/out/address/key.d.ts +67 -0
  30. package/out/address/key.d.ts.map +1 -0
  31. package/out/address/key.js +91 -0
  32. package/out/address/key.js.map +1 -0
  33. package/out/address/layout.d.ts +104 -0
  34. package/out/address/layout.d.ts.map +1 -0
  35. package/out/address/layout.js +123 -0
  36. package/out/address/layout.js.map +1 -0
  37. package/out/address/layouts/generated.d.ts +21 -0
  38. package/out/address/layouts/generated.d.ts.map +1 -0
  39. package/out/address/layouts/generated.js +1380 -0
  40. package/out/address/layouts/generated.js.map +1 -0
  41. package/out/address/layouts/index.d.ts +81 -0
  42. package/out/address/layouts/index.d.ts.map +1 -0
  43. package/out/address/layouts/index.js +184 -0
  44. package/out/address/layouts/index.js.map +1 -0
  45. package/out/address/render.d.ts +64 -0
  46. package/out/address/render.d.ts.map +1 -0
  47. package/out/address/render.js +115 -0
  48. package/out/address/render.js.map +1 -0
  49. package/out/{address-system-conventions.d.ts → address/system-conventions.d.ts} +6 -5
  50. package/out/address/system-conventions.d.ts.map +1 -0
  51. package/out/{address-system-conventions.js → address/system-conventions.js} +2 -2
  52. package/out/address/system-conventions.js.map +1 -0
  53. package/out/au/delivery-service.d.ts.map +1 -1
  54. package/out/au/level-designator.d.ts.map +1 -1
  55. package/out/au/state.d.ts.map +1 -1
  56. package/out/ca/province.d.ts.map +1 -1
  57. package/out/component.d.ts +50 -0
  58. package/out/component.d.ts.map +1 -0
  59. package/out/component.js +84 -0
  60. package/out/component.js.map +1 -0
  61. package/out/country/codes.d.ts +2 -2
  62. package/out/country/codes.d.ts.map +1 -1
  63. package/out/country/display-names.d.ts.map +1 -1
  64. package/out/country/display-names.js.map +1 -1
  65. package/out/country/index.d.ts +3 -2
  66. package/out/country/index.d.ts.map +1 -1
  67. package/out/country/index.js +3 -2
  68. package/out/country/index.js.map +1 -1
  69. package/out/country/{reference-data.d.ts → reference/data.d.ts} +1 -1
  70. package/out/country/reference/data.d.ts.map +1 -0
  71. package/out/country/{reference-data.js → reference/data.js} +1 -1
  72. package/out/country/reference/data.js.map +1 -0
  73. package/out/country/{reference.d.ts → reference/index.d.ts} +1 -1
  74. package/out/country/reference/index.d.ts.map +1 -0
  75. package/out/country/{reference.js → reference/index.js} +2 -2
  76. package/out/country/reference/index.js.map +1 -0
  77. package/out/country/region-languages.d.ts +25 -0
  78. package/out/country/region-languages.d.ts.map +1 -0
  79. package/out/country/region-languages.js +41 -0
  80. package/out/country/region-languages.js.map +1 -0
  81. package/out/de/bundesland.d.ts.map +1 -1
  82. package/out/de/postleitzahl.d.ts.map +1 -1
  83. package/out/de/street-type.d.ts.map +1 -1
  84. package/out/es/co-official-languages.d.ts +37 -0
  85. package/out/es/co-official-languages.d.ts.map +1 -0
  86. package/out/es/co-official-languages.js +61 -0
  87. package/out/es/co-official-languages.js.map +1 -0
  88. package/out/es/index.d.ts +1 -0
  89. package/out/es/index.d.ts.map +1 -1
  90. package/out/es/index.js +1 -0
  91. package/out/es/index.js.map +1 -1
  92. package/out/fr/departement.d.ts.map +1 -1
  93. package/out/fr/region.d.ts.map +1 -1
  94. package/out/gb/country.d.ts.map +1 -1
  95. package/out/gb/index.d.ts +2 -2
  96. package/out/gb/index.d.ts.map +1 -1
  97. package/out/gb/index.js +2 -2
  98. package/out/gb/index.js.map +1 -1
  99. package/out/gb/{postcode-area.d.ts → postcode/area.d.ts} +1 -1
  100. package/out/gb/postcode/area.d.ts.map +1 -0
  101. package/out/gb/{postcode-area.js → postcode/area.js} +1 -1
  102. package/out/gb/postcode/area.js.map +1 -0
  103. package/out/gb/{postcode.d.ts → postcode/index.d.ts} +1 -1
  104. package/out/gb/postcode/index.d.ts.map +1 -0
  105. package/out/gb/{postcode.js → postcode/index.js} +1 -1
  106. package/out/gb/postcode/index.js.map +1 -0
  107. package/out/gb/street-type.d.ts.map +1 -1
  108. package/out/index.d.ts +4 -2
  109. package/out/index.d.ts.map +1 -1
  110. package/out/index.js +4 -2
  111. package/out/index.js.map +1 -1
  112. package/out/jp/address-unit.d.ts.map +1 -1
  113. package/out/jp/prefecture.d.ts.map +1 -1
  114. package/out/level-semantics.d.ts.map +1 -1
  115. package/out/level-semantics.js.map +1 -1
  116. package/out/nz/delivery-service.d.ts.map +1 -1
  117. package/out/placetype-map.d.ts +83 -0
  118. package/out/placetype-map.d.ts.map +1 -0
  119. package/out/placetype-map.js +130 -0
  120. package/out/placetype-map.js.map +1 -0
  121. package/out/postcode/shapes.d.ts +47 -0
  122. package/out/postcode/shapes.d.ts.map +1 -0
  123. package/out/postcode/shapes.js +38 -0
  124. package/out/postcode/shapes.js.map +1 -0
  125. package/out/postcode/shapes.json +80 -0
  126. package/out/{postcode-systems.d.ts → postcode/systems.d.ts} +8 -1
  127. package/out/postcode/systems.d.ts.map +1 -0
  128. package/out/{postcode-systems.js → postcode/systems.js} +9 -2
  129. package/out/postcode/systems.js.map +1 -0
  130. package/out/us/floor-designator.d.ts.map +1 -1
  131. package/out/us/index.d.ts +2 -2
  132. package/out/us/index.js +2 -2
  133. package/out/us/military-address.d.ts.map +1 -1
  134. package/out/us/po-box.d.ts +6 -0
  135. package/out/us/po-box.d.ts.map +1 -1
  136. package/out/us/po-box.js +35 -10
  137. package/out/us/po-box.js.map +1 -1
  138. package/out/us/state.d.ts.map +1 -1
  139. package/out/us/{street-directional.d.ts → street/directional.d.ts} +1 -1
  140. package/out/us/street/directional.d.ts.map +1 -0
  141. package/out/us/{street-directional.js → street/directional.js} +2 -2
  142. package/out/us/street/directional.js.map +1 -0
  143. package/out/us/{street-suffix.d.ts → street/suffix.d.ts} +1 -1
  144. package/out/us/street/suffix.d.ts.map +1 -0
  145. package/out/us/{street-suffix.js → street/suffix.js} +2 -2
  146. package/out/us/street/suffix.js.map +1 -0
  147. package/out/us/zipcode.d.ts +1 -1
  148. package/out/us/zipcode.d.ts.map +1 -1
  149. package/package.json +110 -30
  150. package/lib/tools/build-country-surface-lexicon.ts +0 -177
  151. package/lib/tools/export-country-surfaces.ts +0 -48
  152. package/lib/tools/generate-country-population.ts +0 -142
  153. package/lib/tools/generate-country-reference.ts +0 -168
  154. package/lib/tools/generate-official-languages.ts +0 -210
  155. package/lib/tools/index.ts +0 -12
  156. package/out/address-system-conventions.d.ts.map +0 -1
  157. package/out/address-system-conventions.js.map +0 -1
  158. package/out/country/reference-data.d.ts.map +0 -1
  159. package/out/country/reference-data.js.map +0 -1
  160. package/out/country/reference.d.ts.map +0 -1
  161. package/out/country/reference.js.map +0 -1
  162. package/out/gb/postcode-area.d.ts.map +0 -1
  163. package/out/gb/postcode-area.js.map +0 -1
  164. package/out/gb/postcode.d.ts.map +0 -1
  165. package/out/gb/postcode.js.map +0 -1
  166. package/out/postcode-systems.d.ts.map +0 -1
  167. package/out/postcode-systems.js.map +0 -1
  168. package/out/tools/build-country-surface-lexicon.d.ts +0 -43
  169. package/out/tools/build-country-surface-lexicon.d.ts.map +0 -1
  170. package/out/tools/build-country-surface-lexicon.js +0 -149
  171. package/out/tools/build-country-surface-lexicon.js.map +0 -1
  172. package/out/tools/export-country-surfaces.d.ts +0 -15
  173. package/out/tools/export-country-surfaces.d.ts.map +0 -1
  174. package/out/tools/export-country-surfaces.js +0 -34
  175. package/out/tools/export-country-surfaces.js.map +0 -1
  176. package/out/tools/generate-country-population.d.ts +0 -42
  177. package/out/tools/generate-country-population.d.ts.map +0 -1
  178. package/out/tools/generate-country-population.js +0 -106
  179. package/out/tools/generate-country-population.js.map +0 -1
  180. package/out/tools/generate-country-reference.d.ts +0 -36
  181. package/out/tools/generate-country-reference.d.ts.map +0 -1
  182. package/out/tools/generate-country-reference.js +0 -107
  183. package/out/tools/generate-country-reference.js.map +0 -1
  184. package/out/tools/generate-official-languages.d.ts +0 -50
  185. package/out/tools/generate-official-languages.d.ts.map +0 -1
  186. package/out/tools/generate-official-languages.js +0 -149
  187. package/out/tools/generate-official-languages.js.map +0 -1
  188. package/out/tools/index.d.ts +0 -12
  189. package/out/tools/index.d.ts.map +0 -1
  190. package/out/tools/index.js +0 -12
  191. package/out/tools/index.js.map +0 -1
  192. package/out/us/street-directional.d.ts.map +0 -1
  193. package/out/us/street-directional.js.map +0 -1
  194. package/out/us/street-suffix.d.ts.map +0 -1
  195. package/out/us/street-suffix.js.map +0 -1
  196. /package/lib/country/{reference-data.ts → reference/data.ts} +0 -0
  197. /package/lib/gb/{postcode-area.ts → postcode/area.ts} +0 -0
  198. /package/lib/gb/{postcode.ts → postcode/index.ts} +0 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Component-tag → resolver-placetype mapping, the equivalence groups a query expands into, and the test
7
+ * that tells a widened match from an exact one.
8
+ */
9
+
10
+ import type { ComponentTag } from "#component"
11
+
12
+ /**
13
+ * Mapping from mailwoman's address-component tags to the resolver's placetype taxonomy.
14
+ *
15
+ * PARTIAL on purpose: a tag absent from the map is NOT queried, and the resolver pass leaves its classifier attribution
16
+ * untouched. Omission is therefore a routing decision, not an oversight.
17
+ */
18
+ export type PlacetypeMap = Partial<Record<ComponentTag, string>>
19
+
20
+ /**
21
+ * The map used when a backend does not supply its own.
22
+ *
23
+ * `street` and `house_number` are absent because WOF admin has no rows for them — they resolve through the situs
24
+ * extracts instead, which are keyed by street, not by placetype.
25
+ *
26
+ * The JP tiers are present because the candidate gazetteer keys them: 91.3% of Japanese records carry a kanji or kana
27
+ * key (49,255 of 53,920), a prefecture is a WOF `region`, a municipality a `locality` (its filter group admits the
28
+ * `borough` wards and `localadmin`), and a district (大字 / 町名) sits in the `locality` band beside the neighbourhoods.
29
+ * Measured on 300 JP board rows: with these entries and the JP rungs on the admin ladder, 271 resolve within 15 km;
30
+ * without them, 0. Only the character-path CJK model emits the tags, so no Latin parse reaches these rows.
31
+ */
32
+ export const DEFAULT_PLACETYPE_MAP: PlacetypeMap = {
33
+ country: "country",
34
+ region: "region",
35
+ locality: "locality",
36
+ dependent_locality: "locality",
37
+ subregion: "county",
38
+ prefecture: "region",
39
+ municipality: "locality",
40
+ district: "locality",
41
+ // `postcode` (mailwoman tag) maps to WOF's `postalcode` placetype. Resolves only when the
42
+ // backend has the postcode extract available — `WOFSQLitePlaceLookup` auto-routes `postalcode`
43
+ // queries to a `postalcode_us` (or similarly-named) extract, falling back to main if absent.
44
+ postcode: "postalcode",
45
+ }
46
+
47
+ /**
48
+ * Where a country's WOF typing of a tier departs from {@link DEFAULT_PLACETYPE_MAP}, the entries that differ. The map
49
+ * names the tier by tag; WOF names it by placetype, and the two agree for most countries and not for all.
50
+ *
51
+ * - **TW** — 鄉鎮市區, the tier below the 縣市 that the parse tags `subregion`, is `locality` or `localadmin` in WOF for 164 of
52
+ * the 178 held-out districts the candidate table carries and `county` for 14 (the census in
53
+ * `docs/records/evals/2026-09-08-v8-cjk-regs.md` §5). Under the default `county` band `臺北市中正區` reached nothing and
54
+ * the admin tier fell to the 縣市. The `locality` group admits `localadmin` too, the rule the JP `municipality` tag
55
+ * already relies on, and the exact-type preference in ranking keeps a `locality` row ahead of a same-name
56
+ * `localadmin` row.
57
+ */
58
+ const COUNTRY_PLACETYPE_OVERRIDES: Readonly<Record<string, PlacetypeMap>> = {
59
+ tw: { subregion: "locality" },
60
+ }
61
+
62
+ /**
63
+ * The placetype map for a resolve scoped to `countryCode` (ISO alpha-2, any case): the default map with the country's
64
+ * overrides applied, or the default map itself when the country has none or is unknown. The identity of the default
65
+ * object is preserved in that case, so a caller comparing maps to decide whether a second resolve is needed reads
66
+ * "unchanged" correctly.
67
+ */
68
+ export function placetypeMapForCountry(countryCode: string | null | undefined): PlacetypeMap {
69
+ const overrides = countryCode ? COUNTRY_PLACETYPE_OVERRIDES[countryCode.toLowerCase()] : undefined
70
+
71
+ return overrides ? { ...DEFAULT_PLACETYPE_MAP, ...overrides } : DEFAULT_PLACETYPE_MAP
72
+ }
73
+
74
+ /**
75
+ * Placetype-equivalence groups for lookup FILTERING. WOF splits a single addressing tier across several placetypes, but
76
+ * an address's span can name ANY of them. A backend that filters to the one "obvious" placetype makes the equivalents
77
+ * unreachable, so a fuzzy same-name place in the wrong tier wins instead.
78
+ *
79
+ * Three tiers are affected (the value of each entry is the set the SQL filter should accept; the FIRST entry is the
80
+ * canonical/requested type, which extract routing keys off):
81
+ *
82
+ * - **`locality`** — `locality` (most cities), `borough` (Brooklyn, the Paris arrondissements, the London boroughs), and
83
+ * `localadmin` (FR communes, US towns/townships in New England). Without the group, Brooklyn-the-borough (pop 2.5M)
84
+ * was unreachable and the fuzzy "Brooklyn Park, MN" won.
85
+ * - **`region`** — `region` + `macroregion` (#718). WOF does NOT model every country's top-level civil division as
86
+ * `region`: Italian regions (Lombardia, Veneto, Toscana…) are `macroregion` (their PROVINCES are `region`), and the
87
+ * post-2016 French régions (Île-de-France) are `macroregion` too. An address's `region` span names exactly those, so
88
+ * a `region`-only filter resolved them to NOTHING (confirmed against the IT/FR eval rows). US states / DE
89
+ * Bundesländer / ES provincias are genuine `region`, so the EXACT-type match is preferred in ranking (see the
90
+ * resolve.ts fallback-quality annotation) — the macro is the recall safety net, not a demotion.
91
+ * - **`county`** — `county` + `macrocounty` (#718). The `subregion` ComponentTag maps to `county` via
92
+ * {@link DEFAULT_PLACETYPE_MAP}; WOF carries `macrocounty` for FR départements-grouping / DE / GB tiers above the
93
+ * county. Proactive (no eval row exercises `subregion` today) but symmetric with `region` — biasing to inclusion,
94
+ * since a missed resolution costs more than a too-broad candidate (which is QA-visible). Same exact-type preference
95
+ * applies.
96
+ *
97
+ * This table is the single source of truth for that expansion, shared by every lookup backend
98
+ * (`@mailwoman/core/resolver-wof-sqlite`, `@mailwoman/core/resolver-wof-wasm`, and the demo's httpvfs lookup) so the
99
+ * Node and browser resolvers can't drift. Keyed by the REQUESTED placetype. Placetypes without an entry pass through
100
+ * unchanged — an explicit `placetype: "borough"` query stays narrow.
101
+ */
102
+ export const PLACETYPE_FILTER_GROUPS: Readonly<Record<string, readonly string[]>> = {
103
+ locality: ["locality", "borough", "localadmin"],
104
+ region: ["region", "macroregion"],
105
+ county: ["county", "macrocounty"],
106
+ }
107
+
108
+ /**
109
+ * Expand a placetype filter through {@link PLACETYPE_FILTER_GROUPS}, deduplicated and order-preserving (the first entry
110
+ * stays first — extract routing keys off it). `null`/`undefined` (no filter) passes through untouched.
111
+ */
112
+ export function expandPlacetypeFilter(placetypes: null): null
113
+ export function expandPlacetypeFilter(placetypes: readonly string[]): string[]
114
+ export function expandPlacetypeFilter(placetypes: readonly string[] | null): string[] | null
115
+
116
+ export function expandPlacetypeFilter(placetypes: readonly string[] | null): string[] | null {
117
+ if (!placetypes) return null
118
+ const out: string[] = []
119
+
120
+ for (const placetype of placetypes) {
121
+ for (const expanded of PLACETYPE_FILTER_GROUPS[placetype] ?? [placetype]) {
122
+ if (!out.includes(expanded)) {
123
+ out.push(expanded)
124
+ }
125
+ }
126
+ }
127
+
128
+ return out
129
+ }
130
+
131
+ /**
132
+ * Macro/broader-tier members of {@link PLACETYPE_FILTER_GROUPS} — the recall safety net a query may fall through to
133
+ * when no candidate of the EXACT requested placetype exists (#718). DELIBERATELY scoped to the `macro*` tiers only: the
134
+ * `locality` group's `borough`/`localadmin` are genuine peers (Brooklyn-the-borough is a first-class locality answer,
135
+ * #404-class), NOT fallbacks — so they must NOT be deprioritized or annotated. Only `macroregion`/`macrocounty` are a
136
+ * broader admin tier standing in for a true `region`/`county`.
137
+ */
138
+ const MACRO_FALLBACK_PLACETYPES: ReadonlySet<string> = new Set(["macroregion", "macrocounty"])
139
+
140
+ /**
141
+ * Did `candidatePlacetype` resolve `requestedPlacetype` only via a BROADER admin tier (a macro-type fallback within the
142
+ * {@link PLACETYPE_FILTER_GROUPS} expansion), rather than the exact type (#718)?
143
+ *
144
+ * `region` → `region` is exact (false); `region` → `macroregion` is a fallback (true). Scoped to the `macro*` tiers
145
+ * (see {@link MACRO_FALLBACK_PLACETYPES}) so the `locality` group's borough/localadmin peers stay exact. The resolver
146
+ * uses this to (a) prefer an exact-type candidate in ranking and (b) annotate `resolutionQuality: "fallback"` when only
147
+ * a macro-type matched. A placetype outside the requested group, or any non-macro member, is treated as exact (false).
148
+ */
149
+ export function isPlacetypeFallback(requestedPlacetype: string, candidatePlacetype: string): boolean {
150
+ const group = PLACETYPE_FILTER_GROUPS[requestedPlacetype]
151
+
152
+ if (!group) return false
153
+
154
+ if (candidatePlacetype === requestedPlacetype) return false
155
+
156
+ return MACRO_FALLBACK_PLACETYPES.has(candidatePlacetype) && group.includes(candidatePlacetype)
157
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "version": "postcode-shapes-v1",
3
+ "note": "Postcode SHAPE patterns for detecting postcode-like spans in free text, ordered most-specific to least. Priority IS the array index: a lower index wins an overlap. This is a SHAPE test, not gazetteer membership — see postcode-systems.ts for the per-system membership question. Alphanumeric patterns require UPPERCASE letters, which keeps them off lowercase prose. The regex bodies are written in the subset both JavaScript RegExp and Python re accept, because two runtimes read this file; a row that needs different source text in the two dialects needs a second field and a stated reason, not a loosened comparison.",
4
+ "shapes": [
5
+ {
6
+ "label": "JP-marked",
7
+ "kind": "designated",
8
+ "pattern": "(?<=〒\\s?)\\d{3}-\\d{4}\\b",
9
+ "unicode": true,
10
+ "javascriptOnly": "Python's `re` refuses a variable-width lookbehind, and `\\s?` makes this one variable-width. Nothing on the Python side compiles this row.",
11
+ "note": "The digits behind 〒, optionally spaced (〒506-0025, 〒 100-0001). The character path keeps the mark in the text, and on a venue-led line the model has read the digits as a house number. A designated match may overwrite any existing label."
12
+ },
13
+ {
14
+ "label": "GB",
15
+ "kind": "alnum",
16
+ "pattern": "\\b[A-Z]{1,2}\\d[A-Z\\d]?\\s+\\d[A-Z]{2}\\b",
17
+ "note": "Outward + space + inward: SW1A 1AA, EH8 9YL, W1J 9PN, IP13 6SU, B12 8QX."
18
+ },
19
+ {
20
+ "label": "CA",
21
+ "kind": "alnum",
22
+ "pattern": "\\b[A-Z]\\d[A-Z]\\s?\\d[A-Z]\\d\\b",
23
+ "note": "A1A 1A1, space optional: M5V 2T6, H2X 2T6, H3B 1A3."
24
+ },
25
+ {
26
+ "label": "IE",
27
+ "kind": "alnum",
28
+ "pattern": "\\b(?:[A-Z]\\d{2}|D6W)\\s+[A-Z\\d]{4}\\b",
29
+ "note": "Eircode: routing key (letter + 2 digits, or the D6W special) + a 4-alnum unique part — D02 AF30, T12 X70A, F91 Y5CY. Space REQUIRED: a glued 7-alnum blob is too forgeable for the ADD path. No GB collision, because a letter+2-digit GB outward always has a 3-character inward (B12 8QX), never 4."
30
+ },
31
+ {
32
+ "label": "DE",
33
+ "kind": "alnum",
34
+ "pattern": "\\bD-\\d{5}\\b",
35
+ "note": "The D-prefixed form: D-68161."
36
+ },
37
+ {
38
+ "label": "NL",
39
+ "kind": "alnum",
40
+ "pattern": "\\b\\d{4}\\s?[A-Z]{2}\\b",
41
+ "note": "1234 AB / 1234AB — space optional, glued is common. The US \"2737 CA\" collision (a ZIP+4 tail followed by a state) is settled by longest-match-wins, which lets the ZIP+4 claim it."
42
+ },
43
+ {
44
+ "label": "ZIP4",
45
+ "kind": "numeric",
46
+ "pattern": "\\b\\d{5}-\\d{4}\\b",
47
+ "note": "US ZIP+4. A numeric match is eligible only for snapping, never for adding a postcode where the model emitted none."
48
+ },
49
+ {
50
+ "label": "BR",
51
+ "kind": "numeric",
52
+ "pattern": "\\b\\d{5}-\\d{3}\\b",
53
+ "note": "CEP: 70390-100, 95090-020. Without it the generic NUM5 below matched the five-digit head and the trailing-smear clip discarded the sector suffix, so the repair pass truncated a parse the model had got right (repair OFF \"95090-020\", repair ON \"95090\"). The trailing word boundary keeps it off a ZIP+4's first nine characters, since \"94610-2737\" has a digit after \"273\"."
54
+ },
55
+ {
56
+ "label": "JP",
57
+ "kind": "numeric",
58
+ "pattern": "\\b\\d{3}-\\d{4}\\b",
59
+ "note": "100-0001."
60
+ },
61
+ {
62
+ "label": "PT",
63
+ "kind": "numeric",
64
+ "pattern": "\\b\\d{4}-\\d{3}\\b",
65
+ "note": "3060-187."
66
+ },
67
+ {
68
+ "label": "PL",
69
+ "kind": "numeric",
70
+ "pattern": "\\b\\d{2}-\\d{3}\\b",
71
+ "note": "47-400."
72
+ },
73
+ {
74
+ "label": "NUM5",
75
+ "kind": "numeric",
76
+ "pattern": "\\b\\d{5}\\b",
77
+ "note": "The bare 5-digit shape US, FR, DE and ES all share. Last, so every more-specific row above claims its overlap first."
78
+ }
79
+ ]
80
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Postcode SHAPE patterns — which substrings of a line LOOK like a postcode, ordered most-specific
7
+ * to least. Priority IS the index: a lower index wins an overlap.
8
+ *
9
+ * The DATA lives in `./shapes.json` so non-TS consumers read the identical record, the
10
+ * same arrangement `us/street-suffix.json` has. Two runtimes need this table and both used to
11
+ * carry their own typed copy: `@mailwoman/neural`'s postcode repair, and the Python trainer's
12
+ * `features/postcode_shapes.py`, which paints the train-side anchor on the spans inference paints.
13
+ * Hand-mirrored, they drifted twice — the IE Eircode row was TS-only for a month, the BR CEP row
14
+ * for five weeks — and each time the trainer painted one fewer shape than inference, silently.
15
+ *
16
+ * This is a SHAPE test, not a gazetteer-membership test. A bare `68161` matches the US, German,
17
+ * French, Spanish and Italian 5-digit shapes; `./systems.ts` answers the membership
18
+ * question, and neither module reads the other.
19
+ *
20
+ * REGEX DIALECT. The bodies are written in the subset both JavaScript `RegExp` and Python `re`
21
+ * accept, which is what lets one file serve both. A row needing different source text in the two
22
+ * dialects needs a second field and a stated reason, not a loosened comparison on either side.
23
+ */
24
+
25
+ import postcodeShapeData from "./shapes.json" with { type: "json" }
26
+
27
+ /**
28
+ * What a match is eligible to do. `designated` may overwrite any existing label, `alnum` may add a postcode where the
29
+ * model emitted none, and `numeric` may only snap an existing one — so a numeric shape can never invent a postcode over
30
+ * a hyphenated house number.
31
+ */
32
+ export type PostcodeShapeKind = "alnum" | "numeric" | "designated"
33
+
34
+ /**
35
+ * One shape: the label it reports, what it is eligible to do, and the compiled pattern.
36
+ */
37
+ export interface PostcodeShape {
38
+ readonly label: string
39
+ readonly kind: PostcodeShapeKind
40
+ readonly re: RegExp
41
+ }
42
+
43
+ /**
44
+ * Every postcode shape, in priority order. Compiled once at module load; each `RegExp` carries the `g` flag because
45
+ * callers scan a whole line, and `u` as well where the row declares it.
46
+ */
47
+ export const POSTCODE_SHAPES: readonly PostcodeShape[] = postcodeShapeData.shapes.map((shape) => ({
48
+ label: shape.label,
49
+ kind: shape.kind as PostcodeShapeKind,
50
+ re: new RegExp(shape.pattern, "unicode" in shape && shape.unicode ? "gu" : "g"),
51
+ }))
52
+
53
+ /**
54
+ * The record's own version string, so a consumer that vendors a copy can say which revision it holds.
55
+ */
56
+ export const POSTCODE_SHAPES_VERSION: string = postcodeShapeData.version
@@ -186,8 +186,15 @@ export function isUnitGradePostcodeHit(parsed: string, resolverName: string | un
186
186
  * **The JP row is the JP board through the served path** (`jp-served-resolve.run.ts`, 2,000 rows, seed 42): the 586
187
187
  * rows carrying a postcode the candidate table keys, graded on the row's own entrance point — the locality-first answer
188
188
  * is the municipality centroid, the postcode answer the code's 町域 centroid from the WOF extract.
189
+ *
190
+ * **SG** is the limiting case of the tier: a six-digit Singapore postcode names ONE building, so the code's point IS
191
+ * the address, and the only locality above it is the city-state itself. Measured on a 300-row seeded draw of the
192
+ * Overture-SG register (`postalcode-sg-overture.db` folded, each row geocoded as `<number> <street> Singapore
193
+ * <postcode>` and graded on its own point): the postcode point answers 300 of 300 within 1 km (p99 0.16 km);
194
+ * locality-first answers 185, and the other 115 take the "Singapore" locality centroid (p75 6.30 km, p90 11.43 km, p99
195
+ * 24.18 km). The postcode point is closer on those 115 and equal on the rest; it is never farther.
189
196
  */
190
- export const AREA_POSTCODE_FINER_THAN_LOCALITY: ReadonlySet<string> = new Set(["DE", "JP"])
197
+ export const AREA_POSTCODE_FINER_THAN_LOCALITY: ReadonlySet<string> = new Set(["DE", "JP", "SG"])
191
198
 
192
199
  /**
193
200
  * True when this country's area-grade postal code outranks its locality. Absent or unknown country → false, so the
package/lib/us/index.ts CHANGED
@@ -13,7 +13,7 @@ export * from "#us/floor-designator"
13
13
  export * from "#us/military-address"
14
14
  export * from "#us/po-box"
15
15
  export * from "#us/state"
16
- export * from "#us/street-directional"
17
- export * from "#us/street-suffix"
16
+ export * from "#us/street/directional"
17
+ export * from "#us/street/suffix"
18
18
  export * from "#us/unit-designator"
19
19
  export * from "#us/zipcode"
package/lib/us/po-box.ts CHANGED
@@ -31,11 +31,37 @@ export const US_PO_BOX_DESIGNATORS = [
31
31
  export type USPoBoxDesignator = (typeof US_PO_BOX_DESIGNATORS)[number]
32
32
 
33
33
  /**
34
- * Matches a leading PO-box designator + its identifier. Allows "P.O. Box", "PO BOX", "Post Office Box", "Box 12-A",
35
- * etc. The id is alphanumeric with optional dashes (USPS caller/firm ids exist).
34
+ * Recognition patterns for {@link US_PO_BOX_DESIGNATORS}. Keeping the surface grammar next to the canonical designator
35
+ * prevents the exported lexicon and matcher from drifting apart.
36
36
  */
37
- const PO_BOX_RE =
38
- /^\s*(?:(p\.?\s*o\.?\s*box)|(post\s+office\s+box)|(firm\s+caller)|(caller)|(drawer)|(lockbox)|(box))\s*#?\s*([\dA-Za-z][\dA-Za-z-]*)\s*$/i
37
+ const PO_BOX_DESIGNATOR_PATTERNS: ReadonlyArray<readonly [USPoBoxDesignator, string]> = [
38
+ ["POST OFFICE BOX", String.raw`post\s+office\s+box`],
39
+ ["PO BOX", String.raw`p\.?\s*o\.?\s*box`],
40
+ ["P O BOX", String.raw`p\.?\s*o\.?\s*box`],
41
+ ["FIRM CALLER", String.raw`firm\s+caller`],
42
+ ["CALLER", "caller"],
43
+ ["DRAWER", "drawer"],
44
+ ["LOCKBOX", "lockbox"],
45
+ ["BOX", "box"],
46
+ ]
47
+
48
+ /**
49
+ * One standalone matcher per USPS designator. The id is alphanumeric with optional dashes (USPS caller/firm ids exist).
50
+ */
51
+ const PO_BOX_MATCHERS = PO_BOX_DESIGNATOR_PATTERNS.map(([designator, pattern]) => ({
52
+ designator,
53
+ designatorRe: new RegExp(String.raw`^\s*${pattern}\s*$`, "i"),
54
+ phraseRe: new RegExp(String.raw`^\s*(${pattern})\s*#?\s*([\dA-Za-z][\dA-Za-z-]*)\s*$`, "i"),
55
+ }))
56
+
57
+ /**
58
+ * True when `input` is a USPS PO-box designator without its box identifier. This is useful to consumers that compose a
59
+ * phrase and need to distinguish USPS vocabulary from their own locale-specific aliases (for example, the corpus's
60
+ * `POB` training variant).
61
+ */
62
+ export function isUSPoBoxDesignator(input: unknown): input is string {
63
+ return typeof input === "string" && PO_BOX_MATCHERS.some(({ designatorRe }) => designatorRe.test(input))
64
+ }
39
65
 
40
66
  /**
41
67
  * Type-predicate: does the input look like a standalone PO Box address? Case-insensitive and tolerant of "P.O. Box",
@@ -43,7 +69,7 @@ const PO_BOX_RE =
43
69
  * all-caps "PO BOX 123".)
44
70
  */
45
71
  export function isPOBox(input: unknown): boolean {
46
- return typeof input === "string" && PO_BOX_RE.test(input)
72
+ return matchPOBox(input) !== null
47
73
  }
48
74
 
49
75
  /**
@@ -67,13 +93,14 @@ export interface PoBoxMatch {
67
93
  */
68
94
  export function matchPOBox(input: unknown): PoBoxMatch | null {
69
95
  if (typeof input !== "string") return null
70
- const m = PO_BOX_RE.exec(input)
71
96
 
72
- if (!m) return null
73
- const matched = (m[1] ?? m[2] ?? m[3] ?? m[4] ?? m[5] ?? m[6] ?? m[7] ?? "").trim()
74
- const id = m[8]!
97
+ for (const { phraseRe } of PO_BOX_MATCHERS) {
98
+ const match = phraseRe.exec(input)
99
+
100
+ if (match) return { matched: match[1]!.trim(), id: match[2]! }
101
+ }
75
102
 
76
- return { matched, id }
103
+ return null
77
104
  }
78
105
 
79
106
  /**
@@ -10,7 +10,7 @@
10
10
  * @see {@link https://pe.usps.com/text/pub28/28apc_002.htm USPS Pub 28 Appendix C1}
11
11
  */
12
12
 
13
- import { matchCase } from "#us/street-suffix"
13
+ import { matchCase } from "#us/street/suffix"
14
14
 
15
15
  /**
16
16
  * The 8 directional abbreviations accepted by the USPS. The USPS prefers the abbreviation over the fully-spelled-out
@@ -24,7 +24,7 @@
24
24
  * @see {@link https://pe.usps.com/text/pub28/28apc_002.htm USPS Street Suffix Abbreviations}
25
25
  */
26
26
 
27
- import streetSuffixData from "./street-suffix.json" with { type: "json" }
27
+ import streetSuffixData from "../street-suffix.json" with { type: "json" }
28
28
 
29
29
  /**
30
30
  * Canonical USPS street suffix → list of recognized variants. The first variant in each list is the preferred USPS
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Render a `ComponentTag`-keyed dict into a country-localized string — the inverse of the parser.
7
+ *
8
+ * The order lives in `@mailwoman/codex/address-layouts`, as data. This module is the public surface over
9
+ * {@linkcode renderAddress}: {@linkcode formatAddress} is the join, and {@linkcode formatAddressRow} is the join plus
10
+ * the tags the layout printed, which is what every corpus adapter actually wants.
11
+ *
12
+ * IT USED TO WRAP A THIRD-PARTY ENGINE, and 229 of this file's 438 lines existed to work around templates written
13
+ * against OpenStreetMap's tag vocabulary rather than this project's: a pass that parsed 295 mustache templates at
14
+ * module load to discover which of them could render a sub-locality, a second that spliced a missing line back in
15
+ * afterwards, a third that removed a connector the template wrote between two slots when one was empty, and a
16
+ * translation layer between the two vocabularies. Owning the layouts deletes all four — a layout that declares a
17
+ * `dependent_locality` slot needs no interrogation about whether it has one, and a line assembled from present values
18
+ * never writes a connector around an absent one.
19
+ */
20
+ import { type ComponentDict } from "#address/render";
21
+ import type { ComponentTag } from "#component";
22
+ export type { ComponentDict } from "#address/render";
23
+ /**
24
+ * Options accepted by {@linkcode formatAddress} and {@linkcode formatAddressRow}.
25
+ */
26
+ export interface FormatAddressOptions {
27
+ /**
28
+ * Replace the layout's line breaks with this separator. Default `"\n"`: the envelope form.
29
+ */
30
+ separator?: string;
31
+ /**
32
+ * Join the lines the way the COUNTRY does, for the single-line form a query or a corpus row takes — `", "` for most,
33
+ * `" "` for Japan and Korea, and nothing at all for the Chinese-script systems, whose admin run is unseparated.
34
+ *
35
+ * It is an option rather than each caller's literal because the literal is wrong outside the anglophone systems:
36
+ * joining Japan's lines with a comma gives `1-9-1, 丸の内, 千代田区, 東京都 100-0005`, which is the romanized convention
37
+ * printed backwards. `separator` wins when both are given.
38
+ */
39
+ singleLine?: boolean;
40
+ }
41
+ /**
42
+ * Render a component dict into an idiomatic per-country address string.
43
+ *
44
+ * Returns an empty string when the dict is empty, and when no layout names `country` — 55 of the 252 shipped country
45
+ * records carry no usable skeleton, and answering nothing for one of those reports absence rather than inventing an
46
+ * order. Throws nothing; a partial dict degrades to the parts the layout can print.
47
+ */
48
+ export declare function formatAddress(components: ComponentDict, country: string, opts?: FormatAddressOptions): string;
49
+ /**
50
+ * A rendered address and the components that survived the render.
51
+ */
52
+ export interface AddressRow {
53
+ /**
54
+ * The rendered string.
55
+ */
56
+ readonly raw: string;
57
+ /**
58
+ * The subset of the input dict the layout PRINTED, with the caller's original values. This is the half a corpus row
59
+ * needs: a label whose text is not in `raw` cannot be aligned against it.
60
+ */
61
+ readonly components: ComponentDict;
62
+ /**
63
+ * Tags the dict carried a value for that the layout has no slot for, NAMED rather than silently dropped. France
64
+ * absorbing a region into its postcode line is the common case.
65
+ */
66
+ readonly unplaced: readonly ComponentTag[];
67
+ }
68
+ /**
69
+ * Render `components` for `country` and report what the layout printed, in one pass.
70
+ *
71
+ * Returns null when nothing rendered — an empty dict, a country with no layout, or a dict whose every value falls in a
72
+ * slot this country omits. Every corpus adapter asked both questions and paid for two renders to get them, then
73
+ * recovered the alignment by searching the output string for each value; that search cannot tell a component the layout
74
+ * dropped from one whose value happens to sit inside another — `Paris` inside `Rue de Paris`. The render knows, so the
75
+ * answer is read rather than inferred.
76
+ */
77
+ export declare function formatAddressRow(components: ComponentDict, country: string, opts?: FormatAddressOptions): AddressRow | null;
78
+ /**
79
+ * Which of `components` occur verbatim in `raw`, case- and whitespace-insensitively.
80
+ *
81
+ * This is a question about a string somebody else built — a committed golden fixture, a source's own address line — and
82
+ * it is the WEAKER of the two reconciliations: a substring test cannot tell a component the renderer dropped from one
83
+ * whose value happens to sit inside another. Anything rendered through a layout should read
84
+ * {@linkcode formatAddressRow}'s `components` instead, which the render knows rather than infers.
85
+ */
86
+ export declare function componentsPresentIn(components: ComponentDict, raw: string): ComponentDict;
87
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../lib/address/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB;AAQD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,MAAM,CAEjH;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAA;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC/B,UAAU,EAAE,aAAa,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,oBAAyB,GAC7B,UAAU,GAAG,IAAI,CAwBnB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAazF"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Render a `ComponentTag`-keyed dict into a country-localized string — the inverse of the parser.
7
+ *
8
+ * The order lives in `@mailwoman/codex/address-layouts`, as data. This module is the public surface over
9
+ * {@linkcode renderAddress}: {@linkcode formatAddress} is the join, and {@linkcode formatAddressRow} is the join plus
10
+ * the tags the layout printed, which is what every corpus adapter actually wants.
11
+ *
12
+ * IT USED TO WRAP A THIRD-PARTY ENGINE, and 229 of this file's 438 lines existed to work around templates written
13
+ * against OpenStreetMap's tag vocabulary rather than this project's: a pass that parsed 295 mustache templates at
14
+ * module load to discover which of them could render a sub-locality, a second that spliced a missing line back in
15
+ * afterwards, a third that removed a connector the template wrote between two slots when one was empty, and a
16
+ * translation layer between the two vocabularies. Owning the layouts deletes all four — a layout that declares a
17
+ * `dependent_locality` slot needs no interrogation about whether it has one, and a line assembled from present values
18
+ * never writes a connector around an absent one.
19
+ */
20
+ import { layoutForCountry, lineJoinForCountry } from "#address/layouts/index";
21
+ import { joinRendering, renderAddress } from "#address/render";
22
+ function separatorFor(country, opts) {
23
+ if (opts.separator !== undefined)
24
+ return opts.separator;
25
+ return opts.singleLine ? lineJoinForCountry(country) : "\n";
26
+ }
27
+ /**
28
+ * Render a component dict into an idiomatic per-country address string.
29
+ *
30
+ * Returns an empty string when the dict is empty, and when no layout names `country` — 55 of the 252 shipped country
31
+ * records carry no usable skeleton, and answering nothing for one of those reports absence rather than inventing an
32
+ * order. Throws nothing; a partial dict degrades to the parts the layout can print.
33
+ */
34
+ export function formatAddress(components, country, opts = {}) {
35
+ return formatAddressRow(components, country, opts)?.raw ?? "";
36
+ }
37
+ /**
38
+ * Render `components` for `country` and report what the layout printed, in one pass.
39
+ *
40
+ * Returns null when nothing rendered — an empty dict, a country with no layout, or a dict whose every value falls in a
41
+ * slot this country omits. Every corpus adapter asked both questions and paid for two renders to get them, then
42
+ * recovered the alignment by searching the output string for each value; that search cannot tell a component the layout
43
+ * dropped from one whose value happens to sit inside another — `Paris` inside `Rue de Paris`. The render knows, so the
44
+ * answer is read rather than inferred.
45
+ */
46
+ export function formatAddressRow(components, country, opts = {}) {
47
+ const layout = layoutForCountry(country);
48
+ if (!layout)
49
+ return null;
50
+ const rendering = renderAddress(layout, components);
51
+ if (!rendering.placed.length)
52
+ return null;
53
+ const raw = joinRendering(rendering, separatorFor(country, opts));
54
+ if (!raw)
55
+ return null;
56
+ const placed = {};
57
+ for (const tag of rendering.placed) {
58
+ const value = components[tag];
59
+ if (value) {
60
+ placed[tag] = value;
61
+ }
62
+ }
63
+ return { raw, components: placed, unplaced: rendering.unplaced };
64
+ }
65
+ /**
66
+ * Which of `components` occur verbatim in `raw`, case- and whitespace-insensitively.
67
+ *
68
+ * This is a question about a string somebody else built — a committed golden fixture, a source's own address line — and
69
+ * it is the WEAKER of the two reconciliations: a substring test cannot tell a component the renderer dropped from one
70
+ * whose value happens to sit inside another. Anything rendered through a layout should read
71
+ * {@linkcode formatAddressRow}'s `components` instead, which the render knows rather than infers.
72
+ */
73
+ export function componentsPresentIn(components, raw) {
74
+ const haystack = raw.toLowerCase().replaceAll(/\s+/g, " ");
75
+ const out = {};
76
+ for (const [tag, value] of Object.entries(components)) {
77
+ if (!value)
78
+ continue;
79
+ if (haystack.includes(value.toLowerCase().replaceAll(/\s+/g, " "))) {
80
+ out[tag] = value;
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../lib/address/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC7E,OAAO,EAAE,aAAa,EAAE,aAAa,EAAsB,MAAM,iBAAiB,CAAA;AAyBlF,SAAS,YAAY,CAAC,OAAe,EAAE,IAA0B;IAChE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAA;IAEvD,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,UAAyB,EAAE,OAAe,EAAE,IAAI,GAAyB,EAAE;IACxG,OAAO,gBAAgB,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,CAAA;AAC9D,CAAC;AAsBD;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC/B,UAAyB,EACzB,OAAe,EACf,IAAI,GAAyB,EAAE;IAE/B,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAExC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAExB,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAEnD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAEzC,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IAEjE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IAErB,MAAM,MAAM,GAAkB,EAAE,CAAA;IAEhC,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAA;QAE7B,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACpB,CAAC;IACF,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAA;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAyB,EAAE,GAAW;IACzE,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1D,MAAM,GAAG,GAAkB,EAAE,CAAA;IAE7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,KAAK;YAAE,SAAQ;QAEpB,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,GAAmB,CAAC,GAAG,KAAK,CAAA;QACjC,CAAC;IACF,CAAC;IAED,OAAO,GAAG,CAAA;AACX,CAAC"}