@mailwoman/resolver-wof-sqlite 10.0.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (476) hide show
  1. package/README.md +8 -8
  2. package/lib/address/point/interpolation.ts +39 -37
  3. package/lib/address/point/schema.ts +17 -40
  4. package/lib/address/point.ts +315 -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 +123 -165
  9. package/lib/build/population-corrections.ts +64 -0
  10. package/lib/build/slim.ts +43 -113
  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 +64 -67
  18. package/lib/candidate/lookup.ts +78 -382
  19. package/lib/candidate/name-roles.ts +15 -33
  20. package/lib/candidate/own-name.ts +34 -31
  21. package/lib/candidate/place-attrs.ts +20 -13
  22. package/lib/candidate/schema.ts +29 -65
  23. package/lib/candidate/scoring.ts +30 -69
  24. package/lib/capital-schema.ts +21 -17
  25. package/lib/capitals.ts +26 -24
  26. package/lib/coincident-roles.ts +74 -55
  27. package/lib/convention/schema.ts +13 -7
  28. package/lib/convention.ts +144 -0
  29. package/lib/coverage-manifest-schema.ts +34 -39
  30. package/lib/currency-backfill.ts +22 -67
  31. package/lib/env.ts +1 -1
  32. package/lib/exact-match.ts +31 -20
  33. package/lib/extracts.ts +96 -73
  34. package/lib/fst/autocomplete.ts +52 -35
  35. package/lib/fst/builder.ts +30 -36
  36. package/lib/fst/deserialize-web.ts +8 -9
  37. package/lib/fst/format.ts +41 -23
  38. package/lib/fst/freshness.ts +17 -95
  39. package/lib/fst/matcher.ts +2 -2
  40. package/lib/fst/serialize.ts +45 -60
  41. package/lib/fst/types.ts +71 -59
  42. package/lib/fts/query.ts +5 -36
  43. package/lib/fts.ts +280 -0
  44. package/lib/geonames/aliases.ts +71 -142
  45. package/lib/geonames/postal.ts +161 -70
  46. package/lib/index.ts +6 -6
  47. package/lib/interpolation.ts +43 -96
  48. package/lib/lookup.ts +132 -253
  49. package/lib/name-score.ts +9 -7
  50. package/lib/nsul/lookup.ts +36 -53
  51. package/lib/nsul/schema.ts +16 -43
  52. package/lib/paths.ts +159 -0
  53. package/lib/place-importance-schema.ts +57 -164
  54. package/lib/poi/lookup.ts +100 -109
  55. package/lib/poi/schema.ts +42 -26
  56. package/lib/polygon-schema.ts +10 -6
  57. package/lib/postal/city/alias/lookup.ts +22 -40
  58. package/lib/postal/city/alias/schema.ts +14 -10
  59. package/lib/postal/city/candidate-schema.ts +26 -20
  60. package/lib/postcode-point-lookup.ts +9 -19
  61. package/lib/primary-preference.ts +44 -124
  62. package/lib/proximity-rerank.ts +43 -32
  63. package/lib/ranking-weights.ts +98 -76
  64. package/lib/region-keys.ts +68 -40
  65. package/lib/reverse.ts +108 -72
  66. package/lib/schema.ts +74 -42
  67. package/lib/search-fetch.ts +90 -68
  68. package/lib/sqlite/convention-source.ts +12 -20
  69. package/lib/sqlite/utils.ts +18 -21
  70. package/lib/street/centroid/schema.ts +37 -42
  71. package/lib/street/{centroid/index.ts → centroid.ts} +15 -24
  72. package/lib/street/morphology/fst/builder.ts +50 -41
  73. package/lib/street/morphology/fst/loader.ts +35 -28
  74. package/lib/street/name-lookup.ts +43 -35
  75. package/lib/street/normalize.ts +85 -216
  76. package/lib/street/segment-schema.ts +116 -43
  77. package/lib/{street/index.ts → street.ts} +1 -1
  78. package/lib/types.ts +94 -140
  79. package/lib/unified-schema.ts +29 -29
  80. package/lib/uprn/existence.ts +30 -19
  81. package/lib/uprn/lookup.ts +28 -67
  82. package/lib/uprn/schema.ts +18 -40
  83. package/lib/weights-overlay-linker.ts +204 -232
  84. package/out/address/point/interpolation.d.ts +19 -23
  85. package/out/address/point/interpolation.d.ts.map +1 -1
  86. package/out/address/point/interpolation.js +37 -37
  87. package/out/address/point/interpolation.js.map +1 -1
  88. package/out/address/point/schema.d.ts +12 -35
  89. package/out/address/point/schema.d.ts.map +1 -1
  90. package/out/address/point/schema.js +3 -19
  91. package/out/address/point/schema.js.map +1 -1
  92. package/out/address/point.d.ts +56 -0
  93. package/out/address/point.d.ts.map +1 -0
  94. package/out/address/point.js +205 -0
  95. package/out/address/point.js.map +1 -0
  96. package/out/{address/index.js → address.d.ts} +2 -2
  97. package/out/address.d.ts.map +1 -0
  98. package/out/{address/index.d.ts → address.js} +2 -2
  99. package/out/address.js.map +1 -0
  100. package/out/ancestry/backfill.d.ts +11 -64
  101. package/out/ancestry/backfill.d.ts.map +1 -1
  102. package/out/ancestry/backfill.js +14 -74
  103. package/out/ancestry/backfill.js.map +1 -1
  104. package/out/ancestry.d.ts +49 -0
  105. package/out/ancestry.d.ts.map +1 -0
  106. package/out/ancestry.js +59 -0
  107. package/out/ancestry.js.map +1 -0
  108. package/out/build/candidate.d.ts +87 -107
  109. package/out/build/candidate.d.ts.map +1 -1
  110. package/out/build/candidate.js +19 -95
  111. package/out/build/candidate.js.map +1 -1
  112. package/out/build/population-corrections.d.ts +52 -0
  113. package/out/build/population-corrections.d.ts.map +1 -0
  114. package/out/build/population-corrections.js +40 -0
  115. package/out/build/population-corrections.js.map +1 -0
  116. package/out/build/slim.d.ts +8 -44
  117. package/out/build/slim.d.ts.map +1 -1
  118. package/out/build/slim.js +32 -93
  119. package/out/build/slim.js.map +1 -1
  120. package/out/candidate/alias-bags.d.ts +6 -6
  121. package/out/candidate/alias-bags.d.ts.map +1 -1
  122. package/out/candidate/alias-bags.js +16 -17
  123. package/out/candidate/alias-bags.js.map +1 -1
  124. package/out/candidate/ancestors/schema.d.ts +73 -57
  125. package/out/candidate/ancestors/schema.d.ts.map +1 -1
  126. package/out/candidate/ancestors/schema.js +56 -48
  127. package/out/candidate/ancestors/schema.js.map +1 -1
  128. package/out/candidate/ancestors/sidecar.d.ts +3 -8
  129. package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
  130. package/out/candidate/ancestors/sidecar.js +15 -22
  131. package/out/candidate/ancestors/sidecar.js.map +1 -1
  132. package/out/candidate/country-display-names.d.ts +5 -15
  133. package/out/candidate/country-display-names.d.ts.map +1 -1
  134. package/out/candidate/country-display-names.js +6 -20
  135. package/out/candidate/country-display-names.js.map +1 -1
  136. package/out/candidate/extract-fold.d.ts +7 -15
  137. package/out/candidate/extract-fold.d.ts.map +1 -1
  138. package/out/candidate/extract-fold.js +5 -47
  139. package/out/candidate/extract-fold.js.map +1 -1
  140. package/out/candidate/fts.d.ts +5 -23
  141. package/out/candidate/fts.d.ts.map +1 -1
  142. package/out/candidate/fts.js +5 -23
  143. package/out/candidate/fts.js.map +1 -1
  144. package/out/candidate/importance.d.ts +60 -65
  145. package/out/candidate/importance.d.ts.map +1 -1
  146. package/out/candidate/importance.js +55 -62
  147. package/out/candidate/importance.js.map +1 -1
  148. package/out/candidate/lookup.d.ts +11 -51
  149. package/out/candidate/lookup.d.ts.map +1 -1
  150. package/out/candidate/lookup.js +48 -361
  151. package/out/candidate/lookup.js.map +1 -1
  152. package/out/candidate/name-roles.d.ts +9 -21
  153. package/out/candidate/name-roles.d.ts.map +1 -1
  154. package/out/candidate/name-roles.js +15 -33
  155. package/out/candidate/name-roles.js.map +1 -1
  156. package/out/candidate/own-name.d.ts +23 -25
  157. package/out/candidate/own-name.d.ts.map +1 -1
  158. package/out/candidate/own-name.js +34 -31
  159. package/out/candidate/own-name.js.map +1 -1
  160. package/out/candidate/place-attrs.d.ts +20 -13
  161. package/out/candidate/place-attrs.d.ts.map +1 -1
  162. package/out/candidate/place-attrs.js +4 -5
  163. package/out/candidate/place-attrs.js.map +1 -1
  164. package/out/candidate/schema.d.ts +27 -62
  165. package/out/candidate/schema.d.ts.map +1 -1
  166. package/out/candidate/schema.js +10 -22
  167. package/out/candidate/schema.js.map +1 -1
  168. package/out/candidate/scoring.d.ts +7 -8
  169. package/out/candidate/scoring.d.ts.map +1 -1
  170. package/out/candidate/scoring.js +30 -69
  171. package/out/candidate/scoring.js.map +1 -1
  172. package/out/capital-schema.d.ts +21 -17
  173. package/out/capital-schema.d.ts.map +1 -1
  174. package/out/capital-schema.js +17 -15
  175. package/out/capital-schema.js.map +1 -1
  176. package/out/capitals.d.ts +26 -24
  177. package/out/capitals.d.ts.map +1 -1
  178. package/out/capitals.js +23 -21
  179. package/out/capitals.js.map +1 -1
  180. package/out/coincident-roles.d.ts +60 -43
  181. package/out/coincident-roles.d.ts.map +1 -1
  182. package/out/coincident-roles.js +61 -50
  183. package/out/coincident-roles.js.map +1 -1
  184. package/out/convention/schema.d.ts +12 -6
  185. package/out/convention/schema.d.ts.map +1 -1
  186. package/out/convention/schema.js +6 -4
  187. package/out/convention/schema.js.map +1 -1
  188. package/out/convention.d.ts +97 -0
  189. package/out/convention.d.ts.map +1 -0
  190. package/out/convention.js +81 -0
  191. package/out/convention.js.map +1 -0
  192. package/out/coverage-manifest-schema.d.ts +33 -38
  193. package/out/coverage-manifest-schema.d.ts.map +1 -1
  194. package/out/coverage-manifest-schema.js +20 -29
  195. package/out/coverage-manifest-schema.js.map +1 -1
  196. package/out/currency-backfill.d.ts +11 -33
  197. package/out/currency-backfill.d.ts.map +1 -1
  198. package/out/currency-backfill.js +12 -39
  199. package/out/currency-backfill.js.map +1 -1
  200. package/out/env.d.ts +1 -1
  201. package/out/env.js +1 -1
  202. package/out/exact-match.d.ts +19 -10
  203. package/out/exact-match.d.ts.map +1 -1
  204. package/out/exact-match.js +31 -20
  205. package/out/exact-match.js.map +1 -1
  206. package/out/extracts.d.ts +65 -54
  207. package/out/extracts.d.ts.map +1 -1
  208. package/out/extracts.js +63 -55
  209. package/out/extracts.js.map +1 -1
  210. package/out/fst/autocomplete.d.ts +28 -20
  211. package/out/fst/autocomplete.d.ts.map +1 -1
  212. package/out/fst/autocomplete.js +38 -27
  213. package/out/fst/autocomplete.js.map +1 -1
  214. package/out/fst/builder.d.ts.map +1 -1
  215. package/out/fst/builder.js +30 -36
  216. package/out/fst/builder.js.map +1 -1
  217. package/out/fst/deserialize-web.d.ts +3 -3
  218. package/out/fst/deserialize-web.d.ts.map +1 -1
  219. package/out/fst/deserialize-web.js +8 -9
  220. package/out/fst/deserialize-web.js.map +1 -1
  221. package/out/fst/format.d.ts +41 -23
  222. package/out/fst/format.d.ts.map +1 -1
  223. package/out/fst/format.js +41 -23
  224. package/out/fst/format.js.map +1 -1
  225. package/out/fst/freshness.d.ts +11 -73
  226. package/out/fst/freshness.d.ts.map +1 -1
  227. package/out/fst/freshness.js +12 -76
  228. package/out/fst/freshness.js.map +1 -1
  229. package/out/fst/matcher.d.ts +1 -1
  230. package/out/fst/matcher.js +2 -2
  231. package/out/fst/matcher.js.map +1 -1
  232. package/out/fst/serialize.d.ts +30 -26
  233. package/out/fst/serialize.d.ts.map +1 -1
  234. package/out/fst/serialize.js +44 -58
  235. package/out/fst/serialize.js.map +1 -1
  236. package/out/fst/types.d.ts +71 -59
  237. package/out/fst/types.d.ts.map +1 -1
  238. package/out/fst/types.js +1 -4
  239. package/out/fst/types.js.map +1 -1
  240. package/out/{fst/index.js → fst.d.ts} +1 -1
  241. package/out/fst.d.ts.map +1 -0
  242. package/out/{fst/index.d.ts → fst.js} +1 -1
  243. package/out/fst.js.map +1 -0
  244. package/out/fts/query.d.ts +2 -27
  245. package/out/fts/query.d.ts.map +1 -1
  246. package/out/fts/query.js +5 -36
  247. package/out/fts/query.js.map +1 -1
  248. package/out/fts.d.ts +126 -0
  249. package/out/fts.d.ts.map +1 -0
  250. package/out/fts.js +188 -0
  251. package/out/fts.js.map +1 -0
  252. package/out/geonames/aliases.d.ts +26 -62
  253. package/out/geonames/aliases.d.ts.map +1 -1
  254. package/out/geonames/aliases.js +43 -115
  255. package/out/geonames/aliases.js.map +1 -1
  256. package/out/geonames/postal.d.ts +84 -44
  257. package/out/geonames/postal.d.ts.map +1 -1
  258. package/out/geonames/postal.js +114 -72
  259. package/out/geonames/postal.js.map +1 -1
  260. package/out/{geonames/index.js → geonames.d.ts} +1 -1
  261. package/out/geonames.d.ts.map +1 -0
  262. package/out/{geonames/index.d.ts → geonames.js} +1 -1
  263. package/out/geonames.js.map +1 -0
  264. package/out/index.d.ts +6 -6
  265. package/out/index.d.ts.map +1 -1
  266. package/out/index.js +5 -5
  267. package/out/index.js.map +1 -1
  268. package/out/interpolation.d.ts +15 -52
  269. package/out/interpolation.d.ts.map +1 -1
  270. package/out/interpolation.js +25 -68
  271. package/out/interpolation.js.map +1 -1
  272. package/out/lookup.d.ts +22 -51
  273. package/out/lookup.d.ts.map +1 -1
  274. package/out/lookup.js +114 -220
  275. package/out/lookup.js.map +1 -1
  276. package/out/name-score.d.ts +2 -2
  277. package/out/name-score.d.ts.map +1 -1
  278. package/out/name-score.js +9 -7
  279. package/out/name-score.js.map +1 -1
  280. package/out/nsul/lookup.d.ts +33 -34
  281. package/out/nsul/lookup.d.ts.map +1 -1
  282. package/out/nsul/lookup.js +26 -40
  283. package/out/nsul/lookup.js.map +1 -1
  284. package/out/nsul/schema.d.ts +16 -43
  285. package/out/nsul/schema.d.ts.map +1 -1
  286. package/out/nsul/schema.js +9 -35
  287. package/out/nsul/schema.js.map +1 -1
  288. package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
  289. package/out/nsul.d.ts.map +1 -0
  290. package/out/{nsul/index.js → nsul.js} +1 -1
  291. package/out/nsul.js.map +1 -0
  292. package/out/paths.d.ts +124 -0
  293. package/out/paths.d.ts.map +1 -0
  294. package/out/paths.js +104 -0
  295. package/out/paths.js.map +1 -0
  296. package/out/place-importance-schema.d.ts +49 -141
  297. package/out/place-importance-schema.d.ts.map +1 -1
  298. package/out/place-importance-schema.js +25 -140
  299. package/out/place-importance-schema.js.map +1 -1
  300. package/out/poi/lookup.d.ts +38 -42
  301. package/out/poi/lookup.d.ts.map +1 -1
  302. package/out/poi/lookup.js +67 -90
  303. package/out/poi/lookup.js.map +1 -1
  304. package/out/poi/schema.d.ts +41 -25
  305. package/out/poi/schema.d.ts.map +1 -1
  306. package/out/poi/schema.js +25 -17
  307. package/out/poi/schema.js.map +1 -1
  308. package/out/{poi/index.js → poi.d.ts} +1 -1
  309. package/out/poi.d.ts.map +1 -0
  310. package/out/{poi/index.d.ts → poi.js} +1 -1
  311. package/out/poi.js.map +1 -0
  312. package/out/polygon-schema.d.ts +10 -6
  313. package/out/polygon-schema.d.ts.map +1 -1
  314. package/out/polygon-schema.js +2 -2
  315. package/out/polygon-schema.js.map +1 -1
  316. package/out/postal/city/alias/lookup.d.ts +20 -23
  317. package/out/postal/city/alias/lookup.d.ts.map +1 -1
  318. package/out/postal/city/alias/lookup.js +17 -30
  319. package/out/postal/city/alias/lookup.js.map +1 -1
  320. package/out/postal/city/alias/schema.d.ts +14 -10
  321. package/out/postal/city/alias/schema.d.ts.map +1 -1
  322. package/out/postal/city/alias/schema.js +10 -8
  323. package/out/postal/city/alias/schema.js.map +1 -1
  324. package/out/postal/city/candidate-schema.d.ts +25 -19
  325. package/out/postal/city/candidate-schema.d.ts.map +1 -1
  326. package/out/postal/city/candidate-schema.js +21 -17
  327. package/out/postal/city/candidate-schema.js.map +1 -1
  328. package/out/{postal/index.d.ts → postal.d.ts} +1 -1
  329. package/out/postal.d.ts.map +1 -0
  330. package/out/{postal/index.js → postal.js} +1 -1
  331. package/out/postal.js.map +1 -0
  332. package/out/postcode-point-lookup.d.ts +9 -19
  333. package/out/postcode-point-lookup.d.ts.map +1 -1
  334. package/out/postcode-point-lookup.js +5 -17
  335. package/out/postcode-point-lookup.js.map +1 -1
  336. package/out/primary-preference.d.ts +38 -92
  337. package/out/primary-preference.d.ts.map +1 -1
  338. package/out/primary-preference.js +13 -97
  339. package/out/primary-preference.js.map +1 -1
  340. package/out/proximity-rerank.d.ts +40 -29
  341. package/out/proximity-rerank.d.ts.map +1 -1
  342. package/out/proximity-rerank.js +38 -29
  343. package/out/proximity-rerank.js.map +1 -1
  344. package/out/ranking-weights.d.ts +91 -62
  345. package/out/ranking-weights.d.ts.map +1 -1
  346. package/out/ranking-weights.js +26 -25
  347. package/out/ranking-weights.js.map +1 -1
  348. package/out/region-keys.d.ts +48 -29
  349. package/out/region-keys.d.ts.map +1 -1
  350. package/out/region-keys.js +68 -40
  351. package/out/region-keys.js.map +1 -1
  352. package/out/reverse.d.ts +75 -50
  353. package/out/reverse.d.ts.map +1 -1
  354. package/out/reverse.js +61 -49
  355. package/out/reverse.js.map +1 -1
  356. package/out/schema.d.ts +74 -42
  357. package/out/schema.d.ts.map +1 -1
  358. package/out/schema.js +4 -5
  359. package/out/schema.js.map +1 -1
  360. package/out/search-fetch.d.ts +16 -12
  361. package/out/search-fetch.d.ts.map +1 -1
  362. package/out/search-fetch.js +83 -63
  363. package/out/search-fetch.js.map +1 -1
  364. package/out/sqlite/convention-source.d.ts +11 -15
  365. package/out/sqlite/convention-source.d.ts.map +1 -1
  366. package/out/sqlite/convention-source.js +11 -20
  367. package/out/sqlite/convention-source.js.map +1 -1
  368. package/out/sqlite/utils.d.ts +18 -18
  369. package/out/sqlite/utils.d.ts.map +1 -1
  370. package/out/sqlite/utils.js +14 -15
  371. package/out/sqlite/utils.js.map +1 -1
  372. package/out/street/centroid/schema.d.ts +37 -42
  373. package/out/street/centroid/schema.d.ts.map +1 -1
  374. package/out/street/centroid/schema.js +9 -21
  375. package/out/street/centroid/schema.js.map +1 -1
  376. package/out/street/centroid.d.ts +30 -0
  377. package/out/street/centroid.d.ts.map +1 -0
  378. package/out/street/{centroid/index.js → centroid.js} +12 -23
  379. package/out/street/centroid.js.map +1 -0
  380. package/out/street/morphology/fst/builder.d.ts +20 -10
  381. package/out/street/morphology/fst/builder.d.ts.map +1 -1
  382. package/out/street/morphology/fst/builder.js +34 -33
  383. package/out/street/morphology/fst/builder.js.map +1 -1
  384. package/out/street/morphology/fst/loader.d.ts +32 -25
  385. package/out/street/morphology/fst/loader.d.ts.map +1 -1
  386. package/out/street/morphology/fst/loader.js +19 -19
  387. package/out/street/morphology/fst/loader.js.map +1 -1
  388. package/out/street/name-lookup.d.ts +24 -18
  389. package/out/street/name-lookup.d.ts.map +1 -1
  390. package/out/street/name-lookup.js +32 -31
  391. package/out/street/name-lookup.js.map +1 -1
  392. package/out/street/normalize.d.ts +35 -136
  393. package/out/street/normalize.d.ts.map +1 -1
  394. package/out/street/normalize.js +74 -191
  395. package/out/street/normalize.js.map +1 -1
  396. package/out/street/segment-schema.d.ts +79 -39
  397. package/out/street/segment-schema.d.ts.map +1 -1
  398. package/out/street/segment-schema.js +71 -21
  399. package/out/street/segment-schema.js.map +1 -1
  400. package/out/{street/index.js → street.d.ts} +2 -2
  401. package/out/street.d.ts.map +1 -0
  402. package/out/{street/index.d.ts → street.js} +2 -2
  403. package/out/street.js.map +1 -0
  404. package/out/types.d.ts +69 -138
  405. package/out/types.d.ts.map +1 -1
  406. package/out/types.js +0 -6
  407. package/out/types.js.map +1 -1
  408. package/out/unified-schema.d.ts +8 -12
  409. package/out/unified-schema.d.ts.map +1 -1
  410. package/out/unified-schema.js +29 -29
  411. package/out/unified-schema.js.map +1 -1
  412. package/out/uprn/existence.d.ts +28 -17
  413. package/out/uprn/existence.d.ts.map +1 -1
  414. package/out/uprn/existence.js +26 -16
  415. package/out/uprn/existence.js.map +1 -1
  416. package/out/uprn/lookup.d.ts +14 -44
  417. package/out/uprn/lookup.d.ts.map +1 -1
  418. package/out/uprn/lookup.js +20 -56
  419. package/out/uprn/lookup.js.map +1 -1
  420. package/out/uprn/schema.d.ts +18 -40
  421. package/out/uprn/schema.d.ts.map +1 -1
  422. package/out/uprn/schema.js +10 -32
  423. package/out/uprn/schema.js.map +1 -1
  424. package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
  425. package/out/uprn.d.ts.map +1 -0
  426. package/out/{uprn/index.js → uprn.js} +1 -1
  427. package/out/uprn.js.map +1 -0
  428. package/out/weights-overlay-linker.d.ts +132 -141
  429. package/out/weights-overlay-linker.d.ts.map +1 -1
  430. package/out/weights-overlay-linker.js +86 -154
  431. package/out/weights-overlay-linker.js.map +1 -1
  432. package/package.json +88 -593
  433. package/lib/address/point/index.ts +0 -328
  434. package/lib/convention/index.ts +0 -154
  435. package/lib/fts/index.ts +0 -361
  436. package/out/address/index.d.ts.map +0 -1
  437. package/out/address/index.js.map +0 -1
  438. package/out/address/point/index.d.ts +0 -53
  439. package/out/address/point/index.d.ts.map +0 -1
  440. package/out/address/point/index.js +0 -222
  441. package/out/address/point/index.js.map +0 -1
  442. package/out/ancestry/index.d.ts +0 -45
  443. package/out/ancestry/index.d.ts.map +0 -1
  444. package/out/ancestry/index.js +0 -55
  445. package/out/ancestry/index.js.map +0 -1
  446. package/out/convention/index.d.ts +0 -106
  447. package/out/convention/index.d.ts.map +0 -1
  448. package/out/convention/index.js +0 -93
  449. package/out/convention/index.js.map +0 -1
  450. package/out/fst/index.d.ts.map +0 -1
  451. package/out/fst/index.js.map +0 -1
  452. package/out/fts/index.d.ts +0 -184
  453. package/out/fts/index.d.ts.map +0 -1
  454. package/out/fts/index.js +0 -277
  455. package/out/fts/index.js.map +0 -1
  456. package/out/geonames/index.d.ts.map +0 -1
  457. package/out/geonames/index.js.map +0 -1
  458. package/out/nsul/index.d.ts.map +0 -1
  459. package/out/nsul/index.js.map +0 -1
  460. package/out/poi/index.d.ts.map +0 -1
  461. package/out/poi/index.js.map +0 -1
  462. package/out/postal/index.d.ts.map +0 -1
  463. package/out/postal/index.js.map +0 -1
  464. package/out/street/centroid/index.d.ts +0 -40
  465. package/out/street/centroid/index.d.ts.map +0 -1
  466. package/out/street/centroid/index.js.map +0 -1
  467. package/out/street/index.d.ts.map +0 -1
  468. package/out/street/index.js.map +0 -1
  469. package/out/uprn/index.d.ts.map +0 -1
  470. package/out/uprn/index.js.map +0 -1
  471. /package/lib/{fst/index.ts → fst.ts} +0 -0
  472. /package/lib/{geonames/index.ts → geonames.ts} +0 -0
  473. /package/lib/{nsul/index.ts → nsul.ts} +0 -0
  474. /package/lib/{poi/index.ts → poi.ts} +0 -0
  475. /package/lib/{postal/index.ts → postal.ts} +0 -0
  476. /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/README.md CHANGED
