@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
@@ -0,0 +1,316 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file SQLite implementation of core's `AddressPointLookup`.
6
+ */
7
+
8
+ import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
9
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
10
+ import type { PathBuilderLike } from "path-ts"
11
+
12
+ import type { AddressPointDatabase, AddressPointTable } from "#address/point/schema"
13
+ import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
14
+ import {
15
+ normalizeHouseNumberForKey,
16
+ normalizeLocalityForKey,
17
+ normalizeLocalityForKeyLocale,
18
+ type NameKey,
19
+ type StreetKey,
20
+ streetKeyVariants,
21
+ streetLocaleForSurface,
22
+ stripArrondissement,
23
+ type StreetLocale,
24
+ } from "#street/normalize"
25
+
26
+ /**
27
+ * The columns this lookup reads, typed from the shared {@link AddressPointTable}
28
+ * so a writer-side rename fails to compile here.
29
+ */
30
+ type AddressPointRow = Pick<AddressPointTable, "lat" | "lon" | "source" | "release" | "locality_norm" | "postcode">
31
+
32
+ /**
33
+ * The column list every prepared SELECT uses.
34
+ * It must match the fields of `AddressPointRow`.
35
+ */
36
+ const SELECT_COLS = "lat, lon, source, release, locality_norm, postcode"
37
+
38
+ /**
39
+ * Finds an address point by exact normalized `(street, number)` within a postcode,
40
+ * a locality, or a bounding box.
41
+ *
42
+ * Query normalization uses the extract's `streetLocale`, which must match the
43
+ * locale the extract was built with.
44
+ * Street matching is exact after normalization.
45
+ *
46
+ * Scopes are tried from most to least selective: postcode, locality, locality suffix, then bounding box.
47
+ * When several rows match, any one of them can answer, because unit siblings share the building coordinate.
48
+ */
49
+ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressPointDatabase>
50
+ implements AddressPointLookup, Disposable
51
+ {
52
+ readonly #db: DatabaseClient<DB>
53
+ readonly #locale: StreetLocale
54
+ readonly #localityKeys: "full" | "abbreviated"
55
+ readonly #byPostcode: PreparedGet<[postcode: string, street: StreetKey, number: string], AddressPointRow> | undefined
56
+ readonly #byPostcodeLocality:
57
+ | PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
58
+ | undefined
59
+ readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
60
+ /**
61
+ * This query matches the locality key by its suffix, for a `zh` query that gives the 鄉鎮市區 without its 縣市.
62
+ *
63
+ * The (street, number) index narrows the rows first, so the `LIKE` scans only rows that share the pair.
64
+ */
65
+ readonly #byLocalityTail:
66
+ | PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
67
+ | undefined
68
+ readonly #byBbox:
69
+ | PreparedGet<
70
+ [street: StreetKey, number: string, minLat: number, maxLat: number, minLon: number, maxLon: number],
71
+ AddressPointRow
72
+ >
73
+ | undefined
74
+
75
+ /**
76
+ * @param dbPath Extract path.
77
+ * @param opts.streetLocale The street-normalization locale the extract was built with.
78
+ * A mismatch makes every key miss.
79
+ * @param opts.localityKeys Whether the extract's `locality_norm` holds full place names.
80
+ * With `"abbreviated"`, a row's locality can select a row but never rejects one.
81
+ * The US situs extract uses NAD city fields.
82
+ * Some counties abbreviate them (`addi` for Addison).
83
+ * Other counties replace them with the parent town.
84
+ *
85
+ * @defaultValue `"us"`
86
+ * @defaultValue `"abbreviated"` for `"us"` and `"full"` for every other locale.
87
+ */
88
+ constructor(
89
+ dbPath: PathBuilderLike,
90
+ opts: { streetLocale?: StreetLocale; localityKeys?: "full" | "abbreviated" } = {}
91
+ ) {
92
+ this.#db = new DatabaseClient<DB>(dbPath, { readOnly: true })
93
+ this.#locale = opts.streetLocale ?? "us"
94
+ this.#localityKeys = opts.localityKeys ?? (this.#locale === "us" ? "abbreviated" : "full")
95
+
96
+ // An extract without an `address_point` table, such as an interrupted build, makes every lookup miss.
97
+ if (hasTable(this.#db, "address_point")) {
98
+ this.#byPostcode = prepareGet(
99
+ this.#db,
100
+ `SELECT ${SELECT_COLS} FROM address_point
101
+ WHERE postcode = ? AND street_norm = ? AND number = ? LIMIT 1`
102
+ )
103
+
104
+ this.#byPostcodeLocality = prepareGet(
105
+ this.#db,
106
+ `SELECT ${SELECT_COLS} FROM address_point
107
+ WHERE postcode = ? AND locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
108
+ )
109
+
110
+ this.#byLocality = prepareGet(
111
+ this.#db,
112
+ `SELECT ${SELECT_COLS} FROM address_point
113
+ WHERE locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
114
+ )
115
+
116
+ this.#byBbox = prepareGet(
117
+ this.#db,
118
+ `SELECT ${SELECT_COLS} FROM address_point
119
+ WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
120
+ )
121
+
122
+ this.#byLocalityTail = prepareGet(
123
+ this.#db,
124
+ `SELECT ${SELECT_COLS} FROM address_point
125
+ WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
126
+ )
127
+ }
128
+ }
129
+
130
+ find(query: {
131
+ street: string
132
+ number: string
133
+ postcode?: string
134
+ locality?: string
135
+ region?: string
136
+ subregion?: string
137
+ bbox: { minLat: number; maxLat: number; minLon: number; maxLon: number } | null
138
+ }): AddressPointHit | null {
139
+ if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
140
+ const number = normalizeHouseNumberForKey(query.number, this.#locale)
141
+
142
+ if (!number) return null
143
+
144
+ // A `zh` extract keys locality as 縣市 + 鄉鎮市區.
145
+ // The parser tags 縣市 as `region` and 鄉鎮市區 as `subregion`.
146
+ // The Taiwanese register has no postcode, so this pair becomes the locality key.
147
+ // A query with only the 鄉鎮市區 matches the stored key by its suffix.
148
+ const scoped =
149
+ this.#locale === "zh" && !query.locality && query.subregion
150
+ ? query.region
151
+ ? { ...query, locality: `${query.region}${query.subregion}` }
152
+ : { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
153
+ : query
154
+
155
+ // `streetKeyVariants` yields the literal key first.
156
+ // Each variant runs the full number fallback sequence.
157
+ // The first variant with a match wins.
158
+ let row: AddressPointRow | null = null
159
+
160
+ for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
161
+ row = this.#findForKey(streetNorm, number, scoped)
162
+
163
+ if (row) break
164
+ }
165
+
166
+ if (!row) return null
167
+
168
+ return {
169
+ lat: row.lat,
170
+ lon: row.lon,
171
+ source: row.source,
172
+ release: row.release,
173
+ localityNorm: row.locality_norm || null,
174
+ postcode: row.postcode || null,
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Tries one normalized street key with the exact number, then fallback number forms.
180
+ * Each fallback runs only after the previous forms missed.
181
+ */
182
+ #findForKey(
183
+ streetNorm: StreetKey,
184
+ number: string,
185
+ query: {
186
+ street: string
187
+ number: string
188
+ postcode?: string
189
+ locality?: string
190
+ localityTail?: NameKey
191
+ bbox: { minLat: number; maxLat: number; minLon: number; maxLon: number } | null
192
+ }
193
+ ) {
194
+ let row = this.#probe(streetNorm, number, query)
195
+
196
+ // The registers store one number per point, so a range such as "385-387" falls back to its low end.
197
+ if (!row) {
198
+ const low = /^(\d+[a-z]?)-\d+[a-z]?$/.exec(number)?.[1]
199
+
200
+ if (low) {
201
+ row = this.#probe(streetNorm, low, query)
202
+ }
203
+ }
204
+
205
+ // BAN stores a letter suffix as "3 a", while G-NAF and most OA sources store "3a".
206
+ // For a number of digits plus one letter, the lookup retries the other spacing
207
+ // and then the bare number.
208
+ if (!row) {
209
+ const joined = /^(\d+)\s+([a-z])$/.exec(number)
210
+ const spaced = /^(\d+)([a-z])$/.exec(number)
211
+
212
+ if (joined) {
213
+ row = this.#probe(streetNorm, `${joined[1]}${joined[2]}`, query) ?? this.#probe(streetNorm, joined[1]!, query)
214
+ } else if (spaced) {
215
+ row = this.#probe(streetNorm, `${spaced[1]} ${spaced[2]}`, query) ?? this.#probe(streetNorm, spaced[1]!, query)
216
+ }
217
+ }
218
+
219
+ // The Taiwanese register stores `14之12` and `30附40` as numbers `14` and `30`.
220
+ // It stores the rest in `unit`.
221
+ // A query number that still includes the sub-number falls back to the base number.
222
+ // That result is approximate.
223
+ if (!row && this.#locale === "zh") {
224
+ const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
225
+
226
+ if (base) {
227
+ row = this.#probe(streetNorm, base, query)
228
+ }
229
+ }
230
+
231
+ return row
232
+ }
233
+
234
+ /**
235
+ * Probes one (street, number) key by postcode, then locality, then locality suffix, then bounding box.
236
+ * Each scope runs only when the previous ones missed.
237
+ */
238
+ #probe(
239
+ streetNorm: StreetKey,
240
+ number: string,
241
+ query: {
242
+ postcode?: string
243
+ locality?: string
244
+ localityTail?: NameKey
245
+ bbox: { minLat: number; maxLat: number; minLon: number; maxLon: number } | null
246
+ }
247
+ ): AddressPointRow | null {
248
+ let row: AddressPointRow | null = null
249
+
250
+ if (query.postcode) {
251
+ const postcode = query.postcode.trim()
252
+ const localityKey = query.locality ? this.#localityKey(query.locality) : null
253
+
254
+ // One postcode can cover several places with the same street and number,
255
+ // so a row whose locality agrees with the query is tried first.
256
+ // The postcode-only row answers only if `#scopeContradicts` accepts it.
257
+ const agreeing = localityKey ? this.#byPostcodeLocality!(postcode, localityKey, streetNorm, number) : null
258
+ const candidate = agreeing ?? this.#byPostcode!(postcode, streetNorm, number)
259
+
260
+ row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : null
261
+ }
262
+
263
+ if (!row && query.locality) {
264
+ row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
265
+ }
266
+
267
+ if (!row && query.localityTail && this.#byLocalityTail) {
268
+ // A Han key contains no `%` or `_`, so the pattern matches a literal suffix.
269
+ row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
270
+ }
271
+
272
+ // The bounding box serves extracts whose points have no postcode or locality, such as OSM.
273
+ if (!row && query.bbox) {
274
+ const b = query.bbox
275
+ const candidate = this.#byBbox!(streetNorm, number, b.minLat, b.maxLat, b.minLon, b.maxLon)
276
+
277
+ // A row with its own scope that the scoped probes missed is a different address in the same box.
278
+ row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : null
279
+ }
280
+
281
+ return row
282
+ }
283
+
284
+ /**
285
+ * Folds the query's locality the way the extract's builder folded `locality_norm`.
286
+ *
287
+ * FR extracts key arrondissements at the base city, so "Paris 13e Arrondissement"
288
+ * and "Paris" produce the same key.
289
+ */
290
+ #localityKey(locality: string): NameKey {
291
+ if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
292
+
293
+ return normalizeLocalityForKeyLocale(locality, this.#locale)
294
+ }
295
+
296
+ /**
297
+ * Returns whether the row's own postcode or locality differs from the query's.
298
+ *
299
+ * A row field that is empty never contradicts.
300
+ * The locality is compared only when the extract's `localityKeys` is `"full"`,
301
+ * because abbreviated keys would reject correct rows.
302
+ */
303
+ #scopeContradicts(row: AddressPointRow, query: { postcode?: string; locality?: string }): boolean {
304
+ if (query.postcode && row.postcode && row.postcode.trim() !== query.postcode.trim()) return true
305
+
306
+ if (this.#localityKeys === "full" && query.locality && row.locality_norm) {
307
+ return row.locality_norm !== this.#localityKey(query.locality)
308
+ }
309
+
310
+ return false
311
+ }
312
+
313
+ [Symbol.dispose](): void {
314
+ this.#db[Symbol.dispose]()
315
+ }
316
+ }
@@ -4,6 +4,6 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  */
6
6
 
