@mailwoman/resolver-wof-sqlite 9.1.0 → 9.3.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 (501) hide show
  1. package/README.md +36 -20
  2. package/lib/address/index.ts +9 -0
  3. package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +40 -28
  4. package/{address-point-schema.ts → lib/address/point-schema.ts} +34 -8
  5. package/lib/address/point.ts +280 -0
  6. package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
  7. package/{ancestry.ts → lib/ancestry/index.ts} +13 -8
  8. package/{build-candidate.ts → lib/build-candidate.ts} +243 -199
  9. package/{build-slim.ts → lib/build-slim.ts} +153 -168
  10. package/lib/candidate/alias-bags.ts +56 -0
  11. package/lib/candidate/ancestors-sidecar.ts +204 -0
  12. package/lib/candidate/country-display-names.ts +79 -0
  13. package/lib/candidate/extract-fold.ts +138 -0
  14. package/lib/candidate/name-roles.ts +238 -0
  15. package/lib/candidate/own-name.ts +146 -0
  16. package/lib/candidate/place-attrs.ts +44 -0
  17. package/lib/candidate-ancestors-schema.ts +195 -0
  18. package/{candidate-fts.ts → lib/candidate-fts.ts} +7 -5
  19. package/{candidate-importance.ts → lib/candidate-importance.ts} +54 -57
  20. package/lib/candidate-lookup.ts +976 -0
  21. package/{candidate-schema.ts → lib/candidate-schema.ts} +34 -6
  22. package/lib/candidate-scoring.ts +261 -0
  23. package/lib/capital-schema.ts +89 -0
  24. package/lib/capitals.ts +148 -0
  25. package/{coincident-roles.ts → lib/coincident-roles.ts} +75 -19
  26. package/{convention.ts → lib/convention/index.ts} +5 -5
  27. package/lib/convention/schema.ts +72 -0
  28. package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +15 -16
  29. package/lib/currency-backfill.ts +334 -0
  30. package/lib/env.ts +50 -0
  31. package/lib/exact-match.ts +113 -0
  32. package/{sharding.ts → lib/extracts.ts} +63 -60
  33. package/lib/fst/autocomplete.ts +176 -0
  34. package/{fst-builder.ts → lib/fst/builder.ts} +30 -27
  35. package/{fst-deserialize-web.ts → lib/fst/deserialize-web.ts} +27 -87
  36. package/lib/fst/format.ts +106 -0
  37. package/{fst-freshness.ts → lib/fst/freshness.ts} +42 -75
  38. package/lib/fst/index.ts +14 -0
  39. package/{fst-matcher.ts → lib/fst/matcher.ts} +1 -1
  40. package/{fst-serialize.ts → lib/fst/serialize.ts} +37 -115
  41. package/{fst-types.ts → lib/fst/types.ts} +4 -2
  42. package/{fts.ts → lib/fts/index.ts} +28 -23
  43. package/{fts-query.ts → lib/fts/query.ts} +2 -2
  44. package/{geonames-aliases.ts → lib/geonames/aliases.ts} +13 -13
  45. package/lib/geonames/index.ts +8 -0
  46. package/{geonames-postal.ts → lib/geonames/postal.ts} +16 -15
  47. package/{index.ts → lib/index.ts} +52 -48
  48. package/{interpolation.ts → lib/interpolation.ts} +137 -41
  49. package/lib/lookup.ts +861 -0
  50. package/{name-score.ts → lib/name-score.ts} +6 -4
  51. package/lib/nsul/index.ts +8 -0
  52. package/lib/nsul/lookup.ts +144 -0
  53. package/lib/nsul/schema.ts +130 -0
  54. package/{place-importance-schema.ts → lib/place-importance-schema.ts} +70 -22
  55. package/lib/poi/index.ts +8 -0
  56. package/{poi-lookup.ts → lib/poi/lookup.ts} +32 -39
  57. package/{poi-schema.ts → lib/poi/schema.ts} +10 -6
  58. package/lib/polygon-schema.ts +47 -0
  59. package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +15 -21
  60. package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +4 -2
  61. package/lib/postal/index.ts +9 -0
  62. package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
  63. package/lib/primary-preference.ts +226 -0
  64. package/lib/proximity-rerank.ts +120 -0
  65. package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
  66. package/lib/region-keys.ts +144 -0
  67. package/{reverse.ts → lib/reverse.ts} +45 -56
  68. package/{schema.ts → lib/schema.ts} +11 -1
  69. package/lib/search-fetch.ts +257 -0
  70. package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
  71. package/lib/sqlite-utils.ts +83 -0
  72. package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +12 -6
  73. package/{street-centroid.ts → lib/street/centroid.ts} +26 -28
  74. package/lib/street/index.ts +13 -0
  75. package/{street-morphology-fst-builder.ts → lib/street/morphology-fst-builder.ts} +24 -14
  76. package/{street-morphology-fst-loader.ts → lib/street/morphology-fst-loader.ts} +14 -12
  77. package/{street-name-lookup.ts → lib/street/name-lookup.ts} +14 -30
  78. package/lib/street/normalize.ts +572 -0
  79. package/{street-segment-schema.ts → lib/street/segment-schema.ts} +13 -8
  80. package/{types.ts → lib/types.ts} +49 -9
  81. package/{unified-schema.ts → lib/unified-schema.ts} +21 -25
  82. package/lib/uprn/existence.ts +84 -0
  83. package/lib/uprn/index.ts +9 -0
  84. package/lib/uprn/lookup.ts +206 -0
  85. package/lib/uprn/schema.ts +124 -0
  86. package/lib/weights-overlay-linker.ts +878 -0
  87. package/out/address/index.d.ts +9 -0
  88. package/out/address/index.d.ts.map +1 -0
  89. package/out/address/index.js +9 -0
  90. package/out/address/index.js.map +1 -0
  91. package/out/{address-point-interpolation.d.ts → address/point-interpolation.d.ts} +11 -10
  92. package/out/address/point-interpolation.d.ts.map +1 -0
  93. package/out/{address-point-interpolation.js → address/point-interpolation.js} +29 -23
  94. package/out/address/point-interpolation.js.map +1 -0
  95. package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +30 -10
  96. package/out/address/point-schema.d.ts.map +1 -0
  97. package/out/{address-point-schema.js → address/point-schema.js} +7 -3
  98. package/out/address/point-schema.js.map +1 -0
  99. package/out/{address-point.d.ts → address/point.d.ts} +16 -8
  100. package/out/address/point.d.ts.map +1 -0
  101. package/out/address/point.js +191 -0
  102. package/out/address/point.js.map +1 -0
  103. package/out/{ancestry-backfill.d.ts → ancestry/backfill.d.ts} +7 -5
  104. package/out/ancestry/backfill.d.ts.map +1 -0
  105. package/out/{ancestry-backfill.js → ancestry/backfill.js} +12 -16
  106. package/out/ancestry/backfill.js.map +1 -0
  107. package/out/{ancestry.d.ts → ancestry/index.d.ts} +6 -5
  108. package/out/ancestry/index.d.ts.map +1 -0
  109. package/out/{ancestry.js → ancestry/index.js} +7 -7
  110. package/out/ancestry/index.js.map +1 -0
  111. package/out/build-candidate.d.ts +88 -7
  112. package/out/build-candidate.d.ts.map +1 -1
  113. package/out/build-candidate.js +145 -149
  114. package/out/build-candidate.js.map +1 -1
  115. package/out/build-slim.d.ts +5 -5
  116. package/out/build-slim.d.ts.map +1 -1
  117. package/out/build-slim.js +127 -142
  118. package/out/build-slim.js.map +1 -1
  119. package/out/candidate/alias-bags.d.ts +19 -0
  120. package/out/candidate/alias-bags.d.ts.map +1 -0
  121. package/out/candidate/alias-bags.js +39 -0
  122. package/out/candidate/alias-bags.js.map +1 -0
  123. package/out/candidate/ancestors-sidecar.d.ts +32 -0
  124. package/out/candidate/ancestors-sidecar.d.ts.map +1 -0
  125. package/out/candidate/ancestors-sidecar.js +140 -0
  126. package/out/candidate/ancestors-sidecar.js.map +1 -0
  127. package/out/candidate/country-display-names.d.ts +35 -0
  128. package/out/candidate/country-display-names.d.ts.map +1 -0
  129. package/out/candidate/country-display-names.js +59 -0
  130. package/out/candidate/country-display-names.js.map +1 -0
  131. package/out/candidate/extract-fold.d.ts +32 -0
  132. package/out/candidate/extract-fold.d.ts.map +1 -0
  133. package/out/candidate/extract-fold.js +104 -0
  134. package/out/candidate/extract-fold.js.map +1 -0
  135. package/out/candidate/name-roles.d.ts +57 -0
  136. package/out/candidate/name-roles.d.ts.map +1 -0
  137. package/out/candidate/name-roles.js +166 -0
  138. package/out/candidate/name-roles.js.map +1 -0
  139. package/out/candidate/own-name.d.ts +50 -0
  140. package/out/candidate/own-name.d.ts.map +1 -0
  141. package/out/candidate/own-name.js +132 -0
  142. package/out/candidate/own-name.js.map +1 -0
  143. package/out/candidate/place-attrs.d.ts +43 -0
  144. package/out/candidate/place-attrs.d.ts.map +1 -0
  145. package/out/candidate/place-attrs.js +15 -0
  146. package/out/candidate/place-attrs.js.map +1 -0
  147. package/out/candidate-ancestors-schema.d.ts +150 -0
  148. package/out/candidate-ancestors-schema.d.ts.map +1 -0
  149. package/out/candidate-ancestors-schema.js +123 -0
  150. package/out/candidate-ancestors-schema.js.map +1 -0
  151. package/out/candidate-fts.d.ts +7 -5
  152. package/out/candidate-fts.d.ts.map +1 -1
  153. package/out/candidate-fts.js +5 -3
  154. package/out/candidate-fts.js.map +1 -1
  155. package/out/candidate-importance.d.ts +16 -16
  156. package/out/candidate-importance.d.ts.map +1 -1
  157. package/out/candidate-importance.js +47 -52
  158. package/out/candidate-importance.js.map +1 -1
  159. package/out/candidate-lookup.d.ts +31 -52
  160. package/out/candidate-lookup.d.ts.map +1 -1
  161. package/out/candidate-lookup.js +380 -172
  162. package/out/candidate-lookup.js.map +1 -1
  163. package/out/candidate-schema.d.ts +31 -7
  164. package/out/candidate-schema.d.ts.map +1 -1
  165. package/out/candidate-schema.js +3 -0
  166. package/out/candidate-schema.js.map +1 -1
  167. package/out/candidate-scoring.d.ts +34 -0
  168. package/out/candidate-scoring.d.ts.map +1 -0
  169. package/out/candidate-scoring.js +196 -0
  170. package/out/candidate-scoring.js.map +1 -0
  171. package/out/capital-schema.d.ts +51 -0
  172. package/out/capital-schema.d.ts.map +1 -0
  173. package/out/capital-schema.js +63 -0
  174. package/out/capital-schema.js.map +1 -0
  175. package/out/capitals.d.ts +69 -0
  176. package/out/capitals.d.ts.map +1 -0
  177. package/out/capitals.js +98 -0
  178. package/out/capitals.js.map +1 -0
  179. package/out/coincident-roles.d.ts +12 -4
  180. package/out/coincident-roles.d.ts.map +1 -1
  181. package/out/coincident-roles.js +43 -11
  182. package/out/coincident-roles.js.map +1 -1
  183. package/out/{convention.d.ts → convention/index.d.ts} +5 -5
  184. package/out/convention/index.d.ts.map +1 -0
  185. package/out/{convention.js → convention/index.js} +4 -4
  186. package/out/convention/index.js.map +1 -0
  187. package/out/convention/schema.d.ts +51 -0
  188. package/out/convention/schema.d.ts.map +1 -0
  189. package/out/convention/schema.js +34 -0
  190. package/out/convention/schema.js.map +1 -0
  191. package/out/coverage-manifest-schema.d.ts +8 -8
  192. package/out/coverage-manifest-schema.d.ts.map +1 -1
  193. package/out/coverage-manifest-schema.js +6 -10
  194. package/out/coverage-manifest-schema.js.map +1 -1
  195. package/out/currency-backfill.d.ts +90 -0
  196. package/out/currency-backfill.d.ts.map +1 -0
  197. package/out/currency-backfill.js +196 -0
  198. package/out/currency-backfill.js.map +1 -0
  199. package/out/env.d.ts +43 -0
  200. package/out/env.d.ts.map +1 -0
  201. package/out/env.js +48 -0
  202. package/out/env.js.map +1 -0
  203. package/out/exact-match.d.ts +25 -0
  204. package/out/exact-match.d.ts.map +1 -0
  205. package/out/exact-match.js +88 -0
  206. package/out/exact-match.js.map +1 -0
  207. package/out/extracts.d.ts +114 -0
  208. package/out/extracts.d.ts.map +1 -0
  209. package/out/{sharding.js → extracts.js} +38 -38
  210. package/out/extracts.js.map +1 -0
  211. package/out/{fst-autocomplete.d.ts → fst/autocomplete.d.ts} +13 -13
  212. package/out/fst/autocomplete.d.ts.map +1 -0
  213. package/out/fst/autocomplete.js +113 -0
  214. package/out/fst/autocomplete.js.map +1 -0
  215. package/out/{fst-builder.d.ts → fst/builder.d.ts} +5 -5
  216. package/out/fst/builder.d.ts.map +1 -0
  217. package/out/{fst-builder.js → fst/builder.js} +24 -24
  218. package/out/fst/builder.js.map +1 -0
  219. package/out/{fst-deserialize-web.d.ts → fst/deserialize-web.d.ts} +3 -3
  220. package/out/fst/deserialize-web.d.ts.map +1 -0
  221. package/out/{fst-deserialize-web.js → fst/deserialize-web.js} +10 -74
  222. package/out/fst/deserialize-web.js.map +1 -0
  223. package/out/fst/format.d.ts +80 -0
  224. package/out/fst/format.d.ts.map +1 -0
  225. package/out/fst/format.js +91 -0
  226. package/out/fst/format.js.map +1 -0
  227. package/out/{fst-freshness.d.ts → fst/freshness.d.ts} +11 -14
  228. package/out/fst/freshness.d.ts.map +1 -0
  229. package/out/{fst-freshness.js → fst/freshness.js} +42 -74
  230. package/out/fst/freshness.js.map +1 -0
  231. package/out/fst/index.d.ts +14 -0
  232. package/out/fst/index.d.ts.map +1 -0
  233. package/out/fst/index.js +14 -0
  234. package/out/fst/index.js.map +1 -0
  235. package/out/{fst-matcher.d.ts → fst/matcher.d.ts} +2 -2
  236. package/out/fst/matcher.d.ts.map +1 -0
  237. package/out/{fst-matcher.js → fst/matcher.js} +1 -1
  238. package/out/fst/matcher.js.map +1 -0
  239. package/out/{fst-serialize.d.ts → fst/serialize.d.ts} +5 -10
  240. package/out/fst/serialize.d.ts.map +1 -0
  241. package/out/{fst-serialize.js → fst/serialize.js} +18 -98
  242. package/out/fst/serialize.js.map +1 -0
  243. package/out/{fst-types.d.ts → fst/types.d.ts} +4 -3
  244. package/out/fst/types.d.ts.map +1 -0
  245. package/out/{fst-types.js → fst/types.js} +1 -1
  246. package/out/fst/types.js.map +1 -0
  247. package/out/{fts.d.ts → fts/index.d.ts} +20 -13
  248. package/out/fts/index.d.ts.map +1 -0
  249. package/out/{fts.js → fts/index.js} +20 -15
  250. package/out/fts/index.js.map +1 -0
  251. package/out/{fts-query.d.ts → fts/query.d.ts} +2 -2
  252. package/out/fts/query.d.ts.map +1 -0
  253. package/out/{fts-query.js → fts/query.js} +2 -2
  254. package/out/fts/query.js.map +1 -0
  255. package/out/{geonames-aliases.d.ts → geonames/aliases.d.ts} +8 -6
  256. package/out/geonames/aliases.d.ts.map +1 -0
  257. package/out/{geonames-aliases.js → geonames/aliases.js} +7 -7
  258. package/out/geonames/aliases.js.map +1 -0
  259. package/out/geonames/index.d.ts +8 -0
  260. package/out/geonames/index.d.ts.map +1 -0
  261. package/out/geonames/index.js +8 -0
  262. package/out/geonames/index.js.map +1 -0
  263. package/out/{geonames-postal.d.ts → geonames/postal.d.ts} +12 -10
  264. package/out/geonames/postal.d.ts.map +1 -0
  265. package/out/{geonames-postal.js → geonames/postal.js} +11 -11
  266. package/out/geonames/postal.js.map +1 -0
  267. package/out/index.d.ts +32 -31
  268. package/out/index.d.ts.map +1 -1
  269. package/out/index.js +24 -24
  270. package/out/index.js.map +1 -1
  271. package/out/interpolation.d.ts +18 -9
  272. package/out/interpolation.d.ts.map +1 -1
  273. package/out/interpolation.js +108 -36
  274. package/out/interpolation.js.map +1 -1
  275. package/out/lookup.d.ts +25 -25
  276. package/out/lookup.d.ts.map +1 -1
  277. package/out/lookup.js +174 -558
  278. package/out/lookup.js.map +1 -1
  279. package/out/name-score.d.ts +0 -10
  280. package/out/name-score.d.ts.map +1 -1
  281. package/out/name-score.js +6 -4
  282. package/out/name-score.js.map +1 -1
  283. package/out/nsul/index.d.ts +8 -0
  284. package/out/nsul/index.d.ts.map +1 -0
  285. package/out/nsul/index.js +8 -0
  286. package/out/nsul/index.js.map +1 -0
  287. package/out/nsul/lookup.d.ts +83 -0
  288. package/out/nsul/lookup.d.ts.map +1 -0
  289. package/out/nsul/lookup.js +80 -0
  290. package/out/nsul/lookup.js.map +1 -0
  291. package/out/nsul/schema.d.ts +100 -0
  292. package/out/nsul/schema.d.ts.map +1 -0
  293. package/out/nsul/schema.js +78 -0
  294. package/out/nsul/schema.js.map +1 -0
  295. package/out/place-importance-schema.d.ts +47 -10
  296. package/out/place-importance-schema.d.ts.map +1 -1
  297. package/out/place-importance-schema.js +56 -10
  298. package/out/place-importance-schema.js.map +1 -1
  299. package/out/poi/index.d.ts +8 -0
  300. package/out/poi/index.d.ts.map +1 -0
  301. package/out/poi/index.js +8 -0
  302. package/out/poi/index.js.map +1 -0
  303. package/out/{poi-lookup.d.ts → poi/lookup.d.ts} +8 -10
  304. package/out/poi/lookup.d.ts.map +1 -0
  305. package/out/{poi-lookup.js → poi/lookup.js} +24 -29
  306. package/out/poi/lookup.js.map +1 -0
  307. package/out/{poi-schema.d.ts → poi/schema.d.ts} +10 -6
  308. package/out/poi/schema.d.ts.map +1 -0
  309. package/out/{poi-schema.js → poi/schema.js} +1 -1
  310. package/out/poi/schema.js.map +1 -0
  311. package/out/polygon-schema.d.ts +37 -0
  312. package/out/polygon-schema.d.ts.map +1 -0
  313. package/out/polygon-schema.js +23 -0
  314. package/out/polygon-schema.js.map +1 -0
  315. package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +7 -6
  316. package/out/postal/city-alias-lookup.d.ts.map +1 -0
  317. package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +12 -16
  318. package/out/postal/city-alias-lookup.js.map +1 -0
  319. package/out/{postal-city-alias-schema.d.ts → postal/city-alias-schema.d.ts} +1 -1
  320. package/out/{postal-city-alias-schema.d.ts.map → postal/city-alias-schema.d.ts.map} +1 -1
  321. package/out/{postal-city-alias-schema.js → postal/city-alias-schema.js} +1 -1
  322. package/out/{postal-city-alias-schema.js.map → postal/city-alias-schema.js.map} +1 -1
  323. package/out/{postal-city-candidate-schema.d.ts → postal/city-candidate-schema.d.ts} +4 -3
  324. package/out/postal/city-candidate-schema.d.ts.map +1 -0
  325. package/out/{postal-city-candidate-schema.js → postal/city-candidate-schema.js} +2 -2
  326. package/out/{postal-city-candidate-schema.js.map → postal/city-candidate-schema.js.map} +1 -1
  327. package/out/postal/index.d.ts +9 -0
  328. package/out/postal/index.d.ts.map +1 -0
  329. package/out/postal/index.js +9 -0
  330. package/out/postal/index.js.map +1 -0
  331. package/out/postcode-point-lookup.d.ts +10 -10
  332. package/out/postcode-point-lookup.d.ts.map +1 -1
  333. package/out/postcode-point-lookup.js +13 -13
  334. package/out/postcode-point-lookup.js.map +1 -1
  335. package/out/primary-preference.d.ts +133 -0
  336. package/out/primary-preference.d.ts.map +1 -0
  337. package/out/primary-preference.js +146 -0
  338. package/out/primary-preference.js.map +1 -0
  339. package/out/proximity-rerank.d.ts +77 -0
  340. package/out/proximity-rerank.d.ts.map +1 -0
  341. package/out/proximity-rerank.js +86 -0
  342. package/out/proximity-rerank.js.map +1 -0
  343. package/out/ranking-weights.d.ts +15 -3
  344. package/out/ranking-weights.d.ts.map +1 -1
  345. package/out/ranking-weights.js +21 -3
  346. package/out/ranking-weights.js.map +1 -1
  347. package/out/region-keys.d.ts +47 -0
  348. package/out/region-keys.d.ts.map +1 -0
  349. package/out/region-keys.js +121 -0
  350. package/out/region-keys.js.map +1 -0
  351. package/out/reverse.d.ts +6 -6
  352. package/out/reverse.d.ts.map +1 -1
  353. package/out/reverse.js +23 -35
  354. package/out/reverse.js.map +1 -1
  355. package/out/schema.d.ts +10 -1
  356. package/out/schema.d.ts.map +1 -1
  357. package/out/schema.js.map +1 -1
  358. package/out/search-fetch.d.ts +59 -0
  359. package/out/search-fetch.d.ts.map +1 -0
  360. package/out/search-fetch.js +184 -0
  361. package/out/search-fetch.js.map +1 -0
  362. package/out/sqlite-convention-source.d.ts +7 -7
  363. package/out/sqlite-convention-source.d.ts.map +1 -1
  364. package/out/sqlite-convention-source.js +5 -5
  365. package/out/sqlite-convention-source.js.map +1 -1
  366. package/out/sqlite-utils.d.ts +24 -6
  367. package/out/sqlite-utils.d.ts.map +1 -1
  368. package/out/sqlite-utils.js +24 -7
  369. package/out/sqlite-utils.js.map +1 -1
  370. package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +12 -7
  371. package/out/street/centroid-schema.d.ts.map +1 -0
  372. package/out/{street-centroid-schema.js → street/centroid-schema.js} +5 -5
  373. package/out/{street-centroid-schema.js.map → street/centroid-schema.js.map} +1 -1
  374. package/out/{street-centroid.d.ts → street/centroid.d.ts} +6 -6
  375. package/out/street/centroid.d.ts.map +1 -0
  376. package/out/{street-centroid.js → street/centroid.js} +18 -24
  377. package/out/street/centroid.js.map +1 -0
  378. package/out/street/index.d.ts +13 -0
  379. package/out/street/index.d.ts.map +1 -0
  380. package/out/street/index.js +13 -0
  381. package/out/street/index.js.map +1 -0
  382. package/out/{street-morphology-fst-builder.d.ts → street/morphology-fst-builder.d.ts} +4 -4
  383. package/out/street/morphology-fst-builder.d.ts.map +1 -0
  384. package/out/{street-morphology-fst-builder.js → street/morphology-fst-builder.js} +18 -13
  385. package/out/street/morphology-fst-builder.js.map +1 -0
  386. package/out/{street-morphology-fst-loader.d.ts → street/morphology-fst-loader.d.ts} +5 -5
  387. package/out/street/morphology-fst-loader.d.ts.map +1 -0
  388. package/out/{street-morphology-fst-loader.js → street/morphology-fst-loader.js} +11 -10
  389. package/out/street/morphology-fst-loader.js.map +1 -0
  390. package/out/{street-name-lookup.d.ts → street/name-lookup.d.ts} +4 -7
  391. package/out/street/name-lookup.d.ts.map +1 -0
  392. package/out/{street-name-lookup.js → street/name-lookup.js} +9 -23
  393. package/out/street/name-lookup.js.map +1 -0
  394. package/out/street/normalize.d.ts +179 -0
  395. package/out/street/normalize.d.ts.map +1 -0
  396. package/out/street/normalize.js +457 -0
  397. package/out/street/normalize.js.map +1 -0
  398. package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +13 -9
  399. package/out/street/segment-schema.d.ts.map +1 -0
  400. package/out/{street-segment-schema.js → street/segment-schema.js} +4 -4
  401. package/out/{street-segment-schema.js.map → street/segment-schema.js.map} +1 -1
  402. package/out/types.d.ts +49 -9
  403. package/out/types.d.ts.map +1 -1
  404. package/out/types.js.map +1 -1
  405. package/out/unified-schema.d.ts +6 -5
  406. package/out/unified-schema.d.ts.map +1 -1
  407. package/out/unified-schema.js +16 -19
  408. package/out/unified-schema.js.map +1 -1
  409. package/out/uprn/existence.d.ts +53 -0
  410. package/out/uprn/existence.d.ts.map +1 -0
  411. package/out/uprn/existence.js +61 -0
  412. package/out/uprn/existence.js.map +1 -0
  413. package/out/uprn/index.d.ts +9 -0
  414. package/out/uprn/index.d.ts.map +1 -0
  415. package/out/uprn/index.js +9 -0
  416. package/out/uprn/index.js.map +1 -0
  417. package/out/uprn/lookup.d.ts +85 -0
  418. package/out/uprn/lookup.d.ts.map +1 -0
  419. package/out/uprn/lookup.js +149 -0
  420. package/out/uprn/lookup.js.map +1 -0
  421. package/out/uprn/schema.d.ts +93 -0
  422. package/out/uprn/schema.d.ts.map +1 -0
  423. package/out/uprn/schema.js +78 -0
  424. package/out/uprn/schema.js.map +1 -0
  425. package/out/weights-overlay-linker.d.ts +299 -0
  426. package/out/weights-overlay-linker.d.ts.map +1 -0
  427. package/out/weights-overlay-linker.js +511 -0
  428. package/out/weights-overlay-linker.js.map +1 -0
  429. package/package.json +622 -154
  430. package/address-point.ts +0 -125
  431. package/candidate-lookup.ts +0 -720
  432. package/fst-autocomplete.ts +0 -204
  433. package/geo.ts +0 -121
  434. package/lookup.ts +0 -1354
  435. package/out/address-point-interpolation.d.ts.map +0 -1
  436. package/out/address-point-interpolation.js.map +0 -1
  437. package/out/address-point-schema.d.ts.map +0 -1
  438. package/out/address-point-schema.js.map +0 -1
  439. package/out/address-point.d.ts.map +0 -1
  440. package/out/address-point.js +0 -84
  441. package/out/address-point.js.map +0 -1
  442. package/out/ancestry-backfill.d.ts.map +0 -1
  443. package/out/ancestry-backfill.js.map +0 -1
  444. package/out/ancestry.d.ts.map +0 -1
  445. package/out/ancestry.js.map +0 -1
  446. package/out/convention.d.ts.map +0 -1
  447. package/out/convention.js.map +0 -1
  448. package/out/fst-autocomplete.d.ts.map +0 -1
  449. package/out/fst-autocomplete.js +0 -130
  450. package/out/fst-autocomplete.js.map +0 -1
  451. package/out/fst-builder.d.ts.map +0 -1
  452. package/out/fst-builder.js.map +0 -1
  453. package/out/fst-deserialize-web.d.ts.map +0 -1
  454. package/out/fst-deserialize-web.js.map +0 -1
  455. package/out/fst-freshness.d.ts.map +0 -1
  456. package/out/fst-freshness.js.map +0 -1
  457. package/out/fst-matcher.d.ts.map +0 -1
  458. package/out/fst-matcher.js.map +0 -1
  459. package/out/fst-serialize.d.ts.map +0 -1
  460. package/out/fst-serialize.js.map +0 -1
  461. package/out/fst-types.d.ts.map +0 -1
  462. package/out/fst-types.js.map +0 -1
  463. package/out/fts-query.d.ts.map +0 -1
  464. package/out/fts-query.js.map +0 -1
  465. package/out/fts.d.ts.map +0 -1
  466. package/out/fts.js.map +0 -1
  467. package/out/geo.d.ts +0 -74
  468. package/out/geo.d.ts.map +0 -1
  469. package/out/geo.js +0 -71
  470. package/out/geo.js.map +0 -1
  471. package/out/geonames-aliases.d.ts.map +0 -1
  472. package/out/geonames-aliases.js.map +0 -1
  473. package/out/geonames-postal.d.ts.map +0 -1
  474. package/out/geonames-postal.js.map +0 -1
  475. package/out/poi-lookup.d.ts.map +0 -1
  476. package/out/poi-lookup.js.map +0 -1
  477. package/out/poi-schema.d.ts.map +0 -1
  478. package/out/poi-schema.js.map +0 -1
  479. package/out/postal-city-alias-lookup.d.ts.map +0 -1
  480. package/out/postal-city-alias-lookup.js.map +0 -1
  481. package/out/postal-city-candidate-schema.d.ts.map +0 -1
  482. package/out/sharding.d.ts +0 -114
  483. package/out/sharding.d.ts.map +0 -1
  484. package/out/sharding.js.map +0 -1
  485. package/out/street-centroid-schema.d.ts.map +0 -1
  486. package/out/street-centroid.d.ts.map +0 -1
  487. package/out/street-centroid.js.map +0 -1
  488. package/out/street-morphology-fst-builder.d.ts.map +0 -1
  489. package/out/street-morphology-fst-builder.js.map +0 -1
  490. package/out/street-morphology-fst-loader.d.ts.map +0 -1
  491. package/out/street-morphology-fst-loader.js.map +0 -1
  492. package/out/street-name-lookup.d.ts.map +0 -1
  493. package/out/street-name-lookup.js.map +0 -1
  494. package/out/street-normalize.d.ts +0 -95
  495. package/out/street-normalize.d.ts.map +0 -1
  496. package/out/street-normalize.js +0 -272
  497. package/out/street-normalize.js.map +0 -1
  498. package/out/street-segment-schema.d.ts.map +0 -1
  499. package/sqlite-utils.ts +0 -46
  500. package/street-normalize.ts +0 -318
  501. /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
