@mailwoman/resolver-wof-sqlite 9.1.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (501) hide show
  1. package/README.md +36 -20
  2. package/lib/address/index.ts +9 -0
  3. package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +40 -28
  4. package/{address-point-schema.ts → lib/address/point-schema.ts} +34 -8
  5. package/lib/address/point.ts +280 -0
  6. package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
  7. package/{ancestry.ts → lib/ancestry/index.ts} +13 -8
  8. package/{build-candidate.ts → lib/build-candidate.ts} +243 -199
  9. package/{build-slim.ts → lib/build-slim.ts} +153 -168
  10. package/lib/candidate/alias-bags.ts +56 -0
  11. package/lib/candidate/ancestors-sidecar.ts +204 -0
  12. package/lib/candidate/country-display-names.ts +79 -0
  13. package/lib/candidate/extract-fold.ts +138 -0
  14. package/lib/candidate/name-roles.ts +238 -0
  15. package/lib/candidate/own-name.ts +146 -0
  16. package/lib/candidate/place-attrs.ts +44 -0
  17. package/lib/candidate-ancestors-schema.ts +195 -0
  18. package/{candidate-fts.ts → lib/candidate-fts.ts} +7 -5
  19. package/{candidate-importance.ts → lib/candidate-importance.ts} +54 -57
  20. package/lib/candidate-lookup.ts +976 -0
  21. package/{candidate-schema.ts → lib/candidate-schema.ts} +34 -6
  22. package/lib/candidate-scoring.ts +261 -0
  23. package/lib/capital-schema.ts +89 -0
  24. package/lib/capitals.ts +148 -0
  25. package/{coincident-roles.ts → lib/coincident-roles.ts} +75 -19
  26. package/{convention.ts → lib/convention/index.ts} +5 -5
  27. package/lib/convention/schema.ts +72 -0
  28. package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +15 -16
  29. package/lib/currency-backfill.ts +334 -0
  30. package/lib/env.ts +50 -0
  31. package/lib/exact-match.ts +113 -0
  32. package/{sharding.ts → lib/extracts.ts} +63 -60
  33. package/lib/fst/autocomplete.ts +176 -0
  34. package/{fst-builder.ts → lib/fst/builder.ts} +30 -27
  35. package/{fst-deserialize-web.ts → lib/fst/deserialize-web.ts} +27 -87
  36. package/lib/fst/format.ts +106 -0
  37. package/{fst-freshness.ts → lib/fst/freshness.ts} +42 -75
  38. package/lib/fst/index.ts +14 -0
  39. package/{fst-matcher.ts → lib/fst/matcher.ts} +1 -1
  40. package/{fst-serialize.ts → lib/fst/serialize.ts} +37 -115
  41. package/{fst-types.ts → lib/fst/types.ts} +4 -2
  42. package/{fts.ts → lib/fts/index.ts} +28 -23
  43. package/{fts-query.ts → lib/fts/query.ts} +2 -2
  44. package/{geonames-aliases.ts → lib/geonames/aliases.ts} +13 -13
  45. package/lib/geonames/index.ts +8 -0
  46. package/{geonames-postal.ts → lib/geonames/postal.ts} +16 -15
  47. package/{index.ts → lib/index.ts} +52 -48
  48. package/{interpolation.ts → lib/interpolation.ts} +137 -41
  49. package/lib/lookup.ts +861 -0
  50. package/{name-score.ts → lib/name-score.ts} +6 -4
  51. package/lib/nsul/index.ts +8 -0
  52. package/lib/nsul/lookup.ts +144 -0
  53. package/lib/nsul/schema.ts +130 -0
  54. package/{place-importance-schema.ts → lib/place-importance-schema.ts} +70 -22
  55. package/lib/poi/index.ts +8 -0
  56. package/{poi-lookup.ts → lib/poi/lookup.ts} +32 -39
  57. package/{poi-schema.ts → lib/poi/schema.ts} +10 -6
  58. package/lib/polygon-schema.ts +47 -0
  59. package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +15 -21
  60. package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +4 -2
  61. package/lib/postal/index.ts +9 -0
  62. package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
  63. package/lib/primary-preference.ts +226 -0
  64. package/lib/proximity-rerank.ts +120 -0
  65. package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
  66. package/lib/region-keys.ts +144 -0
  67. package/{reverse.ts → lib/reverse.ts} +45 -56
  68. package/{schema.ts → lib/schema.ts} +11 -1
  69. package/lib/search-fetch.ts +257 -0
  70. package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
  71. package/lib/sqlite-utils.ts +83 -0
  72. package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +12 -6
  73. package/{street-centroid.ts → lib/street/centroid.ts} +26 -28
  74. package/lib/street/index.ts +13 -0
  75. package/{street-morphology-fst-builder.ts → lib/street/morphology-fst-builder.ts} +24 -14
  76. package/{street-morphology-fst-loader.ts → lib/street/morphology-fst-loader.ts} +14 -12
  77. package/{street-name-lookup.ts → lib/street/name-lookup.ts} +14 -30
  78. package/lib/street/normalize.ts +572 -0
  79. package/{street-segment-schema.ts → lib/street/segment-schema.ts} +13 -8
  80. package/{types.ts → lib/types.ts} +49 -9
  81. package/{unified-schema.ts → lib/unified-schema.ts} +21 -25
  82. package/lib/uprn/existence.ts +84 -0
  83. package/lib/uprn/index.ts +9 -0
  84. package/lib/uprn/lookup.ts +206 -0
  85. package/lib/uprn/schema.ts +124 -0
  86. package/lib/weights-overlay-linker.ts +878 -0
  87. package/out/address/index.d.ts +9 -0
  88. package/out/address/index.d.ts.map +1 -0
  89. package/out/address/index.js +9 -0
  90. package/out/address/index.js.map +1 -0
  91. package/out/{address-point-interpolation.d.ts → address/point-interpolation.d.ts} +11 -10
  92. package/out/address/point-interpolation.d.ts.map +1 -0
  93. package/out/{address-point-interpolation.js → address/point-interpolation.js} +29 -23
  94. package/out/address/point-interpolation.js.map +1 -0
  95. package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +30 -10
  96. package/out/address/point-schema.d.ts.map +1 -0
  97. package/out/{address-point-schema.js → address/point-schema.js} +7 -3
  98. package/out/address/point-schema.js.map +1 -0
  99. package/out/{address-point.d.ts → address/point.d.ts} +16 -8
  100. package/out/address/point.d.ts.map +1 -0
  101. package/out/address/point.js +191 -0
  102. package/out/address/point.js.map +1 -0
  103. package/out/{ancestry-backfill.d.ts → ancestry/backfill.d.ts} +7 -5
  104. package/out/ancestry/backfill.d.ts.map +1 -0
  105. package/out/{ancestry-backfill.js → ancestry/backfill.js} +12 -16
  106. package/out/ancestry/backfill.js.map +1 -0
  107. package/out/{ancestry.d.ts → ancestry/index.d.ts} +6 -5
  108. package/out/ancestry/index.d.ts.map +1 -0
  109. package/out/{ancestry.js → ancestry/index.js} +7 -7
  110. package/out/ancestry/index.js.map +1 -0
  111. package/out/build-candidate.d.ts +88 -7
  112. package/out/build-candidate.d.ts.map +1 -1
  113. package/out/build-candidate.js +145 -149
  114. package/out/build-candidate.js.map +1 -1
  115. package/out/build-slim.d.ts +5 -5
  116. package/out/build-slim.d.ts.map +1 -1
  117. package/out/build-slim.js +127 -142
  118. package/out/build-slim.js.map +1 -1
  119. package/out/candidate/alias-bags.d.ts +19 -0
  120. package/out/candidate/alias-bags.d.ts.map +1 -0
  121. package/out/candidate/alias-bags.js +39 -0
  122. package/out/candidate/alias-bags.js.map +1 -0
  123. package/out/candidate/ancestors-sidecar.d.ts +32 -0
  124. package/out/candidate/ancestors-sidecar.d.ts.map +1 -0
  125. package/out/candidate/ancestors-sidecar.js +140 -0
  126. package/out/candidate/ancestors-sidecar.js.map +1 -0
  127. package/out/candidate/country-display-names.d.ts +35 -0
  128. package/out/candidate/country-display-names.d.ts.map +1 -0
  129. package/out/candidate/country-display-names.js +59 -0
  130. package/out/candidate/country-display-names.js.map +1 -0
  131. package/out/candidate/extract-fold.d.ts +32 -0
  132. package/out/candidate/extract-fold.d.ts.map +1 -0
  133. package/out/candidate/extract-fold.js +104 -0
  134. package/out/candidate/extract-fold.js.map +1 -0
  135. package/out/candidate/name-roles.d.ts +57 -0
  136. package/out/candidate/name-roles.d.ts.map +1 -0
  137. package/out/candidate/name-roles.js +166 -0
  138. package/out/candidate/name-roles.js.map +1 -0
  139. package/out/candidate/own-name.d.ts +50 -0
  140. package/out/candidate/own-name.d.ts.map +1 -0
  141. package/out/candidate/own-name.js +132 -0
  142. package/out/candidate/own-name.js.map +1 -0
  143. package/out/candidate/place-attrs.d.ts +43 -0
  144. package/out/candidate/place-attrs.d.ts.map +1 -0
  145. package/out/candidate/place-attrs.js +15 -0
  146. package/out/candidate/place-attrs.js.map +1 -0
  147. package/out/candidate-ancestors-schema.d.ts +150 -0
  148. package/out/candidate-ancestors-schema.d.ts.map +1 -0
  149. package/out/candidate-ancestors-schema.js +123 -0
  150. package/out/candidate-ancestors-schema.js.map +1 -0
  151. package/out/candidate-fts.d.ts +7 -5
  152. package/out/candidate-fts.d.ts.map +1 -1
  153. package/out/candidate-fts.js +5 -3
  154. package/out/candidate-fts.js.map +1 -1
  155. package/out/candidate-importance.d.ts +16 -16
  156. package/out/candidate-importance.d.ts.map +1 -1
  157. package/out/candidate-importance.js +47 -52
  158. package/out/candidate-importance.js.map +1 -1
  159. package/out/candidate-lookup.d.ts +31 -52
  160. package/out/candidate-lookup.d.ts.map +1 -1
  161. package/out/candidate-lookup.js +380 -172
  162. package/out/candidate-lookup.js.map +1 -1
  163. package/out/candidate-schema.d.ts +31 -7
  164. package/out/candidate-schema.d.ts.map +1 -1
  165. package/out/candidate-schema.js +3 -0
  166. package/out/candidate-schema.js.map +1 -1
  167. package/out/candidate-scoring.d.ts +34 -0
  168. package/out/candidate-scoring.d.ts.map +1 -0
  169. package/out/candidate-scoring.js +196 -0
  170. package/out/candidate-scoring.js.map +1 -0
  171. package/out/capital-schema.d.ts +51 -0
  172. package/out/capital-schema.d.ts.map +1 -0
  173. package/out/capital-schema.js +63 -0
  174. package/out/capital-schema.js.map +1 -0
  175. package/out/capitals.d.ts +69 -0
  176. package/out/capitals.d.ts.map +1 -0
  177. package/out/capitals.js +98 -0
  178. package/out/capitals.js.map +1 -0
  179. package/out/coincident-roles.d.ts +12 -4
  180. package/out/coincident-roles.d.ts.map +1 -1
  181. package/out/coincident-roles.js +43 -11
  182. package/out/coincident-roles.js.map +1 -1
  183. package/out/{convention.d.ts → convention/index.d.ts} +5 -5
  184. package/out/convention/index.d.ts.map +1 -0
  185. package/out/{convention.js → convention/index.js} +4 -4
  186. package/out/convention/index.js.map +1 -0
  187. package/out/convention/schema.d.ts +51 -0
  188. package/out/convention/schema.d.ts.map +1 -0
  189. package/out/convention/schema.js +34 -0
  190. package/out/convention/schema.js.map +1 -0
  191. package/out/coverage-manifest-schema.d.ts +8 -8
  192. package/out/coverage-manifest-schema.d.ts.map +1 -1
  193. package/out/coverage-manifest-schema.js +6 -10
  194. package/out/coverage-manifest-schema.js.map +1 -1
  195. package/out/currency-backfill.d.ts +90 -0
  196. package/out/currency-backfill.d.ts.map +1 -0
  197. package/out/currency-backfill.js +196 -0
  198. package/out/currency-backfill.js.map +1 -0
  199. package/out/env.d.ts +43 -0
  200. package/out/env.d.ts.map +1 -0
  201. package/out/env.js +48 -0
  202. package/out/env.js.map +1 -0
  203. package/out/exact-match.d.ts +25 -0
  204. package/out/exact-match.d.ts.map +1 -0
  205. package/out/exact-match.js +88 -0
  206. package/out/exact-match.js.map +1 -0
  207. package/out/extracts.d.ts +114 -0
  208. package/out/extracts.d.ts.map +1 -0
  209. package/out/{sharding.js → extracts.js} +38 -38
  210. package/out/extracts.js.map +1 -0
  211. package/out/{fst-autocomplete.d.ts → fst/autocomplete.d.ts} +13 -13
  212. package/out/fst/autocomplete.d.ts.map +1 -0
  213. package/out/fst/autocomplete.js +113 -0
  214. package/out/fst/autocomplete.js.map +1 -0
  215. package/out/{fst-builder.d.ts → fst/builder.d.ts} +5 -5
  216. package/out/fst/builder.d.ts.map +1 -0
  217. package/out/{fst-builder.js → fst/builder.js} +24 -24
  218. package/out/fst/builder.js.map +1 -0
  219. package/out/{fst-deserialize-web.d.ts → fst/deserialize-web.d.ts} +3 -3
  220. package/out/fst/deserialize-web.d.ts.map +1 -0
  221. package/out/{fst-deserialize-web.js → fst/deserialize-web.js} +10 -74
  222. package/out/fst/deserialize-web.js.map +1 -0
  223. package/out/fst/format.d.ts +80 -0
  224. package/out/fst/format.d.ts.map +1 -0
  225. package/out/fst/format.js +91 -0
  226. package/out/fst/format.js.map +1 -0
  227. package/out/{fst-freshness.d.ts → fst/freshness.d.ts} +11 -14
  228. package/out/fst/freshness.d.ts.map +1 -0
  229. package/out/{fst-freshness.js → fst/freshness.js} +42 -74
  230. package/out/fst/freshness.js.map +1 -0
  231. package/out/fst/index.d.ts +14 -0
  232. package/out/fst/index.d.ts.map +1 -0
  233. package/out/fst/index.js +14 -0
  234. package/out/fst/index.js.map +1 -0
  235. package/out/{fst-matcher.d.ts → fst/matcher.d.ts} +2 -2
  236. package/out/fst/matcher.d.ts.map +1 -0
  237. package/out/{fst-matcher.js → fst/matcher.js} +1 -1
  238. package/out/fst/matcher.js.map +1 -0
  239. package/out/{fst-serialize.d.ts → fst/serialize.d.ts} +5 -10
  240. package/out/fst/serialize.d.ts.map +1 -0
  241. package/out/{fst-serialize.js → fst/serialize.js} +18 -98
  242. package/out/fst/serialize.js.map +1 -0
  243. package/out/{fst-types.d.ts → fst/types.d.ts} +4 -3
  244. package/out/fst/types.d.ts.map +1 -0
  245. package/out/{fst-types.js → fst/types.js} +1 -1
  246. package/out/fst/types.js.map +1 -0
  247. package/out/{fts.d.ts → fts/index.d.ts} +20 -13
  248. package/out/fts/index.d.ts.map +1 -0
  249. package/out/{fts.js → fts/index.js} +20 -15
  250. package/out/fts/index.js.map +1 -0
  251. package/out/{fts-query.d.ts → fts/query.d.ts} +2 -2
  252. package/out/fts/query.d.ts.map +1 -0
  253. package/out/{fts-query.js → fts/query.js} +2 -2
  254. package/out/fts/query.js.map +1 -0
  255. package/out/{geonames-aliases.d.ts → geonames/aliases.d.ts} +8 -6
  256. package/out/geonames/aliases.d.ts.map +1 -0
  257. package/out/{geonames-aliases.js → geonames/aliases.js} +7 -7
  258. package/out/geonames/aliases.js.map +1 -0
  259. package/out/geonames/index.d.ts +8 -0
  260. package/out/geonames/index.d.ts.map +1 -0
  261. package/out/geonames/index.js +8 -0
  262. package/out/geonames/index.js.map +1 -0
  263. package/out/{geonames-postal.d.ts → geonames/postal.d.ts} +12 -10
  264. package/out/geonames/postal.d.ts.map +1 -0
  265. package/out/{geonames-postal.js → geonames/postal.js} +11 -11
  266. package/out/geonames/postal.js.map +1 -0
  267. package/out/index.d.ts +32 -31
  268. package/out/index.d.ts.map +1 -1
  269. package/out/index.js +24 -24
  270. package/out/index.js.map +1 -1
  271. package/out/interpolation.d.ts +18 -9
  272. package/out/interpolation.d.ts.map +1 -1
  273. package/out/interpolation.js +108 -36
  274. package/out/interpolation.js.map +1 -1
  275. package/out/lookup.d.ts +25 -25
  276. package/out/lookup.d.ts.map +1 -1
  277. package/out/lookup.js +174 -558
  278. package/out/lookup.js.map +1 -1
  279. package/out/name-score.d.ts +0 -10
  280. package/out/name-score.d.ts.map +1 -1
  281. package/out/name-score.js +6 -4
  282. package/out/name-score.js.map +1 -1
  283. package/out/nsul/index.d.ts +8 -0
  284. package/out/nsul/index.d.ts.map +1 -0
  285. package/out/nsul/index.js +8 -0
  286. package/out/nsul/index.js.map +1 -0
  287. package/out/nsul/lookup.d.ts +83 -0
  288. package/out/nsul/lookup.d.ts.map +1 -0
  289. package/out/nsul/lookup.js +80 -0
  290. package/out/nsul/lookup.js.map +1 -0
  291. package/out/nsul/schema.d.ts +100 -0
  292. package/out/nsul/schema.d.ts.map +1 -0
  293. package/out/nsul/schema.js +78 -0
  294. package/out/nsul/schema.js.map +1 -0
  295. package/out/place-importance-schema.d.ts +47 -10
  296. package/out/place-importance-schema.d.ts.map +1 -1
  297. package/out/place-importance-schema.js +56 -10
  298. package/out/place-importance-schema.js.map +1 -1
  299. package/out/poi/index.d.ts +8 -0
  300. package/out/poi/index.d.ts.map +1 -0
  301. package/out/poi/index.js +8 -0
  302. package/out/poi/index.js.map +1 -0
  303. package/out/{poi-lookup.d.ts → poi/lookup.d.ts} +8 -10
  304. package/out/poi/lookup.d.ts.map +1 -0
  305. package/out/{poi-lookup.js → poi/lookup.js} +24 -29
  306. package/out/poi/lookup.js.map +1 -0
  307. package/out/{poi-schema.d.ts → poi/schema.d.ts} +10 -6
  308. package/out/poi/schema.d.ts.map +1 -0
  309. package/out/{poi-schema.js → poi/schema.js} +1 -1
  310. package/out/poi/schema.js.map +1 -0
  311. package/out/polygon-schema.d.ts +37 -0
  312. package/out/polygon-schema.d.ts.map +1 -0
  313. package/out/polygon-schema.js +23 -0
  314. package/out/polygon-schema.js.map +1 -0
  315. package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +7 -6
  316. package/out/postal/city-alias-lookup.d.ts.map +1 -0
  317. package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +12 -16
  318. package/out/postal/city-alias-lookup.js.map +1 -0
  319. package/out/{postal-city-alias-schema.d.ts → postal/city-alias-schema.d.ts} +1 -1
  320. package/out/{postal-city-alias-schema.d.ts.map → postal/city-alias-schema.d.ts.map} +1 -1
  321. package/out/{postal-city-alias-schema.js → postal/city-alias-schema.js} +1 -1
  322. package/out/{postal-city-alias-schema.js.map → postal/city-alias-schema.js.map} +1 -1
  323. package/out/{postal-city-candidate-schema.d.ts → postal/city-candidate-schema.d.ts} +4 -3
  324. package/out/postal/city-candidate-schema.d.ts.map +1 -0
  325. package/out/{postal-city-candidate-schema.js → postal/city-candidate-schema.js} +2 -2
  326. package/out/{postal-city-candidate-schema.js.map → postal/city-candidate-schema.js.map} +1 -1
  327. package/out/postal/index.d.ts +9 -0
  328. package/out/postal/index.d.ts.map +1 -0
  329. package/out/postal/index.js +9 -0
  330. package/out/postal/index.js.map +1 -0
  331. package/out/postcode-point-lookup.d.ts +10 -10
  332. package/out/postcode-point-lookup.d.ts.map +1 -1
  333. package/out/postcode-point-lookup.js +13 -13
  334. package/out/postcode-point-lookup.js.map +1 -1
  335. package/out/primary-preference.d.ts +133 -0
  336. package/out/primary-preference.d.ts.map +1 -0
  337. package/out/primary-preference.js +146 -0
  338. package/out/primary-preference.js.map +1 -0
  339. package/out/proximity-rerank.d.ts +77 -0
  340. package/out/proximity-rerank.d.ts.map +1 -0
  341. package/out/proximity-rerank.js +86 -0
  342. package/out/proximity-rerank.js.map +1 -0
  343. package/out/ranking-weights.d.ts +15 -3
  344. package/out/ranking-weights.d.ts.map +1 -1
  345. package/out/ranking-weights.js +21 -3
  346. package/out/ranking-weights.js.map +1 -1
  347. package/out/region-keys.d.ts +47 -0
  348. package/out/region-keys.d.ts.map +1 -0
  349. package/out/region-keys.js +121 -0
  350. package/out/region-keys.js.map +1 -0
  351. package/out/reverse.d.ts +6 -6
  352. package/out/reverse.d.ts.map +1 -1
  353. package/out/reverse.js +23 -35
  354. package/out/reverse.js.map +1 -1
  355. package/out/schema.d.ts +10 -1
  356. package/out/schema.d.ts.map +1 -1
  357. package/out/schema.js.map +1 -1
  358. package/out/search-fetch.d.ts +59 -0
  359. package/out/search-fetch.d.ts.map +1 -0
  360. package/out/search-fetch.js +184 -0
  361. package/out/search-fetch.js.map +1 -0
  362. package/out/sqlite-convention-source.d.ts +7 -7
  363. package/out/sqlite-convention-source.d.ts.map +1 -1
  364. package/out/sqlite-convention-source.js +5 -5
  365. package/out/sqlite-convention-source.js.map +1 -1
  366. package/out/sqlite-utils.d.ts +24 -6
  367. package/out/sqlite-utils.d.ts.map +1 -1
  368. package/out/sqlite-utils.js +24 -7
  369. package/out/sqlite-utils.js.map +1 -1
  370. package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +12 -7
  371. package/out/street/centroid-schema.d.ts.map +1 -0
  372. package/out/{street-centroid-schema.js → street/centroid-schema.js} +5 -5
  373. package/out/{street-centroid-schema.js.map → street/centroid-schema.js.map} +1 -1
  374. package/out/{street-centroid.d.ts → street/centroid.d.ts} +6 -6
  375. package/out/street/centroid.d.ts.map +1 -0
  376. package/out/{street-centroid.js → street/centroid.js} +18 -24
  377. package/out/street/centroid.js.map +1 -0
  378. package/out/street/index.d.ts +13 -0
  379. package/out/street/index.d.ts.map +1 -0
  380. package/out/street/index.js +13 -0
  381. package/out/street/index.js.map +1 -0
  382. package/out/{street-morphology-fst-builder.d.ts → street/morphology-fst-builder.d.ts} +4 -4
  383. package/out/street/morphology-fst-builder.d.ts.map +1 -0
  384. package/out/{street-morphology-fst-builder.js → street/morphology-fst-builder.js} +18 -13
  385. package/out/street/morphology-fst-builder.js.map +1 -0
  386. package/out/{street-morphology-fst-loader.d.ts → street/morphology-fst-loader.d.ts} +5 -5
  387. package/out/street/morphology-fst-loader.d.ts.map +1 -0
  388. package/out/{street-morphology-fst-loader.js → street/morphology-fst-loader.js} +11 -10
  389. package/out/street/morphology-fst-loader.js.map +1 -0
  390. package/out/{street-name-lookup.d.ts → street/name-lookup.d.ts} +4 -7
  391. package/out/street/name-lookup.d.ts.map +1 -0
  392. package/out/{street-name-lookup.js → street/name-lookup.js} +9 -23
  393. package/out/street/name-lookup.js.map +1 -0
  394. package/out/street/normalize.d.ts +179 -0
  395. package/out/street/normalize.d.ts.map +1 -0
  396. package/out/street/normalize.js +457 -0
  397. package/out/street/normalize.js.map +1 -0
  398. package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +13 -9
  399. package/out/street/segment-schema.d.ts.map +1 -0
  400. package/out/{street-segment-schema.js → street/segment-schema.js} +4 -4
  401. package/out/{street-segment-schema.js.map → street/segment-schema.js.map} +1 -1
  402. package/out/types.d.ts +49 -9
  403. package/out/types.d.ts.map +1 -1
  404. package/out/types.js.map +1 -1
  405. package/out/unified-schema.d.ts +6 -5
  406. package/out/unified-schema.d.ts.map +1 -1
  407. package/out/unified-schema.js +16 -19
  408. package/out/unified-schema.js.map +1 -1
  409. package/out/uprn/existence.d.ts +53 -0
  410. package/out/uprn/existence.d.ts.map +1 -0
  411. package/out/uprn/existence.js +61 -0
  412. package/out/uprn/existence.js.map +1 -0
  413. package/out/uprn/index.d.ts +9 -0
  414. package/out/uprn/index.d.ts.map +1 -0
  415. package/out/uprn/index.js +9 -0
  416. package/out/uprn/index.js.map +1 -0
  417. package/out/uprn/lookup.d.ts +85 -0
  418. package/out/uprn/lookup.d.ts.map +1 -0
  419. package/out/uprn/lookup.js +149 -0
  420. package/out/uprn/lookup.js.map +1 -0
  421. package/out/uprn/schema.d.ts +93 -0
  422. package/out/uprn/schema.d.ts.map +1 -0
  423. package/out/uprn/schema.js +78 -0
  424. package/out/uprn/schema.js.map +1 -0
  425. package/out/weights-overlay-linker.d.ts +299 -0
  426. package/out/weights-overlay-linker.d.ts.map +1 -0
  427. package/out/weights-overlay-linker.js +511 -0
  428. package/out/weights-overlay-linker.js.map +1 -0
  429. package/package.json +622 -154
  430. package/address-point.ts +0 -125
  431. package/candidate-lookup.ts +0 -720
  432. package/fst-autocomplete.ts +0 -204
  433. package/geo.ts +0 -121
  434. package/lookup.ts +0 -1354
  435. package/out/address-point-interpolation.d.ts.map +0 -1
  436. package/out/address-point-interpolation.js.map +0 -1
  437. package/out/address-point-schema.d.ts.map +0 -1
  438. package/out/address-point-schema.js.map +0 -1
  439. package/out/address-point.d.ts.map +0 -1
  440. package/out/address-point.js +0 -84
  441. package/out/address-point.js.map +0 -1
  442. package/out/ancestry-backfill.d.ts.map +0 -1
  443. package/out/ancestry-backfill.js.map +0 -1
  444. package/out/ancestry.d.ts.map +0 -1
  445. package/out/ancestry.js.map +0 -1
  446. package/out/convention.d.ts.map +0 -1
  447. package/out/convention.js.map +0 -1
  448. package/out/fst-autocomplete.d.ts.map +0 -1
  449. package/out/fst-autocomplete.js +0 -130
  450. package/out/fst-autocomplete.js.map +0 -1
  451. package/out/fst-builder.d.ts.map +0 -1
  452. package/out/fst-builder.js.map +0 -1
  453. package/out/fst-deserialize-web.d.ts.map +0 -1
  454. package/out/fst-deserialize-web.js.map +0 -1
  455. package/out/fst-freshness.d.ts.map +0 -1
  456. package/out/fst-freshness.js.map +0 -1
  457. package/out/fst-matcher.d.ts.map +0 -1
  458. package/out/fst-matcher.js.map +0 -1
  459. package/out/fst-serialize.d.ts.map +0 -1
  460. package/out/fst-serialize.js.map +0 -1
  461. package/out/fst-types.d.ts.map +0 -1
  462. package/out/fst-types.js.map +0 -1
  463. package/out/fts-query.d.ts.map +0 -1
  464. package/out/fts-query.js.map +0 -1
  465. package/out/fts.d.ts.map +0 -1
  466. package/out/fts.js.map +0 -1
  467. package/out/geo.d.ts +0 -74
  468. package/out/geo.d.ts.map +0 -1
  469. package/out/geo.js +0 -71
  470. package/out/geo.js.map +0 -1
  471. package/out/geonames-aliases.d.ts.map +0 -1
  472. package/out/geonames-aliases.js.map +0 -1
  473. package/out/geonames-postal.d.ts.map +0 -1
  474. package/out/geonames-postal.js.map +0 -1
  475. package/out/poi-lookup.d.ts.map +0 -1
  476. package/out/poi-lookup.js.map +0 -1
  477. package/out/poi-schema.d.ts.map +0 -1
  478. package/out/poi-schema.js.map +0 -1
  479. package/out/postal-city-alias-lookup.d.ts.map +0 -1
  480. package/out/postal-city-alias-lookup.js.map +0 -1
  481. package/out/postal-city-candidate-schema.d.ts.map +0 -1
  482. package/out/sharding.d.ts +0 -114
  483. package/out/sharding.d.ts.map +0 -1
  484. package/out/sharding.js.map +0 -1
  485. package/out/street-centroid-schema.d.ts.map +0 -1
  486. package/out/street-centroid.d.ts.map +0 -1
  487. package/out/street-centroid.js.map +0 -1
  488. package/out/street-morphology-fst-builder.d.ts.map +0 -1
  489. package/out/street-morphology-fst-builder.js.map +0 -1
  490. package/out/street-morphology-fst-loader.d.ts.map +0 -1
  491. package/out/street-morphology-fst-loader.js.map +0 -1
  492. package/out/street-name-lookup.d.ts.map +0 -1
  493. package/out/street-name-lookup.js.map +0 -1
  494. package/out/street-normalize.d.ts +0 -95
  495. package/out/street-normalize.d.ts.map +0 -1
  496. package/out/street-normalize.js +0 -272
  497. package/out/street-normalize.js.map +0 -1
  498. package/out/street-segment-schema.d.ts.map +0 -1
  499. package/sqlite-utils.ts +0 -46
  500. package/street-normalize.ts +0 -318
  501. /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * The name_key normalizer is the SHARED {@link normalizeLocalityForKey} — the query side (the demo
24
24
  * resolver {@link WOFCandidateTableLookup}) MUST use the same function, the one-normalizer
