@mailwoman/codex 9.2.0 → 9.4.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 (295) hide show
  1. package/README.md +4 -4
  2. package/{address-system-conventions.ts → lib/address-system-conventions.ts} +9 -9
  3. package/{au → lib/au}/index.ts +4 -4
  4. package/{ca → lib/ca}/index.ts +3 -3
  5. package/{ca → lib/ca}/postal-code.ts +1 -1
  6. package/{ca → lib/ca}/province.ts +1 -1
  7. package/{ca → lib/ca}/street-type.ts +10 -12
  8. package/{country → lib/country}/codes.ts +1 -1
  9. package/{country → lib/country}/country.ts +43 -14
  10. package/lib/country/index.ts +17 -0
  11. package/{country → lib/country}/reference.ts +2 -2
  12. package/{country → lib/country}/subdivision.ts +4 -4
  13. package/{de → lib/de}/index.ts +3 -3
  14. package/{es → lib/es}/codigo-postal.ts +22 -4
  15. package/{es → lib/es}/index.ts +1 -1
  16. package/{fr → lib/fr}/cedex.ts +1 -1
  17. package/{fr → lib/fr}/code-postal.ts +2 -2
  18. package/{fr → lib/fr}/departement.ts +5 -2
  19. package/{fr → lib/fr}/index.ts +5 -5
  20. package/{fr → lib/fr}/region.ts +1 -1
  21. package/{fr → lib/fr}/voie.ts +5 -13
  22. package/{gb → lib/gb}/country.ts +2 -10
  23. package/{gb → lib/gb}/index.ts +4 -4
  24. package/{gb → lib/gb}/postcode-area.ts +1 -1
  25. package/{gb → lib/gb}/street-type.ts +1 -1
  26. package/{index.ts → lib/index.ts} +13 -12
  27. package/{it → lib/it}/cap.ts +21 -3
  28. package/{it → lib/it}/index.ts +1 -1
  29. package/{jp → lib/jp}/index.ts +4 -3
  30. package/lib/jp/municipality-register.ts +62 -0
  31. package/{jp → lib/jp}/prefecture.ts +7 -5
  32. package/lib/kr/index.ts +10 -0
  33. package/lib/kr/sigungu-register.ts +294 -0
  34. package/{normalize.ts → lib/normalize.ts} +15 -1
  35. package/{nz → lib/nz}/delivery-service.ts +1 -1
  36. package/{nz → lib/nz}/index.ts +2 -2
  37. package/{postcode-systems.ts → lib/postcode-systems.ts} +40 -24
  38. package/lib/register-completion.ts +24 -0
  39. package/{tools → lib/tools}/build-country-surface-lexicon.ts +10 -9
  40. package/{tools → lib/tools}/export-country-surfaces.ts +8 -8
  41. package/{tools → lib/tools}/generate-country-population.ts +4 -5
  42. package/{tools → lib/tools}/generate-country-reference.ts +4 -5
  43. package/{tools → lib/tools}/generate-official-languages.ts +7 -9
  44. package/{tools → lib/tools}/index.ts +3 -3
  45. package/{us → lib/us}/index.ts +8 -8
  46. package/{us → lib/us}/po-box.ts +1 -1
  47. package/{us → lib/us}/street-directional.ts +2 -2
  48. package/{us → lib/us}/street-suffix.ts +1 -1
  49. package/{us → lib/us}/unit-designator.ts +1 -1
  50. package/{us → lib/us}/zipcode.ts +1 -1
  51. package/out/address-system-conventions.d.ts +1 -1
  52. package/out/address-system-conventions.d.ts.map +1 -1
  53. package/out/address-system-conventions.js +8 -8
  54. package/out/address-system-conventions.js.map +1 -1
  55. package/out/au/delivery-service.d.ts.map +1 -1
  56. package/out/au/delivery-service.js.map +1 -1
  57. package/out/au/index.d.ts +4 -4
  58. package/out/au/index.d.ts.map +1 -1
  59. package/out/au/index.js +4 -4
  60. package/out/au/index.js.map +1 -1
  61. package/out/au/level-designator.d.ts.map +1 -1
  62. package/out/au/level-designator.js.map +1 -1
  63. package/out/au/postcode.d.ts.map +1 -1
  64. package/out/au/postcode.js.map +1 -1
  65. package/out/au/state.d.ts.map +1 -1
  66. package/out/au/state.js.map +1 -1
  67. package/out/ca/index.d.ts +3 -3
  68. package/out/ca/index.d.ts.map +1 -1
  69. package/out/ca/index.js +3 -3
  70. package/out/ca/index.js.map +1 -1
  71. package/out/ca/postal-code.d.ts +1 -1
  72. package/out/ca/postal-code.d.ts.map +1 -1
  73. package/out/ca/postal-code.js.map +1 -1
  74. package/out/ca/province.d.ts.map +1 -1
  75. package/out/ca/province.js +1 -1
  76. package/out/ca/province.js.map +1 -1
  77. package/out/ca/street-type.d.ts.map +1 -1
  78. package/out/ca/street-type.js +9 -12
  79. package/out/ca/street-type.js.map +1 -1
  80. package/out/country/codes.d.ts.map +1 -1
  81. package/out/country/codes.js.map +1 -1
  82. package/out/country/country.d.ts +13 -11
  83. package/out/country/country.d.ts.map +1 -1
  84. package/out/country/country.js +35 -13
  85. package/out/country/country.js.map +1 -1
  86. package/out/country/display-names.d.ts.map +1 -1
  87. package/out/country/display-names.js.map +1 -1
  88. package/out/country/index.d.ts +10 -10
  89. package/out/country/index.d.ts.map +1 -1
  90. package/out/country/index.js +10 -10
  91. package/out/country/index.js.map +1 -1
  92. package/out/country/names.d.ts.map +1 -1
  93. package/out/country/names.js.map +1 -1
  94. package/out/country/official-languages.d.ts.map +1 -1
  95. package/out/country/official-languages.js.map +1 -1
  96. package/out/country/population.d.ts.map +1 -1
  97. package/out/country/population.js.map +1 -1
  98. package/out/country/reference-data.d.ts.map +1 -1
  99. package/out/country/reference-data.js.map +1 -1
  100. package/out/country/reference.d.ts +1 -1
  101. package/out/country/reference.d.ts.map +1 -1
  102. package/out/country/reference.js +2 -2
  103. package/out/country/reference.js.map +1 -1
  104. package/out/country/subdivision.d.ts.map +1 -1
  105. package/out/country/subdivision.js +4 -4
  106. package/out/country/subdivision.js.map +1 -1
  107. package/out/de/bundesland.d.ts.map +1 -1
  108. package/out/de/bundesland.js.map +1 -1
  109. package/out/de/index.d.ts +3 -3
  110. package/out/de/index.d.ts.map +1 -1
  111. package/out/de/index.js +3 -3
  112. package/out/de/index.js.map +1 -1
  113. package/out/de/postleitzahl.d.ts.map +1 -1
  114. package/out/de/postleitzahl.js.map +1 -1
  115. package/out/de/street-type.d.ts.map +1 -1
  116. package/out/de/street-type.js.map +1 -1
  117. package/out/es/codigo-postal.d.ts +11 -0
  118. package/out/es/codigo-postal.d.ts.map +1 -1
  119. package/out/es/codigo-postal.js +20 -3
  120. package/out/es/codigo-postal.js.map +1 -1
  121. package/out/es/index.d.ts +1 -1
  122. package/out/es/index.d.ts.map +1 -1
  123. package/out/es/index.js +1 -1
  124. package/out/es/index.js.map +1 -1
  125. package/out/fr/cedex.d.ts +1 -1
  126. package/out/fr/cedex.d.ts.map +1 -1
  127. package/out/fr/cedex.js +1 -1
  128. package/out/fr/cedex.js.map +1 -1
  129. package/out/fr/code-postal.d.ts +2 -2
  130. package/out/fr/code-postal.d.ts.map +1 -1
  131. package/out/fr/code-postal.js +2 -2
  132. package/out/fr/code-postal.js.map +1 -1
  133. package/out/fr/departement.d.ts +2 -2
  134. package/out/fr/departement.d.ts.map +1 -1
  135. package/out/fr/departement.js +3 -0
  136. package/out/fr/departement.js.map +1 -1
  137. package/out/fr/index.d.ts +5 -5
  138. package/out/fr/index.d.ts.map +1 -1
  139. package/out/fr/index.js +5 -5
  140. package/out/fr/index.js.map +1 -1
  141. package/out/fr/region.d.ts.map +1 -1
  142. package/out/fr/region.js +1 -1
  143. package/out/fr/region.js.map +1 -1
  144. package/out/fr/voie.d.ts.map +1 -1
  145. package/out/fr/voie.js +4 -11
  146. package/out/fr/voie.js.map +1 -1
  147. package/out/gb/country.d.ts.map +1 -1
  148. package/out/gb/country.js +1 -9
  149. package/out/gb/country.js.map +1 -1
  150. package/out/gb/index.d.ts +4 -4
  151. package/out/gb/index.d.ts.map +1 -1
  152. package/out/gb/index.js +4 -4
  153. package/out/gb/index.js.map +1 -1
  154. package/out/gb/postcode-area.d.ts +1 -1
  155. package/out/gb/postcode-area.d.ts.map +1 -1
  156. package/out/gb/postcode-area.js.map +1 -1
  157. package/out/gb/postcode.d.ts.map +1 -1
  158. package/out/gb/postcode.js.map +1 -1
  159. package/out/gb/street-type.d.ts +1 -1
  160. package/out/gb/street-type.d.ts.map +1 -1
  161. package/out/gb/street-type.js +1 -1
  162. package/out/gb/street-type.js.map +1 -1
  163. package/out/index.d.ts +12 -12
  164. package/out/index.d.ts.map +1 -1
  165. package/out/index.js +12 -12
  166. package/out/index.js.map +1 -1
  167. package/out/it/cap.d.ts +11 -0
  168. package/out/it/cap.d.ts.map +1 -1
  169. package/out/it/cap.js +19 -2
  170. package/out/it/cap.js.map +1 -1
  171. package/out/it/index.d.ts +1 -1
  172. package/out/it/index.d.ts.map +1 -1
  173. package/out/it/index.js +1 -1
  174. package/out/it/index.js.map +1 -1
  175. package/out/jp/address-unit.d.ts.map +1 -1
  176. package/out/jp/address-unit.js.map +1 -1
  177. package/out/jp/index.d.ts +4 -3
  178. package/out/jp/index.d.ts.map +1 -1
  179. package/out/jp/index.js +4 -3
  180. package/out/jp/index.js.map +1 -1
  181. package/out/jp/municipality-register.d.ts +48 -0
  182. package/out/jp/municipality-register.d.ts.map +1 -0
  183. package/out/jp/municipality-register.js +41 -0
  184. package/out/jp/municipality-register.js.map +1 -0
  185. package/out/jp/postal-code.d.ts.map +1 -1
  186. package/out/jp/postal-code.js.map +1 -1
  187. package/out/jp/prefecture.d.ts +4 -1
  188. package/out/jp/prefecture.d.ts.map +1 -1
  189. package/out/jp/prefecture.js +6 -5
  190. package/out/jp/prefecture.js.map +1 -1
  191. package/out/kr/index.d.ts +10 -0
  192. package/out/kr/index.d.ts.map +1 -0
  193. package/out/kr/index.js +10 -0
  194. package/out/kr/index.js.map +1 -0
  195. package/out/kr/sigungu-register.d.ts +37 -0
  196. package/out/kr/sigungu-register.d.ts.map +1 -0
  197. package/out/kr/sigungu-register.js +283 -0
  198. package/out/kr/sigungu-register.js.map +1 -0
  199. package/out/level-semantics.d.ts.map +1 -1
  200. package/out/level-semantics.js.map +1 -1
  201. package/out/normalize.d.ts +8 -0
  202. package/out/normalize.d.ts.map +1 -1
  203. package/out/normalize.js +14 -1
  204. package/out/normalize.js.map +1 -1
  205. package/out/nz/delivery-service.d.ts +1 -1
  206. package/out/nz/delivery-service.d.ts.map +1 -1
  207. package/out/nz/delivery-service.js +1 -1
  208. package/out/nz/delivery-service.js.map +1 -1
  209. package/out/nz/index.d.ts +2 -2
  210. package/out/nz/index.d.ts.map +1 -1
  211. package/out/nz/index.js +2 -2
  212. package/out/nz/index.js.map +1 -1
  213. package/out/nz/postcode.d.ts.map +1 -1
  214. package/out/nz/postcode.js.map +1 -1
  215. package/out/postcode-systems.d.ts +26 -15
  216. package/out/postcode-systems.d.ts.map +1 -1
  217. package/out/postcode-systems.js +38 -23
  218. package/out/postcode-systems.js.map +1 -1
  219. package/out/register-completion.d.ts +14 -0
  220. package/out/register-completion.d.ts.map +1 -0
  221. package/out/register-completion.js +23 -0
  222. package/out/register-completion.js.map +1 -0
  223. package/out/tools/build-country-surface-lexicon.d.ts +1 -1
  224. package/out/tools/build-country-surface-lexicon.d.ts.map +1 -1
  225. package/out/tools/build-country-surface-lexicon.js +10 -9
  226. package/out/tools/build-country-surface-lexicon.js.map +1 -1
  227. package/out/tools/export-country-surfaces.d.ts +1 -1
  228. package/out/tools/export-country-surfaces.d.ts.map +1 -1
  229. package/out/tools/export-country-surfaces.js +7 -7
  230. package/out/tools/export-country-surfaces.js.map +1 -1
  231. package/out/tools/generate-country-population.d.ts.map +1 -1
  232. package/out/tools/generate-country-population.js +4 -4
  233. package/out/tools/generate-country-population.js.map +1 -1
  234. package/out/tools/generate-country-reference.d.ts.map +1 -1
  235. package/out/tools/generate-country-reference.js +4 -4
  236. package/out/tools/generate-country-reference.js.map +1 -1
  237. package/out/tools/generate-official-languages.d.ts.map +1 -1
  238. package/out/tools/generate-official-languages.js +7 -7
  239. package/out/tools/generate-official-languages.js.map +1 -1
  240. package/out/tools/index.d.ts +3 -3
  241. package/out/tools/index.d.ts.map +1 -1
  242. package/out/tools/index.js +3 -3
  243. package/out/tools/index.js.map +1 -1
  244. package/out/us/floor-designator.d.ts.map +1 -1
  245. package/out/us/floor-designator.js.map +1 -1
  246. package/out/us/index.d.ts +8 -8
  247. package/out/us/index.d.ts.map +1 -1
  248. package/out/us/index.js +8 -8
  249. package/out/us/index.js.map +1 -1
  250. package/out/us/military-address.d.ts.map +1 -1
  251. package/out/us/military-address.js.map +1 -1
  252. package/out/us/po-box.d.ts +1 -1
  253. package/out/us/po-box.d.ts.map +1 -1
  254. package/out/us/po-box.js +1 -1
  255. package/out/us/po-box.js.map +1 -1
  256. package/out/us/state.d.ts.map +1 -1
  257. package/out/us/state.js.map +1 -1
  258. package/out/us/street-directional.d.ts.map +1 -1
  259. package/out/us/street-directional.js +2 -2
  260. package/out/us/street-directional.js.map +1 -1
  261. package/out/us/street-suffix.d.ts +1 -1
  262. package/out/us/street-suffix.d.ts.map +1 -1
  263. package/out/us/street-suffix.js +1 -1
  264. package/out/us/street-suffix.js.map +1 -1
  265. package/out/us/unit-designator.d.ts +1 -1
  266. package/out/us/unit-designator.d.ts.map +1 -1
  267. package/out/us/unit-designator.js +1 -1
  268. package/out/us/unit-designator.js.map +1 -1
  269. package/out/us/zipcode.d.ts +1 -1
  270. package/out/us/zipcode.d.ts.map +1 -1
  271. package/out/us/zipcode.js +1 -1
  272. package/out/us/zipcode.js.map +1 -1
  273. package/package.json +78 -267
  274. package/country/index.ts +0 -17
  275. /package/{au → lib/au}/delivery-service.ts +0 -0
  276. /package/{au → lib/au}/level-designator.ts +0 -0
  277. /package/{au → lib/au}/postcode.ts +0 -0
  278. /package/{au → lib/au}/state.ts +0 -0
  279. /package/{country → lib/country}/display-names.ts +0 -0
  280. /package/{country → lib/country}/names.ts +0 -0
  281. /package/{country → lib/country}/official-languages.ts +0 -0
  282. /package/{country → lib/country}/population.ts +0 -0
  283. /package/{country → lib/country}/reference-data.ts +0 -0
  284. /package/{de → lib/de}/bundesland.ts +0 -0
  285. /package/{de → lib/de}/postleitzahl.ts +0 -0
  286. /package/{de → lib/de}/street-type.ts +0 -0
  287. /package/{gb → lib/gb}/postcode.ts +0 -0
  288. /package/{jp → lib/jp}/address-unit.ts +0 -0
  289. /package/{jp → lib/jp}/postal-code.ts +0 -0
  290. /package/{level-semantics.ts → lib/level-semantics.ts} +0 -0
  291. /package/{nz → lib/nz}/postcode.ts +0 -0
  292. /package/{us → lib/us}/floor-designator.ts +0 -0
  293. /package/{us → lib/us}/military-address.ts +0 -0
  294. /package/{us → lib/us}/state.ts +0 -0
  295. /package/{us → lib/us}/street-suffix.json +0 -0