@@ -39,7 +39,7 @@ export type WOFPlacetype =
39
39
  * treat it as ordinal, not absolute.
40
40
  *
41
41
  * `id` is the WOF place id. It's named generically (not `wof_id`) so the shape stays structurally compatible with
42
- * `@mailwoman/resolver`'s `ResolvedPlace` — `WOFSqlitePlaceLookup` satisfies the generic `ResolverBackend` contract
42
+ * `@mailwoman/resolver`'s `ResolvedPlace` — `WOFSQLitePlaceLookup` satisfies the generic `ResolverBackend` contract
43
43
  * without an adapter shim.
44
44
  *
45
45
  * `distanceKm` is populated only when the query carried `near` (and the place has a centroid). Useful for downstream
@@ -91,8 +91,8 @@ export interface PlaceCandidate {
91
91
  /**
92
92
  * STRICT encyclopedia-evidence importance in [0, 1], fan-out-guarded per #1497 — CARRIED, NEVER RANKED ON.
93
93
  *
94
- * RESERVED SLOT awaiting a strict-channel source: present only when a shard's `place_importance` table carries the
95
- * split columns, and no shipped shard does — the FTS lookup's clauses emit NULL for everything today. `undefined`
94
+ * RESERVED SLOT awaiting a strict-channel source: present only when a extract's `place_importance` table carries the
95
+ * split columns, and no shipped extract does — the FTS lookup's clauses emit NULL for everything today. `undefined`
96
96
  * means either "no encyclopedia entry for this place" or "this gazetteer predates the split"; both are absence, and
97
97
  * neither is 0. The BLENDED prior the ranking reads is {@link PlaceCandidate.importance}.
98
98
  *
@@ -122,6 +122,21 @@ export interface PlaceCandidate {
122
122
  * the falsehood-detection differentiator.
123
123
  */
124
124
  mismatch?: boolean
125
+ /**
126
+ * Admin-containment stamp (#1717 stage 2) — TRI-STATE, mirroring `ResolvedPlace.containedByQualifier` in
127
+ * `@mailwoman/core`: `true` = the ancestors sidecar vouches this candidate sits under the query's
128
+ * {@link FindPlaceQuery.regionQualifier}; `false` = evaluated and not vouched for; absent = never evaluated (no
129
+ * qualifier on the query, or an artifact without the sidecar). Absence is required — the resolver walk reads it as
130
+ * `unavailable`, never as "not contained".
131
+ */
132
+ containedByQualifier?: boolean
133
+ /**
134
+ * The #1882 exemption's firing mark (#1893), mirroring `ResolvedPlace.variantAliasExempted` in `@mailwoman/core`:
135
+ * present only when this candidate's row would have taken the cross-country alias penalty and the exemption prevented
136
+ * it. Emitted by the candidate-table backend alone — the WASM FTS lookup never runs the ranker, and its candidates
137
+ * omit the field (not evaluated, never "did not fire").
138
+ */
139
+ variantAliasExempted?: true
125
140
  }