25
- * discipline the address-point shard uses, so build/query stay consistent by construction.
25
+ * discipline the address-point extract uses, so build/query stay consistent by construction.
26
26
  *
27
27
  * Measured (2026-06-20, vs the 2.6 GB full-DB FTS): ~5 M rows; ~12 range fetches per 8-query
28
28
  * session (the full DB needs 243); US locality 96.8% (region bbox), EU coord parity 88.6%.
@@ -32,27 +32,43 @@
32
32
  * `candidate-importance.ts`, which owns that join and explains why the id would be wrong). It is
33
33
  * optional: without {@link BuildCandidateOptions.importance} the column is NULL on every row, which
34
34
  * the consumer reads as unmeasured and ignores.
35
+ *
36
+ * The build also materializes the ANCESTORS SIDECAR (`candidate_ancestor` closure rows +
37
+ * `candidate_interval` pre/post labels) from the source `ancestors` table — the containment
38
+ * lineage behind {@link WOFCandidateTableLookup.ancestors} and the admin-coherence check.
39
+ * `candidate-ancestors-schema.ts` owns the encoding decision and the DAG/absence semantics.
35
40
  */
36
41
 
37
- import { existsSync, rmSync } from "node:fs"
38
- import { DatabaseSync } from "node:sqlite"
39
-
40
- import { DatabaseClient } from "@mailwoman/core/kysley/client"
42
+ import { COUNTRY_POPULATION } from "@mailwoman/codex/country"
43
+ import { pathExists } from "@mailwoman/core/fs/readers"
44
+ import { removePath } from "@mailwoman/core/fs/writers"
45
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
41
46
 