@@ -30,10 +30,28 @@ export type CAP = Tagged<string, "CAP">
30
30
  export const CAP_PATTERN = /^\d{5}$/
31
31
 
32
32
  /**
33
- * Narrow a string to a {@link CAP}, or `null` when it is not one.
33
+ * Normalize a CAP surface form to the bare five digits: trim surrounding whitespace (`" 00184 "` `"00184"`). Returns
34
+ * null when the result is not a five-digit code.
34
35
  */
35
- export function parseCAP(input: string): CAP | null {
36
- const s = input.trim()
36
+ export function normalizeCAP(raw: unknown): CAP | null {
37
+ if (typeof raw !== "string") return null
38
+ const s = raw.trim()
37
39
 
38
40
  return CAP_PATTERN.test(s) ? (s as CAP) : null
39
41
  }
42
+
43
+ /**
44
+ * Type-predicate for a (normalized) Italian CAP.
45
+ */
46
+ export function isCAP(input: unknown): input is CAP {
47
+ return typeof input === "string" && CAP_PATTERN.test(input)
48
+ }
49
+
50
+ /**
51
+ * Narrow a string to a {@link CAP}, or `null` when it is not one.
52
+ *
53
+ * @deprecated Use {@link normalizeCAP}.
54
+ */
55
+ export function parseCAP(input: string): CAP | null {
56
+ return normalizeCAP(input)
57
+ }
@@ -6,4 +6,4 @@
6
6
  * Italian (IT) address-system reference data.
7
7
  */
8
8
 
9
- export * from "./cap.ts"
9
+ export * from "#it/cap"
@@ -8,6 +8,7 @@
8
8
  * in for the street names Japan does not use.
9
9
  */
10
10
 
11
- export * from "./address-unit.ts"
12
- export * from "./postal-code.ts"
13
- export * from "./prefecture.ts"
11
+ export * from "#jp/address-unit"
12
+ export * from "#jp/municipality-register"
13
+ export * from "#jp/postal-code"
14
+ export * from "#jp/prefecture"
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The towns whose own name carries the city marker 市 (shi) before the town suffix 町 / 村 (chō / son).
7
+ *
8
+ * A Japanese municipality span closes at 市 in the ordinary case (`富山市`, `神戸市西区`), and a character model
9
+ * learns that boundary from 1,700 cities. A town whose NAME contains 市 defeats it: `中新川郡上市町` reads as the
10
+ * city `上市` plus a district beginning with 町. Over the 1,892 municipalities in Japan Post's KEN_ALL list the
11
+ * shape has six members, so the boundary is stated here rather than learned — a positive attestation from the
12
+ * postal register, consumed after decode by `@mailwoman/neural`'s JP municipality repair. Two of the six (`上市町`,
13
+ * `下市町`) put 市 immediately before the suffix, the boundary the model closes at; the other four are listed for the same
14
+ * repair so an early close inside them is also repaired.
15
+ */
16
+
17
+ import { completeFromRegister } from "#register-completion"
18
+
19
+ /**
20
+ * One town from Japan Post's KEN_ALL municipality list whose own name contains 市.
21
+ */
22
+ export interface JapaneseInnerShiTown {
23
+ /**
24
+ * The prefecture, as written (`富山県`).
25
+ */
26
+ prefecture: string
27
+ /**
28
+ * The county (郡) the town belongs to, as the postal form writes it before the town.
29
+ */
30
+ county: string
31
+ /**
32
+ * The town's own name with its suffix (`上市町`).
33
+ */
34
+ town: string
35
+ }
36
+
37
+ /**
38
+ * The six towns, from KEN_ALL (1,892 municipalities). Ordered by prefecture code.
39
+ */
40
+ export const JP_INNER_SHI_TOWNS: readonly JapaneseInnerShiTown[] = [
41
+ { prefecture: "北海道", county: "余市郡", town: "余市町" },
42
+ { prefecture: "栃木県", county: "芳賀郡", town: "市貝町" },
43
+ { prefecture: "富山県", county: "中新川郡", town: "上市町" },
44
+ { prefecture: "山梨県", county: "西八代郡", town: "市川三郷町" },
45
+ { prefecture: "奈良県", county: "吉野郡", town: "下市町" },
46
+ { prefecture: "兵庫県", county: "神崎郡", town: "市川町" },
47
+ ]
48
+
49
+ /**
50
+ * Every surface a municipality span may legitimately close on for these towns: the postal form with the county
51
+ * (`中新川郡上市町`) and the bare town (`上市町`).
52
+ */
53
+ export const JP_INNER_SHI_TOWN_NAMES: readonly string[] = JP_INNER_SHI_TOWNS.flatMap((t) => [t.county + t.town, t.town])
54
+
55
+ /**
56
+ * The characters a municipality surface must absorb from what follows it to become one of the register's names, or null
57
+ * when no name extends it. `following` is the text after the surface; the answer is a prefix of it. A surface that
58
+ * already IS a register name answers null: nothing to absorb.
59
+ */
60
+ export function jpMunicipalityCompletion(surface: string, following: string): string | null {
61
+ return completeFromRegister(JP_INNER_SHI_TOWN_NAMES, surface, following)
62
+ }
@@ -6,7 +6,10 @@
6
6
  * The 47 Japanese prefectures (都道府県, todōfuken), keyed by their ISO 3166-2:JP code — the two-digit