126
141
 
127
142
  /**
@@ -163,6 +178,24 @@ export interface FindPlaceQuery {
163
178
  * ISO 3166-1 alpha-2 — narrows to one country.
164
179
  */
165
180
  country?: string
181
+ /**
182
+ * ISO 3166-1 alpha-2 — narrows the TYPO-FUZZY tier only (#1585). Exact and qualifier-strip probes stay worldwide (a
183
+ * locale hint is a prior, never a hard filter on exact matches), but a typo CORRECTION into a different country's
184
+ * namespace is nearly always a scrape, so the corrected-key probes honor this scope and a scoped-empty ABSTAINS
185
+ * rather than falling through to a world-fuzzy candidate. Ignored when `country` is set (already narrower).
186
+ */
187
+ fuzzyCountry?: string
188
+ /**
189
+ * Restrict name matching to PRIMARY-keyed rows (#1632) — set by probes whose surface is a RE-READING (a token taken
190
+ * out of a longer classified span), which never named an alias. See the ResolverBackend contract in
191
+ * `@mailwoman/core/resolver`.
192
+ */
193
+ primaryOnly?: boolean
194
+ /**
195
+ * Alias-row NAME ROLES the probe refuses to answer through (#1730) — the bare-toponym side races pass `abbr`/`gloss`.
196
+ * Role-NULL alias rows (the exonym tier) stay open; backends/artifacts without a role column ignore it.
197
+ */
198
+ excludeNameRoles?: readonly string[]
166
199
  /**
167
200
  * WOF place id — narrows to descendants of this place.
168
201
  */
