@mailwoman/resolver-wof-sqlite 9.2.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 (488) hide show
  1. package/README.md +16 -19
  2. package/lib/address/index.ts +9 -0
  3. package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +24 -22
  4. package/{address-point-schema.ts → lib/address/point-schema.ts} +18 -4
  5. package/{address-point.ts → lib/address/point.ts} +89 -27
  6. package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
  7. package/{ancestry.ts → lib/ancestry/index.ts} +5 -3
  8. package/{build-candidate.ts → lib/build-candidate.ts} +84 -77
  9. package/{build-slim.ts → lib/build-slim.ts} +150 -165
  10. package/{candidate → lib/candidate}/alias-bags.ts +8 -6
  11. package/{candidate → lib/candidate}/ancestors-sidecar.ts +13 -15
  12. package/{candidate → lib/candidate}/country-display-names.ts +2 -2
  13. package/{candidate/shard-fold.ts → lib/candidate/extract-fold.ts} +26 -25
  14. package/{candidate → lib/candidate}/name-roles.ts +17 -16
  15. package/{candidate → lib/candidate}/own-name.ts +1 -1
  16. package/{candidate → lib/candidate}/place-attrs.ts +3 -3
  17. package/{candidate-ancestors-schema.ts → lib/candidate-ancestors-schema.ts} +4 -4
  18. package/{candidate-fts.ts → lib/candidate-fts.ts} +3 -3
  19. package/{candidate-importance.ts → lib/candidate-importance.ts} +53 -57
  20. package/{candidate-lookup.ts → lib/candidate-lookup.ts} +76 -73
  21. package/{candidate-schema.ts → lib/candidate-schema.ts} +5 -5
  22. package/{candidate-scoring.ts → lib/candidate-scoring.ts} +15 -22
  23. package/{capital-schema.ts → lib/capital-schema.ts} +5 -6
  24. package/{capitals.ts → lib/capitals.ts} +2 -2
  25. package/{coincident-roles.ts → lib/coincident-roles.ts} +8 -11
  26. package/{convention.ts → lib/convention/index.ts} +3 -3
  27. package/{convention-schema.ts → lib/convention/schema.ts} +2 -2
  28. package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +9 -10
  29. package/{currency-backfill.ts → lib/currency-backfill.ts} +126 -41
  30. package/lib/env.ts +50 -0
  31. package/{exact-match.ts → lib/exact-match.ts} +23 -14
  32. package/{sharding.ts → lib/extracts.ts} +61 -58
  33. package/{fst-autocomplete.ts → lib/fst/autocomplete.ts} +4 -4
  34. package/{fst-builder.ts → lib/fst/builder.ts} +17 -16
  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} +41 -74
  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} +24 -19
  43. package/{fts-query.ts → lib/fts/query.ts} +1 -1
  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} +14 -13
  47. package/{index.ts → lib/index.ts} +37 -37
  48. package/{interpolation.ts → lib/interpolation.ts} +27 -25
  49. package/{lookup.ts → lib/lookup.ts} +129 -141
  50. package/lib/nsul/index.ts +8 -0
  51. package/lib/nsul/lookup.ts +144 -0
  52. package/lib/nsul/schema.ts +130 -0
  53. package/{place-importance-schema.ts → lib/place-importance-schema.ts} +7 -8
  54. package/lib/poi/index.ts +8 -0
  55. package/{poi-lookup.ts → lib/poi/lookup.ts} +22 -28
  56. package/{poi-schema.ts → lib/poi/schema.ts} +3 -4
  57. package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +14 -20
  58. package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +2 -2
  59. package/lib/postal/index.ts +9 -0
  60. package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
  61. package/{primary-preference.ts → lib/primary-preference.ts} +29 -10
  62. package/{proximity-rerank.ts → lib/proximity-rerank.ts} +2 -2
  63. package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
  64. package/{region-keys.ts → lib/region-keys.ts} +9 -9
  65. package/{reverse.ts → lib/reverse.ts} +34 -46
  66. package/{schema.ts → lib/schema.ts} +10 -0
  67. package/{search-fetch.ts → lib/search-fetch.ts} +29 -28
  68. package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
  69. package/{sqlite-utils.ts → lib/sqlite-utils.ts} +14 -18
  70. package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +5 -5
  71. package/{street-centroid.ts → lib/street/centroid.ts} +14 -21
  72. package/lib/street/index.ts +13 -0
  73. package/{street-morphology-fst-builder.ts → lib/street/morphology-fst-builder.ts} +24 -14
  74. package/{street-morphology-fst-loader.ts → lib/street/morphology-fst-loader.ts} +14 -12
  75. package/{street-name-lookup.ts → lib/street/name-lookup.ts} +14 -30
  76. package/{street-normalize.ts → lib/street/normalize.ts} +44 -19
  77. package/{street-segment-schema.ts → lib/street/segment-schema.ts} +7 -7
  78. package/{types.ts → lib/types.ts} +18 -12
  79. package/{unified-schema.ts → lib/unified-schema.ts} +20 -24
  80. package/lib/uprn/existence.ts +84 -0
  81. package/lib/uprn/index.ts +9 -0
  82. package/{uprn-lookup.ts → lib/uprn/lookup.ts} +14 -18
  83. package/{uprn-schema.ts → lib/uprn/schema.ts} +3 -3
  84. package/lib/weights-overlay-linker.ts +878 -0
  85. package/out/address/index.d.ts +9 -0
  86. package/out/address/index.d.ts.map +1 -0
  87. package/out/address/index.js +9 -0
  88. package/out/address/index.js.map +1 -0
  89. package/out/{address-point-interpolation.d.ts → address/point-interpolation.d.ts} +11 -10
  90. package/out/address/point-interpolation.d.ts.map +1 -0
  91. package/out/{address-point-interpolation.js → address/point-interpolation.js} +18 -18
  92. package/out/address/point-interpolation.js.map +1 -0
  93. package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +16 -6
  94. package/out/address/point-schema.d.ts.map +1 -0
  95. package/out/{address-point-schema.js → address/point-schema.js} +7 -3
  96. package/out/address/point-schema.js.map +1 -0
  97. package/out/{address-point.d.ts → address/point.d.ts} +16 -8
  98. package/out/address/point.d.ts.map +1 -0
  99. package/out/{address-point.js → address/point.js} +71 -20
  100. package/out/address/point.js.map +1 -0
  101. package/out/{ancestry-backfill.d.ts → ancestry/backfill.d.ts} +7 -5
  102. package/out/ancestry/backfill.d.ts.map +1 -0
  103. package/out/{ancestry-backfill.js → ancestry/backfill.js} +12 -16
  104. package/out/ancestry/backfill.js.map +1 -0
  105. package/out/{ancestry.d.ts → ancestry/index.d.ts} +4 -3
  106. package/out/ancestry/index.d.ts.map +1 -0
  107. package/out/{ancestry.js → ancestry/index.js} +3 -2
  108. package/out/ancestry/index.js.map +1 -0
  109. package/out/build-candidate.d.ts +30 -24
  110. package/out/build-candidate.d.ts.map +1 -1
  111. package/out/build-candidate.js +50 -52
  112. package/out/build-candidate.js.map +1 -1
  113. package/out/build-slim.d.ts +4 -4
  114. package/out/build-slim.d.ts.map +1 -1
  115. package/out/build-slim.js +124 -139
  116. package/out/build-slim.js.map +1 -1
  117. package/out/candidate/alias-bags.d.ts +5 -3
  118. package/out/candidate/alias-bags.d.ts.map +1 -1
  119. package/out/candidate/alias-bags.js +2 -2
  120. package/out/candidate/alias-bags.js.map +1 -1
  121. package/out/candidate/ancestors-sidecar.d.ts +9 -10
  122. package/out/candidate/ancestors-sidecar.d.ts.map +1 -1
  123. package/out/candidate/ancestors-sidecar.js +7 -7
  124. package/out/candidate/ancestors-sidecar.js.map +1 -1
  125. package/out/candidate/country-display-names.d.ts +1 -1
  126. package/out/candidate/country-display-names.d.ts.map +1 -1
  127. package/out/candidate/country-display-names.js +1 -1
  128. package/out/candidate/country-display-names.js.map +1 -1
  129. package/out/candidate/extract-fold.d.ts +32 -0
  130. package/out/candidate/extract-fold.d.ts.map +1 -0
  131. package/out/candidate/{shard-fold.js → extract-fold.js} +20 -20
  132. package/out/candidate/extract-fold.js.map +1 -0
  133. package/out/candidate/name-roles.d.ts +13 -11
  134. package/out/candidate/name-roles.d.ts.map +1 -1
  135. package/out/candidate/name-roles.js +11 -10
  136. package/out/candidate/name-roles.js.map +1 -1
  137. package/out/candidate/own-name.d.ts +1 -1
  138. package/out/candidate/own-name.d.ts.map +1 -1
  139. package/out/candidate/own-name.js +1 -1
  140. package/out/candidate/own-name.js.map +1 -1
  141. package/out/candidate/place-attrs.d.ts +3 -3
  142. package/out/candidate/place-attrs.d.ts.map +1 -1
  143. package/out/candidate/place-attrs.js +1 -1
  144. package/out/candidate/place-attrs.js.map +1 -1
  145. package/out/candidate-ancestors-schema.d.ts +4 -4
  146. package/out/candidate-ancestors-schema.d.ts.map +1 -1
  147. package/out/candidate-ancestors-schema.js +2 -2
  148. package/out/candidate-ancestors-schema.js.map +1 -1
  149. package/out/candidate-fts.d.ts +3 -3
  150. package/out/candidate-fts.d.ts.map +1 -1
  151. package/out/candidate-fts.js +1 -1
  152. package/out/candidate-fts.js.map +1 -1
  153. package/out/candidate-importance.d.ts +16 -16
  154. package/out/candidate-importance.d.ts.map +1 -1
  155. package/out/candidate-importance.js +46 -51
  156. package/out/candidate-importance.js.map +1 -1
  157. package/out/candidate-lookup.d.ts +11 -9
  158. package/out/candidate-lookup.d.ts.map +1 -1
  159. package/out/candidate-lookup.js +61 -58
  160. package/out/candidate-lookup.js.map +1 -1
  161. package/out/candidate-schema.d.ts +5 -5
  162. package/out/candidate-schema.d.ts.map +1 -1
  163. package/out/candidate-schema.js.map +1 -1
  164. package/out/candidate-scoring.d.ts +7 -7
  165. package/out/candidate-scoring.d.ts.map +1 -1
  166. package/out/candidate-scoring.js +10 -14
  167. package/out/candidate-scoring.js.map +1 -1
  168. package/out/capital-schema.d.ts +3 -3
  169. package/out/capital-schema.d.ts.map +1 -1
  170. package/out/capital-schema.js +2 -2
  171. package/out/capital-schema.js.map +1 -1
  172. package/out/capitals.d.ts +1 -1
  173. package/out/capitals.d.ts.map +1 -1
  174. package/out/capitals.js +2 -2
  175. package/out/capitals.js.map +1 -1
  176. package/out/coincident-roles.d.ts +6 -5
  177. package/out/coincident-roles.d.ts.map +1 -1
  178. package/out/coincident-roles.js +2 -4
  179. package/out/coincident-roles.js.map +1 -1
  180. package/out/{convention.d.ts → convention/index.d.ts} +4 -4
  181. package/out/convention/index.d.ts.map +1 -0
  182. package/out/{convention.js → convention/index.js} +2 -2
  183. package/out/convention/index.js.map +1 -0
  184. package/out/{convention-schema.d.ts → convention/schema.d.ts} +2 -2
  185. package/out/convention/schema.d.ts.map +1 -0
  186. package/out/{convention-schema.js → convention/schema.js} +3 -3
  187. package/out/convention/schema.js.map +1 -0
  188. package/out/coverage-manifest-schema.d.ts +8 -8
  189. package/out/coverage-manifest-schema.d.ts.map +1 -1
  190. package/out/coverage-manifest-schema.js +4 -4
  191. package/out/coverage-manifest-schema.js.map +1 -1
  192. package/out/currency-backfill.d.ts +51 -7
  193. package/out/currency-backfill.d.ts.map +1 -1
  194. package/out/currency-backfill.js +65 -49
  195. package/out/currency-backfill.js.map +1 -1
  196. package/out/env.d.ts +43 -0
  197. package/out/env.d.ts.map +1 -0
  198. package/out/env.js +48 -0
  199. package/out/env.js.map +1 -0
  200. package/out/exact-match.d.ts +5 -5
  201. package/out/exact-match.d.ts.map +1 -1
  202. package/out/exact-match.js +10 -11
  203. package/out/exact-match.js.map +1 -1
  204. package/out/extracts.d.ts +114 -0
  205. package/out/extracts.d.ts.map +1 -0
  206. package/out/{sharding.js → extracts.js} +38 -38
  207. package/out/extracts.js.map +1 -0
  208. package/out/{fst-autocomplete.d.ts → fst/autocomplete.d.ts} +2 -2
  209. package/out/fst/autocomplete.d.ts.map +1 -0
  210. package/out/{fst-autocomplete.js → fst/autocomplete.js} +3 -3
  211. package/out/fst/autocomplete.js.map +1 -0
  212. package/out/{fst-builder.d.ts → fst/builder.d.ts} +5 -5
  213. package/out/fst/builder.d.ts.map +1 -0
  214. package/out/{fst-builder.js → fst/builder.js} +14 -13
  215. package/out/fst/builder.js.map +1 -0
  216. package/out/{fst-deserialize-web.d.ts → fst/deserialize-web.d.ts} +3 -3
  217. package/out/fst/deserialize-web.d.ts.map +1 -0
  218. package/out/{fst-deserialize-web.js → fst/deserialize-web.js} +10 -74
  219. package/out/fst/deserialize-web.js.map +1 -0
  220. package/out/fst/format.d.ts +80 -0
  221. package/out/fst/format.d.ts.map +1 -0
  222. package/out/fst/format.js +91 -0
  223. package/out/fst/format.js.map +1 -0
  224. package/out/{fst-freshness.d.ts → fst/freshness.d.ts} +10 -13
  225. package/out/fst/freshness.d.ts.map +1 -0
  226. package/out/{fst-freshness.js → fst/freshness.js} +41 -73
  227. package/out/fst/freshness.js.map +1 -0
  228. package/out/fst/index.d.ts +14 -0
  229. package/out/fst/index.d.ts.map +1 -0
  230. package/out/fst/index.js +14 -0
  231. package/out/fst/index.js.map +1 -0
  232. package/out/{fst-matcher.d.ts → fst/matcher.d.ts} +2 -2
  233. package/out/fst/matcher.d.ts.map +1 -0
  234. package/out/{fst-matcher.js → fst/matcher.js} +1 -1
  235. package/out/fst/matcher.js.map +1 -0
  236. package/out/{fst-serialize.d.ts → fst/serialize.d.ts} +5 -10
  237. package/out/fst/serialize.d.ts.map +1 -0
  238. package/out/{fst-serialize.js → fst/serialize.js} +18 -98
  239. package/out/fst/serialize.js.map +1 -0
  240. package/out/{fst-types.d.ts → fst/types.d.ts} +4 -3
  241. package/out/fst/types.d.ts.map +1 -0
  242. package/out/{fst-types.js → fst/types.js} +1 -1
  243. package/out/fst/types.js.map +1 -0
  244. package/out/{fts.d.ts → fts/index.d.ts} +16 -9
  245. package/out/fts/index.d.ts.map +1 -0
  246. package/out/{fts.js → fts/index.js} +16 -11
  247. package/out/fts/index.js.map +1 -0
  248. package/out/{fts-query.d.ts → fts/query.d.ts} +2 -2
  249. package/out/fts/query.d.ts.map +1 -0
  250. package/out/{fts-query.js → fts/query.js} +1 -1
  251. package/out/fts/query.js.map +1 -0
  252. package/out/{geonames-aliases.d.ts → geonames/aliases.d.ts} +8 -6
  253. package/out/geonames/aliases.d.ts.map +1 -0
  254. package/out/{geonames-aliases.js → geonames/aliases.js} +7 -7
  255. package/out/geonames/aliases.js.map +1 -0
  256. package/out/geonames/index.d.ts +8 -0
  257. package/out/geonames/index.d.ts.map +1 -0
  258. package/out/geonames/index.js +8 -0
  259. package/out/geonames/index.js.map +1 -0
  260. package/out/{geonames-postal.d.ts → geonames/postal.d.ts} +10 -8
  261. package/out/geonames/postal.d.ts.map +1 -0
  262. package/out/{geonames-postal.js → geonames/postal.js} +9 -9
  263. package/out/geonames/postal.js.map +1 -0
  264. package/out/index.d.ts +32 -32
  265. package/out/index.d.ts.map +1 -1
  266. package/out/index.js +24 -24
  267. package/out/index.js.map +1 -1
  268. package/out/interpolation.d.ts +10 -9
  269. package/out/interpolation.d.ts.map +1 -1
  270. package/out/interpolation.js +20 -20
  271. package/out/interpolation.js.map +1 -1
  272. package/out/lookup.d.ts +21 -20
  273. package/out/lookup.d.ts.map +1 -1
  274. package/out/lookup.js +102 -112
  275. package/out/lookup.js.map +1 -1
  276. package/out/name-score.d.ts.map +1 -1
  277. package/out/name-score.js.map +1 -1
  278. package/out/nsul/index.d.ts +8 -0
  279. package/out/nsul/index.d.ts.map +1 -0
  280. package/out/nsul/index.js +8 -0
  281. package/out/nsul/index.js.map +1 -0
  282. package/out/nsul/lookup.d.ts +83 -0
  283. package/out/nsul/lookup.d.ts.map +1 -0
  284. package/out/nsul/lookup.js +80 -0
  285. package/out/nsul/lookup.js.map +1 -0
  286. package/out/nsul/schema.d.ts +100 -0
  287. package/out/nsul/schema.d.ts.map +1 -0
  288. package/out/nsul/schema.js +78 -0
  289. package/out/nsul/schema.js.map +1 -0
  290. package/out/place-importance-schema.d.ts +5 -5
  291. package/out/place-importance-schema.d.ts.map +1 -1
  292. package/out/place-importance-schema.js +3 -3
  293. package/out/place-importance-schema.js.map +1 -1
  294. package/out/poi/index.d.ts +8 -0
  295. package/out/poi/index.d.ts.map +1 -0
  296. package/out/poi/index.js +8 -0
  297. package/out/poi/index.js.map +1 -0
  298. package/out/{poi-lookup.d.ts → poi/lookup.d.ts} +8 -10
  299. package/out/poi/lookup.d.ts.map +1 -0
  300. package/out/{poi-lookup.js → poi/lookup.js} +14 -18
  301. package/out/poi/lookup.js.map +1 -0
  302. package/out/{poi-schema.d.ts → poi/schema.d.ts} +4 -4
  303. package/out/poi/schema.d.ts.map +1 -0
  304. package/out/{poi-schema.js → poi/schema.js} +1 -1
  305. package/out/poi/schema.js.map +1 -0
  306. package/out/polygon-schema.d.ts.map +1 -1
  307. package/out/polygon-schema.js.map +1 -1
  308. package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +6 -5
  309. package/out/postal/city-alias-lookup.d.ts.map +1 -0
  310. package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +11 -15
  311. package/out/postal/city-alias-lookup.js.map +1 -0
  312. package/out/{postal-city-alias-schema.d.ts → postal/city-alias-schema.d.ts} +1 -1
  313. package/out/{postal-city-alias-schema.d.ts.map → postal/city-alias-schema.d.ts.map} +1 -1
  314. package/out/{postal-city-alias-schema.js → postal/city-alias-schema.js} +1 -1
  315. package/out/{postal-city-alias-schema.js.map → postal/city-alias-schema.js.map} +1 -1
  316. package/out/{postal-city-candidate-schema.d.ts → postal/city-candidate-schema.d.ts} +3 -3
  317. package/out/{postal-city-candidate-schema.d.ts.map → postal/city-candidate-schema.d.ts.map} +1 -1
  318. package/out/{postal-city-candidate-schema.js → postal/city-candidate-schema.js} +2 -2
  319. package/out/{postal-city-candidate-schema.js.map → postal/city-candidate-schema.js.map} +1 -1
  320. package/out/postal/index.d.ts +9 -0
  321. package/out/postal/index.d.ts.map +1 -0
  322. package/out/postal/index.js +9 -0
  323. package/out/postal/index.js.map +1 -0
  324. package/out/postcode-point-lookup.d.ts +10 -10
  325. package/out/postcode-point-lookup.d.ts.map +1 -1
  326. package/out/postcode-point-lookup.js +13 -13
  327. package/out/postcode-point-lookup.js.map +1 -1
  328. package/out/primary-preference.d.ts +13 -5
  329. package/out/primary-preference.d.ts.map +1 -1
  330. package/out/primary-preference.js +16 -8
  331. package/out/primary-preference.js.map +1 -1
  332. package/out/proximity-rerank.d.ts +2 -2
  333. package/out/proximity-rerank.d.ts.map +1 -1
  334. package/out/proximity-rerank.js +2 -2
  335. package/out/proximity-rerank.js.map +1 -1
  336. package/out/ranking-weights.d.ts +15 -3
  337. package/out/ranking-weights.d.ts.map +1 -1
  338. package/out/ranking-weights.js +21 -3
  339. package/out/ranking-weights.js.map +1 -1
  340. package/out/region-keys.d.ts +8 -8
  341. package/out/region-keys.d.ts.map +1 -1
  342. package/out/region-keys.js +9 -9
  343. package/out/region-keys.js.map +1 -1
  344. package/out/reverse.d.ts +6 -6
  345. package/out/reverse.d.ts.map +1 -1
  346. package/out/reverse.js +20 -29
  347. package/out/reverse.js.map +1 -1
  348. package/out/schema.d.ts +9 -0
  349. package/out/schema.d.ts.map +1 -1
  350. package/out/schema.js.map +1 -1
  351. package/out/search-fetch.d.ts +12 -10
  352. package/out/search-fetch.d.ts.map +1 -1
  353. package/out/search-fetch.js +21 -20
  354. package/out/search-fetch.js.map +1 -1
  355. package/out/sqlite-convention-source.d.ts +7 -7
  356. package/out/sqlite-convention-source.d.ts.map +1 -1
  357. package/out/sqlite-convention-source.js +5 -5
  358. package/out/sqlite-convention-source.js.map +1 -1
  359. package/out/sqlite-utils.d.ts +8 -8
  360. package/out/sqlite-utils.d.ts.map +1 -1
  361. package/out/sqlite-utils.js +7 -8
  362. package/out/sqlite-utils.js.map +1 -1
  363. package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +6 -6
  364. package/out/{street-centroid-schema.d.ts.map → street/centroid-schema.d.ts.map} +1 -1
  365. package/out/{street-centroid-schema.js → street/centroid-schema.js} +5 -5
  366. package/out/{street-centroid-schema.js.map → street/centroid-schema.js.map} +1 -1
  367. package/out/{street-centroid.d.ts → street/centroid.d.ts} +6 -6
  368. package/out/street/centroid.d.ts.map +1 -0
  369. package/out/{street-centroid.js → street/centroid.js} +13 -19
  370. package/out/street/centroid.js.map +1 -0
  371. package/out/street/index.d.ts +13 -0
  372. package/out/street/index.d.ts.map +1 -0
  373. package/out/street/index.js +13 -0
  374. package/out/street/index.js.map +1 -0
  375. package/out/{street-morphology-fst-builder.d.ts → street/morphology-fst-builder.d.ts} +4 -4
  376. package/out/street/morphology-fst-builder.d.ts.map +1 -0
  377. package/out/{street-morphology-fst-builder.js → street/morphology-fst-builder.js} +18 -13
  378. package/out/street/morphology-fst-builder.js.map +1 -0
  379. package/out/{street-morphology-fst-loader.d.ts → street/morphology-fst-loader.d.ts} +5 -5
  380. package/out/street/morphology-fst-loader.d.ts.map +1 -0
  381. package/out/{street-morphology-fst-loader.js → street/morphology-fst-loader.js} +11 -10
  382. package/out/street/morphology-fst-loader.js.map +1 -0
  383. package/out/{street-name-lookup.d.ts → street/name-lookup.d.ts} +4 -7
  384. package/out/street/name-lookup.d.ts.map +1 -0
  385. package/out/{street-name-lookup.js → street/name-lookup.js} +9 -23
  386. package/out/street/name-lookup.js.map +1 -0
  387. package/out/{street-normalize.d.ts → street/normalize.d.ts} +25 -14
  388. package/out/street/normalize.d.ts.map +1 -0
  389. package/out/{street-normalize.js → street/normalize.js} +36 -17
  390. package/out/street/normalize.js.map +1 -0
  391. package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +8 -8
  392. package/out/{street-segment-schema.d.ts.map → street/segment-schema.d.ts.map} +1 -1
  393. package/out/{street-segment-schema.js → street/segment-schema.js} +4 -4
  394. package/out/{street-segment-schema.js.map → street/segment-schema.js.map} +1 -1
  395. package/out/types.d.ts +18 -12
  396. package/out/types.d.ts.map +1 -1
  397. package/out/types.js.map +1 -1
  398. package/out/unified-schema.d.ts +5 -4
  399. package/out/unified-schema.d.ts.map +1 -1
  400. package/out/unified-schema.js +15 -18
  401. package/out/unified-schema.js.map +1 -1
  402. package/out/uprn/existence.d.ts +53 -0
  403. package/out/uprn/existence.d.ts.map +1 -0
  404. package/out/uprn/existence.js +61 -0
  405. package/out/uprn/existence.js.map +1 -0
  406. package/out/uprn/index.d.ts +9 -0
  407. package/out/uprn/index.d.ts.map +1 -0
  408. package/out/uprn/index.js +9 -0
  409. package/out/uprn/index.js.map +1 -0
  410. package/out/{uprn-lookup.d.ts → uprn/lookup.d.ts} +4 -4
  411. package/out/uprn/lookup.d.ts.map +1 -0
  412. package/out/{uprn-lookup.js → uprn/lookup.js} +11 -14
  413. package/out/uprn/lookup.js.map +1 -0
  414. package/out/{uprn-schema.d.ts → uprn/schema.d.ts} +4 -4
  415. package/out/uprn/schema.d.ts.map +1 -0
  416. package/out/{uprn-schema.js → uprn/schema.js} +4 -4
  417. package/out/uprn/schema.js.map +1 -0
  418. package/out/weights-overlay-linker.d.ts +184 -26
  419. package/out/weights-overlay-linker.d.ts.map +1 -1
  420. package/out/weights-overlay-linker.js +338 -86
  421. package/out/weights-overlay-linker.js.map +1 -1
  422. package/package.json +465 -269
  423. package/out/address-point-interpolation.d.ts.map +0 -1
  424. package/out/address-point-interpolation.js.map +0 -1
  425. package/out/address-point-schema.d.ts.map +0 -1
  426. package/out/address-point-schema.js.map +0 -1
  427. package/out/address-point.d.ts.map +0 -1
  428. package/out/address-point.js.map +0 -1
  429. package/out/ancestry-backfill.d.ts.map +0 -1
  430. package/out/ancestry-backfill.js.map +0 -1
  431. package/out/ancestry.d.ts.map +0 -1
  432. package/out/ancestry.js.map +0 -1
  433. package/out/candidate/shard-fold.d.ts +0 -31
  434. package/out/candidate/shard-fold.d.ts.map +0 -1
  435. package/out/candidate/shard-fold.js.map +0 -1
  436. package/out/convention-schema.d.ts.map +0 -1
  437. package/out/convention-schema.js.map +0 -1
  438. package/out/convention.d.ts.map +0 -1
  439. package/out/convention.js.map +0 -1
  440. package/out/fst-autocomplete.d.ts.map +0 -1
  441. package/out/fst-autocomplete.js.map +0 -1
  442. package/out/fst-builder.d.ts.map +0 -1
  443. package/out/fst-builder.js.map +0 -1
  444. package/out/fst-deserialize-web.d.ts.map +0 -1
  445. package/out/fst-deserialize-web.js.map +0 -1
  446. package/out/fst-freshness.d.ts.map +0 -1
  447. package/out/fst-freshness.js.map +0 -1
  448. package/out/fst-matcher.d.ts.map +0 -1
  449. package/out/fst-matcher.js.map +0 -1
  450. package/out/fst-serialize.d.ts.map +0 -1
  451. package/out/fst-serialize.js.map +0 -1
  452. package/out/fst-types.d.ts.map +0 -1
  453. package/out/fst-types.js.map +0 -1
  454. package/out/fts-query.d.ts.map +0 -1
  455. package/out/fts-query.js.map +0 -1
  456. package/out/fts.d.ts.map +0 -1
  457. package/out/fts.js.map +0 -1
  458. package/out/geonames-aliases.d.ts.map +0 -1
  459. package/out/geonames-aliases.js.map +0 -1
  460. package/out/geonames-postal.d.ts.map +0 -1
  461. package/out/geonames-postal.js.map +0 -1
  462. package/out/poi-lookup.d.ts.map +0 -1
  463. package/out/poi-lookup.js.map +0 -1
  464. package/out/poi-schema.d.ts.map +0 -1
  465. package/out/poi-schema.js.map +0 -1
  466. package/out/postal-city-alias-lookup.d.ts.map +0 -1
  467. package/out/postal-city-alias-lookup.js.map +0 -1
  468. package/out/sharding.d.ts +0 -114
  469. package/out/sharding.d.ts.map +0 -1
  470. package/out/sharding.js.map +0 -1
  471. package/out/street-centroid.d.ts.map +0 -1
  472. package/out/street-centroid.js.map +0 -1
  473. package/out/street-morphology-fst-builder.d.ts.map +0 -1
  474. package/out/street-morphology-fst-builder.js.map +0 -1
  475. package/out/street-morphology-fst-loader.d.ts.map +0 -1
  476. package/out/street-morphology-fst-loader.js.map +0 -1
  477. package/out/street-name-lookup.d.ts.map +0 -1
  478. package/out/street-name-lookup.js.map +0 -1
  479. package/out/street-normalize.d.ts.map +0 -1
  480. package/out/street-normalize.js.map +0 -1
  481. package/out/uprn-lookup.d.ts.map +0 -1
  482. package/out/uprn-lookup.js.map +0 -1
  483. package/out/uprn-schema.d.ts.map +0 -1
  484. package/out/uprn-schema.js.map +0 -1
  485. package/weights-overlay-linker.ts +0 -377
  486. /package/{name-score.ts → lib/name-score.ts} +0 -0
  487. /package/{polygon-schema.ts → lib/polygon-schema.ts} +0 -0
  488. /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