7
7
  * numeric string `"01"`..`"47"` standing in for `JP-01`..`JP-47`.
8
8
  *
9
- * The contrast with `fr/region.ts`, `de/bundesland.ts`, and `us/state.ts` is in the name itself.
9
+ * Compared to `fr/region.ts`, `de/bundesland.ts`, and `us/state.ts`,
10
+ * this is a more complex case because the top-level admin unit comes in four legally distinct flavours,
11
+ * and the prefecture name is written on the address line (not inferred from postcode).
12
+ *
10
13
  * "都道府県" is four kanji because the top-level admin unit comes in four legally distinct flavours,
11
14
  * even though all 47 are peers in practice:
12
15
  *
@@ -23,6 +26,8 @@
23
26
  * `postal-code.ts` for why the postcode is nonetheless the single most reliable anchor.
24
27
  */
25
28
 
29
+ import { foldToken } from "#normalize"
30
+
26
31
  /**
27
32
  * A to/dō/fu/ken classification of the top-level admin unit.
28
33
  */
@@ -130,10 +135,7 @@ export function isJapanesePrefectureCode(input: unknown): input is JapanesePrefe
130
135
  * them. We never strip from an unseparated bare name, so `kyoto` stays `kyoto`.
131
136
  */
132
137
  function foldRomaji(s: string): string {
133
- const lowered = s
134
- .toLowerCase()
135
- .normalize("NFD")
136
- .replaceAll(/[\u0300-\u036F]/g, "")
138
+ const lowered = foldToken(s)
137
139
 
138
140
  // Strip an appended type-suffix only when a separator (hyphen / space / middle dot) precedes it.
139
141
  const desuffixed = lowered.replace(/[-\s·][\s]*(to|do|fu|ken)$/, "")
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The Korean address system as the road-name address register (도로명주소) writes it: the 시군구 register the
7
+ * decode-time boundary repair reads.
8
+ */
9
+
10
+ export * from "#kr/sigungu-register"
@@ -0,0 +1,294 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The Korean 시군구 (si / gun / gu: the second administrative tier) as the road-name address register writes them,
7
+ * each under its 시/도 (the first tier). 249 pairs over 226 distinct names; a compound city is
8
+ * one name (`성남시분당구`), the way the register keys it. 세종특별자치시 has no second tier and is absent.
9
+ *
10
+ * Why a register: a character model closes a `subregion` span where the training set taught it names end, and a
11
+ * name it never saw (`해운대구`, held out of the training corpus by the board's design and unseen by the shipped
12
+ * model for the same reason) closes early, at `해` or `해운대`. The register states every boundary outright, so the
13
+ * decode-time repair in `@mailwoman/neural` extends a run to the one name the following characters spell.
14
+ *
15
+ * Source: 행정안전부 (Ministry of the Interior and Safety), 도로명주소 (juso.go.kr), 공공누리 제1유형 (KOGL Type 1):
16
+ * attribution required; commercial use, derivatives and redistribution permitted.
17
+ */
18
+
19
+ import { completeFromRegister } from "#register-completion"
20
+
21
+ /**
22
+ * One second-tier unit under its first-tier region, both as the register writes them.
23
+ */
24
+ export interface KoreanSigungu {
25
+ region: string
26
+ city: string
27
+ }
28
+
29
+ /**
30
+ * The register: every (시/도, 시군구) pair the road-name address register carries, in register order.
31
+ */
32
+ export const KR_SIGUNGU: readonly KoreanSigungu[] = [
33
+ { region: "강원도", city: "강릉시" },
34
+ { region: "강원도", city: "고성군" },
35
+ { region: "강원도", city: "동해시" },
36
+ { region: "강원도", city: "삼척시" },
37
+ { region: "강원도", city: "속초시" },
38
+ { region: "강원도", city: "양구군" },
39
+ { region: "강원도", city: "양양군" },
40
+ { region: "강원도", city: "영월군" },
41
+ { region: "강원도", city: "원주시" },
42
+ { region: "강원도", city: "인제군" },
43
+ { region: "강원도", city: "정선군" },
44
+ { region: "강원도", city: "철원군" },
45
+ { region: "강원도", city: "춘천시" },
46
+ { region: "강원도", city: "태백시" },
47
+ { region: "강원도", city: "평창군" },
48
+ { region: "강원도", city: "홍천군" },
49
+ { region: "강원도", city: "화천군" },
50
+ { region: "강원도", city: "횡성군" },
51
+ { region: "경기도", city: "가평군" },
52
+ { region: "경기도", city: "고양시덕양구" },
53
+ { region: "경기도", city: "고양시일산동구" },
54
+ { region: "경기도", city: "고양시일산서구" },
55
+ { region: "경기도", city: "과천시" },
56
+ { region: "경기도", city: "광명시" },
57
+ { region: "경기도", city: "광주시" },
58
+ { region: "경기도", city: "구리시" },
59
+ { region: "경기도", city: "군포시" },
60
+ { region: "경기도", city: "김포시" },
61
+ { region: "경기도", city: "남양주시" },
62
+ { region: "경기도", city: "동두천시" },
63
+ { region: "경기도", city: "부천시" },
64
+ { region: "경기도", city: "성남시분당구" },
65
+ { region: "경기도", city: "성남시수정구" },
66
+ { region: "경기도", city: "성남시중원구" },
67
+ { region: "경기도", city: "수원시권선구" },
68
+ { region: "경기도", city: "수원시영통구" },
69
+ { region: "경기도", city: "수원시장안구" },
70
+ { region: "경기도", city: "수원시팔달구" },
71
+ { region: "경기도", city: "시흥시" },
72
+ { region: "경기도", city: "안산시단원구" },
73
+ { region: "경기도", city: "안산시상록구" },
74
+ { region: "경기도", city: "안성시" },
75
+ { region: "경기도", city: "안양시동안구" },
76
+ { region: "경기도", city: "안양시만안구" },
77
+ { region: "경기도", city: "양주시" },
78
+ { region: "경기도", city: "양평군" },
79
+ { region: "경기도", city: "여주시" },
80
+ { region: "경기도", city: "연천군" },
81
+ { region: "경기도", city: "오산시" },
82
+ { region: "경기도", city: "용인시기흥구" },
83
+ { region: "경기도", city: "용인시수지구" },
84
+ { region: "경기도", city: "용인시처인구" },
85
+ { region: "경기도", city: "의왕시" },
86
+ { region: "경기도", city: "의정부시" },
87
+ { region: "경기도", city: "이천시" },
88
+ { region: "경기도", city: "파주시" },
89
+ { region: "경기도", city: "평택시" },
90
+ { region: "경기도", city: "포천시" },
91
+ { region: "경기도", city: "하남시" },
92
+ { region: "경기도", city: "화성시" },
93
+ { region: "경상남도", city: "거제시" },
94
+ { region: "경상남도", city: "거창군" },
95
+ { region: "경상남도", city: "고성군" },
96
+ { region: "경상남도", city: "김해시" },
97
+ { region: "경상남도", city: "남해군" },
98
+ { region: "경상남도", city: "밀양시" },
99
+ { region: "경상남도", city: "사천시" },
100
+ { region: "경상남도", city: "산청군" },
101
+ { region: "경상남도", city: "양산시" },
102
+ { region: "경상남도", city: "의령군" },
103
+ { region: "경상남도", city: "진주시" },
104
+ { region: "경상남도", city: "창녕군" },
105
+ { region: "경상남도", city: "창원시마산합포구" },
106
+ { region: "경상남도", city: "창원시마산회원구" },
107
+ { region: "경상남도", city: "창원시성산구" },
108
+ { region: "경상남도", city: "창원시의창구" },
109
+ { region: "경상남도", city: "창원시진해구" },
110
+ { region: "경상남도", city: "통영시" },
111
+ { region: "경상남도", city: "하동군" },
112
+ { region: "경상남도", city: "함안군" },
113
+ { region: "경상남도", city: "함양군" },
114
+ { region: "경상남도", city: "합천군" },
115
+ { region: "경상북도", city: "경산시" },
116
+ { region: "경상북도", city: "경주시" },
117
+ { region: "경상북도", city: "고령군" },
118
+ { region: "경상북도", city: "구미시" },
119
+ { region: "경상북도", city: "군위군" },
120
+ { region: "경상북도", city: "김천시" },
121
+ { region: "경상북도", city: "문경시" },
122
+ { region: "경상북도", city: "봉화군" },
123
+ { region: "경상북도", city: "상주시" },
124
+ { region: "경상북도", city: "성주군" },
125
+ { region: "경상북도", city: "안동시" },
126
+ { region: "경상북도", city: "영덕군" },
127
+ { region: "경상북도", city: "영양군" },
128
+ { region: "경상북도", city: "영주시" },
129
+ { region: "경상북도", city: "영천시" },
130
+ { region: "경상북도", city: "예천군" },
131
+ { region: "경상북도", city: "울릉군" },
132
+ { region: "경상북도", city: "울진군" },
133
+ { region: "경상북도", city: "의성군" },
134
+ { region: "경상북도", city: "청도군" },
135
+ { region: "경상북도", city: "청송군" },
136
+ { region: "경상북도", city: "칠곡군" },
137
+ { region: "경상북도", city: "포항시남구" },
138
+ { region: "경상북도", city: "포항시북구" },
139
+ { region: "광주광역시", city: "광산구" },
140
+ { region: "광주광역시", city: "남구" },
141
+ { region: "광주광역시", city: "동구" },
142
+ { region: "광주광역시", city: "북구" },
143
+ { region: "광주광역시", city: "서구" },
144
+ { region: "대구광역시", city: "남구" },
145
+ { region: "대구광역시", city: "달서구" },
146
+ { region: "대구광역시", city: "달성군" },
147
+ { region: "대구광역시", city: "동구" },
148
+ { region: "대구광역시", city: "북구" },
149
+ { region: "대구광역시", city: "서구" },
150
+ { region: "대구광역시", city: "수성구" },
151
+ { region: "대구광역시", city: "중구" },
152
+ { region: "대전광역시", city: "대덕구" },
153
+ { region: "대전광역시", city: "동구" },
154
+ { region: "대전광역시", city: "서구" },
155
+ { region: "대전광역시", city: "유성구" },
156
+ { region: "대전광역시", city: "중구" },
157
+ { region: "부산광역시", city: "강서구" },
158
+ { region: "부산광역시", city: "금정구" },
159
+ { region: "부산광역시", city: "기장군" },
160
+ { region: "부산광역시", city: "남구" },
161
+ { region: "부산광역시", city: "동구" },
162
+ { region: "부산광역시", city: "동래구" },
163
+ { region: "부산광역시", city: "부산진구" },
164
+ { region: "부산광역시", city: "북구" },
165
+ { region: "부산광역시", city: "사상구" },
166
+ { region: "부산광역시", city: "사하구" },
167
+ { region: "부산광역시", city: "서구" },
168
+ { region: "부산광역시", city: "수영구" },
169
+ { region: "부산광역시", city: "연제구" },
170
+ { region: "부산광역시", city: "영도구" },
171
+ { region: "부산광역시", city: "중구" },
172
+ { region: "부산광역시", city: "해운대구" },
173
+ { region: "서울특별시", city: "강남구" },
174
+ { region: "서울특별시", city: "강동구" },
175
+ { region: "서울특별시", city: "강북구" },
176
+ { region: "서울특별시", city: "강서구" },
177
+ { region: "서울특별시", city: "관악구" },
178
+ { region: "서울특별시", city: "광진구" },
179
+ { region: "서울특별시", city: "구로구" },
180
+ { region: "서울특별시", city: "금천구" },
181
+ { region: "서울특별시", city: "노원구" },
182
+ { region: "서울특별시", city: "도봉구" },
183
+ { region: "서울특별시", city: "동대문구" },
184
+ { region: "서울특별시", city: "동작구" },
185
+ { region: "서울특별시", city: "마포구" },
186
+ { region: "서울특별시", city: "서대문구" },
187
+ { region: "서울특별시", city: "서초구" },
188
+ { region: "서울특별시", city: "성동구" },
189
+ { region: "서울특별시", city: "성북구" },
190
+ { region: "서울특별시", city: "송파구" },
191
+ { region: "서울특별시", city: "양천구" },
192
+ { region: "서울특별시", city: "영등포구" },
193
+ { region: "서울특별시", city: "용산구" },
194
+ { region: "서울특별시", city: "은평구" },
195
+ { region: "서울특별시", city: "종로구" },
196
+ { region: "서울특별시", city: "중구" },
197
+ { region: "서울특별시", city: "중랑구" },
198
+ { region: "울산광역시", city: "남구" },
199
+ { region: "울산광역시", city: "동구" },
200
+ { region: "울산광역시", city: "북구" },
201
+ { region: "울산광역시", city: "울주군" },
202
+ { region: "울산광역시", city: "중구" },
203
+ { region: "인천광역시", city: "강화군" },
204
+ { region: "인천광역시", city: "계양구" },
205
+ { region: "인천광역시", city: "남구" },
206
+ { region: "인천광역시", city: "남동구" },
207
+ { region: "인천광역시", city: "동구" },
208
+ { region: "인천광역시", city: "부평구" },
209
+ { region: "인천광역시", city: "서구" },
210
+ { region: "인천광역시", city: "연수구" },
211
+ { region: "인천광역시", city: "옹진군" },
212
+ { region: "인천광역시", city: "중구" },
213
+ { region: "전라남도", city: "강진군" },
214
+ { region: "전라남도", city: "고흥군" },
215
+ { region: "전라남도", city: "곡성군" },
216
+ { region: "전라남도", city: "광양시" },
217
+ { region: "전라남도", city: "구례군" },
218
+ { region: "전라남도", city: "나주시" },
219
+ { region: "전라남도", city: "담양군" },
220
+ { region: "전라남도", city: "목포시" },
221
+ { region: "전라남도", city: "무안군" },
222
+ { region: "전라남도", city: "보성군" },
223
+ { region: "전라남도", city: "순천시" },
224
+ { region: "전라남도", city: "신안군" },
225
+ { region: "전라남도", city: "여수시" },
226
+ { region: "전라남도", city: "영광군" },
227
+ { region: "전라남도", city: "영암군" },
228
+ { region: "전라남도", city: "완도군" },
229
+ { region: "전라남도", city: "장성군" },
230
+ { region: "전라남도", city: "장흥군" },
231
+ { region: "전라남도", city: "진도군" },
232
+ { region: "전라남도", city: "함평군" },
233
+ { region: "전라남도", city: "해남군" },
234
+ { region: "전라남도", city: "화순군" },
235
+ { region: "전라북도", city: "고창군" },
236
+ { region: "전라북도", city: "군산시" },
237
+ { region: "전라북도", city: "김제시" },
238
+ { region: "전라북도", city: "남원시" },
239
+ { region: "전라북도", city: "무주군" },
240
+ { region: "전라북도", city: "부안군" },
241
+ { region: "전라북도", city: "순창군" },
242
+ { region: "전라북도", city: "완주군" },
243
+ { region: "전라북도", city: "익산시" },
244
+ { region: "전라북도", city: "임실군" },
245
+ { region: "전라북도", city: "장수군" },
246
+ { region: "전라북도", city: "전주시덕진구" },
247
+ { region: "전라북도", city: "전주시완산구" },
248
+ { region: "전라북도", city: "정읍시" },
249
+ { region: "전라북도", city: "진안군" },
250
+ { region: "제주특별자치도", city: "서귀포시" },
251
+ { region: "제주특별자치도", city: "제주시" },
252
+ { region: "충청남도", city: "계룡시" },
253
+ { region: "충청남도", city: "공주시" },
254
+ { region: "충청남도", city: "금산군" },
255
+ { region: "충청남도", city: "논산시" },
256
+ { region: "충청남도", city: "당진시" },
257
+ { region: "충청남도", city: "보령시" },
258
+ { region: "충청남도", city: "부여군" },
259
+ { region: "충청남도", city: "서산시" },
260
+ { region: "충청남도", city: "서천군" },
261
+ { region: "충청남도", city: "아산시" },
262
+ { region: "충청남도", city: "예산군" },
263
+ { region: "충청남도", city: "천안시동남구" },
264
+ { region: "충청남도", city: "천안시서북구" },
265
+ { region: "충청남도", city: "청양군" },
266
+ { region: "충청남도", city: "태안군" },
267
+ { region: "충청남도", city: "홍성군" },
268
+ { region: "충청북도", city: "괴산군" },
269
+ { region: "충청북도", city: "단양군" },
270
+ { region: "충청북도", city: "보은군" },
271
+ { region: "충청북도", city: "영동군" },
272
+ { region: "충청북도", city: "옥천군" },
273
+ { region: "충청북도", city: "음성군" },
274
+ { region: "충청북도", city: "제천시" },
275
+ { region: "충청북도", city: "증평군" },
276
+ { region: "충청북도", city: "진천군" },
277
+ { region: "충청북도", city: "청주시상당구" },
278
+ { region: "충청북도", city: "청주시서원구" },
279
+ { region: "충청북도", city: "청주시청원구" },
280
+ { region: "충청북도", city: "청주시흥덕구" },
281
+ { region: "충청북도", city: "충주시" },
282
+ ]
283
+
284
+ /**
285
+ * Every 시군구 name a `subregion` span may close on.
286
+ */
287
+ export const KR_SIGUNGU_NAMES: readonly string[] = [...new Set(KR_SIGUNGU.map((s) => s.city))]
288
+
289
+ /**
290
+ * The characters a `subregion` surface must absorb from what follows it to become a register name, or null.
291
+ */
292
+ export function krSubregionCompletion(surface: string, following: string): string | null {
293
+ return completeFromRegister(KR_SIGUNGU_NAMES, surface, following)
294
+ }
@@ -41,7 +41,7 @@ export function wordNorm(s: string): string {
41
41
  return s
42
42
  .split(/\s+/)
43
43
  .map((w) => w.replaceAll(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, ""))
44
- .filter(Boolean)
44
+ .filter((w) => w.length > 0)
45
45
  .join(" ")
46
46
  }
47
47
 
@@ -51,3 +51,17 @@ export function wordNorm(s: string): string {
51
51
  export function wordNormLower(s: string): string {
52
52
  return wordNorm(s).toLowerCase()
53
53
  }
54
+
55
+ /**
56
+ * Fold a single token to its lowercase, diacritic-free form: lower-cased, NFD-decomposed, combining marks stripped —
57
+ * `"Côte"` → `"cote"`, `"Tōkyō"` → `"tokyo"`.
58
+ *
59
+ * The shared core of the per-country token matchers. Unlike {@link foldName} it does not touch punctuation or whitespace
60
+ * — each matcher layers its own character filtering (letters-only, hyphen-keeping, suffix-stripping) on top.
61
+ */
62
+ export function foldToken(s: string): string {
63
+ return s
64
+ .toLowerCase()
65
+ .normalize("NFD")
66
+ .replaceAll(/[\u0300-\u036F]/g, "")
67
+ }
@@ -33,7 +33,7 @@
33
33
  * mail, accessed 2026-06-11) name only the six types above. However, real NZ mail and the postal
34
34
  * arena's gold rows carry "Private Box" as a colloquial synonym for a numbered PO Box (same
35
35
  * numbered format, different label). Operator ruling 2026-06-11: "Private Box = recognize-as-used