42
- import { createCandidateFTS } from "./candidate-fts.ts"
43
- import { IMPORTANCE_JOIN_GATE_KM, loadImportanceIndex } from "./candidate-importance.ts"
47
+ import { createCandidateFTS } from "#candidate-fts"
48
+ import { IMPORTANCE_JOIN_RADIUS_KM, loadImportanceIndex } from "#candidate-importance"
44
49
  import {
45
50
  CANDIDATE_COLUMNS,
46
51
  createCandidateStagingTables,
47
52
  createCandidateTable,
48
53
  type CandidateDatabase,
49
- } from "./candidate-schema.ts"
50
- import { normalizeLocalityForKey } from "./street-normalize.ts"
51
-
52
- /**
53
- * Boundary-preserving alias-bag separator (#523, U+E000).
54
- */
55
- const ALIAS_SEP = "\u{E000}"
54
+ } from "#candidate-schema"
55
+ import { explodeAliasBags } from "#candidate/alias-bags"
56
+ import { buildAncestorsSidecar } from "#candidate/ancestors-sidecar"
57
+ import { stageCountryDisplayNames } from "#candidate/country-display-names"
58
+ import { foldExtract } from "#candidate/extract-fold"
59
+ import { GLOSS_KEY_THRESHOLD, stampNameRoles } from "#candidate/name-roles"
60
+ import type { PlaceAttrs } from "#candidate/place-attrs"
61
+ import { createCapitalTable } from "#capital-schema"
62
+ import type { CapitalPoint } from "#capitals"
63
+ import { resurrectCurrencyHoles } from "#currency-backfill"
64
+ import type { WOFDatabase } from "#schema"
65
+ import { normalizeLocalityForKey } from "#street/normalize"
66
+
67
+ // The build's contract is this module path; the passes behind it live in `./candidate/`. Re-exported
68
+ // here so a consumer never has to know which pass owns which name.
69
+ export { stageCountryDisplayNames } from "#candidate/country-display-names"
70
+ export { GLOSS_EXCLUDED_PLACETYPES, GLOSS_KEY_THRESHOLD } from "#candidate/name-roles"
71
+ export type { PlaceAttrs } from "#candidate/place-attrs"
56
72
 
