@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
package/lib/reverse.ts CHANGED
@@ -3,29 +3,23 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Reverse geocoding (#484): `(lat, lon)` → the containing admin hierarchy. Assembly over existing
7
- * implementation, per the 2026-06-11 scoping notes:
6
+ * Reverse geocoding: map a `(lat, lon)` point to the containing admin hierarchy.
8
7
  *
9
- * 1. **Candidate fetch** — the admin DB's `place_bbox` R*Tree (built by `fts.ts`) for places whose
10
- * bbox contains the point, smallest-area-first (so the FIRST polygon confirmation is the
11
- * deepest).
12
- * 2. **PIP confirmation** — ray-cast (geo.ts, the canonical TS port of
13
- * `scripts/eval/pip-containment.py`) against the polygon sidecar DB (`wof-polygons.db`,
14
- * `polygons(id, geom)` with GeoJSON text — built by `scripts/build-wof-polygons.mjs` for the
15
- * demo map). A candidate whose polygon EXISTS but rejects the point is a bbox false positive
16
- * and is dropped entirely; a candidate with no polygon row stays eligible for the
17
- * approximate fallback.
18
- * 3. **Approximate descent** — WOF carries point geometry for most localities (#292: ~99% of JP
19
- * municipalities; ~half of US localities have degenerate bboxes too), so the polygon walk
20
- * usually bottoms out at county level. We then descend tier-by-tier (county → localadmin →
21
- * locality → …) through the winner's DESCENDANTS (the `ancestors` table, reversed), taking
22
- * the PIP-confirmed child when a polygon exists and the nearest-centroid child otherwise —
23
- * the latter flagged `containment: "approximate"`, the demo's honesty convention.
24
- * 4. **Hierarchy assembly** — the deepest place's ancestor chain via the SAME walk forward resolution
25
- * uses (`ancestry.ts`, #404), so consumers get a symmetric tree.
8
+ * 1. Fetch the admin DB's `place_bbox` R*Tree candidates whose bbox contains the point,
9
+ * smallest-area-first, so the first polygon confirmation is the deepest.
10
+ * 2. Confirm each candidate by ray-cast (geo.ts, the canonical TS port of
11
+ * `scripts/eval/pip-containment.py`) against the polygon sidecar DB (`wof-polygons.db`,
12
+ * `polygons(id, geom)` with GeoJSON text, built by `scripts/build-wof-polygons.mjs`). A
13
+ * candidate whose polygon exists but rejects the point is a bbox false positive and is
14
+ * dropped. A candidate with no polygon row stays eligible for the approximate fallback.
15
+ * 3. Descend tier-by-tier through the winner's descendants (the `ancestors` table, reversed),
16
+ * taking the PIP-confirmed child when a polygon exists and the nearest-centroid child
17
+ * otherwise. A centroid-fallback step is flagged `containment: "approximate"`.
18
+ * 4. Assemble the deepest place's ancestor chain through the same walk forward resolution uses,
19
+ * so consumers get a symmetric tree.
26
20
  *
27
- * Reverse quality is country-dependent (polygon coverage: see the #292 JP finding); `containment`
28
- * says so per result rather than pretending.
21
+ * Reverse quality is country-dependent, because polygon coverage is. `containment` records this per
22
+ * result.
29
23
  */
30
24
 
31
25
  import { tryParsingJSON } from "@mailwoman/core/json"
@@ -33,9 +27,10 @@ import { allRows } from "@mailwoman/core/utils"
33
27
  import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
34
28
  import { DatabaseClient } from "@mailwoman/sqlite/client"
35
29
  import { tableExists } from "@mailwoman/sqlite/introspection"
30
+ import type { PathBuilderLike } from "path-ts"
36
31
 
37
- import { ancestorLineage, placetypeDepth } from "#ancestry/index"
38
- import { PLACE_BBOX_TABLE } from "#fts/index"
32
+ import { ancestorLineage, placetypeDepth } from "#ancestry"
33
+ import { PLACE_BBOX_TABLE } from "#fts"
39
34
  import type { WOFDatabase } from "#schema"
40
35
  import type { PlaceCandidate, WOFPlacetype } from "#types"
41
36
  /**
@@ -51,62 +46,88 @@ const MAX_ABS_LONGITUDE = 180
51
46
  /**
52
47
  * How the deepest returned place was confirmed:
53
48
  *
54
- * - `"polygon"` — the point ray-cast INSIDE the place's real (DP-simplified) admin boundary.
55
- * - `"approximate"` — the place has no polygon on record; it won by nearest-centroid among the candidates whose bbox (or
56
- * parent) contains the point. The same honesty convention as the demo's approximate circles — country-dependent data
57
- * reality, surfaced instead of hidden.
49
+ * - `"polygon"`: the point ray-cast inside the place's real (DP-simplified) admin boundary.
50
+ * - `"approximate"`: the place has no polygon on record.
51
+ * It returns the nearest-centroid candidate among places whose bbox or parent contains the point.
52
+ * The demo's approximate circles use the same convention.
53
+ * Both results expose country-dependent data coverage.
58
54
  */
59
55
  export type ContainmentKind = "polygon" | "approximate"
60
56
 
61
57
  export interface ReverseGeocodeResult {
62
58
  /**
63
- * The containment chain, DEEPEST-FIRST (`[0]` is the winning place, then its ancestors up to country) — the same tree
64
- * shape forward resolution attaches via `includeAncestors`. Empty when no candidate's bbox contains the point (open
65
- * ocean, or outside the gazetteer's coverage).
59
+ * The containment chain, deepest-first.
60
+ *
61
+ * `[0]` is the winning place, then its ancestors up to country.
62
+ * This is the same tree shape forward resolution attaches via `includeAncestors`.
63
+ *
64
+ * Empty when no candidate's bbox contains the point (open ocean, or outside the gazetteer's coverage).
66
65
  */
67
66
  hierarchy: PlaceCandidate[]
68
67
  /**
69
- * Containment kind of the DEEPEST place in `hierarchy` (see {@link ContainmentKind}).
68
+ * Containment kind of the deepest place in `hierarchy` (see {@link ContainmentKind}).
70
69
  */
71
70
  containment: ContainmentKind
72
71
  }
73
72
 
74
73
  export interface WOFReverseGeocoderOpts {
75
74
  /**
76
- * Path to the admin gazetteer DB (e.g. `admin-global-priority.db`) — must carry `spr`, `ancestors`, and the
77
- * package-built `place_bbox` R*Tree (`mailwoman gazetteer build fts`). Mutually exclusive with `adminDatabase`.
75
+ * Path to the admin gazetteer DB (e.g. `admin-global-priority.db`).
76
+ *
77
+ * It must include `spr`, `ancestors`, and the package-built `place_bbox`
78
+ * R*Tree (`mailwoman gazetteer build fts`).
79
+ *
80
+ * Mutually exclusive with `adminDatabase`.
78
81
  */
79
- adminDBPath?: string
82
+ adminDBPath?: PathBuilderLike
80
83
  /**
81
- * Pre-opened admin DB — primarily for tests against an inline fixture.
84
+ * Pre-opened admin DB, primarily for tests against an inline fixture.
82
85
  */
83
86
  adminDatabase?: DatabaseClient<WOFDatabase>
84
87
  /**
85
- * Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL — without it every result
86
- * is `containment: "approximate"` (centroid-only mode). Mutually exclusive with `polygonDatabase`.
88
+ * Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`).
89
+ *
90
+ * Optional.
91
+ * Without it every result is `containment: "approximate"` (centroid-only mode).
92
+ *
93
+ * Mutually exclusive with `polygonDatabase`.
87
94
  */
88
- polygonDBPath?: string
95
+ polygonDBPath?: PathBuilderLike
89
96
  /**
90
- * Pre-opened polygon DB — primarily for tests.
97
+ * Pre-opened polygon DB, primarily for tests.
91
98
  */
92
99
  polygonDatabase?: DatabaseClient<WOFDatabase>
93
100
  }
94
101
 
95
102
  export interface ReverseGeocodeOpts {
96
103
  /**
97
- * Restrict the hierarchy to these placetypes (both the bbox candidates and the descent tiers). Default: every admin
98
- * placetype the gazetteer carries. E.g. `["region", "county", "locality"]` to skip the neighbourhood grain.
104
+ * Restrict the hierarchy to these placetypes (both the bbox candidates and the descent tiers).
105
+ *
106
+ * E.g.
107
+ *
108
+ * `["region", "county", "locality"]` to skip the neighborhood grain.
109
+ *
110
+ * @defaultValue every admin placetype in the gazetteer.
99
111
  */
100
112
  placetypes?: WOFPlacetype[]
101
113
  /**
102
- * Cap on the bbox candidate fetch. Default 128 — comfortably covers a dense metro (the most bbox-overlapping point
103
- * we've measured is a few dozen neighbourhoods + the admin chain).
114
+ * Cap on the bbox candidate fetch.
115
+ *
116
+ * Current measurements show that this covers a dense metro.
117
+ *
118
+ * The most bbox-overlapping point we have measured is a few dozen neighborhoods plus the admin chain.
119
+ *
120
+ * @defaultValue `128`
104
121
  */
105
122
  maxCandidates?: number
106
123
  /**
107
- * Approximate (nearest-centroid) steps further than this from the query point are not taken — keeps a sparse
108
- * gazetteer from "refining" to a far-away sibling. Polygon-confirmed steps ignore it (containment is exact regardless
109
- * of centroid distance). Default 25 km.
124
+ * The lookup skips approximate (nearest-centroid) steps farther than this from the query point.
125
+ *
126
+ * This keeps a sparse gazetteer from "refining" to a far-away sibling.
127
+ *
128
+ * Polygon-confirmed steps ignore it (containment is exact regardless of centroid distance).
129
+ *
130
+ * @defaultValue `25 km`
110
131
  */
111
132
  maxApproximateKm?: number
112
133
  }
@@ -115,8 +136,10 @@ const DEFAULT_MAX_CANDIDATES = 128
115
136
  const DEFAULT_MAX_APPROXIMATE_KM = 25
116
137
 
117
138
  /**
118
- * The tier ladder for the approximate descent, coarsest-first. Each tier is attempted among the CURRENT winner's
119
- * descendants; a tier with no rows is skipped (e.g. counties without localadmins jump straight to locality).
139
+ * The tier ladder for the approximate descent, coarsest-first.
140
+ *
141
+ * Each tier is attempted among the current winner's descendants.
142
+ * A tier with no rows is skipped (e.g. counties without localadmins jump straight to locality).
120
143
  */
121
144
  const DESCENT_TIERS: readonly WOFPlacetype[] = [
122
145
  "county",
@@ -140,23 +163,35 @@ interface CandidateRow {
140
163
  lon: number
141
164
  }
142
165
 
143
- function toPlaceCandidate(row: CandidateRow, distanceKm?: number): PlaceCandidate {
144
- const c: PlaceCandidate = {
166
+ const ABSENT_CANDIDATE_FIELDS = {
167
+ parent_id: null,
168
+ distanceKm: null,
169
+ exactMatch: null,
170
+ prominence: null,
171
+ population: null,
172
+ referential: null,
173
+ encyclopedic: null,
174
+ importance: null,
175
+ bbox: null,
176
+ mismatch: null,
177
+ containedByQualifier: null,
178
+ regionScopeMiss: null,
179
+ variantAliasExempted: null,
180
+ } as const satisfies Partial<PlaceCandidate>
181
+
182
+ function toPlaceCandidate(row: CandidateRow, distanceKm: number | null = null): PlaceCandidate {
183
+ return {
184
+ ...ABSENT_CANDIDATE_FIELDS,
145
185
  id: row.id,
146
186
  name: row.name,
147
187
  placetype: row.placetype as WOFPlacetype,
148
188
  country: row.country ?? "",
149
189
  lat: row.lat,
150
190
  lon: row.lon,
151
- parent_id: row.parent_id ?? undefined,
191
+ parent_id: row.parent_id ?? null,
152
192
  score: 0,
193
+ distanceKm,
153
194
  }
154
-
155
- if (distanceKm !== undefined) {
156
- c.distanceKm = distanceKm
157
- }
158
-
159
- return c
160
195
  }
161
196
 
162
197
  export class WOFReverseGeocoder implements Disposable {
@@ -165,10 +200,12 @@ export class WOFReverseGeocoder implements Disposable {
165
200
  readonly #polygons: DatabaseClient<WOFDatabase> | null
166
201
  readonly #ownsPolygons: boolean
167
202
  /**
168
- * Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15 county polygons 1400
169
- * times), so caching the JSON.parse pays for itself immediately. Bounded — cleared wholesale at the cap rather than
170
- * LRU-tracked; the polygons are DP-simplified and small, the cap exists only to keep a long-lived server process
171
- * honest.
203
+ * Parsed-geometry cache.
204
+ *
205
+ * Reverse queries cluster geographically, so caching the JSON.parse avoids repeated work.
206
+ * The cache is bounded and clears wholesale at the cap.
207
+ *
208
+ * The polygons are DP-simplified and small, so the cap only bounds a long-lived server process.
172
209
  */
173
210
  readonly #geometryCache = new Map<number, ParsedGeometry | null>()
174
211
  static readonly #GEOMETRY_CACHE_CAP = 4096
@@ -195,8 +232,9 @@ export class WOFReverseGeocoder implements Disposable {
195
232
 
196
233
  this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.polygonDBPath)
197
234
 
198
- // Fail loudly up front — the R*Tree is a build artifact, not part of the upstream WOF
199
- // distribution, and a missing index would otherwise surface as an opaque SQL error per query.
235
+ // Fail loudly up front.
236
+ // The R*Tree is a build artifact and is not part of the upstream WOF distribution,
237
+ // so a missing index would otherwise surface as an opaque SQL error per query.
200
238
  if (!tableExists(this.#admin, PLACE_BBOX_TABLE)) {
201
239
  throw new Error(
202
240
  `WOFReverseGeocoder: the admin DB has no \`${PLACE_BBOX_TABLE}\` R*Tree. Build it with ` +
@@ -213,18 +251,25 @@ export class WOFReverseGeocoder implements Disposable {
213
251
  }
214
252
 
215
253
  /**
216
- * Resolve a WGS-84 point to its containing admin hierarchy. Async for symmetry with `PlaceLookup.findPlace` (the work
217
- * is sync `node:sqlite` underneath — same convention). Thin wrapper over {@link reverseGeocodeSync}.
254
+ * Resolve a WGS-84 point to its containing admin hierarchy.
255
+ *
256
+ * Async for symmetry with `PlaceLookup.findPlace`
257
+ * (the work is sync `node:sqlite` underneath, the same convention).
258
+ * Thin wrapper over {@link reverseGeocodeSync}.
218
259
  */
219
260
  async reverseGeocode(lat: number, lon: number, opts: ReverseGeocodeOpts = {}): Promise<ReverseGeocodeResult> {
220
261
  return this.reverseGeocodeSync(lat, lon, opts)
221
262
  }
222
263
 
223
264
  /**
224
- * Synchronous core of {@link reverseGeocode} — every step underneath is already sync `node:sqlite`, so this is the
225
- * REAL implementation; the async method above exists only for call-site symmetry with `PlaceLookup.findPlace`.
226
- * Exposed directly for callers that can't await mid-call (e.g. `mailwoman/poi-executor.ts`'s `createPOIExecutor`,
227
- * whose `POIIntentOutcome` return type is synchronous by contract — see `poi-intent.ts`'s `deps.execute`).
265
+ * Synchronous core of {@link reverseGeocode}.
266
+ *
267
+ * Every step underneath is already sync `node:sqlite`, so this is the real implementation.
268
+ * The async method above exists only for call-site symmetry with `PlaceLookup.findPlace`.
269
+ *
270
+ * Exposed directly for callers that can't await mid-call
271
+ * (e.g. `mailwoman/poi-executor.ts`'s `createPOIExecutor`, whose `POIQueryResult`
272
+ * return type is synchronous by interface. See `poi-intent.ts`'s `deps.execute`).
228
273
  */
229
274
  reverseGeocodeSync(lat: number, lon: number, opts: ReverseGeocodeOpts = {}): ReverseGeocodeResult {
230
275
  if (
@@ -239,8 +284,9 @@ export class WOFReverseGeocoder implements Disposable {
239
284
  const maxApproximateKm = opts.maxApproximateKm ?? DEFAULT_MAX_APPROXIMATE_KM
240
285
  const candidates = this.#bboxCandidates(lat, lon, opts)
241
286
 
242
- // PIP walk, smallest-bbox-first: the first polygon that contains the point is the deepest
243
- // polygon-confirmable place. Polygon-rejected candidates are bbox false positives — dropped.
287
+ // PIP walk, smallest-bbox-first.
288
+ // The first polygon that contains the point is the deepest polygon-confirmable place.
289
+ // Drop candidates whose bounding boxes match but whose polygons reject the point.
244
290
  let winner: CandidateRow | null = null
245
291
  let winnerConfirmed = false
246
292
  const pointOnly: CandidateRow[] = []
@@ -261,7 +307,7 @@ export class WOFReverseGeocoder implements Disposable {
261
307
  }
262
308
 
263
309
  if (!winner) {
264
- // No polygon confirmed anywhere — nearest centroid among the polygon-less bbox candidates.
310
+ // No polygon confirmed anywhere, so take the nearest centroid among the polygon-less bbox candidates.
265
311
  let bestKm = Infinity
266
312
 
267
313
  for (const c of pointOnly) {
@@ -279,7 +325,7 @@ export class WOFReverseGeocoder implements Disposable {
279
325
  // Approximate descent into finer tiers than the winner.
280
326
  let current = winner
281
327
  let currentConfirmed = winnerConfirmed
282
- let currentDistanceKm = currentConfirmed ? undefined : haversineKm(lat, lon, current.lat, current.lon)
328
+ let currentDistanceKm = currentConfirmed ? null : haversineKm(lat, lon, current.lat, current.lon)
283
329
 
284
330
  for (const tier of DESCENT_TIERS) {
285
331
  if (placetypeDepth(tier) <= placetypeDepth(current.placetype)) continue
@@ -288,7 +334,7 @@ export class WOFReverseGeocoder implements Disposable {
288
334
  const kids = this.#descendants(current.id, tier, lat, lon, maxApproximateKm)
289
335
  let next: CandidateRow | null = null
290
336
  let nextConfirmed = false
291
- let nextKm: number | undefined
337
+ let nextKm: number | null = null
292
338
  let bestKm = Infinity
293
339
 
294
340
  for (const k of kids) {
@@ -297,12 +343,12 @@ export class WOFReverseGeocoder implements Disposable {
297
343
  if (contains === true) {
298
344
  next = k
299
345
  nextConfirmed = true
300
- nextKm = undefined
346
+ nextKm = null
301
347
 
302
348
  break
303
349
  }
304
350
 
305
- if (contains === false) continue // known not-here — polygon rejected
351
+ if (contains === false) continue // known not-here, polygon rejected
306
352
  const km = haversineKm(lat, lon, k.lat, k.lon)
307
353
 
308
354
  if (km <= maxApproximateKm && km < bestKm) {
@@ -318,17 +364,24 @@ export class WOFReverseGeocoder implements Disposable {
318
364
  currentConfirmed = nextConfirmed
319
365
  currentDistanceKm = nextKm
320
366
  }
321
- // An empty tier is NOT terminal — counties without localadmins jump straight to locality.
367
+ // An empty tier is not terminal.
368
+ // Counties without localadmins jump straight to locality.
322
369
  }
323
370
 
324
- // Hierarchy assembly via the shared ancestor walk. If the descent crossed an ancestry gap
325
- // (the deepest place's recorded lineage misses the PIP root), merge the root's own chain so
326
- // region/country are always present when a polygon confirmed them.
371
+ // Hierarchy assembly via the shared ancestor walk.
372
+ // If the descent crossed an ancestry gap (the deepest place's recorded lineage misses the PIP root),
373
+ // merge the root's own chain so region/country are always present when a polygon confirmed them.
327
374
  const byID = new Map<number, PlaceCandidate>([[current.id, toPlaceCandidate(current, currentDistanceKm)]])
328
375
 
329
376
  for (const a of ancestorLineage(this.#admin, current.id)) {
330
377
  if (!byID.has(a.id)) {
331
- byID.set(a.id, { ...a, placetype: a.placetype as WOFPlacetype, country: a.country ?? "", score: 0 })
378
+ byID.set(a.id, {
379
+ ...ABSENT_CANDIDATE_FIELDS,
380
+ ...a,
381
+ placetype: a.placetype as WOFPlacetype,
382
+ country: a.country ?? "",
383
+ score: 0,
384
+ })
332
385
  }
333
386
  }
334
387
 
@@ -337,7 +390,13 @@ export class WOFReverseGeocoder implements Disposable {
337
390
 
338
391
  for (const a of ancestorLineage(this.#admin, winner.id)) {
339
392
  if (!byID.has(a.id)) {
340
- byID.set(a.id, { ...a, placetype: a.placetype as WOFPlacetype, country: a.country ?? "", score: 0 })
393
+ byID.set(a.id, {
394
+ ...ABSENT_CANDIDATE_FIELDS,
395
+ ...a,
396
+ placetype: a.placetype as WOFPlacetype,
397
+ country: a.country ?? "",
398
+ score: 0,
399
+ })
341
400
  }
342
401
  }
343
402
  }
@@ -395,9 +454,12 @@ export class WOFReverseGeocoder implements Disposable {
395
454
  }
396
455
 
397
456
  /**
398
- * Descendants of `parentID` at one placetype tier, pre-filtered to a centroid window around the query point (a
399
- * generous 4× the approximate cap — polygon-holding children may legitimately have far centroids, e.g. a sprawling
400
- * consolidated city; the precise cap is applied per-candidate in the caller, and only to centroid-fallback steps).
457
+ * Descendants of `parentID` at one placetype tier, pre-filtered to a centroid window
458
+ * around the query point (a generous 4× the approximate cap, since polygon-holding
459
+ * children may legitimately have far centroids, e.g. a sprawling consolidated city).
460
+ *
461
+ * The caller applies the precise cap per candidate.
462
+ * It limits centroid-fallback steps only.
401
463
  */
402
464
  #descendants(
403
465
  parentID: number,
@@ -439,7 +501,8 @@ export class WOFReverseGeocoder implements Disposable {
439
501
  }
440
502
 
441
503
  const row = this.#polygons.prepare(`SELECT geom FROM polygons WHERE id = ?`).get(id) as { geom: string } | undefined
442
- // Malformed row parses to null — treat as no-polygon rather than failing the query.
504
+ // Malformed row parses to null.
505
+ // Treat it as no-polygon so the query does not fail.
443
506
  const geometry = row ? tryParsingJSON<ParsedGeometry>(row.geom) : null
444
507
 
445
508
  this.#geometryCache.set(id, geometry)
package/lib/schema.ts CHANGED
@@ -3,22 +3,25 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Kysely table types for the subset of the Who's On First SQLite schema we touch in Phase 4.2.
6
+ * Kysely table types for the subset of the Who's On First SQLite schema this package touches.
7
7
  *
8
- * The full upstream distribution at data.geocode.earth/wof/dist/sqlite/ ships ~7 tables; this file
9
- * models only the ones we read. Pretending to model the others would be misleading — we haven't
10
- * verified their shapes and they're not part of the resolver's contract.
8
+ * The full upstream distribution at data.geocode.earth/wof/dist/sqlite/ ships about seven tables.
9
+ * This file models only the tables we read. The others are outside the resolver interface. Their shapes remain unverified.
11
10
  *
12
11
  * Authoritative schema docs:
13
12
  *
14
- * - Whosonfirst SQLite README: https://github.com/whosonfirst/go-whosonfirst-sqlite
13
+ * - Whosonfirst SQLite readme: https://github.com/whosonfirst/go-whosonfirst-sqlite
15
14
  * - Per-table sources under https://github.com/whosonfirst/go-whosonfirst-sqlite-features
16
15
  */
17
16
 
17
+ import type { KeyValueMetaTable } from "@mailwoman/sqlite/database-schema"
18
+
18
19
  /**
19
- * The FTS5 virtual table built by this package on first open (NOT shipped by upstream WOF).
20
+ * The FTS5 virtual table this package builds on first open.
21
+ * It is local to this repository.
20
22
  *
21
- * `content` is unindexed — it's there so we can roundtrip the original name back to the caller without a second SELECT.
23
+ * `content` is unindexed and is there so we can roundtrip the original name
24
+ * back to the caller without a second select.
22
25
  * The actual FTS rebuild happens in `fts.ts::buildPlaceSearchFTS`.
23
26
  */
24
27
  export interface PlaceSearchTable {
@@ -29,16 +32,19 @@ export interface PlaceSearchTable {
29
32
  }
30
33
 
31
34
  /**
32
- * `spr` — the Who's On First "Standard Places Response": a denormalized lightweight summary of one row per place. The
33
- * resolver's main lookup table.
35
+ * `spr`, the Who's On First "Standard Places Response": a denormalized lightweight
36
+ * summary of one row per place.
37
+ *
38
+ * The resolver's main lookup table.
39
+ *
40
+ * Lifecycle flags use two conventions, both meaning "currently valid": `is_current = -1`
41
+ * for modern Who's On First and `is_current = 1` for the legacy Mapzen era.
42
+ * Only `is_current = 0` means "not current".
34
43
  *
35
- * Lifecycle flags carry TWO conventions, both meaning "currently valid": `is_current = -1` (modern Who's On First) and
36
- * `is_current = 1` (legacy Mapzen-era). Only `is_current = 0` means "not current". Filters in `lookup.ts` and `fts.ts`
37
- * use `is_current != 0 AND is_deprecated = 0` — see #91 for the diagnostic that uncovered the mixed-convention
38
- * reality.
44
+ * Filters in `lookup.ts` and `fts.ts` use `is_current != 0 and is_deprecated = 0`.
39
45
  *
40
- * Lat/lon live directly on this row — no GeoJSON extraction needed for centroid resolution. `min_*` / `max_*` form a
41
- * bounding box if callers want one (Phase 4.3 candidate).
46
+ * Lat/lon live directly on this row, so centroid resolution needs no GeoJSON extraction.
47
+ * `min_*` and `max_*` form a bounding box if callers want one.
42
48
  */
43
49
  export interface SprTable {
44
50
  id: number
@@ -63,15 +69,21 @@ export interface SprTable {
63
69
  }
64
70
 
65
71
  /**
66
- * Alternate names per place, keyed by language tag subfields (BCP-47 components). Joins back to `spr.id` via `id` (NOT
67
- * `place_id` — the real WOF schema uses the same column name as the spr primary key; this is a normal join across two
68
- * tables with the same FK column name).
72
+ * Alternate names per place, keyed by language tag subfields (BCP-47 components).
73
+ *
74
+ * Joins back to `spr.id` via `id`.
75
+ * The real WOF schema uses the same column name as the spr primary key, so this is
76
+ * a normal join across two tables with the same FK column name.
77
+ *
78
+ * No `kind` column in real WOF.
79
+ * The FTS build concatenates all names per id.
69
80
  *
70
- * No `kind` column in real WOF — the FTS build just concatenates ALL names per id.
81
+ * `official` marks a preferred-form name in an official language of the place's country.
82
+ * This column appears only in unified builds.
71
83
  *
72
- * `official` (#936 ingest bit, our unified builds only; absent in real WOF dumps) marks a PREFERRED-form name in an
73
- * official language of the place's country — the aliases eligible to join the name-exact tier under the option-3 rule.
74
- * See `unified-schema.ts` for the full contract.
84
+ * Real WOF dumps omit it.
85
+ * These are the aliases eligible to join the name-exact tier.
86
+ * See `unified-schema.ts` for the full interface.
75
87
  */
76
88
  export interface NamesTable {
77
89
  id: number
@@ -90,8 +102,11 @@ export interface NamesTable {
90
102
  }
91
103
 
92
104
  /**
93
- * Per-place GeoJSON blob. Centroid lat/lon are already exposed via `spr.{latitude,longitude}` so the resolver doesn't
94
- * need to parse this; we keep the table modeled in case Phase 4.3 wants the full geometry for bbox / polygon work.
105
+ * Per-place GeoJSON blob.
106
+ *
107
+ * Centroid lat/lon are already exposed via `spr.{latitude,longitude}`,
108
+ * so the resolver does not need to parse this.
109
+ * The table stays modeled so a caller that wants the full geometry for bbox or polygon work can read it.
95
110
  */
96
111
  export interface GeojsonTable {
97
112
  id: number
@@ -103,8 +118,10 @@ export interface GeojsonTable {
103
118
  }
104
119
 
105
120
  /**
106
- * Adjacency table for ancestor relationships. One row per (place, ancestor) pair, including transitive ancestors. Used
107
- * to implement `FindPlaceQuery.parentID` (descendant lookup).
121
+ * Adjacency table for ancestor relationships.
122
+ *
123
+ * One row per (place, ancestor) pair, including transitive ancestors.
124
+ * Used to implement `FindPlaceQuery.parentID` (descendant lookup).
108
125
  */
109
126
  export interface AncestorsTable {
110
127
  id: number
@@ -114,9 +131,14 @@ export interface AncestorsTable {
114
131
  }
115
132
 
116
133
  /**
117
- * `place_population` — `id → wof:population`, split off `spr` so a population-rank join is a single indexed probe.
118
- * Written by the build/augment ingest + the GeoNames backfill; read by the candidate build's `neg_rank`. WOF carries
119
- * population for ~15% of localities; absent = unknown, not zero.
134
+ * `place_population` — `id → wof:population`, split off `spr` so a population-rank
135
+ * join is a single indexed probe.
136
+ *
137
+ * Written by the build/augment ingest + the GeoNames backfill.
138
+ * Read by the candidate build's `neg_rank`.
139
+ *
140
+ * WOF records population for ~15% of localities.
141
+ * Absent = unknown rather than zero.
120
142
  */
121
143
  export interface PlacePopulationTable {
122
144
  id: number
@@ -124,8 +146,10 @@ export interface PlacePopulationTable {
124
146
  }
125
147
 
126
148
  /**
127
- * `place_abbr` — `id → abbreviation` (e.g. `IL → Illinois`), derived from `names` rows whose `language = 'abbr'`. Lets
128
- * the resolver accept a 2-letter region abbreviation as an exact match.
149
+ * `place_abbr` — `id → abbreviation` (e.g. `IL → Illinois`), derived from
150
+ * `names` rows whose `language = 'abbr'`.
151
+ *
152
+ * Lets the resolver accept a 2-letter region abbreviation as an exact match.
129
153
  */
130
154
  export interface PlaceAbbrTable {
131
155
  id: number
@@ -133,8 +157,11 @@ export interface PlaceAbbrTable {
133
157
  }
134
158
 
135
159
  /**
136
- * `concordances` — external-id cross-references per place (`id → (other_source, other_id)`), e.g. a GeoNames or
137
- * Overture GERS id. Metadata only; not part of the resolve path.
160
+ * `concordances` — external-id cross-references per place (`id → (other_source, other_id)`),
161
+ * e.g. a GeoNames or Overture gers id.
162
+ *
163
+ * Metadata only.
164
+ * Not part of the resolve path.
138
165
  */
139
166
  export interface ConcordancesTable {
140
167
  id: number
@@ -144,9 +171,12 @@ export interface ConcordancesTable {
144
171
  }
145
172
 
146
173
  /**
147
- * `coincident_roles` (#402) — the dual-role relation: a place that is BOTH an admin region AND a locality (Berlin the
148
- * city-state). One row per (admin, locality) pair the resolver can complete a hierarchy with. Surfaced by
149
- * {@link MailwomanLookupLike.coincidentRolesFor}.
174
+ * `coincident_roles`.
175
+ *
176
+ * The dual-role relation, where a place is both an admin region and a locality (Berlin the city-state).
177
+ *
178
+ * One row per (admin, locality) pair the resolver can complete a hierarchy with.
179
+ * Surfaced by {@link MailwomanLookupLike.coincidentRolesFor}.
150
180
  */
151
181
  export interface CoincidentRolesTable {
152
182
  admin_id: number
@@ -158,22 +188,15 @@ export interface CoincidentRolesTable {
158
188
  }
159
189
 
160
190
  /**
161
- * The full schema we hand to `Kysely<WOFDatabase>` / `new DatabaseClient<WOFDatabase>(...)`. Tables not listed here
162
- * will fail type-checked queries — by design. The reader ({@link WOFSQLitePlaceLookup}) already consumes this; the
163
- * build/augment WRITERS adopt it so a column rename is a compile error on both sides (the drift that bit the corpus
164
- * TIGER adapter).
165
- */
166
- /**
167
- * The provenance row every built extract carries: source fingerprints travelling WITH the database rather than in a
168
- * document that can drift from it. Written by the postcode builders in `mailwoman/gazetteer-pipeline`.
191
+ * The full schema we hand to `Kysely<WOFDatabase>` / `new DatabaseClient<WOFDatabase>(...)`.
192
+ *
193
+ * Tables not listed here will fail type-checked queries, by design.
194
+ * The reader ({@link WOFSQLitePlaceLookup}) already consumes this.
195
+ *
196
+ * The build and augment writers adopt it so a column rename is a compile error on both sides.
169
197
  */
170
- export interface ExtractMetaTable {
171
- key: string
172
- value: string | null
173
- }
174
-
175
198
  export interface WOFDatabase {
176
- meta: ExtractMetaTable
199
+ meta: KeyValueMetaTable
177
200
  place_search: PlaceSearchTable
178
201
  spr: SprTable
179
202
  names: NamesTable