@@ -11,7 +11,7 @@
11
11
  * Only type imports and arithmetic live here: anything with a `node:` import stays out.
12
12
  */
13
13
 
14
- import type { CandidateTable } from "./candidate-schema.ts"
14
+ import type { CandidateTable } from "#candidate-schema"
15
15
 
16
16
  /**
17
17
  * The row shape the re-rank needs. `is_primary` is optional: a reader over an artifact vintage that predates the column
@@ -54,8 +54,8 @@ const SEAT_PLACETYPE = "locality"
54
54
  /**
55
55
  * Over-fetch cap for {@link rankByPrimaryPreference}: the candidate rows for one `name_key` (all same-name places
56
56
  * worldwide) are re-ranked in-process, so the probe fetches this many (population-ordered) before the re-rank rather
57
- * than the caller's small `limit`, ensuring the intended primary isn't cut below the fold by a cluster of more-populous
58
- * foreign aliases. Bounded and small — a single contiguous B-tree scan.
57
+ * than the caller's small `limit`, ensuring the intended primary isn't pushed below the fold by a cluster of
58
+ * more-populous foreign aliases. Bounded and small — a single contiguous B-tree scan.
59
59
  */
60
60
  export const RERANK_FETCH = 64
61
61
 
@@ -66,7 +66,7 @@ export type RankedRow<R> = R & {
66
66
  /**
67
67
  * `neg_rank` plus the bounded cross-country alias penalty — the value the row is ORDERED by, and the base the emitted
68
68
  * `prominence` is derived from (so the resolver walk's `prominence ?? score` sort, `resolve.ts`, agrees with this
69
- * order; the raw `score`/`neg_rank` is left intact for the walk's `minWinningScore` gate).
69
+ * order; the raw `score`/`neg_rank` is left intact for the walk's `minWinningScore` floor).
70
70
  */
71
71
  effectiveNegRank: number
72
72
  /**
@@ -93,6 +93,14 @@ export type RankedRow<R> = R & {
93
93
  * was never asked, never "not contained".
94
94
  */
95
95
  containedByQualifier?: boolean
96
+ /**
97
+ * The #1882 exemption's firing mark (#1893): this row would have taken the cross-country alias penalty and
98
+ * `exemptVariantAliases` prevented it. PRESENT only when the exemption changed this row's treatment; absent on every
99
+ * row it merely inspected — an in-country variant, a primary, a set with no foreign top primary. The winner-level
100
+ * receipt downstream (`mechanism_fired_on.variant_alias_exemption`) counts this mark only when it survives onto the
101
+ * selected candidate.
102
+ */
103
+ variantExempted?: true
96
104
  }