36
- * — codex documents it unofficial/colloquial WITH the 'officially invalid' citation; the shard
36
+ * — codex documents it unofficial/colloquial WITH the 'officially invalid' citation; the extract
37
37
  * teaches recognition (validity and recognition are separate disciplines, same as the AU legacy
38
38
  * flags)." This is encoded in {@link NZ_PRIVATE_BOX_ALIAS} — a distinct export from
39
39
  * {@link NZ_DELIVERY_SERVICE_TYPES} so callers can choose whether to include the unofficial form.
@@ -8,5 +8,5 @@
8
8
  * Restante) and the 4-digit postcode. NZ addresses carry no state/region line.
9
9
  */
10
10
 
11
- export * from "./delivery-service.ts"
12
- export * from "./postcode.ts"
11
+ export * from "#nz/delivery-service"
12
+ export * from "#nz/postcode"
@@ -10,30 +10,32 @@
10
10
  *
11
11
  * It is the shared source of truth for "which systems can this shape be" — consumed by the postcode
12
12
  * anchor (to narrow which systems' street vocabularies it checks) and, in time, by the runtime
13
- * pipeline's locale gate (so its format→locale scoring derives from the same patterns rather than
13
+ * pipeline's `@mailwoman/locale-hint` stage (so its format→locale scoring derives from the same patterns rather than
14
14
  * a second, divergent copy). The point is to unify the DATA, not to couple the modules: callers