@@ -40,7 +40,7 @@ Databases are `ATTACH`ed by a schema name **derived from the filename**, and que
40
40
  - The name does not route. `postcode-ca-overture.db` derives `postcode_ca_overture`, and the router tests `startsWith("postalcode_")` — **"postcode" is not "postalcode"**. It held 843,739 Canadian codes and answered every query with zero hits, which is indistinguishable from "this country has no places".
41
41
  - The database carries `spr` but no `place_search`. It routes, then dies mid-`SELECT`.
42
42
 
43
- The predicate is the **table, not the filename**: a database carrying `spr` is claiming to be a place database, and every lookup path here reaches the FTS index. A relation-table database like `postcode-locality-<cc>.db` carries no `spr`, never makes that claim, and is exempt — which is what keeps the documented default database list working.
43
+ The predicate is the **table rather than the filename**: a database carrying `spr` is claiming to be a place database, and every lookup path here reaches the FTS index. A relation-table database like `postcode-locality-<cc>.db` carries no `spr`, never makes that claim, and is exempt — which is what keeps the documented default database list working.
44
44
 
45
45
  ```
46
46
  WOFSQLitePlaceLookup: …/postcode-ca-overture.db carries "spr" but no "place_search" table, so it
@@ -81,7 +81,7 @@ Cross-database `UNION` queries are not supported in one `findPlace` call — BM2
81
81
  comparable across separately-indexed corpora. Issue two `findPlace` calls and merge in your
82
82
  caller if you need that.
83
83
 
84
- ## Getting the WOF SQLite distribution
84
+ ## Get the WOF SQLite distribution
85
85
 
86
86
  The Geocode Earth team mirrors WOF SQLite distributions at <https://data.geocode.earth/wof/dist/sqlite/>. The two relevant distributions for v1:
87
87
 
@@ -137,7 +137,7 @@ Builds a trimmed WOF SQLite distribution sized for browser-side deployments (Pat
137
137
  # Defaults: --top 1000 localities, --countries US, drops geojson after building aux tables
138
138
  # The slim wof-hot.db distribution is RETIRED (2026-06-20): the demo byte-range-resolves
139
139
  # against the global candidate table. `buildSlimWOFDatabase` remains importable from
140
- # `@mailwoman/resolver-wof-sqlite/build-slim` for test fixtures. Historical invocation:
140
+ # `@mailwoman/resolver-wof-sqlite/build/slim` for test fixtures. Historical invocation:
141
141
  # mailwoman-wof-build-slim \
142
142
  --in /path/to/whosonfirst-data-admin-us-latest.db \
143
143
  --in /path/to/whosonfirst-data-postalcode-us-latest.db \
@@ -175,7 +175,7 @@ const { created, indexedRows, durationMs } = buildPlaceSearchFts(db, {
175
175
  })
176
176
  ```