57
73
  export interface BuildCandidateOptions {
58
74
  /**
@@ -64,14 +80,28 @@ export interface BuildCandidateOptions {
64
80
  */
65
81
  output: string
66
82
  /**
67
- * Optional postcode shards (`spr` rows with `placetype='postalcode'` + real coords, e.g. postalcode-us.db) — folded
83
+ * The capital-status reference entries (#1880) to carry in-artifact — the parsed `data/gazetteer/capitals-v1.json`
84
+ * entries, passed by the CALLER because this module publishes to npm and must not read repo-root paths. Absent → the
85
+ * `capital` table is not created, and the session loader falls back to the repo file where one exists.
86
+ */
87
+ capitals?: readonly CapitalPoint[]
88
+ /**
89
+ * Optional postcode extracts (`spr` rows with `placetype='postalcode'` + real coords, e.g. postalcode-us.db) — folded
68
90
  * in as `postalcode` candidate rows so `findPlace(postalcode)` resolves a ZIP directly (the demo's primary postcode
69
91
  * path; the postcode-*.bin anchor stays the fallback). Matches the slim wof-hot.db, which took one such postcode DB.
70
92
  *
71
- * Each shard's `names` table is folded in too (#1495) — that's where the GeoNames delivery-city names live
93
+ * Each extract's `names` table is folded in too (#1495) — that's where the GeoNames delivery-city names live
72
94
  * ("Brooklyn" for 11201), and they were previously reachable only through FTS.
73
95
  */
74
96
  postcodes?: string[]
97
+ /**
98
+ * Optional LOCALITY extracts (`spr` rows with `placetype='locality'` + real coords, e.g. localities-nz-linz.db — the
99
+ * #1564 NZ suburb tier) — folded through the same extract loop as the postcode extracts, staged as `locality`
100
+ * candidate rows with no region scope and UNMEASURED population (`neg_rank 0`: a extract row ranks behind any
101
+ * populated namesake and wins only where its key is the answer). Each extract's `names` table folds as aliases,
102
+ * `is_primary = 0`, same as the delivery-city pass.
103
+ */
104
+ localities?: string[]
75
105
  /**
76
106
  * Optional WOF admin database carrying a `place_importance` table — the source of the `importance` column (#28), the
77
107
  * toponym-fame prior that decides the bare-city-name class. Joined by `(name_key, country, placetype)` + nearest
@@ -84,10 +114,36 @@ export interface BuildCandidateOptions {
84
114
  * population-derived stand-in written into a column that means fame.
85
115
  */
86
116
  importance?: string
117
+ /**
118
+ * Cross-source currency backfill (#1737). WOF carries deprecated-with-no-successor records for real, populated
119
+ * settlements (Rochester Kent, Aldershot, Telford — 120 GB localities alone), and the currency filter correctly drops
120
+ * them, leaving holes no ranking can fill. When this option is set, pass 1c resurrects a dead locality ONLY under
121
+ * three conditions, positive evidence throughout: no live same-name row of any placetype near the dead record (a
122
+ * distant same-name row is a NAMESAKE and does not block — Rochester, Northumberland pop 318 must not veto Rochester,
123
+ * Kent); an independent GeoNames P-class attestation of the same folded name within
124
+ * {@link CURRENCY_BACKFILL_RADIUS_KM}; and the attestor at or above {@link CURRENCY_BACKFILL_POP_FLOOR}. The staged
125
+ * row keeps the real WOF id, name, centroid and bbox — GeoNames only ATTESTS the place and supplies the population
126
+ * that lets it stand in prominence races. `countries` are judged only where `<cc>.txt` exists under `geonamesDir`;
127
+ * absent dumps are skipped loudly.
128
+ */
129
+ currencyBackfill?: {
130
+ geonamesDir: string
131
+ countries: readonly string[]
132
+ /**
133
+ * The dead placetypes the resurrection judges; `resurrectCurrencyHoles`'s default (`locality`) when absent. The
134
+ * `localadmin` widening #1746 named is admitted here once its census is read.
135
+ */
136
+ deadPlacetypes?: readonly string[]
137
+ }
87
138
  /**
88
139
  * Optional progress callback for CLI / test introspection.
89
140
  */
90
141
  onProgress?: (phase: string, message: string) => void
142
+ /**
143
+ * Key-count threshold for the gloss anomaly detector — {@link GLOSS_KEY_THRESHOLD} unless a test passes a
144
+ * fixture-scale value. The production number is the #1730 sweep's own threshold (4,000 places at >= 50 keys).
145
+ */
146
+ glossKeyThreshold?: number
91
147
  }
