@mailwoman/resolver-wof-sqlite 9.4.0 → 10.1.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 (548) hide show
  1. package/README.md +8 -8
  2. package/lib/address/{point-interpolation.ts → point/interpolation.ts} +40 -38
  3. package/lib/address/point/schema.ts +132 -0
  4. package/lib/address/point.ts +68 -81
  5. package/lib/{address/index.ts → address.ts} +2 -2
  6. package/lib/ancestry/backfill.ts +24 -88
  7. package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
  8. package/lib/build/candidate.ts +608 -0
  9. package/lib/build/population-corrections.ts +64 -0
  10. package/lib/build/slim.ts +338 -0
  11. package/lib/candidate/alias-bags.ts +74 -14
  12. package/lib/candidate/ancestors/schema.ts +211 -0
  13. package/lib/candidate/{ancestors-sidecar.ts → ancestors/sidecar.ts} +17 -24
  14. package/lib/candidate/country-display-names.ts +6 -20
  15. package/lib/candidate/extract-fold.ts +12 -57
  16. package/lib/candidate/fts.ts +28 -0
  17. package/lib/candidate/importance.ts +221 -0
  18. package/lib/candidate/lookup.ts +669 -0
  19. package/lib/candidate/name-roles.ts +16 -34
  20. package/lib/candidate/own-name.ts +34 -31
  21. package/lib/candidate/place-attrs.ts +20 -13
  22. package/lib/candidate/schema.ts +211 -0
  23. package/lib/candidate/scoring.ts +222 -0
  24. package/lib/capital-schema.ts +21 -17
  25. package/lib/capitals.ts +26 -24
  26. package/lib/coincident-roles.ts +74 -55
  27. package/lib/convention/schema.ts +13 -7
  28. package/lib/convention.ts +144 -0
  29. package/lib/coverage-manifest-schema.ts +34 -39
  30. package/lib/currency-backfill.ts +24 -69
  31. package/lib/env.ts +1 -1
  32. package/lib/exact-match.ts +31 -20
  33. package/lib/extracts.ts +96 -73
  34. package/lib/fst/autocomplete.ts +52 -35
  35. package/lib/fst/builder.ts +30 -36
  36. package/lib/fst/deserialize-web.ts +8 -9
  37. package/lib/fst/format.ts +41 -23
  38. package/lib/fst/freshness.ts +17 -95
  39. package/lib/fst/matcher.ts +2 -2
  40. package/lib/fst/serialize.ts +45 -60
  41. package/lib/fst/types.ts +71 -59
  42. package/lib/fts/query.ts +5 -36
  43. package/lib/fts.ts +280 -0
  44. package/lib/geonames/aliases.ts +72 -149
  45. package/lib/geonames/postal.ts +162 -77
  46. package/lib/index.ts +20 -27
  47. package/lib/interpolation.ts +43 -96
  48. package/lib/lookup.ts +137 -257
  49. package/lib/name-score.ts +9 -7
  50. package/lib/nsul/lookup.ts +36 -53
  51. package/lib/nsul/schema.ts +16 -43
  52. package/lib/paths.ts +159 -0
  53. package/lib/place-importance-schema.ts +57 -164
  54. package/lib/poi/lookup.ts +100 -109
  55. package/lib/poi/schema.ts +42 -26
  56. package/lib/polygon-schema.ts +10 -6
  57. package/lib/postal/city/alias/lookup.ts +79 -0
  58. package/lib/postal/{city-alias-schema.ts → city/alias/schema.ts} +14 -10
  59. package/lib/postal/{city-candidate-schema.ts → city/candidate-schema.ts} +26 -20
  60. package/lib/postal.ts +9 -0
  61. package/lib/postcode-point-lookup.ts +9 -19
  62. package/lib/primary-preference.ts +45 -125
  63. package/lib/proximity-rerank.ts +43 -32
  64. package/lib/ranking-weights.ts +98 -76
  65. package/lib/region-keys.ts +68 -40
  66. package/lib/reverse.ts +108 -72
  67. package/lib/schema.ts +74 -42
  68. package/lib/search-fetch.ts +90 -68
  69. package/lib/sqlite/convention-source.ts +57 -0
  70. package/lib/sqlite/utils.ts +79 -0
  71. package/lib/street/centroid/schema.ts +149 -0
  72. package/lib/street/centroid.ts +16 -25
  73. package/lib/street/{morphology-fst-builder.ts → morphology/fst/builder.ts} +56 -41
  74. package/lib/street/{morphology-fst-loader.ts → morphology/fst/loader.ts} +36 -29
  75. package/lib/street/name-lookup.ts +43 -35
  76. package/lib/street/normalize.ts +85 -216
  77. package/lib/street/segment-schema.ts +116 -43
  78. package/lib/{street/index.ts → street.ts} +3 -3
  79. package/lib/types.ts +94 -140
  80. package/lib/unified-schema.ts +29 -29
  81. package/lib/uprn/existence.ts +30 -19
  82. package/lib/uprn/lookup.ts +28 -67
  83. package/lib/uprn/schema.ts +18 -40
  84. package/lib/weights-overlay-linker.ts +204 -232
  85. package/out/address/point/interpolation.d.ts +45 -0
  86. package/out/address/point/interpolation.d.ts.map +1 -0
  87. package/out/address/{point-interpolation.js → point/interpolation.js} +38 -38
  88. package/out/address/point/interpolation.js.map +1 -0
  89. package/out/address/point/schema.d.ts +74 -0
  90. package/out/address/point/schema.d.ts.map +1 -0
  91. package/out/address/{point-schema.js → point/schema.js} +4 -20
  92. package/out/address/point/schema.js.map +1 -0
  93. package/out/address/point.d.ts +24 -21
  94. package/out/address/point.d.ts.map +1 -1
  95. package/out/address/point.js +60 -77
  96. package/out/address/point.js.map +1 -1
  97. package/out/{address/index.js → address.d.ts} +3 -3
  98. package/out/address.d.ts.map +1 -0
  99. package/out/{address/index.d.ts → address.js} +3 -3
  100. package/out/address.js.map +1 -0
  101. package/out/ancestry/backfill.d.ts +11 -64
  102. package/out/ancestry/backfill.d.ts.map +1 -1
  103. package/out/ancestry/backfill.js +18 -76
  104. package/out/ancestry/backfill.js.map +1 -1
  105. package/out/ancestry.d.ts +49 -0
  106. package/out/ancestry.d.ts.map +1 -0
  107. package/out/ancestry.js +59 -0
  108. package/out/ancestry.js.map +1 -0
  109. package/out/build/candidate.d.ts +170 -0
  110. package/out/build/candidate.d.ts.map +1 -0
  111. package/out/{build-candidate.js → build/candidate.js} +33 -102
  112. package/out/build/candidate.js.map +1 -0
  113. package/out/build/population-corrections.d.ts +52 -0
  114. package/out/build/population-corrections.d.ts.map +1 -0
  115. package/out/build/population-corrections.js +40 -0
  116. package/out/build/population-corrections.js.map +1 -0
  117. package/out/build/slim.d.ts +44 -0
  118. package/out/build/slim.d.ts.map +1 -0
  119. package/out/{build-slim.js → build/slim.js} +33 -94
  120. package/out/build/slim.js.map +1 -0
  121. package/out/candidate/alias-bags.d.ts +13 -5
  122. package/out/candidate/alias-bags.d.ts.map +1 -1
  123. package/out/candidate/alias-bags.js +55 -12
  124. package/out/candidate/alias-bags.js.map +1 -1
  125. package/out/candidate/ancestors/schema.d.ts +166 -0
  126. package/out/candidate/ancestors/schema.d.ts.map +1 -0
  127. package/out/candidate/ancestors/schema.js +131 -0
  128. package/out/candidate/ancestors/schema.js.map +1 -0
  129. package/out/candidate/ancestors/sidecar.d.ts +27 -0
  130. package/out/candidate/ancestors/sidecar.d.ts.map +1 -0
  131. package/out/candidate/{ancestors-sidecar.js → ancestors/sidecar.js} +17 -24
  132. package/out/candidate/ancestors/sidecar.js.map +1 -0
  133. package/out/candidate/country-display-names.d.ts +5 -15
  134. package/out/candidate/country-display-names.d.ts.map +1 -1
  135. package/out/candidate/country-display-names.js +6 -20
  136. package/out/candidate/country-display-names.js.map +1 -1
  137. package/out/candidate/extract-fold.d.ts +8 -16
  138. package/out/candidate/extract-fold.d.ts.map +1 -1
  139. package/out/candidate/extract-fold.js +6 -48
  140. package/out/candidate/extract-fold.js.map +1 -1
  141. package/out/candidate/fts.d.ts +19 -0
  142. package/out/candidate/fts.d.ts.map +1 -0
  143. package/out/candidate/fts.js +22 -0
  144. package/out/candidate/fts.js.map +1 -0
  145. package/out/candidate/importance.d.ts +127 -0
  146. package/out/candidate/importance.d.ts.map +1 -0
  147. package/out/candidate/importance.js +162 -0
  148. package/out/candidate/importance.js.map +1 -0
  149. package/out/candidate/lookup.d.ts +36 -0
  150. package/out/candidate/lookup.d.ts.map +1 -0
  151. package/out/candidate/lookup.js +456 -0
  152. package/out/candidate/lookup.js.map +1 -0
  153. package/out/candidate/name-roles.d.ts +10 -22
  154. package/out/candidate/name-roles.d.ts.map +1 -1
  155. package/out/candidate/name-roles.js +15 -33
  156. package/out/candidate/name-roles.js.map +1 -1
  157. package/out/candidate/own-name.d.ts +23 -25
  158. package/out/candidate/own-name.d.ts.map +1 -1
  159. package/out/candidate/own-name.js +34 -31
  160. package/out/candidate/own-name.js.map +1 -1
  161. package/out/candidate/place-attrs.d.ts +20 -13
  162. package/out/candidate/place-attrs.d.ts.map +1 -1
  163. package/out/candidate/place-attrs.js +4 -5
  164. package/out/candidate/place-attrs.js.map +1 -1
  165. package/out/candidate/schema.d.ts +120 -0
  166. package/out/candidate/schema.d.ts.map +1 -0
  167. package/out/{candidate-schema.js → candidate/schema.js} +11 -23
  168. package/out/candidate/schema.js.map +1 -0
  169. package/out/{candidate-scoring.d.ts → candidate/scoring.d.ts} +8 -9
  170. package/out/candidate/scoring.d.ts.map +1 -0
  171. package/out/candidate/scoring.js +157 -0
  172. package/out/candidate/scoring.js.map +1 -0
  173. package/out/capital-schema.d.ts +21 -17
  174. package/out/capital-schema.d.ts.map +1 -1
  175. package/out/capital-schema.js +17 -15
  176. package/out/capital-schema.js.map +1 -1
  177. package/out/capitals.d.ts +26 -24
  178. package/out/capitals.d.ts.map +1 -1
  179. package/out/capitals.js +23 -21
  180. package/out/capitals.js.map +1 -1
  181. package/out/coincident-roles.d.ts +60 -43
  182. package/out/coincident-roles.d.ts.map +1 -1
  183. package/out/coincident-roles.js +61 -50
  184. package/out/coincident-roles.js.map +1 -1
  185. package/out/convention/schema.d.ts +12 -6
  186. package/out/convention/schema.d.ts.map +1 -1
  187. package/out/convention/schema.js +6 -4
  188. package/out/convention/schema.js.map +1 -1
  189. package/out/convention.d.ts +97 -0
  190. package/out/convention.d.ts.map +1 -0
  191. package/out/convention.js +81 -0
  192. package/out/convention.js.map +1 -0
  193. package/out/coverage-manifest-schema.d.ts +33 -38
  194. package/out/coverage-manifest-schema.d.ts.map +1 -1
  195. package/out/coverage-manifest-schema.js +20 -29
  196. package/out/coverage-manifest-schema.js.map +1 -1
  197. package/out/currency-backfill.d.ts +13 -35
  198. package/out/currency-backfill.d.ts.map +1 -1
  199. package/out/currency-backfill.js +12 -39
  200. package/out/currency-backfill.js.map +1 -1
  201. package/out/env.d.ts +2 -2
  202. package/out/env.js +1 -1
  203. package/out/exact-match.d.ts +19 -10
  204. package/out/exact-match.d.ts.map +1 -1
  205. package/out/exact-match.js +31 -20
  206. package/out/exact-match.js.map +1 -1
  207. package/out/extracts.d.ts +65 -54
  208. package/out/extracts.d.ts.map +1 -1
  209. package/out/extracts.js +63 -55
  210. package/out/extracts.js.map +1 -1
  211. package/out/fst/autocomplete.d.ts +28 -20
  212. package/out/fst/autocomplete.d.ts.map +1 -1
  213. package/out/fst/autocomplete.js +38 -27
  214. package/out/fst/autocomplete.js.map +1 -1
  215. package/out/fst/builder.d.ts.map +1 -1
  216. package/out/fst/builder.js +30 -36
  217. package/out/fst/builder.js.map +1 -1
  218. package/out/fst/deserialize-web.d.ts +3 -3
  219. package/out/fst/deserialize-web.d.ts.map +1 -1
  220. package/out/fst/deserialize-web.js +8 -9
  221. package/out/fst/deserialize-web.js.map +1 -1
  222. package/out/fst/format.d.ts +41 -23
  223. package/out/fst/format.d.ts.map +1 -1
  224. package/out/fst/format.js +41 -23
  225. package/out/fst/format.js.map +1 -1
  226. package/out/fst/freshness.d.ts +11 -73
  227. package/out/fst/freshness.d.ts.map +1 -1
  228. package/out/fst/freshness.js +12 -76
  229. package/out/fst/freshness.js.map +1 -1
  230. package/out/fst/matcher.d.ts +1 -1
  231. package/out/fst/matcher.d.ts.map +1 -1
  232. package/out/fst/matcher.js +2 -2
  233. package/out/fst/matcher.js.map +1 -1
  234. package/out/fst/serialize.d.ts +30 -26
  235. package/out/fst/serialize.d.ts.map +1 -1
  236. package/out/fst/serialize.js +44 -58
  237. package/out/fst/serialize.js.map +1 -1
  238. package/out/fst/types.d.ts +71 -59
  239. package/out/fst/types.d.ts.map +1 -1
  240. package/out/fst/types.js +1 -4
  241. package/out/fst/types.js.map +1 -1
  242. package/out/{fst/index.js → fst.d.ts} +1 -1
  243. package/out/fst.d.ts.map +1 -0
  244. package/out/{fst/index.d.ts → fst.js} +1 -1
  245. package/out/fst.js.map +1 -0
  246. package/out/fts/query.d.ts +2 -27
  247. package/out/fts/query.d.ts.map +1 -1
  248. package/out/fts/query.js +5 -36
  249. package/out/fts/query.js.map +1 -1
  250. package/out/fts.d.ts +126 -0
  251. package/out/fts.d.ts.map +1 -0
  252. package/out/fts.js +188 -0
  253. package/out/fts.js.map +1 -0
  254. package/out/geonames/aliases.d.ts +26 -67
  255. package/out/geonames/aliases.d.ts.map +1 -1
  256. package/out/geonames/aliases.js +44 -121
  257. package/out/geonames/aliases.js.map +1 -1
  258. package/out/geonames/postal.d.ts +84 -50
  259. package/out/geonames/postal.d.ts.map +1 -1
  260. package/out/geonames/postal.js +115 -78
  261. package/out/geonames/postal.js.map +1 -1
  262. package/out/{geonames/index.js → geonames.d.ts} +1 -1
  263. package/out/geonames.d.ts.map +1 -0
  264. package/out/{geonames/index.d.ts → geonames.js} +1 -1
  265. package/out/geonames.js.map +1 -0
  266. package/out/index.d.ts +20 -21
  267. package/out/index.d.ts.map +1 -1
  268. package/out/index.js +14 -15
  269. package/out/index.js.map +1 -1
  270. package/out/interpolation.d.ts +15 -52
  271. package/out/interpolation.d.ts.map +1 -1
  272. package/out/interpolation.js +25 -68
  273. package/out/interpolation.js.map +1 -1
  274. package/out/lookup.d.ts +24 -53
  275. package/out/lookup.d.ts.map +1 -1
  276. package/out/lookup.js +117 -223
  277. package/out/lookup.js.map +1 -1
  278. package/out/name-score.d.ts +2 -2
  279. package/out/name-score.d.ts.map +1 -1
  280. package/out/name-score.js +9 -7
  281. package/out/name-score.js.map +1 -1
  282. package/out/nsul/lookup.d.ts +33 -34
  283. package/out/nsul/lookup.d.ts.map +1 -1
  284. package/out/nsul/lookup.js +26 -40
  285. package/out/nsul/lookup.js.map +1 -1
  286. package/out/nsul/schema.d.ts +16 -43
  287. package/out/nsul/schema.d.ts.map +1 -1
  288. package/out/nsul/schema.js +9 -35
  289. package/out/nsul/schema.js.map +1 -1
  290. package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
  291. package/out/nsul.d.ts.map +1 -0
  292. package/out/{nsul/index.js → nsul.js} +1 -1
  293. package/out/nsul.js.map +1 -0
  294. package/out/paths.d.ts +124 -0
  295. package/out/paths.d.ts.map +1 -0
  296. package/out/paths.js +104 -0
  297. package/out/paths.js.map +1 -0
  298. package/out/place-importance-schema.d.ts +49 -141
  299. package/out/place-importance-schema.d.ts.map +1 -1
  300. package/out/place-importance-schema.js +25 -140
  301. package/out/place-importance-schema.js.map +1 -1
  302. package/out/poi/lookup.d.ts +38 -42
  303. package/out/poi/lookup.d.ts.map +1 -1
  304. package/out/poi/lookup.js +67 -90
  305. package/out/poi/lookup.js.map +1 -1
  306. package/out/poi/schema.d.ts +41 -25
  307. package/out/poi/schema.d.ts.map +1 -1
  308. package/out/poi/schema.js +25 -17
  309. package/out/poi/schema.js.map +1 -1
  310. package/out/{poi/index.js → poi.d.ts} +1 -1
  311. package/out/poi.d.ts.map +1 -0
  312. package/out/{poi/index.d.ts → poi.js} +1 -1
  313. package/out/poi.js.map +1 -0
  314. package/out/polygon-schema.d.ts +10 -6
  315. package/out/polygon-schema.d.ts.map +1 -1
  316. package/out/polygon-schema.js +2 -2
  317. package/out/polygon-schema.js.map +1 -1
  318. package/out/postal/{city-alias-lookup.d.ts → city/alias/lookup.d.ts} +22 -25
  319. package/out/postal/city/alias/lookup.d.ts.map +1 -0
  320. package/out/postal/city/alias/lookup.js +49 -0
  321. package/out/postal/city/alias/lookup.js.map +1 -0
  322. package/out/postal/{city-alias-schema.d.ts → city/alias/schema.d.ts} +15 -11
  323. package/out/postal/city/alias/schema.d.ts.map +1 -0
  324. package/out/postal/{city-alias-schema.js → city/alias/schema.js} +11 -9
  325. package/out/postal/city/alias/schema.js.map +1 -0
  326. package/out/postal/city/candidate-schema.d.ts +72 -0
  327. package/out/postal/city/candidate-schema.d.ts.map +1 -0
  328. package/out/postal/city/candidate-schema.js +60 -0
  329. package/out/postal/city/candidate-schema.js.map +1 -0
  330. package/out/postal.d.ts +9 -0
  331. package/out/postal.d.ts.map +1 -0
  332. package/out/postal.js +9 -0
  333. package/out/postal.js.map +1 -0
  334. package/out/postcode-point-lookup.d.ts +9 -19
  335. package/out/postcode-point-lookup.d.ts.map +1 -1
  336. package/out/postcode-point-lookup.js +5 -17
  337. package/out/postcode-point-lookup.js.map +1 -1
  338. package/out/primary-preference.d.ts +39 -93
  339. package/out/primary-preference.d.ts.map +1 -1
  340. package/out/primary-preference.js +13 -97
  341. package/out/primary-preference.js.map +1 -1
  342. package/out/proximity-rerank.d.ts +40 -29
  343. package/out/proximity-rerank.d.ts.map +1 -1
  344. package/out/proximity-rerank.js +38 -29
  345. package/out/proximity-rerank.js.map +1 -1
  346. package/out/ranking-weights.d.ts +91 -62
  347. package/out/ranking-weights.d.ts.map +1 -1
  348. package/out/ranking-weights.js +26 -25
  349. package/out/ranking-weights.js.map +1 -1
  350. package/out/region-keys.d.ts +48 -29
  351. package/out/region-keys.d.ts.map +1 -1
  352. package/out/region-keys.js +68 -40
  353. package/out/region-keys.js.map +1 -1
  354. package/out/reverse.d.ts +75 -50
  355. package/out/reverse.d.ts.map +1 -1
  356. package/out/reverse.js +61 -49
  357. package/out/reverse.js.map +1 -1
  358. package/out/schema.d.ts +74 -42
  359. package/out/schema.d.ts.map +1 -1
  360. package/out/schema.js +4 -5
  361. package/out/schema.js.map +1 -1
  362. package/out/search-fetch.d.ts +16 -12
  363. package/out/search-fetch.d.ts.map +1 -1
  364. package/out/search-fetch.js +83 -63
  365. package/out/search-fetch.js.map +1 -1
  366. package/out/sqlite/convention-source.d.ts +25 -0
  367. package/out/sqlite/convention-source.d.ts.map +1 -0
  368. package/out/sqlite/convention-source.js +48 -0
  369. package/out/sqlite/convention-source.js.map +1 -0
  370. package/out/sqlite/utils.d.ts +45 -0
  371. package/out/sqlite/utils.d.ts.map +1 -0
  372. package/out/sqlite/utils.js +56 -0
  373. package/out/sqlite/utils.js.map +1 -0
  374. package/out/street/centroid/schema.d.ts +100 -0
  375. package/out/street/centroid/schema.d.ts.map +1 -0
  376. package/out/street/centroid/schema.js +66 -0
  377. package/out/street/centroid/schema.js.map +1 -0
  378. package/out/street/centroid.d.ts +9 -19
  379. package/out/street/centroid.d.ts.map +1 -1
  380. package/out/street/centroid.js +11 -22
  381. package/out/street/centroid.js.map +1 -1
  382. package/out/street/{morphology-fst-builder.d.ts → morphology/fst/builder.d.ts} +21 -11
  383. package/out/street/morphology/fst/builder.d.ts.map +1 -0
  384. package/out/street/{morphology-fst-builder.js → morphology/fst/builder.js} +40 -34
  385. package/out/street/morphology/fst/builder.js.map +1 -0
  386. package/out/street/morphology/fst/loader.d.ts +74 -0
  387. package/out/street/morphology/fst/loader.d.ts.map +1 -0
  388. package/out/street/{morphology-fst-loader.js → morphology/fst/loader.js} +21 -21
  389. package/out/street/morphology/fst/loader.js.map +1 -0
  390. package/out/street/name-lookup.d.ts +24 -18
  391. package/out/street/name-lookup.d.ts.map +1 -1
  392. package/out/street/name-lookup.js +32 -31
  393. package/out/street/name-lookup.js.map +1 -1
  394. package/out/street/normalize.d.ts +35 -136
  395. package/out/street/normalize.d.ts.map +1 -1
  396. package/out/street/normalize.js +74 -191
  397. package/out/street/normalize.js.map +1 -1
  398. package/out/street/segment-schema.d.ts +79 -39
  399. package/out/street/segment-schema.d.ts.map +1 -1
  400. package/out/street/segment-schema.js +71 -21
  401. package/out/street/segment-schema.js.map +1 -1
  402. package/out/{street/index.d.ts → street.d.ts} +4 -4
  403. package/out/street.d.ts.map +1 -0
  404. package/out/{street/index.js → street.js} +4 -4
  405. package/out/street.js.map +1 -0
  406. package/out/types.d.ts +69 -138
  407. package/out/types.d.ts.map +1 -1
  408. package/out/types.js +0 -6
  409. package/out/types.js.map +1 -1
  410. package/out/unified-schema.d.ts +8 -12
  411. package/out/unified-schema.d.ts.map +1 -1
  412. package/out/unified-schema.js +29 -29
  413. package/out/unified-schema.js.map +1 -1
  414. package/out/uprn/existence.d.ts +28 -17
  415. package/out/uprn/existence.d.ts.map +1 -1
  416. package/out/uprn/existence.js +26 -16
  417. package/out/uprn/existence.js.map +1 -1
  418. package/out/uprn/lookup.d.ts +14 -44
  419. package/out/uprn/lookup.d.ts.map +1 -1
  420. package/out/uprn/lookup.js +20 -56
  421. package/out/uprn/lookup.js.map +1 -1
  422. package/out/uprn/schema.d.ts +18 -40
  423. package/out/uprn/schema.d.ts.map +1 -1
  424. package/out/uprn/schema.js +10 -32
  425. package/out/uprn/schema.js.map +1 -1
  426. package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
  427. package/out/uprn.d.ts.map +1 -0
  428. package/out/{uprn/index.js → uprn.js} +1 -1
  429. package/out/uprn.js.map +1 -0
  430. package/out/weights-overlay-linker.d.ts +132 -141
  431. package/out/weights-overlay-linker.d.ts.map +1 -1
  432. package/out/weights-overlay-linker.js +86 -154
  433. package/out/weights-overlay-linker.js.map +1 -1
  434. package/package.json +94 -599
  435. package/lib/address/point-schema.ts +0 -155
  436. package/lib/build-candidate.ts +0 -632
  437. package/lib/build-slim.ts +0 -408
  438. package/lib/candidate-ancestors-schema.ts +0 -195
  439. package/lib/candidate-fts.ts +0 -46
  440. package/lib/candidate-importance.ts +0 -224
  441. package/lib/candidate-lookup.ts +0 -977
  442. package/lib/candidate-schema.ts +0 -247
  443. package/lib/candidate-scoring.ts +0 -261
  444. package/lib/convention/index.ts +0 -154
  445. package/lib/fts/index.ts +0 -361
  446. package/lib/postal/city-alias-lookup.ts +0 -97
  447. package/lib/postal/index.ts +0 -9
  448. package/lib/sqlite-convention-source.ts +0 -65
  449. package/lib/sqlite-utils.ts +0 -82
  450. package/lib/street/centroid-schema.ts +0 -154
  451. package/out/address/index.d.ts.map +0 -1
  452. package/out/address/index.js.map +0 -1
  453. package/out/address/point-interpolation.d.ts +0 -49
  454. package/out/address/point-interpolation.d.ts.map +0 -1
  455. package/out/address/point-interpolation.js.map +0 -1
  456. package/out/address/point-schema.d.ts +0 -97
  457. package/out/address/point-schema.d.ts.map +0 -1
  458. package/out/address/point-schema.js.map +0 -1
  459. package/out/ancestry/index.d.ts +0 -45
  460. package/out/ancestry/index.d.ts.map +0 -1
  461. package/out/ancestry/index.js +0 -55
  462. package/out/ancestry/index.js.map +0 -1
  463. package/out/build-candidate.d.ts +0 -184
  464. package/out/build-candidate.d.ts.map +0 -1
  465. package/out/build-candidate.js.map +0 -1
  466. package/out/build-slim.d.ts +0 -80
  467. package/out/build-slim.d.ts.map +0 -1
  468. package/out/build-slim.js.map +0 -1
  469. package/out/candidate/ancestors-sidecar.d.ts +0 -32
  470. package/out/candidate/ancestors-sidecar.d.ts.map +0 -1
  471. package/out/candidate/ancestors-sidecar.js.map +0 -1
  472. package/out/candidate-ancestors-schema.d.ts +0 -150
  473. package/out/candidate-ancestors-schema.d.ts.map +0 -1
  474. package/out/candidate-ancestors-schema.js +0 -123
  475. package/out/candidate-ancestors-schema.js.map +0 -1
  476. package/out/candidate-fts.d.ts +0 -37
  477. package/out/candidate-fts.d.ts.map +0 -1
  478. package/out/candidate-fts.js +0 -40
  479. package/out/candidate-fts.js.map +0 -1
  480. package/out/candidate-importance.d.ts +0 -132
  481. package/out/candidate-importance.d.ts.map +0 -1
  482. package/out/candidate-importance.js +0 -169
  483. package/out/candidate-importance.js.map +0 -1
  484. package/out/candidate-lookup.d.ts +0 -76
  485. package/out/candidate-lookup.d.ts.map +0 -1
  486. package/out/candidate-lookup.js +0 -768
  487. package/out/candidate-lookup.js.map +0 -1
  488. package/out/candidate-schema.d.ts +0 -155
  489. package/out/candidate-schema.d.ts.map +0 -1
  490. package/out/candidate-schema.js.map +0 -1
  491. package/out/candidate-scoring.d.ts.map +0 -1
  492. package/out/candidate-scoring.js +0 -196
  493. package/out/candidate-scoring.js.map +0 -1
  494. package/out/convention/index.d.ts +0 -106
  495. package/out/convention/index.d.ts.map +0 -1
  496. package/out/convention/index.js +0 -93
  497. package/out/convention/index.js.map +0 -1
  498. package/out/fst/index.d.ts.map +0 -1
  499. package/out/fst/index.js.map +0 -1
  500. package/out/fts/index.d.ts +0 -184
  501. package/out/fts/index.d.ts.map +0 -1
  502. package/out/fts/index.js +0 -277
  503. package/out/fts/index.js.map +0 -1
  504. package/out/geonames/index.d.ts.map +0 -1
  505. package/out/geonames/index.js.map +0 -1
  506. package/out/nsul/index.d.ts.map +0 -1
  507. package/out/nsul/index.js.map +0 -1
  508. package/out/poi/index.d.ts.map +0 -1
  509. package/out/poi/index.js.map +0 -1
  510. package/out/postal/city-alias-lookup.d.ts.map +0 -1
  511. package/out/postal/city-alias-lookup.js +0 -62
  512. package/out/postal/city-alias-lookup.js.map +0 -1
  513. package/out/postal/city-alias-schema.d.ts.map +0 -1
  514. package/out/postal/city-alias-schema.js.map +0 -1
  515. package/out/postal/city-candidate-schema.d.ts +0 -66
  516. package/out/postal/city-candidate-schema.d.ts.map +0 -1
  517. package/out/postal/city-candidate-schema.js +0 -56
  518. package/out/postal/city-candidate-schema.js.map +0 -1
  519. package/out/postal/index.d.ts +0 -9
  520. package/out/postal/index.d.ts.map +0 -1
  521. package/out/postal/index.js +0 -9
  522. package/out/postal/index.js.map +0 -1
  523. package/out/sqlite-convention-source.d.ts +0 -29
  524. package/out/sqlite-convention-source.d.ts.map +0 -1
  525. package/out/sqlite-convention-source.js +0 -57
  526. package/out/sqlite-convention-source.js.map +0 -1
  527. package/out/sqlite-utils.d.ts +0 -45
  528. package/out/sqlite-utils.d.ts.map +0 -1
  529. package/out/sqlite-utils.js +0 -57
  530. package/out/sqlite-utils.js.map +0 -1
  531. package/out/street/centroid-schema.d.ts +0 -105
  532. package/out/street/centroid-schema.d.ts.map +0 -1
  533. package/out/street/centroid-schema.js +0 -78
  534. package/out/street/centroid-schema.js.map +0 -1
  535. package/out/street/index.d.ts.map +0 -1
  536. package/out/street/index.js.map +0 -1
  537. package/out/street/morphology-fst-builder.d.ts.map +0 -1
  538. package/out/street/morphology-fst-builder.js.map +0 -1
  539. package/out/street/morphology-fst-loader.d.ts +0 -67
  540. package/out/street/morphology-fst-loader.d.ts.map +0 -1
  541. package/out/street/morphology-fst-loader.js.map +0 -1
  542. package/out/uprn/index.d.ts.map +0 -1
  543. package/out/uprn/index.js.map +0 -1
  544. /package/lib/{fst/index.ts → fst.ts} +0 -0
  545. /package/lib/{geonames/index.ts → geonames.ts} +0 -0
  546. /package/lib/{nsul/index.ts → nsul.ts} +0 -0
  547. /package/lib/{poi/index.ts → poi.ts} +0 -0
  548. /package/lib/{uprn/index.ts → uprn.ts} +0 -0
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
9
9
 