177
177
 
178
- ## Ranking
178
+ ## Rank places
179
179
 
180
180
  The resolver scores candidates by:
181
181
 
@@ -237,11 +237,11 @@ are all stripped safely before going to FTS5. Per-token rules:
237
237
 
238
238
  - **Bare tokens** (`"Paris"`, `"62701"`) become FTS5 **phrase matches**: `"Paris"` matches places
239
239
  named exactly "Paris", `"62701"` matches the postcode 62701 exactly.
240
- - **Trailing `*`** (`"627*"`, `"Pari*"`) becomes FTS5 **prefix syntax**: `627*` matches every
240
+ - **A trailing `*`** (`"627*"`, `"Pari*"`) becomes FTS5 **prefix syntax**: `627*` matches every
241
241
  postcode starting with 627, `Pari*` matches Paris / Parishville / etc. The caller explicitly
242
242
  signals "prefix"; bare tokens stay phrase-matched for safety.
243
- - **Multiple tokens** join with implicit AND: `"Pari* TX"` matches places whose name contains
244
- both a `Pari*`-prefixed word AND the word `TX`.
243
+ - **Multiple tokens** join with an implicit `AND`: `"Pari* TX"` matches places whose name contains
244
+ both a `Pari*`-prefixed word and the word `TX`.
245
245
 