92
148
 
93
149
  export interface BuildCandidateResult {
@@ -98,11 +154,26 @@ export interface BuildCandidateResult {
98
154
  abbrevs: number
99
155
  postcodes: number
100
156
  /**
101
- * Delivery-city (and other `names`-table) aliases folded onto postcode rows — #1495. Zero here means the shards
102
- * carried no alias names, NOT that the pass was skipped: a shard with no `names` table reports that separately
157
+ * Delivery-city (and other `names`-table) aliases folded onto postcode rows — #1495. Zero here means the extracts
158
+ * carried no alias names, NOT that the pass was skipped: a extract with no `names` table reports that separately
103
159
  * through `onProgress`.
104
160
  */
105
161
  postcodeAliases: number
162
+ /**
163
+ * Closure rows in the `candidate_ancestor` sidecar (see candidate-ancestors-schema.ts). Zero means the source
164
+ * `ancestors` table contributed nothing within the resolvable placetypes — a finding, reported rather than implied.
165
+ */
166
+ ancestorRows: number
167
+ /**
168
+ * Places carrying at least one closure row.
169
+ */
170
+ ancestorPlaces: number
171
+ /**
172
+ * Places that received a pre/post interval label — the canonical-parent forest's node count. Places outside it (no
173
+ * recorded ancestry, extract rows, cycle-skipped) have NO label: containment against them is unverifiable, never
174
+ * false.
175
+ */
176
+ intervalPlaces: number
106
177
  /**
107
178
  * Places that took an `importance` score from the join (#28).
108
179
  *
@@ -112,49 +183,44 @@ export interface BuildCandidateResult {
112
183
  importanceScored?: number
113
184
  /**
114
185
  * Places whose `(name_key, country, placetype)` matched a scored group but whose nearest scored centroid was outside
115
- * {@link IMPORTANCE_JOIN_GATE_KM} — a different town wearing the same name, refused rather than scored.
186
+ * {@link IMPORTANCE_JOIN_RADIUS_KM} — a different town wearing the same name, refused rather than scored.
116
187
  *
117
188
  * Worth watching across rebuilds: a jump here means the score source and the admin source have drifted apart, and the
118
189
  * join is being asked to guess.
119
190
  */
120
- importanceGated?: number
121
- }
122
-
123
- interface PlaceAttrs {
124
- cid: number
125
- rid: number
126
- ptid: number
127
- name: string
128
- lat: number
129
- lon: number
130
- mnLat: number
131
- mnLon: number
132
- mxLat: number
133
- mxLon: number
134
- pop: number
135
- neg: number
136
- pkey: string
191
+ importanceFiltered?: number
192
+ /**
193
+ * Alias rows stamped `name_role = 'gloss'` — the anomaly detector's certain core (#1730).
194
+ */
195
+ roleGloss: number
196
+ /**
197
+ * Alias rows stamped `name_role = 'abbr'` — the variant-in-official-language provenance signal (#1730/#936).
198
+ */
199
+ roleAbbr: number
137
200
  /**
138
- * The place's toponym-fame score, or null when the score source has no measurement for it (#28). A property of the
139
- * PLACE, so it rides {@link stageRow} onto the alias and abbrev rows too — that is how a bare `Moscow` reaches
140
- * Москва's score through the alias row that carries the key.
201
+ * Admin places whose staged key count reached {@link GLOSS_KEY_THRESHOLD} — the sweep's tail, reported so the stamped
202
+ * fraction has its denominator.
141
203
  */
142
- imp: number | null
204
+ keyTailPlaces: number
205
+ /**
206
+ * Of {@link BuildCandidateResult.keyTailPlaces}, how many carry at least one stamped role row.
207
+ */
208
+ keyTailWithRole: number
143
209
  }
144
210
 
145
211
  export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<BuildCandidateResult> {
146
212
  const progress = opts.onProgress ?? (() => {})
147
213
 
148
- if (existsSync(opts.output)) {
149
- rmSync(opts.output)
214
+ if (await pathExists(opts.output)) {
215
+ await removePath(opts.output)
150
216
  }
151
217
 
152
- const src = new DatabaseSync(opts.input, { readOnly: true })
153
- const out = new DatabaseSync(opts.output)
218
+ using src = new DatabaseClient<WOFDatabase>(opts.input, { readOnly: true })
219
+ await using kdb = new DatabaseClient<CandidateDatabase>(opts.output)
154
220
  // Build-tuning pragmas (raw — Kysely doesn't model PRAGMA). The code dictionaries + the transient
155
221
  // staging table come from the SHARED schema DDL, so they can't drift from {@link CandidateDatabase}.
156
- out.exec("PRAGMA page_size=8192; PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-2000000;")
157
- const kdb = new DatabaseClient<CandidateDatabase>({ database: out })
222
+ kdb.exec("PRAGMA page_size=8192; PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-2000000;")
223
+
158
224
  await createCandidateStagingTables(kdb)
159
225
 
160
226
  // --- compact code maps (country/placetype → small int, shrinks the clustered key). The ids are
@@ -239,13 +305,13 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
239
305
  // The hot path — millions of clustered rows. Kept a single positional prepared statement (the fastest
240
306
  // node:sqlite insert) rather than a per-row query builder. Placeholders come from CANDIDATE_COLUMNS so
241
307
  // the column COUNT can't drift; the positional run() args below MUST stay in CANDIDATE_COLUMNS order.
242
- const insStage = out.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`)
308
+ const insStage = kdb.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`)
243
309
 
244
310
  // --- pass 1: primaries (and the per-place attrs the alias/abbrev passes reuse) ---
245
311
  progress("primaries", "indexing place names")
246
312
  const attrs = new Map<number, PlaceAttrs>()
247
313
  let nPrim = 0
248
- out.exec("BEGIN")
314
+ kdb.exec("BEGIN")
249
315
 
250
316
  for (const r of src
251
317
  .prepare(
@@ -261,7 +327,12 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
261
327
  const cid = ccID(r.country as string | null)
262
328
  const ptid = ptID(r.placetype as string | null)
263
329
  const rid = regionOf.get(sid) ?? 0
264
- const pop = Number(r.pop) || 0
330
+ // A zero population on a COUNTRY row is a WOF absence artifact, never a real zero — 147 of 237
331
+ // primary country records carried none (measured 2026-08-18, #1650), which ranked those nations
332
+ // below any namesake hamlet in every prominence race ("Georgia" → Georgia VT). The codex table is
333
+ // the secondary source; a country absent from it too stays at zero honestly.
334
+ const wofPop = Number(r.pop) || 0
335
+ const pop = wofPop === 0 && r.placetype === "country" ? (COUNTRY_POPULATION[String(r.country ?? "")] ?? 0) : wofPop
265
336
  const neg = -Math.log10(pop + 1)
266
337
  const name = String(r.name ?? "")
267
338
  const pkey = normalizeLocalityForKey(name)
@@ -304,21 +375,22 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
304
375
  a.mxLon,
305
376
  pop,
306
377
  1,
307
- a.imp
378
+ a.imp,
379
+ null
308
380
  )
309
381
 
310
382
  nPrim++
311
383
  }
312
384
  }
313
385
 
314
- out.exec("COMMIT")
386
+ kdb.exec("COMMIT")
315
387
  progress("primaries", `${nPrim.toLocaleString()} primaries; ${attrs.size.toLocaleString()} places`)
316
388
 
317
389
  if (importance) {
318
390
  progress(
319
391
  "importance",
320
392
  `${importance.matched.toLocaleString()} places scored; ` +
321
- `${importance.gated.toLocaleString()} refused (nearest same-name place > ${IMPORTANCE_JOIN_GATE_KM} km away)`
393
+ `${importance.refused.toLocaleString()} refused (nearest same-name place > ${IMPORTANCE_JOIN_RADIUS_KM} km away)`
322
394
  )
323
395
  }
324
396
 
@@ -339,39 +411,58 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
339
411
  a.mxLon,
340
412
  a.pop,
341
413
  isPrimary,
342
- a.imp
414
+ a.imp,
415
+ null
343
416
  )