15
15
  * depend on this pure function, never on each other.
16
16
  *
17
17
  * Note this is a SHAPE test, not a gazetteer-membership test. A bare `68161` matches the US,
18
- * German, AND French 5-digit shapes, so it returns `["us", "de", "fr"]` — the shape alone cannot
19
- * split the numeric-postcode systems. The anchor uses real gazetteer membership for the finer
18
+ * German, French, Spanish and Italian 5-digit shapes, so it returns `["us", "de", "fr", "es", "it"]` —
19
+ * the shape alone cannot split the numeric-postcode systems. The anchor uses real gazetteer membership for the finer
20
20
  * call; this function answers the coarser, model-free "which systems is this shape even eligible
21
21
  * for".
22
22
  */
23
23
 
24
- import { normalizeAuPostcode } from "./au/index.ts"
25
- import { normalizeCaPostalCode } from "./ca/index.ts"
26
- import { normalizePLZ } from "./de/index.ts"
27
- import { normalizeCodePostal } from "./fr/index.ts"
28
- import { normalizeUkPostcode } from "./gb/index.ts"
29
- import { normalizeJpPostalCode } from "./jp/index.ts"
30
- import { normalizeNZPostcode } from "./nz/index.ts"
31
- import { isZipCode } from "./us/index.ts"
24
+ import { normalizeAuPostcode } from "#au/index"
25
+ import { normalizeCaPostalCode } from "#ca/index"
26
+ import { normalizePLZ } from "#de/index"
27
+ import { normalizeCodigoPostal } from "#es/index"
28
+ import { normalizeCodePostal } from "#fr/index"
29
+ import { normalizeUkPostcode } from "#gb/index"
30
+ import { normalizeCAP } from "#it/index"
31
+ import { normalizeJpPostalCode } from "#jp/index"
32
+ import { normalizeNZPostcode } from "#nz/index"
33
+ import { isZipCode } from "#us/index"
32
34
 
