@mailwoman/resolver-wof-sqlite 10.0.0 → 10.2.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 (477) hide show
  1. package/README.md +9 -8
  2. package/lib/address/point/interpolation.ts +45 -39
  3. package/lib/address/point/schema.ts +17 -40
  4. package/lib/address/point.ts +316 -0
  5. package/lib/{address/index.ts → address.ts} +1 -1
  6. package/lib/ancestry/backfill.ts +20 -86
  7. package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
  8. package/lib/build/candidate.ts +122 -174
  9. package/lib/build/population-corrections.ts +64 -0
  10. package/lib/build/slim.ts +44 -114
  11. package/lib/candidate/alias-bags.ts +16 -17
  12. package/lib/candidate/ancestors/schema.ts +77 -61
  13. package/lib/candidate/ancestors/sidecar.ts +15 -22
  14. package/lib/candidate/country-display-names.ts +6 -20
  15. package/lib/candidate/extract-fold.ts +10 -55
  16. package/lib/candidate/fts.ts +5 -23
  17. package/lib/candidate/importance.ts +65 -68
  18. package/lib/candidate/lookup.ts +124 -415
  19. package/lib/candidate/name-roles.ts +15 -33
  20. package/lib/candidate/own-name.ts +35 -32
  21. package/lib/candidate/place-attrs.ts +20 -13
  22. package/lib/candidate/schema.ts +29 -65
  23. package/lib/candidate/scoring.ts +54 -93
  24. package/lib/capital-schema.ts +21 -17
  25. package/lib/capitals.ts +29 -27
  26. package/lib/coincident-roles.ts +85 -55
  27. package/lib/convention/schema.ts +15 -16
  28. package/lib/convention.ts +144 -0
  29. package/lib/coverage-manifest-schema.ts +40 -45
  30. package/lib/currency-backfill.ts +34 -79
  31. package/lib/env.ts +1 -1
  32. package/lib/exact-match.ts +32 -21
  33. package/lib/extracts.ts +97 -74
  34. package/lib/fst/autocomplete.ts +54 -37
  35. package/lib/fst/builder.ts +37 -41
  36. package/lib/fst/deserialize-web.ts +16 -17
  37. package/lib/fst/format.ts +41 -23
  38. package/lib/fst/freshness.ts +43 -120
  39. package/lib/fst/matcher.ts +7 -5
  40. package/lib/fst/serialize.ts +54 -74
  41. package/lib/fst/types.ts +78 -75
  42. package/lib/{fst/index.ts → fst.ts} +1 -0
  43. package/lib/fts/query.ts +5 -36
  44. package/lib/fts.ts +280 -0
  45. package/lib/geonames/aliases.ts +73 -144
  46. package/lib/geonames/postal.ts +162 -71
  47. package/lib/index.ts +9 -8
  48. package/lib/interpolation.ts +48 -100
  49. package/lib/lookup.ts +155 -270
  50. package/lib/name-score.ts +9 -7
  51. package/lib/nsul/lookup.ts +37 -54
  52. package/lib/nsul/schema.ts +16 -43
  53. package/lib/paths.ts +159 -0
  54. package/lib/place-importance-schema.ts +62 -169
  55. package/lib/poi/lookup.ts +104 -121
  56. package/lib/poi/schema.ts +42 -26
  57. package/lib/polygon-schema.ts +10 -6
  58. package/lib/postal/city/alias/lookup.ts +22 -40
  59. package/lib/postal/city/alias/schema.ts +14 -10
  60. package/lib/postal/city/candidate-schema.ts +26 -20
  61. package/lib/postcode-point-lookup.ts +8 -27
  62. package/lib/primary-preference.ts +46 -126
  63. package/lib/proximity-rerank.ts +44 -33
  64. package/lib/ranking-weights.ts +105 -78
  65. package/lib/region-keys.ts +70 -42
  66. package/lib/reverse.ts +149 -86
  67. package/lib/schema.ts +73 -50
  68. package/lib/search-fetch.ts +90 -68
  69. package/lib/sqlite/convention-source.ts +15 -23
  70. package/lib/sqlite/utils.ts +19 -22
  71. package/lib/street/centroid/schema.ts +37 -42
  72. package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
  73. package/lib/street/morphology/fst/builder.ts +60 -42
  74. package/lib/street/morphology/fst/loader.ts +40 -32
  75. package/lib/street/name-lookup.ts +45 -41
  76. package/lib/street/normalize.ts +88 -219
  77. package/lib/street/segment-schema.ts +116 -43
  78. package/lib/{street/index.ts → street.ts} +1 -1
  79. package/lib/types.ts +109 -150
  80. package/lib/unified-schema.ts +29 -29
  81. package/lib/uprn/existence.ts +30 -19
  82. package/lib/uprn/lookup.ts +29 -68
  83. package/lib/uprn/schema.ts +18 -40
  84. package/lib/weights-overlay-linker.ts +236 -252
  85. package/out/address/point/interpolation.d.ts +20 -23
  86. package/out/address/point/interpolation.d.ts.map +1 -1
  87. package/out/address/point/interpolation.js +42 -39
  88. package/out/address/point/interpolation.js.map +1 -1
  89. package/out/address/point/schema.d.ts +12 -35
  90. package/out/address/point/schema.d.ts.map +1 -1
  91. package/out/address/point/schema.js +3 -19
  92. package/out/address/point/schema.js.map +1 -1
  93. package/out/address/point.d.ts +57 -0
  94. package/out/address/point.d.ts.map +1 -0
  95. package/out/address/point.js +206 -0
  96. package/out/address/point.js.map +1 -0
  97. package/out/{address/index.js → address.d.ts} +2 -2
  98. package/out/address.d.ts.map +1 -0
  99. package/out/{address/index.d.ts → address.js} +2 -2
  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 +14 -74
  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 +82 -112
  110. package/out/build/candidate.d.ts.map +1 -1
  111. package/out/build/candidate.js +17 -100
  112. package/out/build/candidate.js.map +1 -1
  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 +8 -44
  118. package/out/build/slim.d.ts.map +1 -1
  119. package/out/build/slim.js +33 -94
  120. package/out/build/slim.js.map +1 -1
  121. package/out/candidate/alias-bags.d.ts +6 -6
  122. package/out/candidate/alias-bags.d.ts.map +1 -1
  123. package/out/candidate/alias-bags.js +16 -17
  124. package/out/candidate/alias-bags.js.map +1 -1
  125. package/out/candidate/ancestors/schema.d.ts +73 -57
  126. package/out/candidate/ancestors/schema.d.ts.map +1 -1
  127. package/out/candidate/ancestors/schema.js +56 -48
  128. package/out/candidate/ancestors/schema.js.map +1 -1
  129. package/out/candidate/ancestors/sidecar.d.ts +3 -8
  130. package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
  131. package/out/candidate/ancestors/sidecar.js +15 -22
  132. package/out/candidate/ancestors/sidecar.js.map +1 -1
  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 +7 -15
  138. package/out/candidate/extract-fold.d.ts.map +1 -1
  139. package/out/candidate/extract-fold.js +5 -47
  140. package/out/candidate/extract-fold.js.map +1 -1
  141. package/out/candidate/fts.d.ts +5 -23
  142. package/out/candidate/fts.d.ts.map +1 -1
  143. package/out/candidate/fts.js +5 -23
  144. package/out/candidate/fts.js.map +1 -1
  145. package/out/candidate/importance.d.ts +60 -65
  146. package/out/candidate/importance.d.ts.map +1 -1
  147. package/out/candidate/importance.js +56 -63
  148. package/out/candidate/importance.js.map +1 -1
  149. package/out/candidate/lookup.d.ts +18 -55
  150. package/out/candidate/lookup.d.ts.map +1 -1
  151. package/out/candidate/lookup.js +82 -385
  152. package/out/candidate/lookup.js.map +1 -1
  153. package/out/candidate/name-roles.d.ts +9 -21
  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 +35 -32
  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 +27 -62
  166. package/out/candidate/schema.d.ts.map +1 -1
  167. package/out/candidate/schema.js +10 -22
  168. package/out/candidate/schema.js.map +1 -1
  169. package/out/candidate/scoring.d.ts +7 -8
  170. package/out/candidate/scoring.d.ts.map +1 -1
  171. package/out/candidate/scoring.js +52 -89
  172. package/out/candidate/scoring.js.map +1 -1
  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 +27 -25
  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 +72 -50
  184. package/out/coincident-roles.js.map +1 -1
  185. package/out/convention/schema.d.ts +14 -14
  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 +34 -39
  194. package/out/coverage-manifest-schema.d.ts.map +1 -1
  195. package/out/coverage-manifest-schema.js +25 -34
  196. package/out/coverage-manifest-schema.js.map +1 -1
  197. package/out/currency-backfill.d.ts +16 -38
  198. package/out/currency-backfill.d.ts.map +1 -1
  199. package/out/currency-backfill.js +19 -46
  200. package/out/currency-backfill.js.map +1 -1
  201. package/out/env.d.ts +1 -1
  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 +32 -21
  206. package/out/exact-match.js.map +1 -1
  207. package/out/extracts.d.ts +66 -55
  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 +29 -21
  212. package/out/fst/autocomplete.d.ts.map +1 -1
  213. package/out/fst/autocomplete.js +39 -28
  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 +37 -41
  217. package/out/fst/builder.js.map +1 -1
  218. package/out/fst/deserialize-web.d.ts +4 -4
  219. package/out/fst/deserialize-web.d.ts.map +1 -1
  220. package/out/fst/deserialize-web.js +15 -16
  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 +19 -81
  227. package/out/fst/freshness.d.ts.map +1 -1
  228. package/out/fst/freshness.js +31 -94
  229. package/out/fst/freshness.js.map +1 -1
  230. package/out/fst/matcher.d.ts +5 -4
  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 +28 -28
  235. package/out/fst/serialize.d.ts.map +1 -1
  236. package/out/fst/serialize.js +53 -71
  237. package/out/fst/serialize.js.map +1 -1
  238. package/out/fst/types.d.ts +78 -74
  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} +2 -1
  243. package/out/fst.d.ts.map +1 -0
  244. package/out/{fst/index.d.ts → fst.js} +2 -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 -62
  255. package/out/geonames/aliases.d.ts.map +1 -1
  256. package/out/geonames/aliases.js +45 -117
  257. package/out/geonames/aliases.js.map +1 -1
  258. package/out/geonames/postal.d.ts +84 -44
  259. package/out/geonames/postal.d.ts.map +1 -1
  260. package/out/geonames/postal.js +115 -73
  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 +9 -8
  267. package/out/index.d.ts.map +1 -1
  268. package/out/index.js +6 -5
  269. package/out/index.js.map +1 -1
  270. package/out/interpolation.d.ts +18 -55
  271. package/out/interpolation.d.ts.map +1 -1
  272. package/out/interpolation.js +27 -69
  273. package/out/interpolation.js.map +1 -1
  274. package/out/lookup.d.ts +23 -51
  275. package/out/lookup.d.ts.map +1 -1
  276. package/out/lookup.js +136 -234
  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 +51 -143
  299. package/out/place-importance-schema.d.ts.map +1 -1
  300. package/out/place-importance-schema.js +28 -143
  301. package/out/place-importance-schema.js.map +1 -1
  302. package/out/poi/lookup.d.ts +40 -47
  303. package/out/poi/lookup.d.ts.map +1 -1
  304. package/out/poi/lookup.js +69 -96
  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 +20 -23
  319. package/out/postal/city/alias/lookup.d.ts.map +1 -1
  320. package/out/postal/city/alias/lookup.js +17 -30
  321. package/out/postal/city/alias/lookup.js.map +1 -1
  322. package/out/postal/city/alias/schema.d.ts +14 -10
  323. package/out/postal/city/alias/schema.d.ts.map +1 -1
  324. package/out/postal/city/alias/schema.js +10 -8
  325. package/out/postal/city/alias/schema.js.map +1 -1
  326. package/out/postal/city/candidate-schema.d.ts +25 -19
  327. package/out/postal/city/candidate-schema.d.ts.map +1 -1
  328. package/out/postal/city/candidate-schema.js +21 -17
  329. package/out/postal/city/candidate-schema.js.map +1 -1
  330. package/out/{postal/index.d.ts → postal.d.ts} +1 -1
  331. package/out/postal.d.ts.map +1 -0
  332. package/out/{postal/index.js → postal.js} +1 -1
  333. package/out/postal.js.map +1 -0
  334. package/out/postcode-point-lookup.d.ts +6 -24
  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 +38 -92
  339. package/out/primary-preference.d.ts.map +1 -1
  340. package/out/primary-preference.js +15 -99
  341. package/out/primary-preference.js.map +1 -1
  342. package/out/proximity-rerank.d.ts +41 -30
  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 +98 -64
  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 +50 -31
  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 +78 -50
  355. package/out/reverse.d.ts.map +1 -1
  356. package/out/reverse.js +98 -61
  357. package/out/reverse.js.map +1 -1
  358. package/out/schema.d.ts +72 -49
  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 +12 -16
  367. package/out/sqlite/convention-source.d.ts.map +1 -1
  368. package/out/sqlite/convention-source.js +13 -22
  369. package/out/sqlite/convention-source.js.map +1 -1
  370. package/out/sqlite/utils.d.ts +19 -19
  371. package/out/sqlite/utils.d.ts.map +1 -1
  372. package/out/sqlite/utils.js +14 -15
  373. package/out/sqlite/utils.js.map +1 -1
  374. package/out/street/centroid/schema.d.ts +37 -42
  375. package/out/street/centroid/schema.d.ts.map +1 -1
  376. package/out/street/centroid/schema.js +9 -21
  377. package/out/street/centroid/schema.js.map +1 -1
  378. package/out/street/centroid.d.ts +30 -0
  379. package/out/street/centroid.d.ts.map +1 -0
  380. package/out/street/{centroid/index.js → centroid.js} +13 -24
  381. package/out/street/centroid.js.map +1 -0
  382. package/out/street/morphology/fst/builder.d.ts +20 -10
  383. package/out/street/morphology/fst/builder.d.ts.map +1 -1
  384. package/out/street/morphology/fst/builder.js +44 -34
  385. package/out/street/morphology/fst/builder.js.map +1 -1
  386. package/out/street/morphology/fst/loader.d.ts +33 -26
  387. package/out/street/morphology/fst/loader.d.ts.map +1 -1
  388. package/out/street/morphology/fst/loader.js +23 -22
  389. package/out/street/morphology/fst/loader.js.map +1 -1
  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 +34 -35
  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 +77 -194
  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.js → street.d.ts} +2 -2
  403. package/out/street.d.ts.map +1 -0
  404. package/out/{street/index.d.ts → street.js} +2 -2
  405. package/out/street.js.map +1 -0
  406. package/out/types.d.ts +84 -149
  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 +21 -57
  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 +138 -144
  431. package/out/weights-overlay-linker.d.ts.map +1 -1
  432. package/out/weights-overlay-linker.js +105 -159
  433. package/out/weights-overlay-linker.js.map +1 -1
  434. package/package.json +91 -596
  435. package/lib/address/point/index.ts +0 -328
  436. package/lib/convention/index.ts +0 -154
  437. package/lib/fts/index.ts +0 -361
  438. package/out/address/index.d.ts.map +0 -1
  439. package/out/address/index.js.map +0 -1
  440. package/out/address/point/index.d.ts +0 -53
  441. package/out/address/point/index.d.ts.map +0 -1
  442. package/out/address/point/index.js +0 -222
  443. package/out/address/point/index.js.map +0 -1
  444. package/out/ancestry/index.d.ts +0 -45
  445. package/out/ancestry/index.d.ts.map +0 -1
  446. package/out/ancestry/index.js +0 -55
  447. package/out/ancestry/index.js.map +0 -1
  448. package/out/convention/index.d.ts +0 -106
  449. package/out/convention/index.d.ts.map +0 -1
  450. package/out/convention/index.js +0 -93
  451. package/out/convention/index.js.map +0 -1
  452. package/out/fst/index.d.ts.map +0 -1
  453. package/out/fst/index.js.map +0 -1
  454. package/out/fts/index.d.ts +0 -184
  455. package/out/fts/index.d.ts.map +0 -1
  456. package/out/fts/index.js +0 -277
  457. package/out/fts/index.js.map +0 -1
  458. package/out/geonames/index.d.ts.map +0 -1
  459. package/out/geonames/index.js.map +0 -1
  460. package/out/nsul/index.d.ts.map +0 -1
  461. package/out/nsul/index.js.map +0 -1
  462. package/out/poi/index.d.ts.map +0 -1
  463. package/out/poi/index.js.map +0 -1
  464. package/out/postal/index.d.ts.map +0 -1
  465. package/out/postal/index.js.map +0 -1
  466. package/out/street/centroid/index.d.ts +0 -40
  467. package/out/street/centroid/index.d.ts.map +0 -1
  468. package/out/street/centroid/index.js.map +0 -1
  469. package/out/street/index.d.ts.map +0 -1
  470. package/out/street/index.js.map +0 -1
  471. package/out/uprn/index.d.ts.map +0 -1
  472. package/out/uprn/index.js.map +0 -1
  473. /package/lib/{geonames/index.ts → geonames.ts} +0 -0
  474. /package/lib/{nsul/index.ts → nsul.ts} +0 -0
  475. /package/lib/{poi/index.ts → poi.ts} +0 -0
  476. /package/lib/{postal/index.ts → postal.ts} +0 -0
  477. /package/lib/{uprn/index.ts → uprn.ts} +0 -0