344
417
  }
345
418
 
346
- // --- pass 2: distinct normalized aliases from place_search.alt_names ---
347
- progress("aliases", "exploding alias bags")
348
- let nAlias = 0
349
- out.exec("BEGIN")
350
-
351
- for (const r of src.prepare("SELECT wof_id, alt_names FROM place_search").iterate()) {
352
- const a = attrs.get(Number(r.wof_id))
353
- const alt = r.alt_names as string | null
354
-
355
- if (!a || !alt) continue
356
- const seen = new Set<string>([a.pkey])
357
-
358
- for (const piece of alt.split(ALIAS_SEP)) {
359
- const k = normalizeLocalityForKey(piece)
360
-
361
- if (!k || seen.has(k)) continue
362
- seen.add(k)
363
- stageRow(k, a, Number(r.wof_id), 0)
419
+ // --- pass 1b: country surfaces across scripts (#1678 thread 1) — see stageCountryDisplayNames ---
420
+ // Never a silent zero: a runtime whose ICU lacks these locales degrades to fewer surfaces, and the count is how a
421
+ // reader tells that apart from the pass not having run.
422
+ progress(
423
+ "country-display-names",
424
+ `${stageCountryDisplayNames({
425
+ attrs,
426
+ iso2ByID: new Map([...ccodes].map(([code, id]) => [id, code])),
427
+ countryPtID: ptID("country"),
428
+ stageRow,
429
+ tx: kdb,
430
+ }).toLocaleString()} country surfaces`
431
+ )
364
432
 
365
- nAlias++
366
- }
433
+ // --- pass 1c: cross-source currency backfill (#1737 — resurrectCurrencyHoles owns the checks). Runs BEFORE
434
+ // the alias pass so a resurrected place's alt names explode like any primary's. ---
435
+ if (opts.currencyBackfill) {
436
+ const nBackfill = await resurrectCurrencyHoles({
437
+ src,
438
+ tx: kdb,
439
+ geonamesDir: opts.currencyBackfill.geonamesDir,
440
+ countries: opts.currencyBackfill.countries,
441
+ ...(opts.currencyBackfill.deadPlacetypes ? { deadPlacetypes: opts.currencyBackfill.deadPlacetypes } : {}),
442
+ attrs,
443
+ ccID,
444
+ ptID,
445
+ regionOf,
446
+ importance,
447
+ stageRow,
448
+ progress,
449
+ })
450
+
451
+ progress("currency-backfill", `${nBackfill.toLocaleString()} resurrections staged`)
452
+ } else {
453
+ // Never a silent absence: a build without the option leaves the deprecated-no-successor holes dead,
454
+ // and the log must say so rather than leave it inferable only from a missing row.
455
+ progress("currency-backfill", "not configured — deprecated-no-successor holes stay dead (#1737)")
367
456
  }
368
457
 
369
- out.exec("COMMIT")
458
+ // --- pass 2: distinct normalized aliases from place_search.alt_names (explodeAliasBags owns the loop) ---
459
+ progress("aliases", "exploding alias bags")
460
+ const { nAlias, keyCounts } = explodeAliasBags(src, kdb, attrs, stageRow)
370
461
  progress("aliases", `${nAlias.toLocaleString()} aliases`)
371
462
 
372
463
  // --- pass 3: region abbreviations (place_abbr) ---
373
464
  let nAbbr = 0
374
- out.exec("BEGIN")
465
+ kdb.exec("BEGIN")
375
466
 
376
467
  for (const r of src.prepare("SELECT id, abbr FROM place_abbr").iterate()) {
377
468
  const a = attrs.get(Number(r.id))
@@ -385,128 +476,40 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
385
476
  nAbbr++
386
477
  }
387
478
 
388
- out.exec("COMMIT")
479
+ kdb.exec("COMMIT")
389
480
  progress("abbrevs", `${nAbbr.toLocaleString()} abbrevs`)
390
481
 
391
- /**
392
- * Pass 4 — fold ONE postcode shard (`spr` rows with `placetype='postalcode'`) in, then pass 4b: the delivery-city
393
- * aliases hanging off the same shard's `names` table.
394
- *
395
- * Extracted rather than inlined because the shard loop is self-contained — it shares only the staging statement and
396
- * the code dictionaries with the passes above, and nothing after it reads anything it produces except the two
397
- * counters it returns.
398
- */
399
- const foldPostcodeShard = (pcDB: string): { primaries: number; aliases: number } => {
400
- progress("postcodes", `reading ${pcDB}`)
401
-
402
- const pc = new DatabaseSync(pcDB, { readOnly: true })
403
- const pcPtid = ptID("postalcode")
404
- // Per-shard, not the admin `attrs` map: pass 1 only ever sees the admin DB, so the alias pass
405
- // below has nothing to join against unless this primary loop records what it staged.
406
- const pcAttrs = new Map<number, PlaceAttrs>()
407
- let primaries = 0
408
- let aliases = 0
409
-
410
- out.exec("BEGIN")
411
-
412
- for (const r of pc
413
- .prepare(
414
- `SELECT id, name, country, latitude, longitude,
415
- min_latitude AS mnlat, min_longitude AS mnlon, max_latitude AS mxlat, max_longitude AS mxlon
416
- FROM spr WHERE placetype='postalcode' AND latitude != 0 AND longitude != 0`
417
- )
418
- .iterate()) {
419
- const name = String(r.name ?? "")
420
- const key = normalizeLocalityForKey(name)
421
-
422
- if (!key) continue
423
-
424
- const lat = r.latitude as number
425
- const lon = r.longitude as number
426
-
427
- // region_id 0 (a postcode is unique by name+country — no same-name disambiguation); neg_rank 0
428
- // (no population). bbox = the postcode's own min/max (falls back to the centroid point).
429
- const a: PlaceAttrs = {
430
- cid: ccID(r.country as string | null),
431
- rid: 0,
432
- ptid: pcPtid,
433
- name,
434
- lat,
435
- lon,
436
- mnLat: (r.mnlat as number) || lat,
437
- mnLon: (r.mnlon as number) || lon,
438
- mxLat: (r.mxlat as number) || lat,
439
- mxLon: (r.mxlon as number) || lon,
440
- pop: 0,
441
- neg: 0,
442
- pkey: key,
443
- // A postcode has no toponym fame — nobody writes an encyclopedia article about SW1A 2AA — and
444
- // the score source carries no `postalcode` rows to join against anyway. NULL is the truthful
445
- // value: unmeasured, so the ranking key leaves postcode rows exactly where they were.
446
- imp: null,
447
- }
448
-
449
- pcAttrs.set(Number(r.id), a)
450
- stageRow(key, a, Number(r.id), 1)
451
-
452
- primaries++
453
- }
454
-
455
- out.exec("COMMIT")
456
-
457
- // --- pass 4b: postcode ALIAS names (#1495) ---
458
- //
459
- // The delivery-city names GeoNames supplies for a ZIP ("Brooklyn" for 11201) are written into
460
- // the shard's `names` table by `postcode/centroid-fills.ts`'s `geonamesNameFill`. Everything
461
- // downstream of `names` picked them up EXCEPT this build: `fts.ts` unions `spr.name` with every
462
- // `names` row into `place_search.alt_names`, so the FTS backend resolved "Brooklyn" → 11201
463
- // while the candidate backend — whose every row IS an exact-tier row — had no key for it at
464
- // all. Pass 2 does the equivalent fold for admin places, but reads the ADMIN `place_search`,
465
- // and `attrs` holds admin ids only, so a postcode shard could never reach it.
466
- //
467
- // Same discipline as pass 2: `is_primary = 0` (so `rankByPrimaryPreference` treats it as an
468
- // alias, not a canonical postcode name), the row stays denormalized onto the POSTCODE's own
469
- // spr_id/coords/bbox, and the display `name` stays the postcode — resolving "brooklyn" answers
470
- // with place 11201, it does not rename the place to its delivery city.
471
- const hasNames = pc.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='names'").get() !== undefined
472
-
473
- if (hasNames) {
474
- out.exec("BEGIN")
475
-
476
- for (const r of pc.prepare("SELECT id, name FROM names").iterate()) {
477
- const a = pcAttrs.get(Number(r.id))
478
-
479
- if (!a) continue
480
-
481
- const k = normalizeLocalityForKey(String(r.name ?? ""))
482
-
483
- // The postcode's own key is already staged as the primary; `INSERT OR IGNORE` at
484
- // materialization dedupes repeats, so this only skips the obvious self-alias.
485
- if (!k || k === a.pkey) continue
486
-
487
- stageRow(k, a, Number(r.id), 0)
488
-
489
- aliases++
490
- }
491
-
492
- out.exec("COMMIT")
493
- } else {
494
- // Never a silent zero: real shards come from `createUnifiedSchema`, which always creates
495
- // `names`. A shard without it has no alias surface to lose, but say so rather than reporting
496
- // "0 aliases" from a table that was never read.
497
- progress("postcode-aliases", `${pcDB} has no \`names\` table — no delivery-city aliases to fold`)
498
- }
499
-
500
- pc.close()
501
-
502
- return { primaries, aliases }
503
- }
504
-
482
+ // --- pass 3b: name roles (#1730 prototype — stampNameRoles owns the detectors) ---
483
+ // Independent of the sidecar below: this writes `cand_stage.name_role`, that writes the ancestor and
484
+ // interval tables, and neither reads the other's output. Ordered by label only.
485
+ const roles = stampNameRoles({
486
+ src,
487
+ out: kdb,
488
+ attrs,
489
+ keyCounts,
490
+ glossThreshold: opts.glossKeyThreshold ?? GLOSS_KEY_THRESHOLD,
491
+ ptcodes,
492
+ ccodes,
493
+ progress,
494
+ })
495
+
496
+ // --- pass 3c: the ancestors sidecar (candidate-ancestors-schema.ts owns the encoding decision) ---
497
+ const sidecar = await buildAncestorsSidecar({ src, out: kdb, attrs, ptID, progress })
498
+
499
+ // --- pass 4 + 4b: postcode and locality extracts (foldExtract owns the per-extract loop) ---
505
500
  let nPostcode = 0
506
501
  let nPostcodeAlias = 0
507
502
 
508
503
  for (const pcDB of opts.postcodes ?? []) {
509
- const folded = foldPostcodeShard(pcDB)
504
+ const folded = foldExtract({
505
+ out: kdb,
506
+ extractPath: pcDB,
507
+ extractPlacetype: "postalcode",
508
+ ccID,
509
+ ptID,
510
+ stageRow,
511
+ progress,
512
+ })
510
513
 
511
514
  nPostcode += folded.primaries
512
515
  nPostcodeAlias += folded.aliases
@@ -516,6 +519,26 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
516
519
  progress("postcodes", `${nPostcode.toLocaleString()} postcodes; ${nPostcodeAlias.toLocaleString()} aliases`)
517
520
  }
518
521
 
522
+ let nLocality = 0
523
+
524
+ for (const locDB of opts.localities ?? []) {
525
+ const folded = foldExtract({
526
+ out: kdb,
527
+ extractPath: locDB,
528
+ extractPlacetype: "locality",
529
+ ccID,
530
+ ptID,
531
+ stageRow,
532
+ progress,
533
+ })
534
+
535
+ nLocality += folded.primaries
536
+ }
537
+
538
+ if (nLocality > 0) {
539
+ progress("localities", `${nLocality.toLocaleString()} extract localities folded`)
540
+ }
541
+
519
542
  // --- code dictionaries: typed batch inserts via kdb (a few hundred rows — Kysely is clean here) ---
520
543
  if (ccodes.size) {
521
544
  await kdb
@@ -531,6 +554,26 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
531
554
  .execute()
532
555
  }
533
556
 
557
+ // --- capital-status reference (#1880's distribution home) — cold, small (~3.7k rows), typed ---
558
+ if (opts.capitals?.length) {
559
+ await createCapitalTable<CandidateDatabase>(kdb)
560
+
561
+ await kdb
562
+ .insertInto("capital")
563
+ .values(
564
+ opts.capitals.map((entry) => ({
565
+ country: entry.country,
566
+ latitude: entry.latitude,
567
+ longitude: entry.longitude,
568
+ level: entry.level,
569
+ keys: JSON.stringify(entry.k),
570
+ }))
571
+ )
572
+ .execute()
573
+
574
+ progress("capitals", `${opts.capitals.length.toLocaleString()} capital-reference rows carried in-artifact`)
575
+ }
576
+
534
577
  // --- materialize the clustered WITHOUT ROWID table (sorted insert → contiguous leaves) ---
535
578
  progress("cluster", "building clustered candidate table + VACUUM")
536
579
  // Column list + clustered-key order are sourced from CANDIDATE_COLUMNS (the first 6 ARE the PRIMARY
@@ -541,26 +584,23 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
541
584
  await createCandidateTable(kdb)
542
585
  // OR IGNORE: an abbrev/alias can normalize to a place's primary key (same place, same rank) → any one
543
586
  // row. The bulk sorted INSERT…SELECT (clustered materialization) stays raw — a single hot bulk statement.
544
- out.exec(`INSERT OR IGNORE INTO candidate (${cols}) SELECT ${cols} FROM cand_stage ORDER BY ${keyOrder};`)
587
+ kdb.exec(`INSERT OR IGNORE INTO candidate (${cols}) SELECT ${cols} FROM cand_stage ORDER BY ${keyOrder};`)
545
588
  await kdb.schema.dropTable("cand_stage").execute()
546
589
  // Typo-tolerant fallback index (the unified gazetteer's second mode): the exact name_key probe can't
547
590
  // recover misspellings, so FTS5-trigram over `name` lets the reader fuzzy-match on an exact+strip miss.
548
591
  progress("fts", "building FTS5-trigram fuzzy index")
549
- createCandidateFTS(out)
592
+ createCandidateFTS(kdb)
550
593
  // page_size MUST be set right before VACUUM: node:sqlite initializes the file at the 4096 default on
551
594
  // `new DatabaseSync`, so the creation-time pragma is a no-op — only a VACUUM rebuilds at the new size.
552
595
  // 8192 matches the sql.js-httpvfs 64 KiB request chunk cleanly (8 pages) and shallows the B-tree.
553
- out.exec("PRAGMA page_size=8192")
554
- out.exec("VACUUM")
596
+ kdb.exec("PRAGMA page_size=8192")
597
+ kdb.exec("VACUUM")
555
598
 
556
599
  const { n: rows } = await kdb
557
600
  .selectFrom("candidate")
558
601
  .select((eb) => eb.fn.countAll<number>().as("n"))
559
602
  .executeTakeFirstOrThrow()
560
603
 
561
- src.close()
562
- await kdb.destroy()
563
-
564
604
  // closes the underlying `out` connection
565
605
  return {
566
606
  rows,
@@ -570,6 +610,10 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
570
610
  abbrevs: nAbbr,
571
611
  postcodes: nPostcode,
572
612
  postcodeAliases: nPostcodeAlias,
573
- ...(importance ? { importanceScored: importance.matched, importanceGated: importance.gated } : {}),
613
+ ancestorRows: sidecar.ancestorRows,
614
+ ancestorPlaces: sidecar.ancestorPlaces,
615
+ intervalPlaces: sidecar.intervalPlaces,
616
+ ...roles,
617
+ ...(importance ? { importanceScored: importance.matched, importanceFiltered: importance.refused } : {}),
574
618
  }
575
619
  }