246
246
  Example: `findPlace({ text: "902*", placetype: "postalcode" })` returns 90201, 90210, 90211, …
247
247
  matching the Los Angeles ZIP corridor.
@@ -256,7 +256,7 @@ This package itself is AGPL-3.0; the WOF data it indexes is CC-BY 4.0. The two l
256
256
 
257
257
  ## Integration tests
258
258
 
259
- `resolver-wof-sqlite/integration.test.ts` exercises the resolver against a real WOF SQLite distribution. The suite is **skipped** when no DB is present — set `MAILWOMAN_WOF_DB` to override the lookup path, otherwise it defaults to `/mnt/playpen/mailwoman-data/wof/whosonfirst-data-admin-us-latest.db` (the canonical lab location). CI runs against the fixture-only suites; operators with real WOF data locally get an extra layer of validation.
259
+ `resolver-wof-sqlite/integration.test.ts` exercises the resolver against a real WOF SQLite distribution. The suite is **skipped** when no DB is present — set `MAILWOMAN_WOF_DB` to override the lookup path, otherwise it defaults to `$MAILWOMAN_DATA_ROOT/db/wof/whosonfirst-data-admin-us-latest.db` (the canonical lab location). CI runs against the fixture-only suites; operators with real WOF data locally get an extra layer of validation.
260
260
 