33
35
  /**
34
36
  * A codex address-system code — the subpath under `@mailwoman/codex/<system>`.
35
37
  */
36
- export type SystemCode = "us" | "de" | "fr" | "ca" | "gb" | "jp" | "au" | "nz"
38
+ export type SystemCode = "us" | "de" | "fr" | "es" | "it" | "ca" | "gb" | "jp" | "au" | "nz"
37
39
 
38
40
  /**
39
41
  * Per-system membership test: each entry returns true when the string is accepted by that system's own postcode shape
@@ -44,6 +46,8 @@ const SYSTEM_ACCEPTS: ReadonlyArray<readonly [SystemCode, (s: string) => boolean
44
46
  ["us", (s) => isZipCode(s)],
45
47
  ["de", (s) => normalizePLZ(s) !== null],
46
48
  ["fr", (s) => normalizeCodePostal(s) !== null],
49
+ ["es", (s) => normalizeCodigoPostal(s) !== null],
50
+ ["it", (s) => normalizeCAP(s) !== null],
47
51
  ["ca", (s) => normalizeCaPostalCode(s) !== null],
48
52
  ["gb", (s) => normalizeUkPostcode(s) !== null],
49
53
  ["jp", (s) => normalizeJpPostalCode(s) !== null],
@@ -51,6 +55,12 @@ const SYSTEM_ACCEPTS: ReadonlyArray<readonly [SystemCode, (s: string) => boolean
51
55
  ["nz", (s) => normalizeNZPostcode(s) !== null],
52
56
  ]
53
57
 
58
+ /**
59
+ * Every address system with a postcode shape, in {@link SYSTEM_ACCEPTS} order. The one list a consumer that needs the
60
+ * universe of systems (rather than the systems a given code fits) should read, so it cannot drift from the table.
61
+ */
62
+ export const SYSTEM_CODES: readonly SystemCode[] = SYSTEM_ACCEPTS.map(([system]) => system)
63
+
54
64
  /**
55
65
  * Every address system whose own postcode shape accepts `postcode`. Empty when no system recognizes the shape (e.g. a
56
66
  * bare `27`, or a 7-digit run). O(number of systems) — a handful of cheap regex tests, run only on the few
@@ -156,22 +166,28 @@ export function isUnitGradePostcodeHit(parsed: string, resolverName: string | un
156
166
  * Membership is earned by a full-panel measurement, the same bar {@link UNIT_GRADE_POSTCODE} sets for CA. Coordinate
157
167
  * p50 on the OpenAddresses panels, locality-first (the default) against the postcode point:
158
168
  *
159
- * | country | rows | locality-first | postcode point | verdict |
160
- * | ------- | ----: | -------------: | -------------: | ------------------------------------------------------------------ |
161
- * | **DE** | 2,997 | 5.84 km | **1.24 km** | postcode, on EVERY percentile incl. p99 (21.50 → 10.57) |
162
- * | FR | 3,000 | **0.97 km** | 2.64 km | locality, closer on 77.5% of rows |
163
- * | IT | 2,833 | **1.34 km** | 3.05 km | locality, closer on 66.4% |
164
- * | ES | 2,929 | **0.68 km** | 0.97 km | locality, but near a coin flip — 46.1% of rows prefer the postcode |
165
- * | US | 577 | **2.28 km** | 4.15 km | locality (see below) |
169
+ * | country | rows | locality-first | postcode point | verdict |
170
+ * | ------- | ----: | -------------: | -------------: | ---------------------------------------------------------------------- |
171
+ * | **DE** | 2,997 | 5.84 km | **1.24 km** | postcode, on EVERY percentile incl. p99 (21.50 → 10.57) |
172
+ * | FR | 3,000 | **0.97 km** | 2.64 km | locality, closer on 77.5% of rows |
173
+ * | IT | 2,833 | **1.34 km** | 3.05 km | locality, closer on 66.4% |
174
+ * | ES | 2,929 | **0.68 km** | 0.97 km | locality, but near a coin flip — 46.1% of rows prefer the postcode |
175
+ * | US | 577 | **2.28 km** | 4.15 km | locality (see below) |
176
+ * | **JP** | 586 | 4.93 km | **0.48 km** | postcode, closer on 97.3% of rows (p90 13.55 → 1.21, p99 19.63 → 3.54) |
166
177
  *
167
178
  * **The US row is measured on the population production actually sends to the ladder.** Its rooftop cascade is US-only
168
179
  * by construction (`selectAddressPointsDB` composes `address-points-us-<slug>.db`), and it serves 94.2% of US queries,
169
- * so only 577 of 10,000 panel rows reach an admin decision at all. Those are the rows no rooftop or interpolation shard
170
- * could place, which skews rural — exactly where a locality centroid sits close and a ZIP zone is wide. Measured over
171
- * all 10,000 rows instead, the US looks like a postcode-first country (2.41 km vs 3.63); that is a selection effect,
172
- * and it is why this table reports 577 rows for the US and full panels for the others, which have no such cascade.
180
+ * so only 577 of 10,000 panel rows reach an admin decision at all. Those are the rows no rooftop or interpolation
181
+ * extract could place, which skews rural — exactly where a locality centroid sits close and a ZIP zone is wide.
182
+ * Measured over all 10,000 rows instead, the US looks like a postcode-first country (2.41 km vs 3.63); that is a
183
+ * selection effect, and it is why this table reports 577 rows for the US and full panels for the others, which have no
184
+ * such cascade.
185
+ *
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
+ * rows carrying a postcode the candidate table keys, graded on the row's own entrance point — the locality-first answer
188
+ * is the municipality centroid, the postcode answer the code's 町域 centroid from the WOF extract.
173
189
  */
174
- export const AREA_POSTCODE_FINER_THAN_LOCALITY: ReadonlySet<string> = new Set(["DE"])
190
+ export const AREA_POSTCODE_FINER_THAN_LOCALITY: ReadonlySet<string> = new Set(["DE", "JP"])
175
191
 
176
192
  /**
177
193
  * True when this country's area-grade postal code outranks its locality. Absent or unknown country → false, so the
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The one read every name register offers a decode-time repair: given the surface a model closed a span on and the
7
+ * text that follows, the characters that would extend the surface to a register name. A surface that already IS a
8
+ * name and is not the prefix of a longer one answers null.
9
+ */
10
+
11
+ /**
12
+ * The remainder of the first register name that begins with `surface` and continues into `following`, or null.
13
+ */
14
+ export function completeFromRegister(names: readonly string[], surface: string, following: string): string | null {
15
+ for (const name of names) {
16
+ if (name.length > surface.length && name.startsWith(surface)) {
17
+ const remainder = name.slice(surface.length)
18
+
19
+ if (following.startsWith(remainder)) return remainder
20
+ }
21
+ }
22
+
23
+ return null
24
+ }