@@ -171,16 +204,24 @@ export interface FindPlaceQuery {
171
204
  * Sibling postcode. When set on a `locality` query AND a `postcode_locality` table is present, triggers the
172
205
  * coordinate-first soft-score path: postcode→candidate localities are injected and scored `0.6·S_pc + 0.3·S_name +
173
206
  * 0.1·S_pop` against the FTS name-match set, recovering small localities the name-match alone misses. Ignored when no
174
- * postcode_locality shard is present.
207
+ * postcode_locality extract is present.
175
208
  */
176
209
  postcode?: string
177
210
  /**
178
211
  * Postcode-containment coherence (#31, Mechanism 2) — when true on a locality query that also carries `postcode`,
179
- * candidate rows within `POSTCODE_CONTAINMENT_GATE_KM` of the postcode's own centroid sort by distance first, the
180
- * rest appended in their original order. Set by the resolver from `ResolveOpts.postcodeContainmentCoherence`; absent
181
- * → the population-first order is untouched (byte-identical).
212
+ * candidate rows within `POSTCODE_CONTAINMENT_THRESHOLD_KM` of the postcode's own centroid sort by distance first,
213
+ * the rest appended in their original order. Set by the resolver from `ResolveOpts.postcodeContainmentCoherence`;
214
+ * absent → the population-first order is untouched (byte-identical).
182
215
  */
183
216
  postcodeContainmentCoherence?: boolean
217
+ /**
218
+ * The tree's parsed REGION qualifier (#1717 stage 2) — set by the resolver on locality lookups when
219
+ * `ResolveOpts.adminContainmentRerank` is on. See the `ResolverBackend` contract in `@mailwoman/core/resolver`: a
220
+ * capable backend stamps `containedByQualifier`, ranks contained candidates first, and may ADD contained same-key
221
+ * candidates a country scope hid — additive only, never a filter. Ignored on an artifact without the ancestors
222
+ * sidecar (candidates then carry no stamp).
223
+ */
224
+ regionQualifier?: string
184
225
  /**
185
226
  * Proximity hint — candidates close to this point get a ranking boost.
186
227
  */
@@ -209,7 +250,6 @@ export interface FindPlaceQuery {
209
250
  * interface is async even though `node:sqlite` is sync — leaves room for `Worker`-backed implementations later without
210
251
  * a public API break.
211
252
  */
212
- export interface PlaceLookup {
253
+ export interface PlaceLookup extends Disposable {
213
254
  findPlace(query: FindPlaceQuery): Promise<PlaceCandidate[]>
214
- close(): void
215
255
  }
@@ -7,28 +7,25 @@
7
7
  * (`scripts/build-unified-wof.ts`). This is the CANONICAL gazetteer — we never use the
8
8
  * off-the-shelf geocode.earth prebuilt dumps (they assign different WOF ids to the same place;
9
9
  * see the `feedback-custom-wof-db-only` memory). The table/column names match the resolver's
10
- * expectations (`lookup.ts`) so `WOFSqlitePlaceLookup` works unchanged, INCLUDING the `ancestors`
10
+ * expectations (`lookup.ts`) so `WOFSQLitePlaceLookup` works unchanged, INCLUDING the `ancestors`
11
11
  * table (which lookup.ts's parent-constraint subquery needs) — see `populateAncestors`. The
12
12
  * `place_search` FTS5 + `place_bbox` R*Tree are built separately by `build-fts` (fts.ts).
13
13
  */
14
14
 
15
- import type { DatabaseSync } from "node:sqlite"
15
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
16
16
 
17
- import { DatabaseClient } from "@mailwoman/core/kysley/client"
17
+ import type { WOFDatabase } from "#schema"
18
18
 
19
- import type { WOFDatabase } from "./schema.ts"
20
-
21
- export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
19
+ export async function createUnifiedSchema(db: DatabaseClient<WOFDatabase>): Promise<void> {
22
20
  // PRAGMAs stay raw — not Kysely-modelled, and these tune the bulk build.
23
21
  db.exec("PRAGMA journal_mode = WAL")
24
22
  db.exec("PRAGMA busy_timeout = 10000")
25
23
  db.exec("PRAGMA synchronous = OFF")
26
24
 
27
- // `kdb` wraps `db` for the DDL (the house idiom); the caller owns `db`'s lifecycle, so we don't
25
+ // `db` wraps `db` for the DDL (the house idiom); the caller owns `db`'s lifecycle, so we don't
28
26
  // destroy it here. The bulk INSERTs (populateAncestors + build-unified-wof) stay on the raw handle.
29
- const kdb = new DatabaseClient<WOFDatabase>({ database: db })
30
27
 
31
- await kdb.schema
28
+ await db.schema
32
29
  .createTable("spr")
33
30
  .ifNotExists()
34
31
  .addColumn("id", "integer", (c) => c.primaryKey())
@@ -56,7 +53,7 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
56
53
  // x_variant rows tagged with an official language ("MSP", "Frisco") stay 0. Primary-name mirror
57
54
  // rows stay 0 too: the name-exact tier already consults spr.name; `official` only marks the
58
55
  // ALIASES eligible to join it. Both are ingest-time facts, never computed at query time.
59
- await kdb.schema
56
+ await db.schema
60
57
  .createTable("names")
61
58
  .ifNotExists()
62
59
  .addColumn("id", "integer", (c) => c.notNull())
@@ -69,7 +66,7 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
69
66
  .addColumn("lastmodified", "integer", (c) => c.notNull().defaultTo(0))
70
67
  .execute()
71
68
 
72
- await kdb.schema
69
+ await db.schema
73
70
  .createTable("concordances")
74
71
  .ifNotExists()
75
72
  .addColumn("id", "integer", (c) => c.notNull())
@@ -78,7 +75,7 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
78
75
  .addColumn("lastmodified", "integer", (c) => c.notNull().defaultTo(0))
79
76
  .execute()
80
77
 
81
- await kdb.schema
78
+ await db.schema
82
79
  .createTable("place_population")
83
80
  .ifNotExists()
84
81
  .addColumn("id", "integer", (c) => c.primaryKey())
@@ -90,7 +87,7 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
90
87
  // `spr.id IN (SELECT id FROM ancestors WHERE ancestor_id = ?)`. The off-the-shelf WOF dumps
91
88
  // ship this table; our build derives it from the parent_id chain (see populateAncestors) since
92
89
  // we don't capture `wof:hierarchy`.
93
- await kdb.schema
90
+ await db.schema
94
91
  .createTable("ancestors")
95
92
  .ifNotExists()
96
93
  .addColumn("id", "integer", (c) => c.notNull())
@@ -107,7 +104,7 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
107
104
  * Sentinel/negative parent_ids and cycles terminate the walk. ~4 rows/place average; a transaction keeps the ~5M
108
105
  * inserts fast.
109
106
  */
110
- export function populateAncestors(db: DatabaseSync): number {
107
+ export function populateAncestors<DB>(db: DatabaseClient<DB>): number {
111
108
  db.exec("DELETE FROM ancestors")
112
109
 
113
110
  const rows = db.prepare("SELECT id, parent_id, placetype FROM spr").all() as Array<{
@@ -151,22 +148,21 @@ export function populateAncestors(db: DatabaseSync): number {
151
148
  return count
152
149
  }
153
150
 
154
- export async function createUnifiedIndexes(db: DatabaseSync): Promise<void> {
155
- const kdb = new DatabaseClient<WOFDatabase>({ database: db })
156
- await kdb.schema.createIndex("spr_by_placetype").ifNotExists().on("spr").column("placetype").execute()
157
- await kdb.schema.createIndex("spr_by_country").ifNotExists().on("spr").column("country").execute()
158
- await kdb.schema.createIndex("spr_by_parent").ifNotExists().on("spr").column("parent_id").execute()
159
- await kdb.schema.createIndex("names_by_id").ifNotExists().on("names").column("id").execute()
160
- await kdb.schema.createIndex("names_by_name").ifNotExists().on("names").column("name").execute()
151
+ export async function createUnifiedIndexes(db: DatabaseClient<WOFDatabase>): Promise<void> {
152
+ await db.schema.createIndex("spr_by_placetype").ifNotExists().on("spr").column("placetype").execute()
153
+ await db.schema.createIndex("spr_by_country").ifNotExists().on("spr").column("country").execute()
154
+ await db.schema.createIndex("spr_by_parent").ifNotExists().on("spr").column("parent_id").execute()
155
+ await db.schema.createIndex("names_by_id").ifNotExists().on("names").column("id").execute()
156
+ await db.schema.createIndex("names_by_name").ifNotExists().on("names").column("name").execute()
161
157
 
162
- await kdb.schema
158
+ await db.schema
163
159
  .createIndex("concordances_by_id")
164
160
  .ifNotExists()
165
161
  .on("concordances")
166
162
  .columns(["id", "lastmodified"])
167
163
  .execute()
168
164
 
169
- await kdb.schema
165
+ await db.schema
170
166
  .createIndex("concordances_by_other_id")
171
167
  .ifNotExists()
172
168
  .on("concordances")
@@ -175,6 +171,6 @@ export async function createUnifiedIndexes(db: DatabaseSync): Promise<void> {
175
171
 
176
172
  // ancestor_id is the hot column (parent-constraint queries `WHERE ancestor_id = ?`); id supports
177
173
  // the reverse lookup.
178
- await kdb.schema.createIndex("ancestors_by_ancestor").ifNotExists().on("ancestors").column("ancestor_id").execute()
179
- await kdb.schema.createIndex("ancestors_by_id").ifNotExists().on("ancestors").column("id").execute()
174
+ await db.schema.createIndex("ancestors_by_ancestor").ifNotExists().on("ancestors").column("ancestor_id").execute()
175
+ await db.schema.createIndex("ancestors_by_id").ifNotExists().on("ancestors").column("id").execute()
180
176
  }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The consult `./lookup.ts`'s docstring instructs: "callers building negative evidence must consult
6
+ * `readLayerCoverage`, not this reader alone." A bare `null` from `nearestUPRN` is two different facts — no UPRN here,
7
+ * or nobody surveyed here — and this is the only place that separates them. It answers an {@link Exclusion} or `null`,
8
+ * so a caller cannot read an unsurveyed cell as an empty one by accident.
9
+ *
10
+ * `radiusM` is a CALLER'S parameter with no default. There is no radius that is correct for both "which property is
11
+ * this coordinate" and "is this street built at all", and picking one here would bury that choice where nobody
12
+ * reviewing an exclusion can see it.
13
+ */
14
+
15
+ import { readLayerCoverage, readLayerManifest, type LayerContractHandle } from "@mailwoman/core/layers"
16
+ import { foldIdentity, requireExclusionBasis, type Exclusion } from "@mailwoman/evidence"
17
+ import { shortCellToParentInt } from "@mailwoman/spatial"
18
+
19
+ import type { UPRNLookup } from "#uprn/lookup"
20
+ import { UPRN_COVERAGE_H3_RESOLUTION, UPRN_H3_RESOLUTION, uprnH3Cell } from "#uprn/schema"
21
+
22
+ /**
23
+ * The identity fold, on purpose: this probe keys on a COORDINATE, not a name, so there is no string folding for the
24
+ * builder and the probe to disagree about. Passing the same identity as both `probeFold` and `layerFold` records that
25
+ * the fold axis is not in play here, rather than silently omitting the check. It is not a stub.
26
+ */
27
+ export const UPRN_EXISTENCE_FOLD = foldIdentity((s) => s)
28
+
29
+ /**
30
+ * The countries OS Open UPRN covers. Northern Ireland is outside the product, so a `null` there is unknown and must
31
+ * never become evidence of absence; the country check is what keeps it out.
32
+ */
33
+ export const UPRN_COVERED_COUNTRIES: ReadonlySet<string> = new Set(["GB"])
34
+
35
+ export interface UPRNAbsenceInput {
36
+ lookup: UPRNLookup
37
+ /**
38
+ * The same file the lookup reads; the layer contract tables live beside the points.
39
+ */
40
+ contractDB: LayerContractHandle
41
+ latitude: number
42
+ longitude: number
43
+ radiusM: number
44
+ /**
45
+ * ISO-2 country of the thing whose absence is being asserted, when known.
46
+ */
47
+ country?: string
48
+ }
49
+
50
+ /**
51
+ * The coverage cell a coordinate falls in, derived from its stored res-9 cell exactly as the builder derives it — never
52
+ * from the centroid, which lands in a different parent for a fraction of cells.
53
+ */
54
+ export function uprnCoverageCell(latitude: number, longitude: number): number {
55
+ return shortCellToParentInt(uprnH3Cell(latitude, longitude), UPRN_H3_RESOLUTION, UPRN_COVERAGE_H3_RESOLUTION)
56
+ }
57
+
58
+ /**
59
+ * An {@link Exclusion} when no UPRN lies within `radiusM` AND the layer's coverage licenses saying so; `null` on a hit
60
+ * (presence is not this probe's business) and on every refusal (unsurveyed cell, `source_present` basis, a country the
61
+ * product does not cover). Never throws on a refusal: the caller falls open to the ranking it already had.
62
+ */
63
+ export async function uprnAbsenceAt(input: UPRNAbsenceInput): Promise<Exclusion | null> {
64
+ const coverageCell = uprnCoverageCell(input.latitude, input.longitude)
65
+ const coverage = await readLayerCoverage(input.contractDB, coverageCell)
66
+
67
+ if (!coverage) return null
68
+
69
+ if (input.lookup.nearestUPRN(input.latitude, input.longitude, input.radiusM)) return null
70
+
71
+ const manifest = await readLayerManifest(input.contractDB)
72
+
73
+ return requireExclusionBasis({
74
+ layer: manifest.name,
75
+ source: manifest.source,
76
+ vintage: manifest.sourceVintage,
77
+ h3Cell: coverageCell,
78
+ cell: coverage,
79
+ probeFold: UPRN_EXISTENCE_FOLD,
80
+ layerFold: UPRN_EXISTENCE_FOLD,
81
+ country: input.country,
82
+ countries: UPRN_COVERED_COUNTRIES,
83
+ })
84
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ export * from "#uprn/existence"
8
+ export * from "#uprn/lookup"
9
+ export * from "#uprn/schema"
@@ -0,0 +1,206 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Node reader for `uprn.db` — the OS Open UPRN layer (`uprn-schema.ts`). Two probes:
7
+ *
8
+ * - **`coordinateOf(uprn)`**: rowid B-tree hit on the `uprn` integer PK.
9
+ * - **`nearestUPRN(lat, lon, radiusM)`**: bounded nearest-point search over the res-9 `h3_cell`
10
+ * index — ring-by-ring `gridDisk` expansion with chunked `IN` probes and haversine ranking.
11
+ * Rings stop as soon as geometry proves no unprobed cell could beat the best hit — a distance
12
+ * bound, not POILookup's row-count accumulation, so the early exit can never strand a nearer
13
+ * point in an unprobed ring.
14
+ *
15
+ * ## `null` is a claim, scoped by coverage
16
+ *
17
+ * OS designates Open UPRN complete for GB (every UPRN in AddressBase Premium with geometry), and
18
+ * the builder writes `layer_coverage` with basis `designated` for every cell the product touches.
19
+ * So a `null` from either probe inside a covered cell is evidence of absence — "no such published
20
+ * GB UPRN" / "no UPRN within the radius". Outside coverage (Northern Ireland, the Isle of Man, the
21
+ * Channel Islands, open water) it is UNKNOWN, per the meaning-of-zero rule — callers building
22
+ * negative evidence must consult `readLayerCoverage`, not this reader alone.
23
+ *
24
+ * `latLngToCell`/`gridDisk` come from `h3-js`; the 48-bit short-cell packing is
25
+ * `@mailwoman/spatial`'s `shortCellToInt` via `uprnFullCell` — never reimplemented here.
26
+ */
27
+
28
+ import { haversineKm, shortCellToInt, type H3Cell } from "@mailwoman/spatial"
29
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
30
+ import { gridDisk } from "h3-js"
31
+
32
+ import { allRows } from "#sqlite-utils"
33
+ import type { UPRNDatabase } from "#uprn/schema"
34
+ import { uprnFullCell } from "#uprn/schema"
35
+
36
+ /**
37
+ * Conservative FLOOR on how much CENTRE distance one unit of res-9 GRID distance buys, metres. Adjacent centres sit √3
38
+ * × edge apart (avg edge 174.4 m → ≈302 m); the worst bearing across a ring costs a further ×0.866, and H3's projection
39
+ * distortion shrinks edges by well under the slack this leaves (the true worst is ≈217 m per grid step). Dividing a
40
+ * radius by this over-counts rings and can never miss a cell; multiplying a grid distance by it under-states reach and
41
+ * can never end the ring walk early.
42
+ */
43
+ const RES9_CENTER_SPACING_FLOOR_M = 150
44
+
45
+ /**
46
+ * Conservative CEILING on a res-9 cell's centre-to-vertex distance, metres (avg edge 174.4 m; distortion stays well
47
+ * under this). A point within `radiusM` of the query sits in a cell whose CENTRE is within `radiusM` + this.
48
+ */
49
+ const RES9_CELL_RADIUS_CEILING_M = 300
50
+
51
+ /**
52
+ * Hard ceiling on `radiusM`. Keeps the probe bounded (10 km → ~72 rings ≈ 15.8k cells ≈ 18 `IN` chunks); a caller who
53
+ * wants a wider search than "which property is this coordinate" has outgrown this reader and should say so loudly.
54
+ */
55
+ export const UPRN_MAX_NEAREST_RADIUS_M = 10_000
56
+
57
+ /**
58
+ * `IN`-list chunk size for the cell probe — far under SQLite's 32,766 bound-variable ceiling.
59
+ */
60
+ const CELL_PROBE_CHUNK = 900
61
+
62
+ export interface UPRNCoordinate {
63
+ latitude: number
64
+ longitude: number
65
+ }
66
+
67
+ export interface UPRNNearestHit {
68
+ uprn: number
69
+ latitude: number
70
+ longitude: number
71
+ /**
72
+ * Haversine distance from the query point, metres.
73
+ */
74
+ distanceM: number
75
+ }
76
+
77
+ export interface UPRNLookupOpts {
78
+ /**
79
+ * Path to a `uprn.db` built by `mailwoman`'s gazetteer pipeline. Opened read-only.
80
+ */
81
+ databasePath?: string
82
+ /**
83
+ * Pre-opened handle (tests / shared connections). Mutually exclusive with `databasePath`.
84
+ */
85
+ database?: DatabaseClient<UPRNDatabase>
86
+ }
87
+
88
+ interface UPRNRow {
89
+ uprn: number
90
+ lat: number
91
+ lon: number
92
+ }
93
+
94
+ /**
95
+ * Node reader over `uprn.db`. `implements Disposable` so callers can `using lookup = new UPRNLookup(...)` — the same
96
+ * precedent as {@link POILookup}.
97
+ */
98
+ export class UPRNLookup implements Disposable {
99
+ #db: DatabaseClient<UPRNDatabase>
100
+ /**
101
+ * Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
102
+ * ownership is membership rather than a flag a later branch has to check.
103
+ */
104
+ readonly #resources = new DisposableStack()
105
+
106
+ /**
107
+ * `uprn` → its point (rowid-alias PK hit).
108
+ */
109
+ readonly #coordinateProbe: ReturnType<DatabaseClient["prepare"]>
110
+
111
+ constructor(opts: UPRNLookupOpts) {
112
+ if (opts.database) {
113
+ this.#db = opts.database
114
+ } else if (opts.databasePath) {
115
+ this.#db = this.#resources.use(new DatabaseClient<UPRNDatabase>(opts.databasePath, { readOnly: true }))
116
+ } else {
117
+ throw new Error("UPRNLookup needs `databasePath` or `database`")
118
+ }
119
+
120
+ this.#coordinateProbe = this.#db.prepare("SELECT lat, lon FROM uprn WHERE uprn = ?")
121
+ }
122
+
123
+ /**
124
+ * The WGS84 point OS publishes for `uprn`, or `null` when the layer holds no such UPRN (see the module docstring for
125
+ * what that `null` claims).
126
+ */
127
+ coordinateOf(uprn: number): UPRNCoordinate | null {
128
+ const row = this.#coordinateProbe.get(uprn) as { lat: number; lon: number } | undefined
129
+
130
+ return row ? { latitude: row.lat, longitude: row.lon } : null
131
+ }
132
+
133
+ /**
134
+ * The single nearest UPRN within `radiusM` metres of the query point, or `null` when no UPRN lies inside the radius.
135
+ *
136
+ * Bounded two ways: `radiusM` is capped at {@link UPRN_MAX_NEAREST_RADIUS_M}, and rings expand outward only until no
137
+ * unprobed cell could beat the best hit found so far (or the radius, when nothing has been found). The stop rule is
138
+ * geometric — a cell at grid distance `g` holds no point nearer than `g` × spacing floor − cell radius, using the
139
+ * same conservative constants the reach math uses — so unlike POILookup's row-count accumulation there is no
140
+ * early-exit ambiguity: a break can never strand a nearer point in an unprobed ring. This is what keeps a
141
+ * capped-radius call over dense ground at milliseconds instead of a full-disk fetch (measured 6.4 s → 13 ms for a 10
142
+ * km radius over central London, 41.6M-row layer; an empty-sea miss at the cap runs the full expansion, 74 ms).
143
+ *
144
+ * @throws {RangeError} When `radiusM` is not a positive finite number, or exceeds the cap.
145
+ */
146
+ nearestUPRN(latitude: number, longitude: number, radiusM: number): UPRNNearestHit | null {
147
+ if (!Number.isFinite(radiusM) || radiusM <= 0) {
148
+ throw new RangeError(`nearestUPRN: radiusM must be a positive finite number, received ${radiusM}`)
149
+ }
150
+
151
+ if (radiusM > UPRN_MAX_NEAREST_RADIUS_M) {
152
+ throw new RangeError(`nearestUPRN: radiusM ${radiusM} exceeds the ${UPRN_MAX_NEAREST_RADIUS_M} m cap`)
153
+ }
154
+
155
+ const origin = uprnFullCell(latitude, longitude)
156
+ const seenCells = new Set<string>()
157
+ let best: UPRNNearestHit | null = null
158
+
159
+ // `ring` is H3 grid distance; the loop terminates because the break bound is at most radiusM, which the
160
+ // RangeError above caps.
161
+ for (let ring = 0; ; ring++) {
162
+ // A cell at grid distance `ring` holds no point nearer than this. Once it exceeds what could still
163
+ // win — the best hit so far, or the radius itself — further rings cannot improve the answer.
164
+ const closestPossibleM = ring * RES9_CENTER_SPACING_FLOOR_M - RES9_CELL_RADIUS_CEILING_M
165
+
166
+ if (closestPossibleM > Math.min(radiusM, best?.distanceM ?? radiusM)) break
167
+
168
+ // gridDisk(origin, ring) returns the WHOLE disk out to `ring`; diffing against what's already been
169
+ // probed derives just this ring's new cells (the POILookup pattern).
170
+ const diskCells = gridDisk(origin, ring) as string[]
171
+ const newCells: number[] = []
172
+
173
+ for (const cell of diskCells) {
174
+ if (!seenCells.has(cell)) {
175
+ seenCells.add(cell)
176
+ newCells.push(shortCellToInt(cell as H3Cell))
177
+ }
178
+ }
179
+
180
+ for (let i = 0; i < newCells.length; i += CELL_PROBE_CHUNK) {
181
+ const chunk = newCells.slice(i, i + CELL_PROBE_CHUNK)
182
+ const placeholders = chunk.map(() => "?").join(", ")
183
+
184
+ // Prepared fresh per chunk arity — a cold, per-call path, same posture as POILookup's batched hydration.
185
+ const rows = allRows<UPRNRow>(
186
+ this.#db.prepare(`SELECT uprn, lat, lon FROM uprn WHERE h3_cell IN (${placeholders})`),
187
+ ...chunk
188
+ )
189
+
190
+ for (const row of rows) {
191
+ const distanceM = haversineKm(latitude, longitude, row.lat, row.lon) * 1000
192
+
193
+ if (distanceM <= radiusM && (best === null || distanceM < best.distanceM)) {
194
+ best = { uprn: row.uprn, latitude: row.lat, longitude: row.lon, distanceM }
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ return best
201
+ }
202
+
203
+ [Symbol.dispose](): void {
204
+ this.#resources[Symbol.dispose]()
205
+ }
206
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for `uprn.db` — the OS Open UPRN spatial layer: every GB Unique Property Reference
7
+ * Number with its WGS84 point, so mailwoman results can carry UPRN as an interoperability key
8
+ * beside our own `@mailwoman/address-id`. One rowid table keyed `uprn INTEGER PRIMARY KEY` (the
9
+ * rowid alias — the optimal shape for an integer-PK point table; `WITHOUT ROWID` buys nothing
10
+ * here), plus a secondary res-9 `h3_cell` index for the bounded nearest-point probe.
11
+ *
12
+ * ## Coordinates are OS's own WGS84 columns
13
+ *
14
+ * The source CSV publishes BOTH coordinate systems per row — OSGB36 eastings/northings AND WGS84
15
+ * `LATITUDE`/`LONGITUDE`. This layer stores OS's own lat/lon verbatim and never reconverts from
16
+ * eastings: `@mailwoman/spatial`'s `osgb36ToWGS84` is a 7-parameter Helmert with a measured p95 of
17
+ * 4.18 m, and re-deriving what the publisher already computed (with OSTN15, exactly) would replace
18
+ * their answer with a strictly worse one.
19
+ *
20
+ * ## Why `h3_cell` exists at all
21
+ *
22
+ * The layer contract requires every domain row to be addressable by at least one spine key —
23
+ * `writeLayerManifest` throws on a manifest that declares none — and UPRN is its own id space, not
24
+ * H3/WOF/address-id/street. The res-9 short cell (`shortCellToInt`, the same packing as poi.db and
25
+ * the OSM situs extracts) is the spine that fits a point table, and its index doubles as the
26
+ * `nearestUPRN` ring probe.
27
+ *
28
+ * The DB also embeds the layer-contract tables from `@mailwoman/core/layers`; the builder
29
+ * (`packages/mailwoman/lib/gazetteer-pipeline/uprn-layer.ts`) writes the manifest and per-res-6-cell
30
+ * coverage.
31
+ */
32
+
33
+ import type { LayerContractDatabase } from "@mailwoman/core/layers"
34
+ import { shortCellToInt, type H3Cell } from "@mailwoman/spatial"
35
+ import { latLngToCell } from "h3-js"
36
+ import type { Kysely } from "kysely"
37
+
38
+ /**
39
+ * Resolution the `uprn` table's `h3_cell` column is keyed at — the shared layer-spine resolution (poi.db, the OSM situs
40
+ * extracts).
41
+ */
42
+ export const UPRN_H3_RESOLUTION = 9
43
+
44
+ /**
45
+ * Resolution of the layer's `layer_coverage` cells — coarse, per the contract (matches poi.db).
46
+ */
47
+ export const UPRN_COVERAGE_H3_RESOLUTION = 6
48
+
49
+ /**
50
+ * One UPRN point. `uprn` is the rowid alias, so the primary probe (`coordinateOf`) is a rowid B-tree hit.
51
+ */
52
+ export interface UPRNTable {
53
+ /**
54
+ * The Unique Property Reference Number — up to 12 digits, so always within `Number.MAX_SAFE_INTEGER`.
55
+ */
56
+ uprn: number
57
+ /**
58
+ * WGS84 latitude, as OS published it (never reconverted from eastings — see the module docstring).
59
+ */
60
+ lat: number
61
+ /**
62
+ * WGS84 longitude, as OS published it.
63
+ */
64
+ lon: number
65
+ /**
66
+ * 48-bit short H3 cell at {@link UPRN_H3_RESOLUTION} (`uprnH3Cell`) — the layer-contract spine key and the
67
+ * `nearestUPRN` probe index.
68
+ */
69
+ h3_cell: number
70
+ }
71
+
72
+ /**
73
+ * Build-provenance key/value pairs the fixed `layer_manifest` columns have no room for: quality-drop counts, the header
74
+ * as found, the upstream licence text verbatim (the Code-Point provenance discipline).
75
+ */
76
+ export interface UPRNMetaTable {
77
+ key: string
78
+ value: string
79
+ }
80
+
81
+ export interface UPRNDatabase extends LayerContractDatabase {
82
+ uprn: UPRNTable
83
+ uprn_meta: UPRNMetaTable
84
+ }
85
+
86
+ /**
87
+ * The full res-9 cell for a UPRN point — the ONE derivation both the builder and every consumer share, so a fixture
88
+ * built by a test and a row built by the real ingest can never disagree on which cell a coordinate keys to.
89
+ */
90
+ export function uprnFullCell(latitude: number, longitude: number): H3Cell {
91
+ return latLngToCell(latitude, longitude, UPRN_H3_RESOLUTION) as H3Cell
92
+ }
93
+
94
+ /**
95
+ * The `h3_cell` column value for a UPRN point: {@link uprnFullCell} packed to the shared 48-bit short-cell integer.
96
+ */
97
+ export function uprnH3Cell(latitude: number, longitude: number): number {
98
+ return shortCellToInt(uprnFullCell(latitude, longitude))
99
+ }
100
+
101
+ export async function createUPRNTable(db: Kysely<UPRNDatabase>): Promise<void> {
102
+ await db.schema
103
+ .createTable("uprn")
104
+ .addColumn("uprn", "integer", (c) => c.primaryKey())
105
+ .addColumn("lat", "real", (c) => c.notNull())
106
+ .addColumn("lon", "real", (c) => c.notNull())
107
+ .addColumn("h3_cell", "integer", (c) => c.notNull())
108
+ .execute()
109
+ }
110
+
111
+ export async function createUPRNMetaTable(db: Kysely<UPRNDatabase>): Promise<void> {
112
+ await db.schema
113
+ .createTable("uprn_meta")
114
+ .addColumn("key", "text", (c) => c.primaryKey())
115
+ .addColumn("value", "text", (c) => c.notNull())
116
+ .execute()
117
+ }
118
+
119
+ /**
120
+ * Secondary index for the `nearestUPRN` ring probe. Builders call this AFTER the bulk load (index-after-load).
121
+ */
122
+ export async function createUPRNIndexes(db: Kysely<UPRNDatabase>): Promise<void> {
123
+ await db.schema.createIndex("uprn_h3_cell").on("uprn").column("h3_cell").execute()
124
+ }