@@ -2,48 +2,16 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- *
6
- * Build the global "candidate" lookup DB from a unified admin WOF DB — the byte-range-optimal
7
- * gazetteer the browser demo resolves against. Instead of FTS5 (whose postings for a common name
8
- * scatter across a multi-GB file → hundreds of serial range fetches), this materializes one
9
- * `WITHOUT ROWID` B-tree keyed `(name_key, country_id, region_id, placetype_id, neg_rank,
10
- * spr_id)`: every place's normalized name + distinct aliases + region abbreviations become rows,
11
- * population rank is precomputed into `neg_rank`, and the rows are bulk-loaded PRE-SORTED so a
12
- * resolve is one contiguous B-tree probe (a handful of pages → 1-2 chunk fetches, regardless of
13
- * global volume).
14
- *
15
- * Each row is DENORMALIZED — it carries the place's display `name`, centroid (`latitude`/
16
- * `longitude`), and `min/max` bbox — so a resolve is one statement, no FTS, no join to spr:
17
- * SELECT spr_id, name, latitude, longitude, min_lat, ... FROM candidate WHERE name_key = ? AND
18
- * country_id = ? AND placetype_id IN (...) [AND latitude BETWEEN ...] ORDER BY neg_rank ASC LIMIT
19
- * K; The demo cascade resolves a parsed region first (its bbox), then constrains the locality to
20
- * that bbox; `region_id` (the place's region-tier ancestor) is also carried for a future region
21
- * 2-step.
22
- *
23
- * The name_key normalizer is the SHARED {@link normalizeLocalityForKey} — the query side (the demo
24
- * resolver {@link WOFCandidateTableLookup}) MUST use the same function, the one-normalizer
25
- * discipline the address-point extract uses, so build/query stay consistent by construction.
26
- *
27
- * Measured (2026-06-20, vs the 2.6 GB full-DB FTS): ~5 M rows; ~12 range fetches per 8-query
28
- * session (the full DB needs 243); US locality 96.8% (region bbox), EU coord parity 88.6%.
29
- *
30
- * #28 adds one more denormalized field, `importance` — the toponym-fame prior that decides a BARE
31
- * city name, joined in from a separate score source by name rather than by id (see
32
- * `candidate-importance.ts`, which owns that join and explains why the id would be wrong). It is
33
- * optional: without {@link BuildCandidateOptions.importance} the column is NULL on every row, which
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.
40
5
  */
41
6
 
42
7
  import { COUNTRY_POPULATION } from "@mailwoman/codex/country"
43
- import { pathExists } from "@mailwoman/core/fs/readers"
8
+ import { pathExists } from "@mailwoman/core/fs/readers/stat"
44
9
  import { removePath } from "@mailwoman/core/fs/writers"
10
+ import { stringifyJSON } from "@mailwoman/core/json"
45
11
  import { DatabaseClient } from "@mailwoman/sqlite/client"
12
+ import type { PathBuilderLike } from "path-ts"
46
13
 
14
+ import { POPULATION_CORRECTIONS } from "#build/population-corrections"
47
15
  import { explodeAliasBags } from "#candidate/alias-bags"
48
16
  import { buildAncestorsSidecar } from "#candidate/ancestors/sidecar"
49
17
  import { stageCountryDisplayNames } from "#candidate/country-display-names"
@@ -64,158 +32,179 @@ import { resurrectCurrencyHoles } from "#currency-backfill"
64
32
  import type { WOFDatabase } from "#schema"
65
33
  import { normalizeLocalityForKey } from "#street/normalize"
66
34
 
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"
72
-
35
+ /**
36
+ * Options for {@link buildCandidateTable}.
37
+ */
73
38
  export interface BuildCandidateOptions {
74
39
  /**
75
- * Source unified admin DB — needs spr, place_population, place_search, place_abbr, ancestors.
40
+ * The source WOF admin database.
41
+ *
42
+ * It must have the `spr`, `place_population`, `place_search`, `place_abbr` and `ancestors` tables.
76
43
  */
77
- input: string
44
+ input: PathBuilderLike
45
+
78
46
  /**
79
- * Output candidate DB path (overwritten if present).
47
+ * The candidate database to write, replacing any existing file.
80
48
  */
81
- output: string
49
+ output: PathBuilderLike
50
+
82
51
  /**
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.
52
+ * The capital reference entries to store in the artifact's `capital` table.
53
+ *
54
+ * The caller supplies these entries because this published module cannot read repository paths.
55
+ * The builder creates no `capital` table when the caller omits them.
56
+ *
57
+ * The capital loader then falls back to the repository's `capitals-v1.json`.
86
58
  */
87
59
  capitals?: readonly CapitalPoint[]
60
+
88
61
  /**
89
- * Optional postcode extracts (`spr` rows with `placetype='postalcode'` + real coords, e.g. postalcode-us.db) — folded
90
- * in as `postalcode` candidate rows so `findPlace(postalcode)` resolves a ZIP directly (the demo's primary postcode
91
- * path; the postcode-*.bin anchor stays the fallback). Matches the slim wof-hot.db, which took one such postcode DB.
62
+ * Postcode extract databases whose located `spr` rows become `postalcode` candidate rows.
92
63
  *
93
- * Each extract's `names` table is folded in too (#1495) — that's where the GeoNames delivery-city names live
94
- * ("Brooklyn" for 11201), and they were previously reachable only through FTS.
64
+ * Each extract's `names` table becomes aliases, such as the delivery city "Brooklyn" for 11201.
95
65
  */
96
- postcodes?: string[]
66
+ postcodes?: readonly PathBuilderLike[]
67
+
97
68
  /**
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 UNMEASURED population (`neg_rank 0`: a extract row ranks behind any populated namesake and wins
101
- * only where its key is the answer). Each extract's `names` table folds as aliases, `is_primary = 0`, same as the
102
- * delivery-city pass. An extract whose `ancestors` table names an admin region for a row gives that row the region's
103
- * scope (`region_id`) plus closure rows for the region and the region's own chain above it; an extract without one
104
- * stays unscoped, as before.
69
+ * Locality extract databases whose located `spr` rows become `locality`
70
+ * candidate rows with zero population.
71
+ *
72
+ * Their `names` tables become non-primary aliases.
73
+ * When an extract's `ancestors` table gives a row's region, the row gets
74
+ * that region's scope and closure rows.
75
+ * Other rows stay unscoped.
105
76
  */
106
- localities?: string[]
77
+ localities?: readonly PathBuilderLike[]
78
+
107
79
  /**
108
- * Optional WOF admin database carrying a `place_importance` table — the source of the `importance` column (#28), the
109
- * toponym-fame prior that decides the bare-city-name class. Joined by `(name_key, country, placetype)` + nearest
110
- * centroid, NOT by id; see `candidate-importance.ts` for why the id join silently drops the foreign homonyms the
111
- * prior exists to demote.
80
+ * A WOF admin database with a `place_importance` table supplies the `importance` column.
112
81
  *
113
- * Omit it and every row's `importance` is NULL — unmeasured, which is what the consumer's positive-evidence-only rule
114
- * already treats as "do not participate", so the artifact is byte-identical to a pre-#28 build except for the empty
115
- * column. That is the honest degradation and it is the DEFAULT: a caller with no score source must not get a
116
- * population-derived stand-in written into a column that means fame.
82
+ * Rows join on name key, country and placetype, then on the nearest centroid.
83
+ * Without this option every row's `importance` is NULL.
84
+ * Consumers treat NULL as unmeasured.
117
85
  */
118
- importance?: string
86
+ importance?: PathBuilderLike
87
+
119
88
  /**
120
- * Cross-source currency backfill (#1737). WOF carries deprecated-with-no-successor records for real, populated
121
- * settlements (Rochester Kent, Aldershot, Telford — 120 GB localities alone), and the currency filter correctly drops
122
- * them, leaving holes no ranking can fill. When this option is set, pass 1c resurrects a dead locality ONLY under
123
- * three conditions, positive evidence throughout: no live same-name row of any placetype near the dead record (a
124
- * distant same-name row is a NAMESAKE and does not block — Rochester, Northumberland pop 318 must not veto Rochester,
125
- * Kent); an independent GeoNames P-class attestation of the same folded name within
126
- * {@link CURRENCY_BACKFILL_RADIUS_KM}; and the attestor at or above {@link CURRENCY_BACKFILL_POP_FLOOR}. The staged
127
- * row keeps the real WOF id, name, centroid and bbox — GeoNames only ATTESTS the place and supplies the population
128
- * that lets it stand in prominence races. `countries` are judged only where `<cc>.txt` exists under `geonamesDir`;
129
- * absent dumps are skipped loudly.
89
+ * Settings for restoring deprecated WOF places that have no successor and have a GeoNames attestation.
90
+ *
91
+ * A deprecated place is restored only when no live row of the same name lies within 10 km and
92
+ * a GeoNames populated place with the same folded name and at least 1,000 people lies within 10 km.
93
+ * Countries without `<cc>.txt` under `geonamesDir` are skipped and reported.
130
94
  */
131
95
  currencyBackfill?: {
132
- geonamesDir: string
96
+ geonamesDir: PathBuilderLike
133
97
  countries: readonly string[]
98
+
134
99
  /**
135
- * The dead placetypes the resurrection judges; `resurrectCurrencyHoles`'s default (`locality`) when absent. The
136
- * `localadmin` widening #1746 named is admitted here once its census is read.
100
+ * The deprecated placetypes eligible for restoration.
101
+ *
102
+ * @defaultValue `locality`
137
103
  */
138
104
  deadPlacetypes?: readonly string[]
139
105
  }
106
+
140
107
  /**
141
- * Optional progress callback for CLI / test introspection.
108
+ * Receives a phase name and a message as each build step runs.
142
109
  */
143
110
  onProgress?: (phase: string, message: string) => void
111
+
144
112
  /**
145
- * Key-count threshold for the gloss anomaly detector — {@link GLOSS_KEY_THRESHOLD} unless a test passes a
146
- * fixture-scale value. The production number is the #1730 sweep's own threshold (4,000 places at >= 50 keys).
113
+ * The key-count threshold for the gloss anomaly detector.
114
+ *
115
+ * Tests override it with a value sized for fixtures.
116
+ *
117
+ * @defaultValue {@linkcode GLOSS_KEY_THRESHOLD}
147
118
  */
148
119
  glossKeyThreshold?: number
149
120
  }
150
121
 
122
+ /**
123
+ * Row and place counts from each stage of {@link buildCandidateTable}.
124
+ */
151
125
  export interface BuildCandidateResult {
152
126
  rows: number
153
127
  places: number
154
128
  primaries: number
155
129
  aliases: number
130
+
156
131
  /**
157
- * Region aliases refused because another same-country region holds the name as its official name (three pairs in the
158
- * admin artifact: `新竹市`, `嘉義市`, `충청남도`). Zero on a source without a `names` table means the rule had nothing to read,
159
- * not that no pair exists.
132
+ * The count of region aliases dropped because another region in the same
133
+ * country uses the name as its official name.
134
+ *
135
+ * The count is always zero for a source without a `names` table.
160
136
  */
161
137
  regionOfficialRefused: number
162
138
  abbrevs: number
163
139
  postcodes: number
140
+
164
141
  /**
165
- * Delivery-city (and other `names`-table) aliases folded onto postcode rows — #1495. Zero here means the extracts
166
- * carried no alias names, NOT that the pass was skipped: a extract with no `names` table reports that separately
167
- * through `onProgress`.
142
+ * The count of aliases added to postcode rows from the extracts' `names` tables.
143
+ *
144
+ * An extract without a `names` table is reported through `onProgress`.
168
145
  */
169
146
  postcodeAliases: number
147
+
170
148
  /**
171
- * Closure rows in the `candidate_ancestor` sidecar (see candidate-ancestors-schema.ts). Zero means the source
172
- * `ancestors` table contributed nothing within the resolvable placetypes — a finding, reported rather than implied.
149
+ * The count of closure rows written to the `candidate_ancestor` sidecar,
150
+ * including rows from locality extracts.
173
151
  */
174
152
  ancestorRows: number
153
+
175
154
  /**
176
- * Places carrying at least one closure row.
155
+ * The count of places with at least one closure row.
177
156
  */
178
157
  ancestorPlaces: number
158
+
179
159
  /**
180
- * Places that received a pre/post interval label — the canonical-parent forest's node count. Places outside it (no
181
- * recorded ancestry, extract rows, cycle-skipped) have NO label: containment against them is unverifiable, never
182
- * false.
160
+ * The count of places that received a pre/post interval label.
161
+ * It equals the node count of the canonical-parent forest.
162
+ *
163
+ * Containment against a place outside the forest is unknown.
183
164
  */
184
165
  intervalPlaces: number
166
+
185
167
  /**
186
- * Places that took an `importance` score from the join (#28).
168
+ * The count of places that received an `importance` score.
187
169
  *
188
- * `undefined` and `0` mean different things. `undefined` is "the pass did not run" — no score source was given. A `0`
189
- * would be the source matching NOTHING, which is a finding. Never collapse the two.
170
+ * It is `null` when no score source was given.
190
171
  */
191
- importanceScored?: number
172
+ importanceScored: number | null
173
+
192
174
  /**
193
- * Places whose `(name_key, country, placetype)` matched a scored group but whose nearest scored centroid was outside
194
- * {@link IMPORTANCE_JOIN_RADIUS_KM} — a different town wearing the same name, refused rather than scored.
175
+ * The count of places that matched a scored group by name key, country and placetype
176
+ * but whose nearest scored centroid lay beyond {@link IMPORTANCE_JOIN_RADIUS_KM}.
195
177
  *
196
- * Worth watching across rebuilds: a jump here means the score source and the admin source have drifted apart, and the
197
- * join is being asked to guess.
178
+ * A large change between rebuilds means the score source and the admin source have diverged.
198
179
  */
199
- importanceFiltered?: number
180
+ importanceFiltered: number | null
181
+
200
182
  /**
201
- * Alias rows stamped `name_role = 'gloss'` — the anomaly detector's certain core (#1730).
183
+ * The count of alias rows the gloss anomaly detector stamped `name_role = 'gloss'`.
202
184
  */
203
185
  roleGloss: number
186
+
204
187
  /**
205
- * Alias rows stamped `name_role = 'abbr'` — the variant-in-official-language provenance signal (#1730/#936).
188
+ * The count of alias rows stamped `name_role = 'abbr'`, which marks a variant in the official language.
206
189
  */
207
190
  roleAbbr: number
191
+
208
192
  /**
209
- * Admin places whose staged key count reached {@link GLOSS_KEY_THRESHOLD} — the sweep's tail, reported so the stamped
210
- * fraction has its denominator.
193
+ * The count of admin places whose staged key count reached the gloss key threshold.
194
+ * It is the denominator for `keyTailWithRole`.
211
195
  */
212
196
  keyTailPlaces: number
197
+
213
198
  /**
214
- * Of {@link BuildCandidateResult.keyTailPlaces}, how many carry at least one stamped role row.
199
+ * The count of places in `keyTailPlaces` with at least one stamped role row.
215
200
  */
216
201
  keyTailWithRole: number
217
202
  }
218
203
 
204
+ /**
205
+ * Builds the candidate lookup database from a WOF admin database and replaces any existing output.
206
+ * The builder adds an FTS5 trigram index for fuzzy lookup.
207
+ */
219
208
  export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<BuildCandidateResult> {
220
209
  const progress = opts.onProgress ?? (() => {})
221
210
 
@@ -224,15 +213,12 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
224
213
  }
225
214
 
226
215
  using src = new DatabaseClient<WOFDatabase>(opts.input, { readOnly: true })
227
- await using kdb = new DatabaseClient<CandidateDatabase>(opts.output)
228
- // Build-tuning pragmas (raw — Kysely doesn't model PRAGMA). The code dictionaries + the transient
229
- // staging table come from the SHARED schema DDL, so they can't drift from {@link CandidateDatabase}.
216
+ using kdb = new DatabaseClient<CandidateDatabase>(opts.output)
217
+
230
218
  kdb.exec("PRAGMA page_size=8192; PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-2000000;")
231
219
 
232
220
  await createCandidateStagingTables(kdb)
233
221
 
234
- // Build compact country and placetype code maps for clustered keys.
235
- // assigned here; the rows are bulk-inserted via kdb once the passes have discovered every code. ---
236
222
  const ccodes = new Map<string, number>()
237
223
  const ptcodes = new Map<string, number>()
238
224
 
@@ -260,9 +246,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
260
246
  return id
261
247
  }