10
- import { placetypeDepth } from "#ancestry/index"
10
+ import { placetypeDepth } from "#ancestry"
11
11
  import {
12
12
  CANDIDATE_ANCESTOR_COLUMNS,
13
13
  CANDIDATE_ANCESTOR_TABLE,
@@ -15,20 +15,15 @@ import {
15
15
  createCandidateAncestorTable,
16
16
  createCandidateIntervalTable,
17
17
  MAX_ANCESTOR_DEPTH,
18
- } from "#candidate-ancestors-schema"
19
- import type { CandidateDatabase } from "#candidate-schema"
18
+ } from "#candidate/ancestors/schema"
20
19
  import type { PlaceAttrs } from "#candidate/place-attrs"
20
+ import type { CandidateDatabase } from "#candidate/schema"
21
21
  import type { WOFDatabase } from "#schema"
22
22
 
23
23
  /**
24
- * Pass 3b — the ancestors sidecar: closure rows + interval labels (candidate-ancestors-schema.ts owns the encoding
25
- * decision and the DAG/absence semantics). Reads the same source `ancestors` table the region stamp reads,
26
- * denormalizing each edge with the parent's name/key from `attrs`, streamed `ORDER BY id` so the clustered `(spr_id,
27
- * depth)` insert is sorted — the contiguous-leaves discipline of the candidate table itself.
28
- *
29
- * Excluded by policy: self rows, and placetypes outside the containment ladder (continent, empire, …: `placetypeDepth` 0)
30
- * — they discriminate nothing a consumer of this sidecar checks. An edge to a parent with no current `spr` row has no
31
- * name to denormalize; it is dropped and counted rather than stored blind.
24
+ * Build the ancestors sidecar (closure rows plus interval labels) from the source
25
+ * `ancestors` table, excluding self rows and placetypes outside the containment ladder.
26
+ * an edge to a parent with no current `spr` row is dropped and counted.
32
27
  */