7
- export * from "#address/point/index"
7
+ export * from "#address/point"
8
8
  export * from "#address/point/interpolation"
9
9
  export * from "#address/point/schema"
@@ -3,86 +3,36 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Repair the truncated ancestry that {@link populateAncestors} (the parent_id closure in
7
- * unified-schema.ts) leaves wherever the closure dead-ends before reaching the top.
8
- *
9
- * Root cause (#440 / #832): a place that straddles multiple parents — New York City spans five
10
- * counties (its boroughs), London 30+ — carries `wof:parent_id = -4`, so the parent_id closure
11
- * dead-ends and the place gets NO region/county/country ancestry. The resolver's region-descendant
12
- * filter then can't reach it: given "New York, NY", NYC (with no NY-state ancestor) is excluded and
13
- * a correctly-parented namesake ("New York Mills", pop 3,190) wins over NYC's 8.8M. The same defect
14
- * orphans London, Singapore, and ~2,850 other localities — the most demo-visible queries.
15
- *
16
- * **The dead end is inherited by children (#1445).** Repairing the `-4` place itself does not repair
17
- * anything BELOW it: the closure walks Brooklyn → New York and stops, because New York's own
18
- * `parent_id` is `-4`. Brooklyn-the-borough (pop 2.5M) therefore carried exactly two ancestor rows —
19
- * itself and New York — with no county, region or country, and the only US locality-tier place named
20
- * "Brooklyn" that survived a New-York-State descendant filter was Pillar Point, a Jefferson County
21
- * hamlet 411 km away carrying "Brooklyn" as an alternate name. All five NYC boroughs, every London
22
- * borough and Hyderabad's zones were in the same state.
23
- *
24
- * So the candidate test is NOT "has only a self ancestor" — that misses every child of a repaired
25
- * place, which by construction has two. It is **"has no `country`-tier ancestor"**. Country is the
26
- * universal terminal for every non-{@link TOP_PLACETYPES} placetype, so its absence is exactly the
27
- * signal that the chain dead-ended somewhere, at whatever depth. On a wide-coverage build this
28
- * selects ~24k places against 2.55M rows.
29
- *
30
- * A place whose `wof:hierarchy` genuinely stops short is NOT a candidate and needs no repair: the
31
- * source is the authority on what a place should have. American Samoa's localities, for instance,
32
- * have `{country_id, locality_id}` and no region in WOF itself — the artifact matching that is
33
- * correct, not truncated.
34
- *
35
- * The authoritative hierarchy IS in the source geojson: `wof:hierarchy` is an array of branches,
36
- * each a `<placetype>_id` → id map (region_id, county_id, country_id, …), fully populated even when
37
- * parent_id is -4. This reads it for every candidate and inserts the missing ancestor rows (one per
38
- * distinct ancestor across branches).
39
- *
40
- * MUST run AFTER populateAncestors and BEFORE the build freezes (VACUUM INTO), so the rows land in
41
- * the shipped artifact — `scripts/build-unified-wof.ts` Phase 3 calls it inline. The standalone
42
- * `scripts/backfill-ancestors-from-hierarchy.ts` is a thin CLI over the same function for ad-hoc
43
- * repair of an already-built DB. Idempotent by the per-pair existence check, not by the candidate
44
- * test: each (id, ancestor_id) is inserted at most once, so a second run over the same DB adds
45
- * nothing.
6
+ * Repair the truncated ancestry left by the `parent_id` closure. Must run after `populateAncestors` and before the build freezes.
46
7
  */
47
8
 
48
9
  import { readWOFFeature } from "@mailwoman/core/resources/whosonfirst"
49
10
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
50
- import { join, resolvePath, type PathBuilderLike } from "path-ts"
11
+ import { PathBuilder, type PathBuilderLike } from "path-ts"
51
12
  import { Globerator } from "spliterator/node/fs"
52
13
 
53
14
  import type { WOFDatabase } from "#schema"
54
15
 
55
- /**
56
- * Genuinely top-level placetypes — they never have (or need) an ancestor, so skip them.
57
- */
58
16
  const TOP_PLACETYPES = new Set(["country", "continent", "empire", "ocean", "marinearea", "planet"])
59
17
 
60
18
  export interface AncestryBackfillResult {
61
- /**
62
- * Places that gained at least one ancestor row.
63
- */
64
19
  placesFixed: number
65
- /**
66
- * Total ancestor rows inserted.
67
- */
68
20
  rowsAdded: number
69
21
  /**
70
- * Candidates whose source geojson could not be found (non-WOF backfilled places, or repos not present locally) —
71
- * skipped, not an error.
22
+ * Candidates whose source geojson was not found (non-WOF backfilled places or absent repos),
23
+ * skipped rather than treated as an error.
72
24
  */
73
25
  noGeojson: number
74
26
  }
75
27
 
76
28
  /**
77
- * Discover the `data` directories under a WOF repos root that hold attached geojson, e.g.
78
- * `<root>/whosonfirst-data/whosonfirst-data-admin-us/data`. Resolves an id to its geojson via these roots. Accepts both
79
- * the nested lab layout (a `whosonfirst-data` group dir holding the admin repos) and a flat layout (admin repos
80
- * directly under the root); searches at most two directory levels deep.
29
+ * Discover the `data` directories under a WOF repos root that hold attached geojson,
30
+ * accepting both the nested lab layout and a flat layout at most two directory levels deep.
81
31
  */
82
- export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promise<string[]> {
83
- const roots: string[] = []
32
+ export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promise<PathBuilder[]> {
33
+ const roots: PathBuilder[] = []
84
34
 
85
- const visit = async (dir: string, depth: number): Promise<void> => {
35
+ const visit = async (dir: PathBuilder, depth: number): Promise<void> => {
86
36
  if (depth > 2) return
87
37
 
88
38
  let names: string[]
@@ -96,7 +46,7 @@ export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promis
96
46
  }
97
47
 
98
48
  for (const name of names) {
99
- const child = join(dir, name)
49
+ const child = dir(name)
100
50
 
101
51
  if (name === "data") {
102
52
  roots.push(child)
@@ -106,15 +56,13 @@ export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promis
106
56
  }
107
57
  }
108
58
 
109
- await visit(resolvePath(reposRoot), 0)
59
+ await visit(PathBuilder.from(reposRoot), 0)
110
60
 
111
61
  return roots
112
62
  }
113
63
 
114
- // `<placetype>_id` key → ancestor placetype. WOF hierarchy keys are e.g. region_id, county_id. Self
115
- // is filtered downstream by the `aid === id` check, so we do NOT special-case locality here: for a
116
- // locality candidate `locality_id` IS self (dropped by aid===id), but for a neighbourhood candidate
117
- // `locality_id` is its PARENT locality — a real ancestor we must keep.
64
+ // The `aid === id` check filters out a place's own `locality_id`.
65
+ // For a neighborhood, that id identifies a real ancestor.
118
66
  function placetypeFromKey(key: string): string | null {
119
67
  if (!key.endsWith("_id")) return null
120
68
 
@@ -122,28 +70,19 @@ function placetypeFromKey(key: string): string | null {
122
70
  }
123
71
 
124
72
  /**
125
- * Insert missing ancestor rows for every place whose ancestry chain dead-ended before reaching a country, by reading
126
- * `wof:hierarchy` from its source geojson under `geojsonRoots` (see {@link discoverAdminDataRoots}). Runs inside a
127
- * single transaction; caller owns connection lifecycle (open, WAL checkpoint, close).
73
+ * Insert missing ancestor rows for places whose ancestry chain ended before reaching a country.
128
74
  *
129
- * `opts.maxID` bounds the candidate scan to ids BELOW it — pass the synthetic-id base (`OVERTURE_ID_BASE`, 8e12) so the
130
- * backfill considers only real WOF places. Overture/GeoNames rows carry synthetic ids and have NO `wof:hierarchy`
131
- * geojson, so probing them is pure waste: on a wide-coverage DB the country-less set is millions of Overture/GeoNames
132
- * leaf localities, and the per-candidate geojson probe across every repo root turns a seconds-long WOF-only pass into a
133
- * ~40-minute one (their ancestry comes from the parent_id closure, not this backfill). Correctness-preserving — the
134
- * skipped rows would have `noGeojson`-skipped anyway. Omit `maxID` (default) for the legacy WOF-only DBs.
75
+ * The function reads `wof:hierarchy` and runs in one transaction under the caller's connection.
76
+ * `opts.maxID` bounds the scan to avoid probing synthetic-id Overture and GeoNames rows.
135
77
  */
136
78
  export async function backfillAncestorsFromHierarchy(
137
79
  db: DatabaseClient<WOFDatabase>,
138
- geojsonRoots: readonly string[],
80
+ geojsonRoots: readonly PathBuilderLike[],
139
81
  opts: { maxID?: number } = {}
140
82
  ): Promise<AncestryBackfillResult> {
141
83
  const maxID = opts.maxID ?? Number.MAX_SAFE_INTEGER
142
84
 
143
- // "No country-tier ancestor" is the dead-end signal at any depth — see the module docstring. The
144
- // earlier "<= 1 ancestor row" test only caught the dead end's origin, never the children that
145
- // inherit it (a child of a repaired -4 place has two rows: itself and that parent) (#1445).
146
- // The id bound is stated first so SQLite prunes by the PK index before the NOT EXISTS runs at all.
85
+ // The id bound is stated first so SQLite prunes by the PK index before the not-exists runs.
147
86
  const candidateBase = db
148
87
  .selectFrom("spr")
149
88
  .where("id", "<", maxID)
@@ -161,12 +100,8 @@ export async function backfillAncestorsFromHierarchy(
161
100
 
162
101
  const candidates = await candidateBase.select(["id", "placetype"]).execute()
163
102
 
164
- // Every candidate's existing ancestors in ONE query rather than an indexed read each. The widened
165
- // candidate test made that per-candidate read the dominant cost of the pass, and the set is bounded:
166
- // a candidate reaching this point has a handful of rows at most. The candidate set rides in as the
167
- // SAME predicate re-issued as a subquery, never a materialized `IN (?, ?, …)` list — node:sqlite
168
- // caps a statement at 32,766 bound variables and the wide-coverage build has 67,521 candidates
169
- // (measured 2026-08-04).
103
+ // Keep the candidate set as a subquery instead of materializing an `IN` list.
104
+ // node:sqlite limits bound variables to 32,766.
170
105
  const alreadyPresent = new Map<number, Set<number>>()
171
106
 
172
107
  for (const row of await db
@@ -206,7 +141,6 @@ export async function backfillAncestorsFromHierarchy(
206
141
  continue
207
142
  }
208
143
 
209
- // Collect distinct (ancestor_id, placetype) across all hierarchy branches, excluding self.
210
144
  const seen = new Map<number, string>()
211
145
 
212
146
  for (const branch of hierarchy) {
@@ -3,25 +3,26 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * The shared ancestor-lineage walk over the WOF `ancestors` table — one place's containment chain
7
- * joined with `spr` for canonical names + centroids, ordered NEAREST-FIRST (deepest placetype
8
- * first, country last).
6
+ * The shared ancestor-lineage walk over the WOF `ancestors` table: one place's containment chain
7
+ * joined with `spr` for canonical names and centroids, ordered nearest-first with the deepest
8
+ * placetype first and the country last.
9
9
  *
10
- * Factored out of `WOFSQLitePlaceLookup.ancestors()` (#404) so the reverse geocoder (`reverse.ts`,
11
- * #484) reuses the SAME walk instead of growing a second one. The placetype-specificity ordering
12
- * lives here as `PLACETYPE_DEPTH` — a single TS map instead of the previous SQL CASE, and
13
- * extended below `localadmin` (locality/borough/neighbourhood/microhood now rank correctly
14
- * instead of sorting last; forward resolution rarely saw those as ANCESTOR placetypes, reverse
15
- * geocoding always does).
10
+ * The reverse geocoder and `WOFSQLitePlaceLookup.ancestors()` share this walk. Placetype
11
+ * specificity lives here in `PLACETYPE_DEPTH`, a single TypeScript map that extends below
12
+ * `localadmin` so locality, borough, neighborhood and microhood rank correctly. Forward
13
+ * resolution rarely sees those as ancestor placetypes, while reverse geocoding always does.
16
14
  */
17
15
 
18
16
  import { allRows } from "@mailwoman/core/utils"
19
17
  import type { DatabaseClient } from "@mailwoman/sqlite/client"
20
18
 
21
19
  /**
22
- * WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve (continent, empire, …)
23
- * map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER` (fst-serialize.ts) — that one is a
24
- * serialization order, this one is containment depth.
20
+ * WOF placetype → containment depth, coarsest = 1.
21
+ *
22
+ * Higher = finer. Placetypes we never resolve (continent, empire, …) map to 0 and sort last.
23
+ *
24
+ * This differs from the FST's `PLACETYPE_ORDER` (fst-serialize.ts), which is a serialization order.
25
+ * This one is containment depth.
25
26
  */
26
27
  export const PLACETYPE_DEPTH: Readonly<Record<string, number>> = {
27
28
  country: 1,
@@ -38,7 +39,7 @@ export const PLACETYPE_DEPTH: Readonly<Record<string, number>> = {
38
39
  }
39
40
 
40
41
  /**
41
- * Containment depth for a placetype — 0 (sorts coarsest) when unknown.
42
+ * Containment depth for a placetype, 0 when unknown (sorts coarsest).
42
43
  */
43
44
  export function placetypeDepth(placetype: string): number {
44
45
  return PLACETYPE_DEPTH[placetype] ?? 0
@@ -57,8 +58,11 @@ export interface AncestorPlaceRow {
57
58
  }
58
59
 
59
60
  /**
60
- * The ancestor lineage of `id` — self excluded, nearest-first. Returns `[]` when the place has no recorded ancestry.
61
- * NOT memoized here; `WOFSQLitePlaceLookup` keeps its own per-id cache.
61
+ * The ancestor lineage of `id`, self excluded, nearest-first.
62
+ *
63
+ * @returns `[]` when the place has no recorded ancestry.
64
+ * Not memoized here.
65
+ * `WOFSQLitePlaceLookup` keeps its own per-id cache.
62
66
  */
63
67
  export function ancestorLineage<DB>(db: DatabaseClient<DB>, id: number, schemaName = "main"): AncestorPlaceRow[] {
64
68
  const rows = allRows<AncestorPlaceRow>(