262
248
 
263
- // Load the importance source before the only pass that sees each place.
264
- // name/country/placetype/centroid together. Absent → every row's `importance` stays NULL. ---
265
- let importance: ReturnType<typeof loadImportanceIndex> | undefined
249
+ let importance: ReturnType<typeof loadImportanceIndex> | null = null
266
250
 
267
251
  if (opts.importance) {
268
252
  progress("importance", `loading place_importance from ${opts.importance}`)
@@ -274,23 +258,13 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
274
258
  (importance.stats.unkeyable ? `; ${importance.stats.unkeyable.toLocaleString()} unkeyable names skipped` : "")
275
259
  )
276
260
  } else {
277
- // Never a silent column of nulls: a build without a score source produces one, and the reason has
278
- // to be visible in the log rather than inferred from the artifact.
279
261
  progress("importance", "no score source given — `importance` will be NULL on every row")
280
262
  }
281
263
 
282
- // Derive each place's region ancestor for same-name disambiguation.
283
264
  progress("region", "loading region ancestry")
284
265
  const regionOf = new Map<number, number>()
285
266
  let multiRegion = 0
286
267
 
287
- // A place can carry more than one region-tier ancestor — 59 do on the 2026-08-03 artifact (42
288
- // localities, mostly Chinese places on an ambiguous boundary). One `region_id` column holds one of
289
- // them, and which one it ought to be is a real question this is not the place to answer.
290
- //
291
- // MIN is arbitrary but STABLE: an unordered pick lets the stamp for those places differ between two
292
- // builds of the same source. The count is logged because the number is expected to grow, and should
293
- // be visible rather than inferred.
294
268
  for (const r of src
295
269
  .prepare(
296
270
  "SELECT id, MIN(ancestor_id) AS ancestor_id, COUNT(DISTINCT ancestor_id) AS n" +
@@ -310,12 +284,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
310
284
  (multiRegion ? ` (${multiRegion.toLocaleString()} carry more than one; stamped with the lowest id)` : "")
311
285
  )
312
286
 
313
- // The hot path — millions of clustered rows. Kept a single positional prepared statement (the fastest
314
- // node:sqlite insert) rather than a per-row query builder. Placeholders come from CANDIDATE_COLUMNS so
315
- // the column COUNT can't drift; the positional run() args below MUST stay in CANDIDATE_COLUMNS order.
316
287
  const insStage = kdb.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`)
317
288
 
318
- // Stage primary names and attributes reused by later alias passes.
319
289
  progress("primaries", "indexing place names")
320
290
  const attrs = new Map<number, PlaceAttrs>()
321
291
  let nPrim = 0
@@ -326,7 +296,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
326
296
  `SELECT s.id AS id, s.name AS name, s.placetype AS placetype, s.country AS country,
327
297
  s.latitude AS lat, s.longitude AS lon,
328
298
  s.min_latitude AS mnlat, s.min_longitude AS mnlon, s.max_latitude AS mxlat, s.max_longitude AS mxlon,
329
- COALESCE(pp.population,0) AS pop
299
+ pp.population AS pop
330
300
  FROM spr s LEFT JOIN place_population pp ON pp.id = s.id
331
301
  WHERE s.is_current != 0 AND s.is_deprecated = 0`
332
302
  )
@@ -335,13 +305,14 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
335
305
  const cid = ccID(r.country as string | null)
336
306
  const ptid = ptID(r.placetype as string | null)
337
307
  const rid = regionOf.get(sid) ?? 0
338
- // A zero population on a COUNTRY row is a WOF absence artifact, never a real zero — 147 of 237
339
- // primary country records carried none (measured 2026-08-18, #1650), which ranked those nations
340
- // below any namesake hamlet in every prominence race ("Georgia" → Georgia VT). The codex table is
341
- // the secondary source; a country absent from it too stays at zero honestly.
342
- const wofPop = Number(r.pop) || 0
343
- const pop = wofPop === 0 && r.placetype === "country" ? (COUNTRY_POPULATION[String(r.country ?? "")] ?? 0) : wofPop
344
- const neg = -Math.log10(pop + 1)
308
+
309
+ const wofPop = r.pop === null || r.pop === undefined ? null : Number(r.pop)
310
+
311
+ const pop =
312
+ POPULATION_CORRECTIONS[sid]?.population ??
313
+ (wofPop === null && r.placetype === "country" ? (COUNTRY_POPULATION[String(r.country ?? "")] ?? null) : wofPop)
314
+
315
+ const neg = -Math.log10((pop ?? 0) + 1)
345
316
  const name = String(r.name ?? "")
346
317
  const pkey = normalizeLocalityForKey(name)
347
318
  const lat = r.lat as number
@@ -424,9 +395,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
424
395
  )
425
396
  }
426
397
 
427
- // Stage country display names across scripts.
428
- // Never a silent zero: a runtime whose ICU lacks these locales degrades to fewer surfaces, and the count is how a
429
- // reader tells that apart from the pass not having run.
430
398
  progress(
431
399
  "country-display-names",
432
400
  `${stageCountryDisplayNames({
@@ -438,8 +406,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
438
406
  }).toLocaleString()} country surfaces`
439
407
  )
440
408
 
441
- // Backfill cross-source currency before later candidate passes.
442
- // the alias pass so a resurrected place's alt names explode like any primary's. ---
443
409
  if (opts.currencyBackfill) {
444
410
  const nBackfill = await resurrectCurrencyHoles({
445
411
  src,
@@ -458,12 +424,9 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
458
424
 
459
425
  progress("currency-backfill", `${nBackfill.toLocaleString()} resurrections staged`)
460
426
  } else {
461
- // Never a silent absence: a build without the option leaves the deprecated-no-successor holes dead,
462
- // and the log must say so rather than leave it inferable only from a missing row.
463
427
  progress("currency-backfill", "not configured — deprecated-no-successor holes stay dead (#1737)")
464
428
  }
465
429
 
466
- // Stage distinct normalized aliases from alternate names.
467
430
  progress("aliases", "exploding alias bags")
468
431
 
469
432
  const { nAlias, keyCounts, regionOfficialRefused } = explodeAliasBags(src, kdb, attrs, stageRow, {
@@ -479,7 +442,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
479
442
  : "")
480
443
  )
481
444
 
482
- // Stage region abbreviations.
483
445
  let nAbbr = 0
484
446
  kdb.exec("BEGIN")
485
447
 
@@ -498,9 +460,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
498
460
  kdb.exec("COMMIT")
499
461
  progress("abbrevs", `${nAbbr.toLocaleString()} abbrevs`)
500
462
 
501
- // Stamp each name's semantic role.
502
- // Independent of the sidecar below: this writes `cand_stage.name_role`, that writes the ancestor and
503
- // interval tables, and neither reads the other's output. Ordered by label only.
504
463
  const roles = stampNameRoles({
505
464
  src,
506
465
  out: kdb,
@@ -512,10 +471,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
512
471
  progress,
513
472
  })
514
473
 
515
- // Stage the encoded ancestors sidecar.
516
474
  const sidecar = await buildAncestorsSidecar({ src, out: kdb, attrs, ptID, progress })
517
475
 
518
- // Fold postcode and locality extracts into candidate records.
519
476
  let nPostcode = 0
520
477
  let nPostcodeAlias = 0
521
478
 
@@ -569,7 +526,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
569
526
  )
570
527
  }
571
528
 
572
- // Insert the small typed code dictionaries through Kysely.
573
529
  if (ccodes.size) {
574
530
  await kdb
575
531
  .insertInto("country_codes")
@@ -584,7 +540,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
584
540
  .execute()
585
541
  }
586
542
 
587
- // Insert the small typed capital-status reference table.
588
543
  if (opts.capitals?.length) {
589
544
  await createCapitalTable<CandidateDatabase>(kdb)
590
545
 
@@ -596,7 +551,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
596
551
  latitude: entry.latitude,
597
552
  longitude: entry.longitude,
598
553
  level: entry.level,
599
- keys: JSON.stringify(entry.k),
554
+ keys: stringifyJSON(entry.k),
600
555
  }))
601
556
  )
602
557
  .execute()
@@ -604,25 +559,18 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
604
559
  progress("capitals", `${opts.capitals.length.toLocaleString()} capital-reference rows carried in-artifact`)
605
560
  }
606
561
 
607
- // Materialize the clustered WITHOUT ROWID table in sorted order.
608
562
  progress("cluster", "building clustered candidate table + VACUUM")
609
- // Column list + clustered-key order are sourced from CANDIDATE_COLUMNS (the first 6 ARE the PRIMARY
610
- // KEY) so the SELECT, the ORDER BY, and the table can't drift. The table comes from the shared
611
- // createCandidateTable().
563
+
612
564
  const cols = CANDIDATE_COLUMNS.join(", ")
613
565
  const keyOrder = CANDIDATE_COLUMNS.slice(0, 6).join(", ")
614
566
  await createCandidateTable(kdb)
615
- // OR IGNORE: an abbrev/alias can normalize to a place's primary key (same place, same rank) → any one
616
- // row. The bulk sorted INSERT…SELECT (clustered materialization) stays raw — a single hot bulk statement.
567
+
617
568
  kdb.exec(`INSERT OR IGNORE INTO candidate (${cols}) SELECT ${cols} FROM cand_stage ORDER BY ${keyOrder};`)
618
569
  await kdb.schema.dropTable("cand_stage").execute()
619
- // Typo-tolerant fallback index (the unified gazetteer's second mode): the exact name_key probe can't
620
- // recover misspellings, so FTS5-trigram over `name` lets the reader fuzzy-match on an exact+strip miss.
570
+
621
571
  progress("fts", "building FTS5-trigram fuzzy index")
622
572
  createCandidateFTS(kdb)
623
- // page_size MUST be set right before VACUUM: node:sqlite initializes the file at the 4096 default on
624
- // `new DatabaseSync`, so the creation-time pragma is a no-op — only a VACUUM rebuilds at the new size.
625
- // 8192 matches the sql.js-httpvfs 64 KiB request chunk cleanly (8 pages) and shallows the B-tree.
573
+
626
574
  kdb.exec("PRAGMA page_size=8192")
627
575
  kdb.exec("VACUUM")
628
576
 
@@ -631,7 +579,6 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
631
579
  .select((eb) => eb.fn.countAll<number>().as("n"))
632
580
  .executeTakeFirstOrThrow()
633
581
 
634
- // closes the underlying `out` connection
635
582
  return {
636
583
  rows,
637
584
  places: attrs.size,
@@ -645,6 +592,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
645
592
  ancestorPlaces: sidecar.ancestorPlaces + nLocalityScoped,
646
593
  intervalPlaces: sidecar.intervalPlaces,
647
594
  ...roles,
648
- ...(importance ? { importanceScored: importance.matched, importanceFiltered: importance.refused } : {}),
595
+ importanceScored: importance ? importance.matched : null,
596
+ importanceFiltered: importance ? importance.refused : null,
649
597
  }
650
598
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Populations the gazetteer records at a fraction of their size, corrected against the record's own concordance.
6
+ *
7
+ * This file records corrections for review. The build must not rewrite populations from a
8
+ * same-name-parent ratio. The detector cannot distinguish a district's mis-recorded namesake
9
+ * city from a small village with the district name. Both places are real.
10
+ *
11
+ * Each entry identifies one row that a reviewer checked against a second source.
12
+ * The row's own `gn:id` must agree with the coordinates. The GeoNames record must identify the same place.
13
+ *
14
+ * The correction applies to the candidate artifact rather than the admin gazetteer.
15
+ * `place_population` in `admin-global-priority.db` still stores the WOF figure, so a reader going
16
+ * there sees WOF's value and a reader going to `candidate.db` sees the corroborated number. The
17
+ * admin database is a transcription of its source, so correcting it there would make the
18
+ * transcription disagree with what it transcribes.
19
+ */
20
+
21
+ /**
22
+ * One corrected population, with what corroborates it.
23
+ */
24
+ export interface PopulationCorrection {
25
+ /**
26
+ * The GeoNames id from the WOF record's own `concordances` row makes this a
27
+ * correction rather than a substitution.
28
+ *
29
+ * The two sources describe the same place according to WOF.
30
+ */
31
+ geonamesID: string
32
+ population: number
33
+ /**
34
+ * What the gazetteer recorded, kept so a reader can see the size of the
35
+ * disagreement without opening the artifact.
36
+ */
37
+ recorded: number
38
+ /**
39
+ * The place and the reviewer's reading.
40
+ */
41
+ note: string
42
+ }
43
+
44
+ /**
45
+ * WOF id → the corrected population.
46
+ *
47
+ * One entry, because one row has been followed.
48
+ * A second correction requires the same review.
49
+ *
50
+ * Read the row's `gn:id` and confirm the coordinates agree.
51
+ * Then record the second source's value.
52
+ */
53
+ export const POPULATION_CORRECTIONS: Readonly<Record<number, PopulationCorrection>> = {
54
+ 102_030_887: {
55
+ geonamesID: "1278149",
56
+ population: 1_175_116,
57
+ recorded: 19_172,
58
+ note:
59
+ "Aurangabad (Chhatrapati Sambhaji Nagar), Maharashtra. The WOF record sits at 19.870 / 75.349 and GeoNames " +
60
+ "1278149 — the id that record's own concordance names — sits at 19.878 / 75.342, about 1 km apart, classed " +
61
+ "`PPLA2`. WOF's 19,172 against its parent district's 3,701,282 is the 193x ratio that surfaced it; the " +
62
+ "corroborated figure makes the city 32% of its district rather than 0.5% of it.",
63
+ },
64
+ }