33
28
  export async function buildAncestorsSidecar(ctx: {
34
29
  src: DatabaseClient<WOFDatabase>
@@ -47,9 +42,8 @@ export async function buildAncestorsSidecar(ctx: {
47
42
  `INSERT INTO ${CANDIDATE_ANCESTOR_TABLE} VALUES (${CANDIDATE_ANCESTOR_COLUMNS.map(() => "?").join(", ")})`
48
43
  )
49
44
 
50
- // The canonical-parent forest the interval labels are computed over. One parent per place — the
51
- // depth-1 edge (finest containment tier, lowest ancestor id; the `regionOf` MIN-stability
52
- // convention). ALL parents stay in the closure rows; only the interval tree canonicalizes.
45
+ // One parent per place (the finest containment tier, lowest ancestor id) canonicalizes the interval tree.
46
+ // All parents stay in the closure rows.
53
47
  const canonicalParentOf = new Map<number, number>()
54
48
  const childrenOf = new Map<number, number[]>()
55
49
  const forest = new Set<number>()
@@ -58,15 +52,15 @@ export async function buildAncestorsSidecar(ctx: {
58
52
  let ancestorPlaces = 0
59
53
  let droppedParents = 0
60
54
 
61
- // Per-child edge buffer; the stream below is grouped by child id, so each flush owns one place.
55
+ // The stream is grouped by child id, so each flush owns one place.
62
56
  let childID = -1
63
57
  let edges: Array<{ aid: number; apt: string }> = []
64
58
 
65
59
  const flush = (): void => {
66
60
  if (childID < 0 || !edges.length) return
67
61
 
68
- // Deterministic nearest-first: containment depth descending, then ancestor id ascending —
69
- // the FTS backend's `ancestorLineage` ordering, made stable across rebuilds.
62
+ // Deterministic nearest-first: containment depth descending, then ancestor id ascending,
63
+ // matching the FTS backend's `ancestorLineage` ordering across rebuilds.
70
64
  edges.sort((a, b) => placetypeDepth(b.apt) - placetypeDepth(a.apt) || a.aid - b.aid)
71
65
 
72
66
  if (edges.length > MAX_ANCESTOR_DEPTH) {
@@ -132,8 +126,8 @@ export async function buildAncestorsSidecar(ctx: {
132
126
  flush()
133
127
  out.exec("COMMIT")
134
128
 
135
- // Interval labels: pre/post-order DFS over the canonical-parent forest. Root order and child
136
- // order are id-ascending so the labels are stable across rebuilds of the same source.
129
+ // Interval labels are a pre/post-order DFS over the canonical-parent forest, with root
130
+ // and child order id-ascending so labels stay stable across rebuilds.
137
131
  const preOf = new Map<number, number>()
138
132
  const postOf = new Map<number, number>()
139
133
 
@@ -160,8 +154,8 @@ export async function buildAncestorsSidecar(ctx: {
160
154
  if (kids && top.next < kids.length) {
161
155
  const kid = kids[top.next++]!
162
156
 
163
- // Each child holds exactly one canonical parent, so a labeled node here means the
164
- // grouping upstream broke — skip rather than corrupt the numbering.
157
+ // Each child holds exactly one canonical parent, so a labeled node means upstream grouping broke.
158
+ // Skip rather than corrupt the numbering.
165
159
  if (preOf.has(kid)) continue
166
160
 
167
161
  preOf.set(kid, counter++)
@@ -173,9 +167,8 @@ export async function buildAncestorsSidecar(ctx: {
173
167
  }
174
168
  }
175
169
 
176
- // A canonical-parent CYCLE (corrupt source ancestry) leaves its members unreachable from any
177
- // root: they simply receive no label, and containment against them reads unverifiable — the
178
- // absence semantics the schema module states. Counted so a jump is visible across rebuilds.
170
+ // A canonical-parent cycle leaves its members unreachable from any root,
171
+ // so they receive no label and containment against them reads unverifiable.
179
172
  const cycleSkipped = forest.size - preOf.size
180
173
 
181
174
  const insInterval = out.prepare(`INSERT INTO ${CANDIDATE_INTERVAL_TABLE} VALUES (?, ?, ?)`)
@@ -2,7 +2,6 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- * @file Pass 1b of the candidate build — fold ICU's country display names onto the country rows.
6
5
  */
7
6
 
8
7
  import { enumerateCountryDisplayNames } from "@mailwoman/codex/country"
@@ -11,22 +10,13 @@ import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
11
10
  import { normalizeLocalityForKey } from "#street/normalize"
12
11
 
13
12
  /**
14
- * Fold every country surface ICU knows onto that country's candidate row (#1678 thread 1).
13
+ * Stages each country name that `Intl.DisplayNames` renders in the codex display-name
14
+ * locales as a non-primary key on that country's candidate row.
15
15
  *
16
- * A bare `格鲁吉亚` (Georgia the country) resolved to NOTHING while `佐治亚州` (Georgia the US state) resolved correctly, and
17
- * the model gave both the same wrong `locality` tag — so the tag was never the variable. Measured 2026-08-15: 140 of
18
- * 237 country rows are synthetic and carry a canonical English name and nothing else; WOF holds no Chinese country
19
- * names at all; and the GeoNames alias fold filters through a Latin-script regex, so neither existing source could ever
20
- * supply them.
16
+ * The row's display name is unchanged, so a query such as `格鲁吉亚` resolves to the existing Georgia row.
17
+ * When several country rows share an ISO code, the most populous one receives the keys.
21
18
  *
22
- * `Intl.DisplayNames` already knows every one — ~280 regions, ~5,244 surfaces, from the same ICU the runtime uses for
23
- * every other locale-sensitive operation. No download, no vendored corpus, no snapshot to drift.
24
- *
25
- * `is_primary = 0`: these are NAMES THE WORLD USES, not the country's canonical name. The display `name` stays whatever
26
- * the gazetteer already had, so resolving `格鲁吉亚` answers with the Georgia country row rather than renaming it.
27
- *
28
- * Returns the row count so the caller can report it — a zero means ICU supplied nothing, which is a different fact from
29
- * the pass not having run.
19
+ * @returns The number of rows staged.
30
20
  */
31
21
  export function stageCountryDisplayNames(ctx: {
32
22
  attrs: Map<number, PlaceAttrs>
@@ -35,8 +25,6 @@ export function stageCountryDisplayNames(ctx: {
35
25
  stageRow: StageRow
36
26
  tx: { exec(sql: string): void }
37
27
  }): number {
38
- // One country row per ISO2. Where a code has several (historic rows surviving the is_current filter), the most
39
- // populous wins — the same tiebreak the ranking uses everywhere else.
40
28
  const countryByISO2 = new Map<string, { sid: number; a: PlaceAttrs }>()
41
29
 
42
30
  for (const [sid, a] of ctx.attrs) {
@@ -48,7 +36,7 @@ export function stageCountryDisplayNames(ctx: {
48
36
 
49
37
  const held = countryByISO2.get(iso2)
50
38
 
51
- if (!held || a.pop > held.a.pop) {
39
+ if (!held || (a.pop ?? 0) > (held.a.pop ?? 0)) {
52
40
  countryByISO2.set(iso2, { sid, a })
53
41
  }
54
42
  }
@@ -64,8 +52,6 @@ export function stageCountryDisplayNames(ctx: {
64
52
 
65
53
  const k = normalizeLocalityForKey(name)
66
54
 
67
- // The country's own key is already staged as its primary; INSERT OR IGNORE at materialization dedupes the
68
- // rest, so this only skips the obvious self-alias.
69
55
  if (!k || k === target.a.pkey) continue
70
56
 
71
57
  ctx.stageRow(k, target.a, target.sid, 0)
@@ -2,27 +2,18 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- * @file Pass 4 of the candidate build — fold a postcode or locality extract into the staging table.
6
5
  */
7
6
 
8
7
  import { DatabaseClient } from "@mailwoman/sqlite/client"
9
8
  import { tableExists } from "@mailwoman/sqlite/introspection"
9
+ import type { PathBuilderLike } from "path-ts"
10
10
 
11
- import { CANDIDATE_ANCESTOR_COLUMNS, CANDIDATE_ANCESTOR_TABLE } from "#candidate-ancestors-schema"
12
- import type { CandidateDatabase } from "#candidate-schema"
11
+ import { CANDIDATE_ANCESTOR_COLUMNS, CANDIDATE_ANCESTOR_TABLE } from "#candidate/ancestors/schema"
13
12
  import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
13
+ import type { CandidateDatabase } from "#candidate/schema"
14
14
  import type { WOFDatabase } from "#schema"
15
15
  import { normalizeLocalityForKey } from "#street/normalize"
16
16
 
17
- /**
18
- * The extract's own region-tier ancestry, when it carries any: extract id → the admin region's id. A locality extract
19
- * derived from a national register (the Taiwanese 鄉鎮市區 from the civil-affairs address points) knows which 縣市 each row
20
- * belongs to, and writes that as an `ancestors` row against the admin region's WOF id. Without it a folded row has no
21
- * region scope, so a `parentID`-scoped probe misses it and the cascade widens to whatever namesake carries the key.
22
- *
23
- * Only a region the admin build staged (`attrs`) counts: an id the admin does not know cannot be denormalized into a
24
- * closure row, and a scope nothing can match would be a silent zero rather than an absence.
25
- */
26
17
  function extractRegionAncestry(pc: DatabaseClient<WOFDatabase>, attrs: Map<number, PlaceAttrs>): Map<number, number> {
27
18
  const regionOf = new Map<number, number>()
28
19
 
@@ -46,28 +37,21 @@ function extractRegionAncestry(pc: DatabaseClient<WOFDatabase>, attrs: Map<numbe
46
37
  }
47
38
 
48
39
  /**
49
- * Fold ONE extract (`spr` rows at `extractPlacetype` carrying real coordinates) in, then pass 4b: the alias names
50
- * hanging off that same extract's `names` table.
40
+ * Stages one postcode or locality extract's located `spr` rows and their `names`
41
+ * aliases into the candidate table.
51
42
  *
52
- * Self-contained by construction — it shares only the staging statement and the code dictionaries with the admin passes
53
- * above it, and nothing downstream reads anything it produces except the counters it returns. The one thing it writes
54
- * beyond the staging table is a depth-1 closure row for a row whose extract names its region (see
55
- * {@link extractRegionAncestry}), so the pick's stamped ancestors agree with the scope it was found under.
43
+ * When `attrs` is given and the extract's `ancestors` table places a row in a known region,
44
+ * the row also gets that region's scope and the region's ancestor chain.
45
+ * The stored ancestors then agree with the row's scope.
56
46
  */
57
47
  export function foldExtract(ctx: {
58
- /**
59
- * The staging connection. The extract itself is opened read-only here and closed before returning.
60
- */
61
48
  out: DatabaseClient<CandidateDatabase>
62
- extractPath: string
49
+ extractPath: PathBuilderLike
63
50
  extractPlacetype: "postalcode" | "locality"
64
51
  ccID: (code: string | null) => number
65
52
  ptID: (pt: string | null) => number
66
53
  stageRow: StageRow
67
- /**
68
- * The admin places pass 1 staged — what an extract's region ancestry is resolved against. Absent, no extract row
69
- * takes a region scope (the postcode extracts, which carry none).
70
- */
54
+
71
55
  attrs?: Map<number, PlaceAttrs>
72
56
  progress: (phase: string, message: string) => void
73
57
  }): { primaries: number; aliases: number; scoped: number; ancestorRows: number } {
@@ -77,8 +61,7 @@ export function foldExtract(ctx: {
77
61
 
78
62
  using pc = new DatabaseClient<WOFDatabase>(extractPath, { readOnly: true })
79
63
  const pcPtid = ptID(extractPlacetype)
80
- // Per-extract, not the admin `attrs` map: pass 1 only ever sees the admin DB, so the alias pass
81
- // below has nothing to join against unless this primary loop records what it staged.
64
+
82
65
  const pcAttrs = new Map<number, PlaceAttrs>()
83
66
  const regionOf = ctx.attrs ? extractRegionAncestry(pc, ctx.attrs) : new Map<number, number>()
84
67
 
@@ -86,10 +69,6 @@ export function foldExtract(ctx: {
86
69
  `INSERT OR IGNORE INTO ${CANDIDATE_ANCESTOR_TABLE} VALUES (${CANDIDATE_ANCESTOR_COLUMNS.map(() => "?").join(", ")})`
87
70
  )
88
71
 
89
- // The region's own closure rows, as the sidecar pass wrote them. A scoped row inherits the whole chain above its
90
- // region — macroregion, country — not the region alone: the widened-scope refusal reads a pick's stamped ancestors
91
- // against the parent the walk resolved, and a 縣市 that resolves to its macroregion record (`桃園市`, which WOF names
92
- // only at that tier) would otherwise contradict a lineage that stops at the region.
93
72
  const regionChain = out.prepare(
94
73
  `SELECT depth, parent_spr_id, parent_placetype_id, parent_name, parent_name_key FROM ${CANDIDATE_ANCESTOR_TABLE} WHERE spr_id = ? ORDER BY depth`
95
74
  )
@@ -118,9 +97,6 @@ export function foldExtract(ctx: {
118
97
  const id = Number(r.id)
119
98
  const rid = regionOf.get(id) ?? 0
120
99
 
121
- // region_id from the extract's own ancestry when it names one, else 0 (a postcode is unique by
122
- // name+country — no same-name disambiguation); neg_rank 0 (no population). bbox = the row's own
123
- // min/max (falls back to the centroid point).
124
100
  const a: PlaceAttrs = {
125
101
  cid: ccID(r.country as string | null),
126
102
  rid,
@@ -135,9 +111,7 @@ export function foldExtract(ctx: {
135
111
  pop: 0,
136
112
  neg: 0,
137
113
  pkey: key,
138
- // A postcode has no toponym fame — nobody writes an encyclopedia article about SW1A 2AA — and
139
- // the score source carries no `postalcode` rows to join against anyway. NULL is the truthful
140
- // value: unmeasured, so the ranking key leaves postcode rows exactly where they were.
114
+
141
115
  imp: null,
142
116
  }
143
117
 
@@ -172,20 +146,6 @@ export function foldExtract(ctx: {
172
146
 
173
147
  out.exec("COMMIT")
174
148
 
175
- // --- pass 4b: postcode ALIAS names (#1495) ---
176
- //
177
- // The delivery-city names GeoNames supplies for a ZIP ("Brooklyn" for 11201) are written into
178
- // the extract's `names` table by `postcode/centroid-fills.ts`'s `geonamesNameFill`. Everything
179
- // downstream of `names` picked them up EXCEPT this build: `fts.ts` unions `spr.name` with every
180
- // `names` row into `place_search.alt_names`, so the FTS backend resolved "Brooklyn" → 11201
181
- // while the candidate backend — whose every row IS an exact-tier row — had no key for it at
182
- // all. Pass 2 does the equivalent fold for admin places, but reads the ADMIN `place_search`,
183
- // and `attrs` holds admin ids only, so a postcode extract could never reach it.
184
- //
185
- // Same discipline as pass 2: `is_primary = 0` (so `rankByPrimaryPreference` treats it as an
186
- // alias, not a canonical postcode name), the row stays denormalized onto the POSTCODE's own
187
- // spr_id/coords/bbox, and the display `name` stays the postcode — resolving "brooklyn" answers
188
- // with place 11201, it does not rename the place to its delivery city.
189
149
  const hasNames = tableExists(pc, "names")
190
150
 
191
151
  if (hasNames) {
@@ -198,8 +158,6 @@ export function foldExtract(ctx: {
198
158
 
199
159
  const k = normalizeLocalityForKey(String(r.name ?? ""))
200
160
 
201
- // The postcode's own key is already staged as the primary; `INSERT OR IGNORE` at
202
- // materialization dedupes repeats, so this only skips the obvious self-alias.
203
161
  if (!k || k === a.pkey) continue
204
162
 
205
163
  stageRow(k, a, Number(r.id), 0)
@@ -209,9 +167,6 @@ export function foldExtract(ctx: {
209
167
 
210
168
  out.exec("COMMIT")
211
169
  } else {
212
- // Never a silent zero: real extracts come from `createUnifiedSchema`, which always creates
213
- // `names`. A extract without it has no alias surface to lose, but say so rather than reporting
214
- // "0 aliases" from a table that was never read.
215
170
  progress("postcode-aliases", `${extractPath} has no \`names\` table — no delivery-city aliases to fold`)
216
171
  }
217
172
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * FTS5-trigram fuzzy index over the candidate gazetteer's normalized `name_key`; raw SQL on purpose, since Kysely cannot express `create virtual table … using fts5`.
7
+ */
8
+
9
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
10
+
11
+ /**
12
+ * Name of the FTS5 trigram virtual table this module owns, on which the reader
13
+ * conditions its fuzzy fallback.
14
+ */
15
+ export const CANDIDATE_FTS_TABLE = "candidate_fts"
16
+
17
+ /**
18
+ * Build (or rebuild) {@link CANDIDATE_FTS_TABLE} from the materialized `candidate` table. call
19
+ * after the candidate B-tree is populated or against an existing candidate DB.
20
+ */
21
+ export function createCandidateFTS<DB>(db: DatabaseClient<DB>): void {
22
+ db.exec(`DROP TABLE IF EXISTS ${CANDIDATE_FTS_TABLE}`)
23
+ db.exec(`CREATE VIRTUAL TABLE ${CANDIDATE_FTS_TABLE} USING fts5(name_key, tokenize='trigram')`)
24
+
25
+ db.exec(
26
+ `INSERT INTO ${CANDIDATE_FTS_TABLE}(name_key) SELECT DISTINCT name_key FROM candidate WHERE name_key IS NOT NULL`
27
+ )
28
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The score side of the candidate build: load a WOF admin database's `place_importance` into a
7
+ * lookup the candidate builder probes per place, so every candidate row can include the
8
+ * toponym-fame prior a bare city name is decided on.
9
+ *
10
+ * The join key is `(name_key, country, placetype)`, the same {@link normalizeLocalityForKey} the
11
+ * candidate build uses for its probe key, so the two sides fold identically. An id join is wrong
12
+ * here, because the score source (`admin-global-priority-importance.db`) and the candidate build's
13
+ * admin source are different snapshots whose ids disagree. An id join silently drops rows and
14
+ * leaves the ranking inert on the queries the prior exists for.
15
+ *
16
+ * The key by itself is not enough. `(warwick, US, locality)` covers eleven different places.
17
+ * The group's maximum would give every Warwick in America the fame of Warwick, Rhode Island.
18
+ * The join picks the nearest centroid when it falls within {@link IMPORTANCE_JOIN_RADIUS_KM}.
19
+ * Two artifacts describing the same settlement put its centroid
20
+ * in almost the same place. Two towns with the same name in one country do not.
21
+ *
22
+ * `place_importance.importance` lands in the column verbatim, the pre-split conflation that is
23
+ * encyclopedia-derived where the concordance matched and a population-derived proxy everywhere
24
+ * else. See {@link CandidateTable.importance} for why the split `encyclopedic` channel is not what
25
+ * is written here.
26
+ *
27
+ * A place with no match gets NULL. NULL means the place's fame is unmeasured.
28
+ * The consumer (`resolver/toponym-prior.ts`) leaves an unmeasured candidate at the position
29
+ * population assigned it.
30
+ */
31
+
32
+ import { haversineKm } from "@mailwoman/spatial"
33
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
34
+ import type { PathBuilderLike } from "path-ts"
35
+
36
+ import type { CandidateDatabase } from "#candidate/schema"
37
+ import { normalizeLocalityForKey } from "#street/normalize"
38
+
39
+ /**
40
+ * How far apart two artifacts may put the same place's centroid and still be read as the same place.
41
+ *
42
+ * Measured from two admin snapshots.
43
+ * The nearest-centroid distances separate into a mode where the two snapshots agree
44
+ * and a background rate of two different towns wearing one name in one country.
45
+ *
46
+ * The background rate does not decay with distance.
47
+ * Ten kilometers separates the two populations.
48
+ *
49
+ * That distance matches the join's purpose.
50
+ * A radius above the floor starts handing one town's fame to another.
51
+ */
52
+ export const IMPORTANCE_JOIN_RADIUS_KM = 10
53
+
54
+ /**
55
+ * One scored place from the source, with its coordinates and importance score.
56
+ */
57
+ interface ScoredPlace {
58
+ lat: number
59
+ lon: number
60
+ importance: number
61
+ }
62
+
63
+ /**
64
+ * What {@link loadImportanceIndex} measured while reading the source.
65
+ *
66
+ * Reported by the builder so a run reports how much of the gazetteer it actually scored,
67
+ * rather than leaving the caller to infer it from a column full of nulls.
68
+ */
69
+ export interface ImportanceIndexStats {
70
+ /**
71
+ * Scored places read out of the source.
72
+ */
73
+ places: number
74
+ /**
75
+ * Distinct `(name_key, country, placetype)` groups they fall into.
76
+ */
77
+ keys: number
78
+ /**
79
+ * Places whose name folded to the empty key and could never be joined
80
+ * (non-Latin punctuation-only names, blanks).
81
+ */
82
+ unkeyable: number
83
+ }
84
+
85
+ /**
86
+ * `(name_key, country, placetype)` → the scored places under it.
87
+ *
88
+ * The separator is U+0000, a character no WOF name contains and no fold can produce,
89
+ * so the three fields cannot smear into one another.
90
+ */
91
+ function groupKey(nameKey: string, country: string | null, placetype: string | null): string {
92
+ return `${nameKey}\u0000${(country ?? "").toUpperCase()}\u0000${placetype ?? ""}`
93
+ }
94
+
95
+ /**
96
+ * A loaded `place_importance`, probed by name + country + placetype + position.
97
+ */
98
+ export class ImportanceIndex {
99
+ readonly #groups: Map<string, ScoredPlace[]>
100
+ readonly stats: ImportanceIndexStats
101
+ /**
102
+ * Places {@link find} matched inside the radius.
103
+ */
104
+ matched = 0
105
+ /**
106
+ * Places {@link find} refused.
107
+ *
108
+ * The key matched a scored group, but the nearest member was outside the radius,
109
+ * so it is a different place wearing the same name.
110
+ *
111
+ * This is the number worth watching across rebuilds.
112
+ * A jump means the score source and the admin source have drifted apart
113
+ * and the join is being asked to guess.
114
+ * It does not mean the radius is too tight.
115
+ */
116
+ refused = 0
117
+
118
+ constructor(groups: Map<string, ScoredPlace[]>, stats: ImportanceIndexStats) {
119
+ this.#groups = groups
120
+ this.stats = stats
121
+ }
122
+
123
+ /**
124
+ * The importance of the scored place nearest `(lat, lon)` sharing `name`'s folded key, `country`
125
+ * and `placetype`, or null when there is no such place within {@link IMPORTANCE_JOIN_RADIUS_KM}.
126
+ *
127
+ * Null is unmeasured.
128
+ * Keep the value null.
129
+ * Do not substitute zero or a population-derived value here.
130
+ *
131
+ * The source column already contains a population-derived fallback where one exists.
132
+ * A second fallback would make an absence indistinguishable from a measurement.
133
+ */
134
+ find(name: string, country: string | null, placetype: string | null, lat: number, lon: number): number | null {
135
+ const nameKey = normalizeLocalityForKey(name)
136
+
137
+ if (!nameKey) return null
138
+
139
+ const group = this.#groups.get(groupKey(nameKey, country, placetype))
140
+
141
+ if (!group) return null
142
+
143
+ let best: ScoredPlace | undefined
144
+ let bestKm = Infinity
145
+
146
+ for (const place of group) {
147
+ const km = haversineKm(lat, lon, place.lat, place.lon)
148
+
149
+ if (km < bestKm) {
150
+ bestKm = km
151
+ best = place
152
+ }
153
+ }
154
+
155
+ if (!best || bestKm > IMPORTANCE_JOIN_RADIUS_KM) {
156
+ this.refused++
157
+
158
+ return null
159
+ }
160
+
161
+ this.matched++
162
+
163
+ return best.importance
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Read `place_importance` from a WOF admin database and join it to `spr`.
169
+ *
170
+ * The join retrieves each place's name, country, placetype, plus its centroid.
171
+ * These fields populate an {@link ImportanceIndex}.
172
+ *
173
+ * Only current, non-deprecated places are indexed.
174
+ * A superseded row's score belongs to a place the gazetteer no longer contains.
175
+ *
176
+ * If that row won the nearest-centroid contest, a live place would inherit the superseded place's fame.
177
+ *
178
+ * The whole table is held in memory on purpose, because the build probes it once for every place
179
+ * and the alternative is a prepared statement per place against a multi-gigabyte database.
180
+ */
181
+ export function loadImportanceIndex(databasePath: PathBuilderLike): ImportanceIndex {
182
+ using db = new DatabaseClient<CandidateDatabase>(databasePath, { readOnly: true })
183
+
184
+ const groups = new Map<string, ScoredPlace[]>()
185
+ let places = 0
186
+ let unkeyable = 0
187
+
188
+ for (const row of db
189
+ .prepare(
190
+ `SELECT s.name AS name, s.country AS country, s.placetype AS placetype,
191
+ s.latitude AS latitude, s.longitude AS longitude, i.importance AS importance
192
+ FROM place_importance i JOIN spr s ON s.id = i.id
193
+ WHERE s.is_current != 0 AND s.is_deprecated = 0`
194
+ )
195
+ .iterate()) {
196
+ const importance = Number(row.importance)
197
+
198
+ if (!Number.isFinite(importance)) continue
199
+ const nameKey = normalizeLocalityForKey(String(row.name ?? ""))
200
+
201
+ if (!nameKey) {
202
+ unkeyable++
203
+
204
+ continue
205
+ }
206
+
207
+ const key = groupKey(nameKey, row.country as string | null, row.placetype as string | null)
208
+ let group = groups.get(key)
209
+
210
+ if (!group) {
211
+ group = []
212
+ groups.set(key, group)
213
+ }
214
+
215
+ group.push({ lat: Number(row.latitude), lon: Number(row.longitude), importance })
216
+
217
+ places++
218
+ }
219
+
220
+ return new ImportanceIndex(groups, { places, keys: groups.size, unkeyable })
221
+ }