97
105
 
98
106
  /**
@@ -126,7 +134,7 @@ export type RankedRow<R> = R & {
126
134
  * the fame prior overrides by design; a probe with NO placetype filter (the browser cascade's last resort, the dev
127
135
  * lookup tools) presents the full tie and this term is all that breaks it.
128
136
  *
129
- * BOTH GATES ARE LOAD-BEARING, and a plain "finer placetype wins" measured wrong before this shape was settled: it
137
+ * BOTH CONDITIONS ARE required, and a plain "finer placetype wins" measured wrong before this shape was settled: it
130
138
  * moved the top slot on 11,377 keys in `candidate.db`, of which only 722 were the seat/district duplicate. The rest
131
139
  * were contests between genuinely distinct places that merely tie — 2,885 `locality → neighbourhood` (a bare city name
132
140
  * losing to a same-named hood), 2,973 `region → county`, 2,662 `postalcode → locality` — and 7,179 of the 11,377 sat at
@@ -162,17 +170,28 @@ export function rankByPrimaryPreference<R extends PrimaryPreferenceRow>(
162
170
  // query is naming THAT place, not colliding with it; the penalty exists for the coincidental-collision
163
171
  // class ("Çançun"/`cancun`), which the detector's measured threshold keeps un-stamped. An artifact
164
172
  // predating the role column carries no 'variant' rows, so the flag no-ops there by construction.
173
+ const wouldPenalize = (r: R): boolean =>
174
+ typeof topCountry === "number" && r.is_primary !== 1 && r.country_id !== topCountry
175
+
165
176
  const isCrossCountryAlias = (r: R): boolean =>
166
- typeof topCountry === "number" &&
167
- r.is_primary !== 1 &&
168
- r.country_id !== topCountry &&
169
- !(exemptVariantAliases && r.name_role === "variant")
177
+ wouldPenalize(r) && !(exemptVariantAliases && r.name_role === "variant")
170
178
 
171
179
  const annotate = (r: R): RankedRow<R> => {
172
180
  const penalized = isCrossCountryAlias(r)
173
181
  const effectiveNegRank = r.neg_rank + (penalized ? delta : 0)
174
182
 
175
- return { ...r, effectiveNegRank, demoted: penalized && effectiveNegRank > topPrimary!.neg_rank }
183
+ // The firing mark (#1893): this row WOULD have taken the cross-country penalty and the exemption
184
+ // prevented it — conditions the winner-level receipt needs, computed where the decision is made. A
185
+ // variant row that was in-country, primary, or keyed under a set with no foreign primary never
186
+ // fired, and stays unmarked.
187
+ const exempted = !penalized && exemptVariantAliases && r.name_role === "variant" && wouldPenalize(r)
188
+
189
+ return {
190
+ ...r,
191
+ effectiveNegRank,
192
+ demoted: penalized && effectiveNegRank > topPrimary!.neg_rank,
193
+ ...(exempted ? { variantExempted: true as const } : {}),
194
+ }
176
195
  }
177
196
 
178
197
  // 1 for a populated-place row that can BE a district's seat, 0 for everything else — no code map, no
@@ -8,12 +8,12 @@
8
8
  * filter. Byte-identical to plain population order when no bias is passed.
9
9
  *
10
10
  * This lives in its own platform-free module because it has to run identically in two places: the Node candidate
11
- * reader and the browser byte-range twin. That is the #861 server↔demo parity contract, and it is the second thing
11
+ * reader and the browser byte-range twin. That is the #861 server↔demo parity contract and the second thing
12
12
  * here held by construction rather than by comment (`primary-preference.ts` was the first). Constants alone were not
13
13
  * enough — the two copies agreed on every literal and still diverged on which field the population term reads and on
14
14
  * whether the combined value is written back, which is the half that actually decides the answer.
15
15
  *
16
- * Two properties are load-bearing and easy to lose when transcribing:
16
+ * Two properties are required and easy to lose when transcribing:
17
17
  *
18
18
  * 1. The population base is `prominence ?? score`, NOT `score`. `prominence` carries the bounded cross-country
19
19
  * primary preference, so reading raw score lets a coincidental foreign alias ride population back over a primary
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * The locality-ranking weights and their shipped defaults. Its own module because every value here is
7
7
  * a measured tuning decision with its rationale attached — the block reads as a reference table, not
8
- * as part of the lookup's control flow, and the tests import it directly to pin one lever at a time.
8
+ * as part of the lookup's control flow, and the tests import it directly to pin one change at a time.
9
9
  */