261
261
  Coverage includes: placetype filtering, country filtering, the empty-result case, FTS5 special-character sanitization, Japanese alt-name resolution, parent-constrained lookup, and a performance budget (`findPlace` < 250 ms against the 142 k-row US admin database).
262
262
 
@@ -3,34 +3,30 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Address-point interpolation — "Method 2" of the resolution ladder (#483, Phase 1 of
7
- * `docs/articles/plan/2026-06-11-resolution-ladder.md`): when the exact address-point tier (#476)
8
- * misses a house number, bracket the number with REAL neighbor points on the same street from the
9
- * same #476 extract and interpolate linearly in house-number space between them. Real occupancy
10
- * replaces TIGER's uniform-spacing assumption — the dominant error term of the TIGER pilot's check
11
- * miss; TIGER range interpolation (`StreetInterpolator`) demotes to the fallback for streets too
12
- * sparse to bracket.
6
+ * Address-point interpolation, the second method of the resolution ladder. When the exact
7
+ * address-point tier misses a house number, bracket the number with real neighbor points on the
8
+ * same street and interpolate linearly in house-number space between them. Real occupancy
9
+ * replaces the tiger range tier's uniform-spacing assumption. Tiger range interpolation
10
+ * (`StreetInterpolator`) is the fallback for streets too sparse to bracket.
13
11
  *
14
- * Matching key is `street_key` — THE shared normalizer plus the route fold
15
- * (`canonicalizeRouteKey`), identical at build time (`scripts/build-address-point-extract.ts`) and
16
- * query time, by construction. Scope is postcode-first like the segment tier; a query without a
17
- * postcode goes straight to the fallback (which carries its own statewide-ambiguity abstention).
12
+ * The matching key is `street_key`, the shared normalizer plus the route fold
13
+ * (`canonicalizeRouteKey`), identical at build time (`mailwoman situs address-points`) and query
14
+ * time by construction. Scope is postcode-first like the segment tier. A query without a postcode
15
+ * goes straight to the fallback. The fallback has its own statewide-ambiguity abstention.
18
16
  *
19
- * Bracketing contract:
17
+ * Neighbor candidates never include the queried number itself, so production never overrides an
18
+ * on-file number and grading against the same extract is non-circular.
20
19
  *
21
- * - Neighbor candidates NEVER include the queried number itself (any unit/duplicate row of it) — in
22
- * production the exact tier would already have answered an on-file number, and in the eval
23
- * this is what makes grading against the same extract non-circular by construction.
24
20
  * - Both-sided bracket (`bracket: "both"`): linear interpolation between the nearest known number
25
- * below and above; `uncertaintyM` = half the distance between them.
26
- * - Single-sided (`bracket: "single"`): linear extrapolation along the two nearest known numbers on
27
- * that side, capped at one pair-span beyond the nearest point (`t ≤ 2` — beyond that the line
28
- * carries no evidence and the query falls through); `uncertaintyM` = the pair distance plus
29
- * the extrapolated overshoot, explicitly larger than the both-sided radius.
30
- * - No bracket (no neighbors, a single known number, or past the extrapolation cap): fall through to
31
- * the TIGER fallback when configured, else null.
21
+ * below and above. `uncertaintyM` is half the distance between them.
22
+ * - Single-sided (`bracket: "single"`): linear extrapolation along the two nearest known numbers
23
+ * on that side, capped at one pair-span beyond the nearest point (`t ≤ 2`, past which the line
24
+ * provides no evidence and the query falls through). `uncertaintyM` is the pair distance plus
25
+ * the extrapolated overshoot, larger than the both-sided radius.
26
+ * - No bracket, meaning no neighbors, a single known number, or past the extrapolation cap: fall
27
+ * through to the tiger fallback when configured, else null.
32
28
  *
33
- * Standalone like the segment tier — core wiring rides the Phase 2 ordered `spatialTiers` list.
29
+ * Standalone like the segment tier, wired through the ordered `spatialTiers` list.
34
30
  */
35
31
 
36
32
  import type { InterpolationLookup } from "@mailwoman/core/resolver"
@@ -42,8 +38,10 @@ import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#i
42
38
  import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
43
39
  import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
44
40
  /**
45
- * Extrapolation cap for a single-sided bracket: at most one pair-span beyond the nearest known point (`t = 2`). Past
46
- * it, the two-point line carries no evidence about the query number.
41
+ * Extrapolation cap for a single-sided bracket: at most one pair-span beyond
42
+ * the nearest known point (`t = 2`).
43
+ *
44
+ * Past it, the two-point line provides no evidence about the query number.
47
45
  */
48
46
  const MAX_EXTRAPOLATION_T = 2
49
47
 
@@ -71,29 +69,31 @@ export class AddressPointInterpolator<
71
69
  > implements InterpolationLookup {
72
70
  readonly #db: DatabaseClient<DB>
73
71
  /**
74
- * Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
75
- * ownership is membership rather than a flag a later branch has to check.
72
+ * Resources this instance opened.
73
+ *
74
+ * A connection handed in by a caller is not in here, so disposal cannot reach it.
75
+ * Ownership is membership rather than a flag a later branch checks.
76
76
  */
77
- readonly #resources = new DisposableStack()
77
+ readonly #resources: DisposableStack
78
78
  readonly #fallback: StreetInterpolator | undefined
79
79
  readonly #byPostcode: PreparedAll<[postcode: string, street: RouteKey, number: number], PointRow> | undefined
80
80
 
81
81
  constructor(opts: { dbPath?: string; database?: DatabaseClient<DB>; fallback?: StreetInterpolator }) {
82
+ using resources = new DisposableStack()
83
+
82
84
  if (opts.database) {
83
85
  this.#db = opts.database
84
86
  } else if (opts.dbPath) {
85
- this.#db = this.#resources.use(new DatabaseClient<DB>(opts.dbPath, { readOnly: true }))
87
+ this.#db = resources.use(new DatabaseClient<DB>(opts.dbPath, { readOnly: true }))
86
88
  } else {
87
89
  throw new Error("AddressPointInterpolator: one of dbPath or database is required")
88
90
  }
89
91
 
90
92
  this.#fallback = opts.fallback
91
93
 
92
- // Degrade gracefully on an empty/tableless extract (#568): with no `address_point` table this tier
93
- // is skipped, deferring to the segment fallback rather than crashing at construction.
94
+ // Degrade gracefully on an extract without an `address_point` table.
95
+ // The tier is skipped and defers to the segment fallback rather than crashing at construction.
94
96
  if (hasTable(this.#db, "address_point")) {
95
- // Strictly-numeric neighbor numbers on the route-folded street key within the ZIP. The
96
- // queried number itself is excluded HERE (see module doc: non-circular by construction).
97
97
  this.#byPostcode = prepareAll(
98
98
  this.#db,
99
99
  `SELECT CAST(number AS INTEGER) AS n, lat, lon, source, release
@@ -103,6 +103,8 @@ export class AddressPointInterpolator<
103
103
  AND CAST(number AS INTEGER) != ?`
104
104
  )
105
105
  }
106
+
107
+ this.#resources = resources.move()
106
108
  }
107
109
 
108
110
  find(query: InterpolationQuery): InterpolatedHit | null {
@@ -111,10 +113,9 @@ export class AddressPointInterpolator<
111
113
  if (!/^\d+$/.test(numberRaw)) return null
112
114
  const n = Number(numberRaw)
113
115
 
114
- // No own table (empty extract) or no postcode → defer to the segment fallback rather than query.
115
116
  if (!this.#byPostcode || !query.postcode) return this.#fallback?.find(query) ?? null
116
117
 
117
- // Key-variant ladder (see `streetKeyVariants`) — same probe order as the exact-point reader.
118
+ // Key-variant ladder (see `streetKeyVariants`), the same probe order as the exact-point reader.
118
119
  let rows: PointRow[] = []
119
120
 
120
121
  for (const variant of streetKeyVariants(query.street)) {
@@ -165,7 +166,8 @@ function anchorsByNumber(rows: readonly PointRow[]): NumberAnchor[] {
165
166
  function interpolateFromNeighbors(rows: readonly PointRow[], n: number): InterpolatedHit | null {
166
167
  const anchors = anchorsByNumber(rows)
167
168
 
168
- // Nearest known number below and above the query (the rows never contain n itself).
169
+ // Nearest known number below and above the query.
170
+ // The rows never contain n itself.
169
171
  let below: NumberAnchor | undefined
170
172
  let above: NumberAnchor | undefined
171
173
 
@@ -196,7 +198,7 @@ function interpolateFromNeighbors(rows: readonly PointRow[], n: number): Interpo
196
198
  }
197
199
 
198
200
  // Single-sided: extrapolate along the two nearest known numbers on the populated side.
199
- // `near` is the anchor closest to n, `far` the next one out; t > 1 by construction.
201
+ // `t > 1` by construction because n lies outside the span.
200
202
  const side = below ? anchors.slice(-2) : anchors.slice(0, 2)
201
203
 
202
204
  if (side.length < 2) return null
@@ -2,18 +2,6 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- *
6
- * Typed schema for the SITUS / rooftop ADDRESS-POINT extracts (`address-points-<cc>-<slug>.db`, built
7
- * by `scripts/build-address-point-extract.ts` — the #476/#567 national rooftop tier behind the
8
- * demo's "type any US address, get the building"). Single source of truth for the columns shared
9
- * by the BUILDER and the READER ({@link AddressPointSqliteLookup}), so a column rename in one is a
10
- * compile error in the other.
11
- *
12
- * The builder's hot INSERT (tens of millions of rows per state) stays a POSITIONAL prepared
13
- * statement for throughput — but its column list is derived from {@link ADDRESS_POINT_COLUMNS}
14
- * here, and its table comes from {@link createAddressPointTable}, so the positional order can't
15
- * silently drift from what the reader expects. (Same convention as the candidate build: typed
16
- * schema guards the contract; positional inserts keep the speed.)
17
5
  */
18
6
 
19
7
  import type { Kysely } from "kysely"
@@ -21,9 +9,8 @@ import type { Kysely } from "kysely"
21
9
  import type { NameKey, RouteKey, StreetKey } from "#street/normalize"
22
10
 
23
11
  /**
24
- * One rooftop address point. `(street_norm, number)` within a `postcode` (preferred) or `locality_norm` scope is the
25
- * lookup; `street_key` is the #483 route-fold key for interpolation. Coordinates are non-null (the builder drops
26
- * non-finite coords). `unit`/`postcode`/`locality_norm` are nullable (not every source carries all three).
12
+ * One rooftop address point; `(street_norm, number)` within a `postcode`
13
+ * or `locality_norm` scope is the lookup.
27
14
  */
28
15
  export interface AddressPointTable {
29
16
  /**
@@ -31,12 +18,12 @@ export interface AddressPointTable {
31
18
  */
32
19
  street_norm: StreetKey
33
20
  /**
34
- * `canonicalizeRouteKey(street_norm)` — the route-fold key (#483 Method 2). Its own brand, so it cannot be
35
- * interchanged with the plain `street_norm` above.
21
+ * `canonicalizeRouteKey(street_norm)` — the route-fold key, branded
22
+ * so it cannot be interchanged with `street_norm`.
36
23
  */
37
24
  street_key: RouteKey
38
25
  /**
39
- * House number, normalized lower-case (kept TEXT — "123-A", "12 1/2" must survive).
26
+ * House number, normalized lower-case (kept text — "123-A", "12 1/2" must survive).
40
27
  */
41
28
  number: string
42
29
  unit: string | null
@@ -45,9 +32,7 @@ export interface AddressPointTable {
45
32
  * Shared {@link normalizeLocalityForKey} of the locality — the fallback scope.
46
33
  */
47
34
  locality_norm: NameKey | null
48
- /**
49
- * The street as it appeared in the source (kept for display / debugging).
50
- */
35
+
51
36
  street_raw: string
52
37
  lat: number
53
38
  lon: number
@@ -55,18 +40,16 @@ export interface AddressPointTable {
55
40
  * Provenance: the dataset this point came from (e.g. `overture:us`, `openaddresses`).
56
41
  */
57
42
  source: string
58
- /**
59
- * The pinned data release the point was ingested from.
60
- */
43
+
61
44
  release: string
62
45
  /**
63
- * The source register's stable administrative key for the point's commune or municipality — BAN's `code_insee`. A
64
- * display name (`locality_norm`) is not a key; the coverage basis is computed per THIS.
46
+ * The source register's stable administrative key for the point's commune or municipality —
47
+ * BAN's `code_insee`; the coverage basis is computed per this key.
65
48
  */
66
49
  admin_code: string | null
67
50
  /**
68
- * The register's own certification flag for the point (BAN `certification_commune`: 1 certified by the commune, 0
69
- * not), or null for a source that states none. A basis is never inferred from a share of these.
51
+ * The register's own certification flag for the point
52
+ * (BAN `certification_commune`: 1 certified, 0 not), never inferred from a share.
70
53
  */
71
54
  certified: number | null
72
55
  }
@@ -79,17 +62,13 @@ export interface AddressPointDatabase {
79
62
  }
80
63
 
81
64
  /**
82
- * The slice of a Kysely handle the `address_point` DDL touches — the parameter type its builders take.
83
- *
84
- * Kysely is invariant in its schema parameter (the incompatibility is in `transaction()`), so a extract that EXTENDS
85
- * `AddressPointTable` — OSM adds `h3_cell` — cannot pass its own handle to a `Kysely<AddressPointDatabase>` parameter.
86
- * Naming only `schema` lets it, and the DDL below needs nothing else.
65
+ * The subset of a Kysely handle the DDL touches, narrowed to `schema`
66
+ * because Kysely is invariant in its schema parameter.
87
67
  */
88
68
  export type AddressPointSchemaHandle = Pick<Kysely<AddressPointDatabase>, "schema">
89
69
 
90
70
  /**
91
- * The `address_point` columns in INSERT order. The builder's positional prepared statement derives its placeholder list
92
- * from this, so the positional order can't drift from the DDL / the reader.
71
+ * The `address_point` columns in insert order, from which the builder derives its positional placeholders.
93
72
  */
94
73
  export const ADDRESS_POINT_COLUMNS = [
95
74
  "street_norm",
@@ -114,7 +93,7 @@ export async function createAddressPointTable(db: AddressPointSchemaHandle): Pro
114
93
  await db.schema
115
94
  .createTable("address_point")
116
95
  .addColumn("street_norm", "text", (c) => c.notNull())
117
- // `street_key` = canonicalizeRouteKey(street_norm): the route-fold key (#483 Method 2).
96
+
118
97
  .addColumn("street_key", "text", (c) => c.notNull())
119
98
  .addColumn("number", "text", (c) => c.notNull())
120
99
  .addColumn("unit", "text")
@@ -147,9 +126,7 @@ export async function createAddressPointIndexes(db: AddressPointSchemaHandle): P
147
126
  .execute()
148
127
 
149
128
  await db.schema.createIndex("idx_ap_streetkey").on("address_point").columns(["postcode", "street_key"]).execute()
150
- // Street-first index for the BBOX scope (#247): OSM points often carry no postcode/locality, so the
151
- // reader scopes a `(street_norm, number)` probe by the resolved locality's bbox (lat/lon BETWEEN). The
152
- // postcode/locality indexes lead with their scope column and can't serve this; US situs never probes by
153
- // bbox so it simply carries one extra (cheap) index on a future rebuild.
129
+ // Street-first index for the bbox scope: OSM points often have no postcode or locality,
130
+ // so the reader scopes a `(street_norm, number)` probe by the resolved locality's bbox.
154
131
  await db.schema.createIndex("idx_ap_street").on("address_point").columns(["street_norm", "number"]).execute()
155
132
  }
@@ -0,0 +1,315 @@
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
+ * It defaults to `"us"`.
80
+ * @param opts.localityKeys Whether the extract's `locality_norm` holds full place names.
81
+ * With `"abbreviated"`, a row's locality can select a row but never rejects one.
82
+ * The US situs extract uses NAD city fields.
83
+ * Some counties abbreviate them (`addi` for Addison).
84
+ * Other counties replace them with the parent town.
85
+ * It defaults to `"abbreviated"` for `"us"` and `"full"` for every other locale.
86
+ */
87
+ constructor(
88
+ dbPath: PathBuilderLike,
89
+ opts: { streetLocale?: StreetLocale; localityKeys?: "full" | "abbreviated" } = {}
90
+ ) {
91
+ this.#db = new DatabaseClient<DB>(dbPath, { readOnly: true })
92
+ this.#locale = opts.streetLocale ?? "us"
93
+ this.#localityKeys = opts.localityKeys ?? (this.#locale === "us" ? "abbreviated" : "full")
94
+
95
+ // An extract without an `address_point` table, such as an interrupted build, makes every lookup miss.
96
+ if (hasTable(this.#db, "address_point")) {
97
+ this.#byPostcode = prepareGet(
98
+ this.#db,
99
+ `SELECT ${SELECT_COLS} FROM address_point
100
+ WHERE postcode = ? AND street_norm = ? AND number = ? LIMIT 1`
101
+ )
102
+
103
+ this.#byPostcodeLocality = prepareGet(
104
+ this.#db,
105
+ `SELECT ${SELECT_COLS} FROM address_point
106
+ WHERE postcode = ? AND locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
107
+ )
108
+
109
+ this.#byLocality = prepareGet(
110
+ this.#db,
111
+ `SELECT ${SELECT_COLS} FROM address_point
112
+ WHERE locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
113
+ )
114
+
115
+ this.#byBbox = prepareGet(
116
+ this.#db,
117
+ `SELECT ${SELECT_COLS} FROM address_point
118
+ WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
119
+ )
120
+
121
+ this.#byLocalityTail = prepareGet(
122
+ this.#db,
123
+ `SELECT ${SELECT_COLS} FROM address_point
124
+ WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
125
+ )
126
+ }
127
+ }
128
+
129
+ find(query: {
130
+ street: string
131
+ number: string
132
+ postcode?: string
133
+ locality?: string
134
+ region?: string
135
+ subregion?: string
136
+ bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
137
+ }): AddressPointHit | null {
138
+ if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
139
+ const number = normalizeHouseNumberForKey(query.number, this.#locale)
140
+
141
+ if (!number) return null
142
+
143
+ // A `zh` extract keys locality as 縣市 + 鄉鎮市區.
144
+ // The parser tags 縣市 as `region` and 鄉鎮市區 as `subregion`.
145
+ // The Taiwanese register has no postcode, so this pair becomes the locality key.
146
+ // A query with only the 鄉鎮市區 matches the stored key by its suffix.
147
+ const scoped =
148
+ this.#locale === "zh" && !query.locality && query.subregion
149
+ ? query.region
150
+ ? { ...query, locality: `${query.region}${query.subregion}` }
151
+ : { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
152
+ : query
153
+
154
+ // `streetKeyVariants` yields the literal key first.
155
+ // Each variant runs the full number fallback sequence.
156
+ // The first variant with a match wins.
157
+ let row: AddressPointRow | undefined
158
+
159
+ for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
160
+ row = this.#findForKey(streetNorm, number, scoped)
161
+
162
+ if (row) break
163
+ }
164
+
165
+ if (!row) return null
166
+
167
+ return {
168
+ lat: row.lat,
169
+ lon: row.lon,
170
+ source: row.source,
171
+ release: row.release,
172
+ ...(row.locality_norm ? { localityNorm: row.locality_norm } : {}),
173
+ ...(row.postcode ? { postcode: row.postcode } : {}),
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Tries one normalized street key with the exact number, then fallback number forms.
179
+ * Each fallback runs only after the previous forms missed.
180
+ */
181
+ #findForKey(
182
+ streetNorm: StreetKey,
183
+ number: string,
184
+ query: {
185
+ street: string
186
+ number: string
187
+ postcode?: string
188
+ locality?: string
189
+ localityTail?: NameKey
190
+ bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
191
+ }
192
+ ) {
193
+ let row = this.#probe(streetNorm, number, query)
194
+
195
+ // The registers store one number per point, so a range such as "385-387" falls back to its low end.
196
+ if (!row) {
197
+ const low = /^(\d+[a-z]?)-\d+[a-z]?$/.exec(number)?.[1]
198
+
199
+ if (low) {
200
+ row = this.#probe(streetNorm, low, query)
201
+ }
202
+ }
203
+
204
+ // BAN stores a letter suffix as "3 a", while G-NAF and most OA sources store "3a".
205
+ // For a number of digits plus one letter, the lookup retries the other spacing
206
+ // and then the bare number.
207
+ if (!row) {
208
+ const joined = /^(\d+)\s+([a-z])$/.exec(number)
209
+ const spaced = /^(\d+)([a-z])$/.exec(number)
210
+
211
+ if (joined) {
212
+ row = this.#probe(streetNorm, `${joined[1]}${joined[2]}`, query) ?? this.#probe(streetNorm, joined[1]!, query)
213
+ } else if (spaced) {
214
+ row = this.#probe(streetNorm, `${spaced[1]} ${spaced[2]}`, query) ?? this.#probe(streetNorm, spaced[1]!, query)
215
+ }
216
+ }
217
+
218
+ // The Taiwanese register stores `14之12` and `30附40` as numbers `14` and `30`.
219
+ // It stores the rest in `unit`.
220
+ // A query number that still includes the sub-number falls back to the base number.
221
+ // That result is approximate.
222
+ if (!row && this.#locale === "zh") {
223
+ const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
224
+
225
+ if (base) {
226
+ row = this.#probe(streetNorm, base, query)
227
+ }
228
+ }
229
+
230
+ return row
231
+ }
232
+
233
+ /**
234
+ * Probes one (street, number) key by postcode, then locality, then locality suffix, then bounding box.
235
+ * Each scope runs only when the previous ones missed.
236
+ */
237
+ #probe(
238
+ streetNorm: StreetKey,
239
+ number: string,
240
+ query: {
241
+ postcode?: string
242
+ locality?: string
243
+ localityTail?: NameKey
244
+ bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
245
+ }
246
+ ): AddressPointRow | undefined {
247
+ let row: AddressPointRow | undefined
248
+
249
+ if (query.postcode) {
250
+ const postcode = query.postcode.trim()
251
+ const localityKey = query.locality ? this.#localityKey(query.locality) : undefined
252
+
253
+ // One postcode can cover several places with the same street and number,
254
+ // so a row whose locality agrees with the query is tried first.
255
+ // The postcode-only row answers only if `#scopeContradicts` accepts it.
256
+ const agreeing = localityKey ? this.#byPostcodeLocality!(postcode, localityKey, streetNorm, number) : undefined
257
+ const candidate = agreeing ?? this.#byPostcode!(postcode, streetNorm, number)
258
+
259
+ row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
260
+ }
261
+
262
+ if (!row && query.locality) {
263
+ row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
264
+ }
265
+
266
+ if (!row && query.localityTail && this.#byLocalityTail) {
267
+ // A Han key contains no `%` or `_`, so the pattern matches a literal suffix.
268
+ row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
269
+ }
270
+
271
+ // The bounding box serves extracts whose points have no postcode or locality, such as OSM.
272
+ if (!row && query.bbox) {
273
+ const b = query.bbox
274
+ const candidate = this.#byBbox!(streetNorm, number, b.minLat, b.maxLat, b.minLon, b.maxLon)
275
+
276
+ // A row with its own scope that the scoped probes missed is a different address in the same box.
277
+ row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
278
+ }
279
+
280
+ return row
281
+ }
282
+
283
+ /**
284
+ * Folds the query's locality the way the extract's builder folded `locality_norm`.
285
+ *
286
+ * FR extracts key arrondissements at the base city, so "Paris 13e Arrondissement"
287
+ * and "Paris" produce the same key.
288
+ */
289
+ #localityKey(locality: string): NameKey {
290
+ if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
291
+
292
+ return normalizeLocalityForKeyLocale(locality, this.#locale)
293
+ }
294
+
295
+ /**
296
+ * Returns whether the row's own postcode or locality differs from the query's.
297
+ *
298
+ * A row field that is empty never contradicts.
299
+ * The locality is compared only when the extract's `localityKeys` is `"full"`,
300
+ * because abbreviated keys would reject correct rows.
301
+ */
302
+ #scopeContradicts(row: AddressPointRow, query: { postcode?: string; locality?: string }): boolean {
303
+ if (query.postcode && row.postcode && row.postcode.trim() !== query.postcode.trim()) return true
304
+
305
+ if (this.#localityKeys === "full" && query.locality && row.locality_norm) {
306
+ return row.locality_norm !== this.#localityKey(query.locality)
307
+ }
308
+
309
+ return false
310
+ }
311
+
312
+ [Symbol.dispose](): void {
313
+ this.#db[Symbol.dispose]()
314
+ }
315
+ }
@@ -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"