10
10
 
11
11
  /**
@@ -96,7 +96,7 @@ export interface RankingWeights {
96
96
  * Fixes unscoped "Åbo" → Turku (its official Swedish name) over a hamlet literally named Åbo; population still orders
97
97
  * within the sub-tier, so Paris → Paris FR is untouched.
98
98
  *
99
- * Default true (operator-promoted 2026-07-03 after the pre-registered gate battery: four intended exonym flips —
99
+ * Default true (operator-promoted 2026-07-03 after the pre-registered eval battery: four intended exonym flips —
100
100
  * Berne→Bern, Bruges→Brugge, Roma→Rome, Åbo→Turku — with the namesake/abbreviation rows and the US/FI panels
101
101
  * byte-identical). Requires a gazetteer carrying the #940 ingest bit — on older DBs without the `official` column the
102
102
  * probe fails soft and behavior is identical to the flag being off.
@@ -114,7 +114,7 @@ export interface RankingWeights {
114
114
 
115
115
  /**
116
116
  * The shipped weights. Every value is a measured decision — change one and re-run the resolver eval; the per-field docs
117
- * on {@link RankingWeights} say what each lever moves and what motivated its current value.
117
+ * on {@link RankingWeights} say what each change moves and what motivated its current value.
118
118
  */
119
119
  export const DEFAULT_WEIGHTS: RankingWeights = {
120
120
  placetypeMatchBoost: 0.5,
@@ -142,7 +142,33 @@ export const DEFAULT_WEIGHTS: RankingWeights = {
142
142
  // consulted — keeps population as an intra-tier prominence tiebreaker, not a cross-tier promoter.
143
143
  // Fixes the 2-letter-region-abbrev bug ("ME" → Maine, not the more-populous Missouri).
144
144
  exactMatchTiering: true,
145
- // #936 option 3 — promoted default-ON 2026-07-03 (gate battery PASS; see the RankingWeights docstring).
145
+ // #936 option 3 — promoted default-ON 2026-07-03 (eval battery PASS; see the RankingWeights docstring).
146
146
  officialNameExact: true,
147
147
  officialNameExactFloor: 100_000,
148
148
  }
149
+
150
+ /**
151
+ * The population contribution as a 0..1 fraction: `min(1, log10(1 + population) / populationScaleLog10)`. Zero for an
152
+ * absent or non-positive population — and zero for a non-positive scale, so a magnitude never carries its own absence.
153
+ * The coordinate-first locality path consumes this fraction directly; {@link populationBoostTerm} scales it.
154
+ */
155
+ export function populationScaleTerm(
156
+ population: number | null | undefined,
157
+ weights: Pick<RankingWeights, "populationScaleLog10">
158
+ ): number {
159
+ if (population == null || population <= 0 || weights.populationScaleLog10 <= 0) return 0
160
+
161
+ return Math.min(1, Math.log10(1 + population) / weights.populationScaleLog10)
162
+ }
163
+
164
+ /**
165
+ * The additive population boost: `populationBoost * populationScaleTerm(...)`, capped at `populationBoost` magnitude at
166
+ * `10^populationScaleLog10` people. Missing population contributes 0 — never a penalty. The one formula behind the Node
167
+ * weighted sum and the WASM re-rank, so the two backends cannot drift.
168
+ */
169
+ export function populationBoostTerm(
170
+ population: number | null | undefined,
171
+ weights: Pick<RankingWeights, "populationBoost" | "populationScaleLog10">
172
+ ): number {
173
+ return weights.populationBoost * populationScaleTerm(population, weights)
174
+ }
@@ -18,7 +18,7 @@
18
18
 
19
19
  import { matchSubdivision, matchSubdivisionIn } from "@mailwoman/codex/country"
20
20
 
21
- import { normalizeLocalityForKey } from "./street-normalize.ts"
21
+ import { normalizeLocalityForKey } from "#street/normalize"
22
22
 
23
23
  /**
24
24
  * The ancestry placetypes that answer for a parsed `region` qualifier — WOF's admin band between country and locality.
@@ -120,14 +120,14 @@ export function regionKeys(value: string, countryAlpha2?: string): Set<string> {
120
120
  /**
121
121
  * The PROBE-side expansion for a region qualifier — {@link regionKeys} plus the county-PREFIXED variant of every key.
122
122
  *
123
- * The verdict machinery intersects two {@link regionKeys} SETS, so `Co. Donegal` meets stored `County Donegal` at the
124
- * shared stripped key `donegal`. A table probe is one-sided: it matches the STORED fold verbatim, and WOF stores Irish
125
- * counties under `county donegal` with no bare `donegal` key (measured on the shipped candidate.db — the qualifier
126
- * probe missed every Irish county until this variant landed). Adding `county <key>` restores the two-sidedness for the
127
- * one stored-form family with an evidenced case; the union is monotone (a wider qualifier set can only find more
128
- * BEARERS, each of which must still genuinely contain a candidate before anything moves). The suffix sibling (`<key>
129
- * province`) is deliberately absent — no stored-form case has been evidenced, and a lever without a board does not get
130
- * built.
123
+ * The verdict implementation intersects two {@link regionKeys} SETS, so `Co. Donegal` meets stored `County Donegal` at
124
+ * the shared stripped key `donegal`. A table probe is one-sided: it matches the STORED fold verbatim, and WOF stores
125
+ * Irish counties under `county donegal` with no bare `donegal` key (measured on the shipped candidate.db — the
126
+ * qualifier probe missed every Irish county until this variant landed). Adding `county <key>` restores the
127
+ * two-sidedness for the one stored-form family with an evidenced case; the union is monotone (a wider qualifier set can
128
+ * only find more BEARERS, each of which must still genuinely contain a candidate before anything moves). The suffix
129
+ * sibling (`<key> province`) is deliberately absent — no stored-form case has been evidenced, and a change without a
130
+ * board does not get built.
131
131
  */
132
132
  export function regionQualifierProbeKeys(value: string, countryAlpha2?: string): Set<string> {
133
133
  const keys = regionKeys(value, countryAlpha2)
@@ -4,7 +4,7 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
6
  * Reverse geocoding (#484): `(lat, lon)` → the containing admin hierarchy. Assembly over existing
7
- * machinery, per the 2026-06-11 scoping notes:
7
+ * implementation, per the 2026-06-11 scoping notes:
8
8
  *
9
9
  * 1. **Candidate fetch** — the admin DB's `place_bbox` R*Tree (built by `fts.ts`) for places whose
10
10
  * bbox contains the point, smallest-area-first (so the FIRST polygon confirmation is the
@@ -28,15 +28,16 @@
28
28
  * says so per result rather than pretending.
29
29
  */
30
30
 
31
- import { DatabaseSync } from "node:sqlite"
31
+ import { tryParsingJSON } from "@mailwoman/core/json"
32
+ import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
33
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
34
+ import { tableExists } from "@mailwoman/sqlite/introspection"
32
35
 
33
- import { tryParsingJSON } from "@mailwoman/core/objects"
34
- import { geometryContains, haversineKm, type GeojsonGeometry } from "@mailwoman/spatial"
35
-
36
- import { ancestorLineage, placetypeDepth } from "./ancestry.ts"
37
- import { PLACE_BBOX_TABLE } from "./fts.ts"
38
- import { allRows } from "./sqlite-utils.ts"
39
- import type { PlaceCandidate, WOFPlacetype } from "./types.ts"
36
+ import { ancestorLineage, placetypeDepth } from "#ancestry/index"
37
+ import { PLACE_BBOX_TABLE } from "#fts/index"
38
+ import type { WOFDatabase } from "#schema"
39
+ import { allRows } from "#sqlite-utils"
40
+ import type { PlaceCandidate, WOFPlacetype } from "#types"
40
41
 
41
42
  /**
42
43
  * Largest absolute latitude in WGS-84 degrees.
@@ -80,7 +81,7 @@ export interface WOFReverseGeocoderOpts {
80
81
  /**
81
82
  * Pre-opened admin DB — primarily for tests against an inline fixture.
82
83
  */
83
- adminDatabase?: DatabaseSync
84
+ adminDatabase?: DatabaseClient<WOFDatabase>
84
85
  /**
85
86
  * Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL — without it every result
86
87
  * is `containment: "approximate"` (centroid-only mode). Mutually exclusive with `polygonDatabase`.
@@ -89,7 +90,7 @@ export interface WOFReverseGeocoderOpts {
89
90
  /**
90
91
  * Pre-opened polygon DB — primarily for tests.
91
92
  */
92
- polygonDatabase?: DatabaseSync
93
+ polygonDatabase?: DatabaseClient<WOFDatabase>
93
94
  }
94
95
 
95
96
  export interface ReverseGeocodeOpts {
@@ -160,9 +161,9 @@ function toPlaceCandidate(row: CandidateRow, distanceKm?: number): PlaceCandidat
160
161
  }
161
162
 
162
163
  export class WOFReverseGeocoder implements Disposable {
163
- readonly #admin: DatabaseSync
164
+ readonly #admin: DatabaseClient<WOFDatabase>
164
165
  readonly #ownsAdmin: boolean
165
- readonly #polygons: DatabaseSync | null
166
+ readonly #polygons: DatabaseClient<WOFDatabase> | null
166
167
  readonly #ownsPolygons: boolean
167
168
  /**
168
169
  * Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15 county polygons 1400
@@ -170,7 +171,7 @@ export class WOFReverseGeocoder implements Disposable {
170
171
  * LRU-tracked; the polygons are DP-simplified and small, the cap exists only to keep a long-lived server process
171
172
  * honest.
172
173
  */
173
- readonly #geometryCache = new Map<number, GeojsonGeometry | null>()
174
+ readonly #geometryCache = new Map<number, ParsedGeometry | null>()
174
175
  static readonly #GEOMETRY_CACHE_CAP = 4096
175
176
 
176
177
  constructor(opts: WOFReverseGeocoderOpts) {
@@ -186,38 +187,29 @@ export class WOFReverseGeocoder implements Disposable {
186
187
  throw new Error("WOFReverseGeocoder: pass either `polygonDatabase` or `polygonDBPath`, not both")
187
188
  }
188
189
 
189
- this.#admin = opts.adminDatabase ?? new DatabaseSync(opts.adminDBPath!, { readOnly: true })
190
+ this.#admin = opts.adminDatabase ?? new DatabaseClient<WOFDatabase>(opts.adminDBPath!, { readOnly: true })
190
191
  this.#ownsAdmin = !opts.adminDatabase
191
192
 
192
193
  this.#polygons =
193
- opts.polygonDatabase ?? (opts.polygonDBPath ? new DatabaseSync(opts.polygonDBPath, { readOnly: true }) : null)
194
+ opts.polygonDatabase ??
195
+ (opts.polygonDBPath ? new DatabaseClient<WOFDatabase>(opts.polygonDBPath, { readOnly: true }) : null)
194
196
 
195
197
  this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.polygonDBPath)
196
198
 
197
199
  // Fail loudly up front — the R*Tree is a build artifact, not part of the upstream WOF
198
200
  // distribution, and a missing index would otherwise surface as an opaque SQL error per query.
199
- const hasBbox = this.#admin
200
- .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
201
- .get(PLACE_BBOX_TABLE)
202
-
203
- if (!hasBbox) {
201
+ if (!tableExists(this.#admin, PLACE_BBOX_TABLE)) {
204
202
  throw new Error(
205
203
  `WOFReverseGeocoder: the admin DB has no \`${PLACE_BBOX_TABLE}\` R*Tree. Build it with ` +
206
204
  "`mailwoman gazetteer build fts <path-to-wof.db>` (see resolver-wof-sqlite/README.md)."
207
205
  )
208
206
  }
209
207
 
210
- if (this.#polygons) {
211
- const hasPolygons = this.#polygons
212
- .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'polygons'`)
213
- .get()
214
-
215
- if (!hasPolygons) {
216
- throw new Error(
217
- "WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
218
- "built by scripts/build-wof-polygons.mjs."
219
- )
220
- }
208
+ if (this.#polygons && !tableExists(this.#polygons, "polygons")) {
209
+ throw new Error(
210
+ "WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
211
+ "built by scripts/build-wof-polygons.mjs."
212
+ )
221
213
  }
222
214
  }
223
215
 
@@ -415,7 +407,7 @@ export class WOFReverseGeocoder implements Disposable {
415
407
  lon: number,
416
408
  maxApproximateKm: number
417
409
  ): CandidateRow[] {
418
- const windowDeg = (maxApproximateKm * 4) / 111
410
+ const window = bboxAround(lat, lon, maxApproximateKm * 4)
419
411
 
420
412
  return allRows<CandidateRow>(
421
413
  this.#admin.prepare(
@@ -427,17 +419,17 @@ export class WOFReverseGeocoder implements Disposable {
427
419
  ),
428
420
  parentID,
429
421
  placetype,
430
- lat - windowDeg,
431
- lat + windowDeg,
432
- lon - windowDeg,
433
- lon + windowDeg
422
+ window.minLat,
423
+ window.maxLat,
424
+ window.minLon,
425
+ window.maxLon
434
426
  )
435
427
  }
436
428
 
437
429
  /**
438
430
  * Parsed GeoJSON geometry for a WOF id, or null when absent / unparseable / no polygon DB.
439
431
  */
440
- #geometry(id: number): GeojsonGeometry | null {
432
+ #geometry(id: number): ParsedGeometry | null {
441
433
  if (!this.#polygons) return null
442
434
  const cached = this.#geometryCache.get(id)
443
435
 
@@ -449,24 +441,20 @@ export class WOFReverseGeocoder implements Disposable {
449
441
 
450
442
  const row = this.#polygons.prepare(`SELECT geom FROM polygons WHERE id = ?`).get(id) as { geom: string } | undefined
451
443
  // Malformed row parses to null — treat as no-polygon rather than failing the query.
452
- const geometry = row ? tryParsingJSON<GeojsonGeometry>(row.geom) : null
444
+ const geometry = row ? tryParsingJSON<ParsedGeometry>(row.geom) : null
453
445
 
454
446
  this.#geometryCache.set(id, geometry)
455
447
 
456
448
  return geometry
457
449
  }
458
450
 
459
- close(): void {
451
+ [Symbol.dispose](): void {
460
452
  if (this.#ownsAdmin) {
461
- this.#admin.close()
453
+ this.#admin.destroy()
462
454
  }
463
455
 
464
456
  if (this.#ownsPolygons) {
465
- this.#polygons?.close()
457
+ this.#polygons?.destroy()
466
458
  }
467
459
  }
468
-
469
- [Symbol.dispose](): void {
470
- this.close()
471
- }
472
460
  }
@@ -163,7 +163,17 @@ export interface CoincidentRolesTable {
163
163
  * build/augment WRITERS adopt it so a column rename is a compile error on both sides (the drift that bit the corpus
164
164
  * TIGER adapter).
165
165
  */
166
+ /**
167
+ * The provenance row every built extract carries: source fingerprints travelling WITH the database rather than in a
168
+ * document that can drift from it. Written by the postcode builders in `mailwoman/gazetteer-pipeline`.
169
+ */
170
+ export interface ExtractMetaTable {
171
+ key: string
172
+ value: string | null
173
+ }
174
+
166
175
  export interface WOFDatabase {
176
+ meta: ExtractMetaTable
167
177
  place_search: PlaceSearchTable
168
178
  spr: SprTable
169
179
  names: NamesTable
@@ -12,14 +12,13 @@
12
12
  * whole corpus, so an exact haversine over the survivors is cheap.
13
13
  */
14
14
 
15
- import type { DatabaseSync, SQLInputValue } from "node:sqlite"
16
-
17
15
  import { bboxAround } from "@mailwoman/spatial"
16
+ import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
18
17
 
19
- import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "./fts.ts"
20
- import type { RankingWeights } from "./ranking-weights.ts"
21
- import { allRows } from "./sqlite-utils.ts"
22
- import type { FindPlaceQuery, WOFPlacetype } from "./types.ts"
18
+ import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "#fts/index"
19
+ import type { RankingWeights } from "#ranking-weights"
20
+ import { allRows } from "#sqlite-utils"
21
+ import type { FindPlaceQuery, WOFPlacetype } from "#types"
23
22
 
24
23
  /**
25
24
  * Query length at or below which the FTS window is widened. A two- or three-character query is almost always a region
@@ -60,19 +59,21 @@ export interface RawSearchRow {
60
59
  max_longitude: number | null
61
60
  population: number | null // from the place_population aux table; null when missing
62
61
  /**
63
- * From `place_importance.encyclopedic` when the shard's table carries the two-score split columns. NULL means the
64
- * place has no Wikipedia article, or the shard predates the split — absence either way, and never 0 (ROAD_TO_V9 §2).
62
+ * From `place_importance.encyclopedic` when the extract's table carries the two-score split columns. NULL means the
63
+ * place has no Wikipedia article, or the extract predates the split — absence either way, and never 0 (ROAD_TO_V9
64
+ * §2).
65
65
  */
66
66
  encyclopedic: number | null
67
67
  }
68
68
 
69
69
  /**
70
- * Fetch the raw candidate rows for a name match on one shard: the BM25-ordered window over `place_search` (widened for
71
- * short queries), plus the population-ordered companion fetch that keeps the prominent holders of a name pool-complete.
72
- * `schemaName` is the routed shard's bare schema name — validated at construction, so it is interpolated directly.
70
+ * Fetch the raw candidate rows for a name match on one extract: the BM25-ordered window over `place_search` (widened
71
+ * for short queries), plus the population-ordered companion fetch that keeps the prominent holders of a name
72
+ * pool-complete. `schemaName` is the routed extract's bare schema name — validated at construction, so it is
73
+ * interpolated directly.
73
74
  */
74
- export function fetchSearchRows(options: {
75
- db: DatabaseSync
75
+ export function fetchSearchRows<DB>(options: {
76
+ db: DatabaseClient<DB>
76
77
  schemaName: string
77
78
  query: FindPlaceQuery
78
79
  placetypes: WOFPlacetype[] | null
@@ -112,7 +113,7 @@ export function fetchSearchRows(options: {
112
113
  // spr table but should never win a contemporary lookup. `is_current = 0` is the only WOF
113
114
  // value that means "not current"; both `-1` (modern) and `1` (legacy) mean current. See #91.
114
115
  // Note: with schema-qualified FROM the bare `place_search` reference in MATCH resolves to
115
- // the FROM table — required by FTS5 parser, see sharding.ts header comment.
116
+ // the FROM table — required by FTS5 parser, see extracts.ts header comment.
116
117
  const where: string[] = ["place_search MATCH ?", "spr.is_current != 0", "spr.is_deprecated = 0"]
117
118
  const params: SQLInputValue[] = [ftsQuery]
118
119
 
@@ -132,10 +133,10 @@ export function fetchSearchRows(options: {
132
133
  }
133
134
 
134
135
  // Bbox + near-with-radius are SQL-level filters via the R*Tree. We only emit the JOIN when
135
- // the active shard has the R*Tree; missing-but-requested is silently treated as no-bbox-
136
- // filter so legacy DBs / shards-without-bbox don't crash.
137
- const shardHasBbox = hasBboxIndex.get(sch) === true
138
- const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) && shardHasBbox
136
+ // the active extract has the R*Tree; missing-but-requested is silently treated as no-bbox-
137
+ // filter so legacy DBs / extracts-without-bbox don't crash.
138
+ const extractHasBbox = hasBboxIndex.get(sch) === true
139
+ const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) && extractHasBbox
139
140
  let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
140
141
 
141
142
  if (useBboxJoin) {
@@ -146,21 +147,21 @@ export function fetchSearchRows(options: {
146
147
  params.push(filterBox.maxLat, filterBox.minLat, filterBox.maxLon, filterBox.minLon)
147
148
  }
148
149
 
149
- // LEFT JOIN the population aux table when present. Missing-on-this-shard means the SELECT
150
+ // LEFT JOIN the population aux table when present. Missing-on-this-extract means the SELECT
150
151
  // just doesn't include the population column; the post-scoring loop treats it as 0.
151
- const shardHasPopulation = hasPopulationIndex.get(sch) === true
152
+ const extractHasPopulation = hasPopulationIndex.get(sch) === true
152
153
 
153
- const populationSelect = shardHasPopulation
154
+ const populationSelect = extractHasPopulation
154
155
  ? `${PLACE_POPULATION_TABLE}.population AS population`
155
156
  : `NULL AS population`
156
157
 
157
- const populationJoin = shardHasPopulation
158
+ const populationJoin = extractHasPopulation
158
159
  ? `LEFT JOIN ${sch}.${PLACE_POPULATION_TABLE} ON ${PLACE_POPULATION_TABLE}.id = spr.id`
159
160
  : ""
160
161
 
161
162
  // The encyclopedic score is CARRIED, never ranked on (ROAD_TO_V9 §2, ratified 2026-08-06) — it
162
- // appears in the SELECT and in no ORDER BY, here or in the companion fetch below. Gated on the
163
- // split column, so a pre-split shard emits a literal NULL and builds no join at all.
163
+ // appears in the SELECT and in no ORDER BY, here or in the companion fetch below. Conditioned on the
164
+ // split column, so a pre-split extract emits a literal NULL and builds no join at all.
164
165
  const { select: encyclopedicSelect, join: encyclopedicJoin } = encyclopedicClauses.get(sch)!
165
166
 
166
167
  // Push the population boost into the ORDER BY when the index is available, so famous places
@@ -177,12 +178,12 @@ export function fetchSearchRows(options: {
177
178
  // column weighted to zero — no weighting isolates name relevance in this schema. The famous-
178
179
  // holder guarantee lives in the population-ordered companion fetch below instead, and the
179
180
  // exact tier breaks ties by population in the post-scoring sort.
180
- const orderByExpr = shardHasPopulation
181
+ const orderByExpr = extractHasPopulation
181
182
  ? `(bm25(place_search) - ? * MIN(1.0, COALESCE(log10(1.0 + ${PLACE_POPULATION_TABLE}.population), 0) / ?))`
182
183
  : "bm25(place_search)"
183
184
 
184
185
  // Schema-qualified FROM with bare-name MATCH — required syntax for FTS5 on attached schemas.
185
- // See sharding.ts header for the gotcha that drove this design.
186
+ // See extracts.ts header for the failure mode that drove this design.
186
187
  const stmt = db.prepare(`
187
188
  SELECT
188
189
  spr.id AS id,
@@ -205,7 +206,7 @@ export function fetchSearchRows(options: {
205
206
  LIMIT ?
206
207
  `)
207
208
 
208
- if (shardHasPopulation) {
209
+ if (extractHasPopulation) {
209
210
  params.push(weights.populationBoost, weights.populationScaleLog10)
210
211
  }
211
212
 
@@ -219,7 +220,7 @@ export function fetchSearchRows(options: {
219
220
  // vs a +4.0 boost cap), so FR Paris never even reaches post-scoring. This fetch makes the
220
221
  // prominent holders of a name pool-complete BY CONSTRUCTION; the exact-tier sort below
221
222
  // decides whether they win. Skipped without a population index (nothing to order by).
222
- if (shardHasPopulation) {
223
+ if (extractHasPopulation) {
223
224
  const popStmt = db.prepare(`
224
225
  SELECT
225
226
  spr.id AS id,
@@ -8,21 +8,20 @@
8
8
  * table keyed by WOF polygon id; this source queries them ON DEMAND by id (one indexed lookup,
9
9
  * memoized) rather than paging the whole table into memory as a code constant — the deliberate
10
10
  * counter to the Pelias "giant dictionary in RAM, no provenance" pattern (see the operator design
11
- * value in memory `feedback-no-load-bearing-trivia`).
11
+ * value in memory `feedback-no-irrelevant-trivia`).
12
12
  *
13
13
  * The asset is the queryable, distributable artifact; the strategy IMPLEMENTATIONS stay in code. An
14
14
  * unknown strategy NAME is surfaced loudly at dispatch (see `lookup.ts`), not silently
15
15
  * swallowed.
16
16
  */
17
17
 
18
- import type { DatabaseSync } from "node:sqlite"
18
+ import { tryParsingJSON } from "@mailwoman/core/json"
19
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
19
20
 
20
- import { tryParsingJSON } from "@mailwoman/core/objects"
21
+ import { ADDRESS_CONVENTION_TABLE, type Convention, type ConventionSource } from "#convention/index"
21
22
 
22
- import { ADDRESS_CONVENTION_TABLE, type Convention, type ConventionSource } from "./convention.ts"
23
-
24
- export class SqliteConventionSource implements ConventionSource {
25
- readonly #db: DatabaseSync
23
+ export class SqliteConventionSource<DB> implements ConventionSource {
24
+ readonly #db: DatabaseClient<DB>
26
25
  readonly #schema: string
27
26
  /**
28
27
  * Memoize per-id lookups (including misses, as `null`) so a hot ancestor chain is queried once.
@@ -31,10 +30,10 @@ export class SqliteConventionSource implements ConventionSource {
31
30
 
32
31
  /**
33
32
  * @param db An open handle to a DB that has the convention asset attached (or is it).
34
- * @param schema The schema name the `address_convention` table lives under (`main` or an ATTACHed shard name —
35
- * `WOFSQLitePlaceLookup` auto-detects which shard carries the table).
33
+ * @param schema The schema name the `address_convention` table lives under (`main` or an ATTACHed extract name —
34
+ * `WOFSQLitePlaceLookup` auto-detects which extract carries the table).
36
35
  */
37
- constructor(db: DatabaseSync, schema: string) {
36
+ constructor(db: DatabaseClient<DB>, schema: string) {
38
37
  this.#db = db
39
38
  this.#schema = schema
40
39
  }
@@ -6,12 +6,12 @@
6
6
  * Small shared helpers for the SQLite-backed lookups.
7
7
  */
8
8
 
9
- import type { DatabaseSync, SQLInputValue } from "node:sqlite"
10
-
11
9
  import { allRows, getRow } from "@mailwoman/core/utils"
10
+ import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
11
+ import { hasColumn as columnExists, tableExists } from "@mailwoman/sqlite/introspection"
12
12
 
13
13
  // The row-shape assertion itself lives in `core` so the readers that cannot depend on this package reach the same
14
- // seam; re-exported here because this module is where this package's readers already look for it.
14
+ // helper; re-exported here because this module is where this package's readers already look for it.
15
15
  export { allRows, getRow } from "@mailwoman/core/utils"
16
16
 
17
17
  /**
@@ -23,8 +23,8 @@ export type PreparedGet<Parameters extends SQLInputValue[], Row> = (...parameter
23
23
  /**
24
24
  * Prepare a single-row query while preserving its exact parameter tuple at every call site.
25
25
  */
26
- export function prepareGet<Parameters extends SQLInputValue[], Row>(
27
- db: DatabaseSync,
26
+ export function prepareGet<Parameters extends SQLInputValue[], Row, DB>(
27
+ db: DatabaseClient<DB>,
28
28
  sql: string
29
29
  ): PreparedGet<Parameters, Row> {
30
30
  const statement = db.prepare(sql)
@@ -40,8 +40,8 @@ export type PreparedAll<Parameters extends SQLInputValue[], Row> = (...parameter
40
40
  /**
41
41
  * Prepare a multi-row query while preserving its exact parameter tuple at every call site.
42
42
  */
43
- export function prepareAll<Parameters extends SQLInputValue[], Row>(
44
- db: DatabaseSync,
43
+ export function prepareAll<Parameters extends SQLInputValue[], Row, DB>(
44
+ db: DatabaseClient<DB>,
45
45
  sql: string
46
46
  ): PreparedAll<Parameters, Row> {
47
47
  const statement = db.prepare(sql)
@@ -51,15 +51,13 @@ export function prepareAll<Parameters extends SQLInputValue[], Row>(
51
51
 
52
52
  /**
53
53
  * True when `name` is a table in the open database. The street-level lookups use this to degrade gracefully on an
54
- * empty/tableless shard — an interrupted `build-*-shard.ts`, or a stray 0-byte file (e.g. `sqlite3 <missing>.db "…"`
55
- * CREATES one) — rather than throwing `no such table` at construction and taking down a whole state's geocode (#568). A
56
- * missing table makes the lookup a no-op miss.
54
+ * empty/tableless extract — an interrupted `build-*-extract.ts`, or a stray 0-byte file (e.g. `sqlite3 <missing>.db
55
+ * "…"` CREATES one) — rather than throwing `no such table` at construction and taking down a whole state's geocode
56
+ * (#568). A missing table makes the lookup a no-op miss.
57
57
  */
58
- export function hasTable(db: DatabaseSync, name: string): boolean {
58
+ export function hasTable<DB>(db: DatabaseClient<DB>, name: string): boolean {
59
59
  try {
60
- const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1").get(name)
61
-
62
- return row !== undefined
60
+ return tableExists(db, name)
63
61
  } catch {
64
62
  return false
65
63
  }
@@ -76,11 +74,9 @@ export function hasTable(db: DatabaseSync, name: string): boolean {
76
74
  * Note the interpolation: PRAGMA does not take bound parameters, so `table` is spliced. Every caller passes a
77
75
  * module-level constant; never pass user input.
78
76
  */
79
- export function hasColumn(db: DatabaseSync, table: string, column: string): boolean {
77
+ export function hasColumn<DB>(db: DatabaseClient<DB>, table: string, column: string): boolean {
80
78
  try {
81
- const rows = allRows<{ name: string }>(db.prepare(`PRAGMA table_info(${table})`))
82
-
83
- return rows.some((r) => String(r.name) === column)
79
+ return columnExists(db, table, column)
84
80
  } catch {
85
81
  return false
86
82
  }