@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
@@ -38,9 +38,13 @@
38
38
  * be wired as a post-step of the main `scripts/build-unified-wof.ts`.
39
39
  */
40
40
 
41
- import type { DatabaseSync } from "node:sqlite"
42
-
41
+ import type { CoincidentLocality } from "@mailwoman/resolver"
43
42
  import { haversineKm } from "@mailwoman/spatial"
43
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
44
+ import { tableExists } from "@mailwoman/sqlite/introspection"
45
+
46
+ import type { WOFDatabase } from "#schema"
47
+ import { allRows } from "#sqlite-utils"
44
48
 
45
49
  /**
46
50
  * Table of places that hold more than one admin role — a locality that is also its county seat. Written by the
@@ -102,16 +106,12 @@ interface CandidateRow {
102
106
  pop: number
103
107
  }
104
108
 
105
- function tableExists(db: DatabaseSync, name: string): boolean {
106
- return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?").get(name)
107
- }
108
-
109
109
  /**
110
110
  * Derive the coincident-roles relation into `db`. Additive — only creates/replaces the `coincident_roles` table; never
111
111
  * touches `spr`/`names`/`ancestors`. Idempotent.
112
112
  */
113
113
  export function buildCoincidentRoles(
114
- db: DatabaseSync,
114
+ db: DatabaseClient<WOFDatabase>,
115
115
  opts: BuildCoincidentRolesOpts = {}
116
116
  ): BuildCoincidentRolesResult {
117
117
  const start = Date.now()
@@ -149,8 +149,8 @@ export function buildCoincidentRoles(
149
149
  // Admin (region/county tier) ⋈ same-name DESCENDANT locality. `place_population` is optional (LEFT
150
150
  // JOIN → 0 when absent). The relative-tolerance filter + relationship classification happen in JS so
151
151
  // the SQL stays a plain join. `spr` exposes the bbox columns we need for the diagonal.
152
- const candidates = db
153
- .prepare(
152
+ const candidates = allRows<CandidateRow>(
153
+ db.prepare(
154
154
  `SELECT r.id AS admin_id, r.placetype AS admin_placetype, r.country AS country, l.id AS locality_id,
155
155
  r.latitude AS rlat, r.longitude AS rlon, l.latitude AS llat, l.longitude AS llon,
156
156
  r.min_latitude, r.min_longitude, r.max_latitude, r.max_longitude,
@@ -163,7 +163,7 @@ export function buildCoincidentRoles(
163
163
  WHERE r.placetype = 'region'
164
164
  AND r.is_current != 0 AND r.is_deprecated = 0`
165
165
  )
166
- .all() as unknown as CandidateRow[]
166
+ )
167
167
 
168
168
  onProgress("filtering", `${candidates.length} candidates`)
169
169
 
@@ -212,7 +212,7 @@ export function buildCoincidentRoles(
212
212
  /**
213
213
  * True iff the relation table exists. Used by the resolver to decide whether completion can run.
214
214
  */
215
- export function coincidentRolesExists(db: DatabaseSync): boolean {
215
+ export function coincidentRolesExists<DB>(db: DatabaseClient<DB>): boolean {
216
216
  return tableExists(db, COINCIDENT_ROLES_TABLE)
217
217
  }
218
218
 
@@ -221,24 +221,24 @@ export function coincidentRolesExists(db: DatabaseSync): boolean {
221
221
  * MULTIPLE same-name descendants; the consumer disambiguates (min distance → population → abstain). Returns an empty
222
222
  * map when the table is absent.
223
223
  */
224
- export function loadCoincidentRoles(db: DatabaseSync): Map<number, CoincidentRole[]> {
224
+ export function loadCoincidentRoles<DB>(db: DatabaseClient<DB>): Map<number, CoincidentRole[]> {
225
225
  const map = new Map<number, CoincidentRole[]>()
226
226
 
227
227
  if (!coincidentRolesExists(db)) return map
228
228
 
229
- const rows = db
230
- .prepare(
231
- `SELECT admin_id, locality_id, relationship_type, admin_placetype, distance_km, locality_population
232
- FROM ${COINCIDENT_ROLES_TABLE}`
233
- )
234
- .all() as unknown as Array<{
229
+ const rows = allRows<{
235
230
  admin_id: number
236
231
  locality_id: number
237
232
  relationship_type: CoincidentRole["relationshipType"]
238
233
  admin_placetype: string
239
234
  distance_km: number
240
235
  locality_population: number
241
- }>
236
+ }>(
237
+ db.prepare(
238
+ `SELECT admin_id, locality_id, relationship_type, admin_placetype, distance_km, locality_population
239
+ FROM ${COINCIDENT_ROLES_TABLE}`
240
+ )
241
+ )
242
242
 
243
243
  for (const r of rows) {
244
244
  const entry: CoincidentRole = {
@@ -260,3 +260,59 @@ export function loadCoincidentRoles(db: DatabaseSync): Map<number, CoincidentRol
260
260
 
261
261
  return map
262
262
  }
263
+
264
+ /**
265
+ * The relation joined with `spr`, as an in-memory map keyed by `admin_id` — the resolver-facing view of
266
+ * {@link loadCoincidentRoles}, carrying the canonical name and coordinates each coincident locality resolves to.
267
+ * Returns an empty map when the relation table is absent.
268
+ */
269
+ export function loadCoincidentLocalities<DB>(db: DatabaseClient<DB>): Map<number, CoincidentLocality[]> {
270
+ const map = new Map<number, CoincidentLocality[]>()
271
+
272
+ if (coincidentRolesExists(db)) {
273
+ const rows = allRows<{
274
+ adminID: number
275
+ id: number
276
+ name: string
277
+ country: string
278
+ lat: number
279
+ lon: number
280
+ relationshipType: string
281
+ population: number
282
+ distanceKm: number
283
+ }>(
284
+ db.prepare(
285
+ `SELECT cr.admin_id AS adminID, s.id AS id, s.name AS name, s.country AS country,
286
+ s.latitude AS lat, s.longitude AS lon,
287
+ cr.relationship_type AS relationshipType, cr.locality_population AS population,
288
+ cr.distance_km AS distanceKm
289
+ FROM ${COINCIDENT_ROLES_TABLE} cr JOIN spr s ON s.id = cr.locality_id`
290
+ )
291
+ )
292
+
293
+ for (const r of rows) {
294
+ const candidate: CoincidentLocality = {
295
+ id: r.id,
296
+ name: r.name,
297
+ placetype: "locality",
298
+ country: r.country,
299
+ lat: r.lat,
300
+ lon: r.lon,
301
+ score: 0,
302
+ relationshipType: r.relationshipType,
303
+ population: r.population,
304
+ distanceKm: r.distanceKm,
305
+ }
306
+
307
+ const list = map.get(r.adminID)
308
+
309
+ if (list) {
310
+ list.push(candidate)
311
+ } else {
312
+ map.set(r.adminID, [candidate])
313
+ }
314
+ }
315
+ }
316
+
317
+ return map
318
+ }
@@ -21,7 +21,7 @@
21
21
  * source).
22
22
  */
23
23
 
24
- import type { FindPlaceQuery, PlaceCandidate } from "./types.ts"
24
+ import type { FindPlaceQuery, PlaceCandidate } from "#types"
25
25
 
26
26
  /**
27
27
  * Soft-scoring weights for the `postcode_area_resolution` strategy: `pc·S_pc + name·S_name + pop·S_pop`.
@@ -76,13 +76,13 @@ export const WORLD_DEFAULT: ResolvedConvention = {
76
76
  export const BUILTIN_STRATEGY_NAMES = ["postcode_area_resolution", "fallback_fuzzy_name_match"] as const
77
77
 
78
78
  /**
79
- * Table name for the convention asset (#290). Carried here so the build script, the runtime source, and the shard
79
+ * Table name for the convention asset (#290). Carried here so the build script, the runtime source, and the extract
80
80
  * auto-detect all agree.
81
81
  */
82
82
  export const ADDRESS_CONVENTION_TABLE = "address_convention"
83
83
 
84
84
  /**
85
- * A named resolution primitive. Returns `null` to abstain (gate unmet / no data) → the dispatcher tries the next
85
+ * A named resolution primitive. Returns `null` to abstain (condition unmet / no data) → the dispatcher tries the next
86
86
  * strategy; returns an array (possibly empty) to claim the result.
87
87
  */
88
88
  export type Strategy = (query: FindPlaceQuery, convention: ResolvedConvention) => Promise<PlaceCandidate[] | null>
@@ -142,8 +142,8 @@ export function mergeConventions(base: Convention, ...overrides: Array<Conventio
142
142
  * region, …, locality). Starts from `WORLD_DEFAULT` so every field is defined regardless of which (if any) ancestors
143
143
  * carry an override.
144
144
  */
145
- export function resolveConvention(source: ConventionSource, ancestorIds: readonly number[]): ResolvedConvention {
146
- const layers = ancestorIds.map((id) => source.get(id))
145
+ export function resolveConvention(source: ConventionSource, ancestorIDs: readonly number[]): ResolvedConvention {
146
+ const layers = ancestorIDs.map((id) => source.get(id))
147
147
  const merged = mergeConventions(WORLD_DEFAULT, ...layers)
148
148
 
149
149
  return {
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for the convention asset — the `address_convention` table `SqliteConventionSource` probes, plus the
7
+ * `meta` provenance row every sealed artifact here carries. The interface is the read/write contract and
8
+ * {@link createAddressConventionTable} creates the table, so a column added to one is a compile error against the
9
+ * other.
10
+ */
11
+
12
+ import type { Kysely } from "kysely"
13
+
14
+ import { ADDRESS_CONVENTION_TABLE } from "#convention/index"
15
+
16
+ /**
17
+ * One convention profile, keyed by the WOF admin polygon it attaches to.
18
+ */
19
+ export interface AddressConventionTable {
20
+ wof_id: number
21
+ /**
22
+ * The `Convention` record, JSON-encoded. Parsed at read time by `SqliteConventionSource`.
23
+ */
24
+ convention: string
25
+ /**
26
+ * Provenance: why this row exists and where it came from.
27
+ */
28
+ source: string
29
+ }
30
+
31
+ /**
32
+ * Key/value provenance for the sealed artifact.
33
+ */
34
+ export interface ConventionMetaTable {
35
+ key: string
36
+ value: string | null
37
+ }
38
+
39
+ export interface ConventionDatabase {
40
+ address_convention: AddressConventionTable
41
+ meta: ConventionMetaTable
42
+ }
43
+
44
+ /**
45
+ * The slice of a Kysely handle the convention DDL touches. Kysely is invariant in its schema parameter, so naming only
46
+ * `schema` lets a builder holding a wider handle pass it without a cast.
47
+ */
48
+ export type ConventionSchemaHandle = Pick<Kysely<ConventionDatabase>, "schema">
49
+
50
+ /**
51
+ * Create `address_convention`. The table name comes from {@link ADDRESS_CONVENTION_TABLE} so the build script, the
52
+ * runtime source, and the extract auto-detect cannot drift apart.
53
+ */
54
+ export async function createAddressConventionTable(db: ConventionSchemaHandle): Promise<void> {
55
+ await db.schema
56
+ .createTable(ADDRESS_CONVENTION_TABLE)
57
+ .addColumn("wof_id", "integer", (column) => column.primaryKey())
58
+ .addColumn("convention", "text", (column) => column.notNull())
59
+ .addColumn("source", "text", (column) => column.notNull())
60
+ .execute()
61
+ }
62
+
63
+ /**
64
+ * Create the artifact's `meta` table.
65
+ */
66
+ export async function createConventionMetaTable(db: ConventionSchemaHandle): Promise<void> {
67
+ await db.schema
68
+ .createTable("meta")
69
+ .addColumn("key", "text", (column) => column.primaryKey())
70
+ .addColumn("value", "text")
71
+ .execute()
72
+ }
@@ -8,9 +8,9 @@
8
8
  * updated at gazetteer REBUILD, not by a hand-edited code PR after someone remembers:
9
9
  *
10
10
  * - `country_coverage`: the hard-country-filter coverage record (#743/#194) — per-country
11
- * promote-gate verdicts + the measured hard-resolve rates that used to live in a code comment on
11
+ * promotion-eval verdicts + the measured hard-resolve rates that used to live in a code comment on
12
12
  * `HARD_PLACE_COUNTRY_SAFELIST`. Presence = measured; `hard_filter_safe = 0` = measured and
13
- * FAILED the gate (FI 69.5%, PL 77.8%) — distinguishable from a country never measured at all
13
+ * FAILED the check (FI 69.5%, PL 77.8%) — distinguishable from a country never measured at all
14
14
  * (the meaning-of-zero rule, `docs/engineering/reference/layer-contract.mdx`).
15
15
  * - `country_bbox`: the coarse guard-B plausibility boxes that used to live in
16
16
  * `resolver/plausibility.ts`'s `COUNTRY_BBOX`. An absent row fails open (never trips the guard),
@@ -23,17 +23,16 @@
23
23
  * manifest returns `undefined` and every consumer falls back to the code constants byte-identically.
24
24
  */
25
25
 
26
- import type { DatabaseSync } from "node:sqlite"
27
-
28
26
  import {
29
27
  hardCountrySafelistFromCoverage,
30
28
  type CountryBBoxFact,
31
29
  type CountryCoverageFact,
32
30
  type GazetteerArtifactCoverage,
33
31
  } from "@mailwoman/core/resolver"
32
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
34
33
  import { sql, type Kysely } from "kysely"
35
34
 
36
- import { hasTable } from "./sqlite-utils.ts"
35
+ import { allRows, hasTable } from "#sqlite-utils"
37
36
 
38
37
  /**
39
38
  * One country's hard-filter coverage measurement — the storage form of {@link CountryCoverageFact}.
@@ -44,7 +43,7 @@ export interface CountryCoverageTable {
44
43
  */
45
44
  country: string
46
45
  /**
47
- * 0/1 — the promote-gate verdict (a verdict column, NOT re-derived from the rate; see the fact type's docstring).
46
+ * 0/1 — the promotion-eval verdict (a verdict column, NOT re-derived from the rate; see the fact type's docstring).
48
47
  */
49
48
  hard_filter_safe: number
50
49
  /**
@@ -56,11 +55,11 @@ export interface CountryCoverageTable {
56
55
  */
57
56
  sample_size: number | null
58
57
  /**
59
- * ISO-8601 date of the measurement / promote gate.
58
+ * ISO-8601 date of the measurement / promote eval.
60
59
  */
61
60
  measured_at: string
62
61
  /**
63
- * The receipt: which panel/gate produced this row.
62
+ * The receipt: which panel/check produced this row.
64
63
  */
65
64
  source: string
66
65
  }
@@ -92,7 +91,7 @@ export interface GazetteerCoverageDatabase {
92
91
  }
93
92
 
94
93
  /**
95
- * Table names the lookup probes (existence-gated, so a candidate.db built before the manifest is byte-stable).
94
+ * Table names the lookup probes (existence-restricted, so a candidate.db built before the manifest is byte-stable).
96
95
  */
97
96
  export const COUNTRY_COVERAGE_TABLE = "country_coverage"
98
97
  /**
@@ -185,7 +184,7 @@ export async function writeGazetteerCoverageManifest(
185
184
  * purpose: this runs inside {@link WOFCandidateTableLookup}'s synchronous constructor (the sync-reader carve-out in
186
185
  * `AGENTS.md`), and the tables are a few dozen rows read once per open.
187
186
  */
188
- export function readGazetteerCoverageManifest(db: DatabaseSync): GazetteerArtifactCoverage | undefined {
187
+ export function readGazetteerCoverageManifest<DB>(db: DatabaseClient<DB>): GazetteerArtifactCoverage | undefined {
189
188
  const hasCoverage = hasTable(db, COUNTRY_COVERAGE_TABLE)
190
189
  const hasBBox = hasTable(db, COUNTRY_BBOX_TABLE)
191
190
 
@@ -194,11 +193,11 @@ export function readGazetteerCoverageManifest(db: DatabaseSync): GazetteerArtifa
194
193
  const countryCoverage = new Map<string, CountryCoverageFact>()
195
194
 
196
195
  if (hasCoverage) {
197
- const rows = db
198
- .prepare(
196
+ const rows = allRows<CountryCoverageTable>(
197
+ db.prepare(
199
198
  `SELECT country, hard_filter_safe, hard_resolve_rate, sample_size, measured_at, source FROM ${COUNTRY_COVERAGE_TABLE}`
200
199
  )
201
- .all() as unknown as CountryCoverageTable[]
200
+ )
202
201
 
203
202
  for (const row of rows) {
204
203
  const country = String(row.country).toUpperCase()
@@ -217,9 +216,9 @@ export function readGazetteerCoverageManifest(db: DatabaseSync): GazetteerArtifa
217
216
  const countryBBoxes = new Map<string, CountryBBoxFact>()
218
217
 
219
218
  if (hasBBox) {
220
- const rows = db
221
- .prepare(`SELECT country, lat_min, lat_max, lon_min, lon_max, source FROM ${COUNTRY_BBOX_TABLE}`)
222
- .all() as unknown as CountryBBoxTable[]
219
+ const rows = allRows<CountryBBoxTable>(
220
+ db.prepare(`SELECT country, lat_min, lat_max, lon_min, lon_max, source FROM ${COUNTRY_BBOX_TABLE}`)
221
+ )
223
222
 
224
223
  for (const row of rows) {
225
224
  const country = String(row.country).toUpperCase()
@@ -0,0 +1,334 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Pass 1c of the candidate build (#1737): resurrect deprecated-with-no-successor WOF localities that a second
7
+ * source independently attests. Extracted from `build-candidate.ts` as a cohesive unit — the checks, their measured
8
+ * constants, and the GeoNames dump reader live together here; the build calls {@link resurrectCurrencyHoles} once,
9
+ * between the primaries pass and the alias pass.
10
+ */
11
+
12
+ import { pathExists } from "@mailwoman/core/fs/readers"
13
+ import { isStrictlyFiner } from "@mailwoman/core/resources/whosonfirst"
14
+ import { haversineKm } from "@mailwoman/spatial"
15
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
16
+ import { resolvePath } from "path-ts"
17
+ import { TSVSpliterator } from "spliterator"
18
+
19
+ import type { loadImportanceIndex } from "#candidate-importance"
20
+ import type { CandidateDatabase } from "#candidate-schema"
21
+ import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
22
+ import type { WOFDatabase } from "#schema"
23
+ import { normalizeLocalityForKey } from "#street/normalize"
24
+
25
+ /**
26
+ * Corroboration radius for the currency backfill (#1737), km — both for the live-near blocker and the GeoNames
27
+ * attestation. Measured basis (2026-08-19 prototype, GB locality slice): at 10 km, 20 of 108 dead names resurrect —
28
+ * Rochester (Kent), Aldershot, Staines, Telford, Ebbw Vale among them — while the `Birmingham/Wolverhampton/…`
29
+ * conurbation blobs stay dead (no attestation) and Swansea/Wrexham stay out because a live same-name row already serves
30
+ * them within the radius.
31
+ */
32
+ const CURRENCY_BACKFILL_RADIUS_KM = 10
33
+
34
+ /**
35
+ * Minimum attestor population for a resurrection. The same prototype measured 44 of 108 dead GB names attested but
36
+ * under this floor — hamlet-scale ghosts whose absence from the index nobody has reported. A floor keeps the pass
37
+ * answering the measured defect (real settlements) rather than re-importing the tail WOF chose to prune.
38
+ */
39
+ const CURRENCY_BACKFILL_POP_FLOOR = 1000
40
+
41
+ /**
42
+ * Pass 1c (#1737): resurrect deprecated-with-no-successor localities that a second source independently attests.
43
+ *
44
+ * WOF deprecations WITH a successor need nothing — the successor indexes. A deprecation with `is_superseded = 0` on a
45
+ * populated place is the shape of an upstream mistake (Rochester Kent, Aldershot, Telford; 120 GB localities alone),
46
+ * and it is indistinguishable from a correct pruning at this layer without outside evidence. So every resurrection
47
+ * requires all three conditions, positive evidence only:
48
+ *
49
+ * 1. NO live same-name spr row of any placetype within {@link CURRENCY_BACKFILL_RADIUS_KM} of the dead record — a live row
50
+ * means the place is alive (possibly under another placetype) and there is no hole. A DISTANT same-name row is a
51
+ * namesake and does not block.
52
+ * 2. A GeoNames feature-class-P attestation of the same folded name within the radius.
53
+ * 3. The attestor at or above {@link CURRENCY_BACKFILL_POP_FLOOR}.
54
+ *
55
+ * The staged row keeps the WOF identity — id, name, centroid, bbox, region ancestry — because the dead record's own
56
+ * data is not what is wrong with it. GeoNames contributes exactly two things: the attestation, and the population that
57
+ * lets the row stand in prominence races (the dead record's own population is absent). Each name is judged once per
58
+ * country; the resurrected place joins `attrs`, so the alias pass explodes its alt names like any primary's.
59
+ */
60
+ export interface CurrencyBackfillOutcomes {
61
+ judged: number
62
+ blocked: number
63
+ unattested: number
64
+ floored: number
65
+ resurrected: number
66
+ }
67
+
68
+ /**
69
+ * One country's read of the check — every dead name judged, by outcome and by the dead row's placetype — so a census
70
+ * can say what a wider dead-row query admits before a build carries it.
71
+ */
72
+ export interface CurrencyBackfillCountryReport extends CurrencyBackfillOutcomes {
73
+ country: string
74
+ deadPlacetypes: readonly string[]
75
+ dumpPresent: boolean
76
+ byDeadPlacetype: Record<string, CurrencyBackfillOutcomes>
77
+ /**
78
+ * The first resurrected names, `placetype:name`, so a census reads what a wider query admits — capped, because the
79
+ * report is a receipt and not the table.
80
+ */
81
+ sample: string[]
82
+ }
83
+
84
+ const REPORT_SAMPLE_SIZE = 25
85
+
86
+ /**
87
+ * The placetypes a dead row may carry to be judged at all. `locality` is the shipped default; `localadmin` is the
88
+ * second cause #1746 named (one GB row of seventeen) and is admitted through this option once its census is read.
89
+ */
90
+ export const DEFAULT_DEAD_PLACETYPES: readonly string[] = ["locality"]
91
+
92
+ function emptyOutcomes(): CurrencyBackfillOutcomes {
93
+ return { judged: 0, blocked: 0, unattested: 0, floored: 0, resurrected: 0 }
94
+ }
95
+
96
+ export async function resurrectCurrencyHoles(ctx: {
97
+ src: DatabaseClient<WOFDatabase>
98
+ /**
99
+ * The candidate build's transaction. Required unless `dryRun` — a dry run judges every row and stages nothing.
100
+ */
101
+ tx?: DatabaseClient<CandidateDatabase>
102
+ geonamesDir: string
103
+ countries: readonly string[]
104
+ attrs: Map<number, PlaceAttrs>
105
+ ccID: (code: string | null) => number
106
+ ptID: (pt: string | null) => number
107
+ regionOf: Map<number, number>
108
+ importance: ReturnType<typeof loadImportanceIndex> | undefined
109
+ stageRow: StageRow
110
+ progress: (phase: string, message: string) => void
111
+ /**
112
+ * Which dead placetypes are judged. Default {@link DEFAULT_DEAD_PLACETYPES}.
113
+ */
114
+ deadPlacetypes?: readonly string[]
115
+ /**
116
+ * Judge and count, stage nothing — the census mode. No transaction is opened.
117
+ */
118
+ dryRun?: boolean
119
+ /**
120
+ * Receives one report per country judged (a country with no dump or no dead rows reports zero outcomes).
121
+ */
122
+ onCountry?: (report: CurrencyBackfillCountryReport) => void
123
+ }): Promise<number> {
124
+ const deadPlacetypes = ctx.deadPlacetypes ?? DEFAULT_DEAD_PLACETYPES
125
+ const dryRun = ctx.dryRun === true
126
+
127
+ if (!dryRun && !ctx.tx) {
128
+ throw new Error(
129
+ "resurrectCurrencyHoles: a build run needs the candidate transaction (tx); pass dryRun to judge only"
130
+ )
131
+ }
132
+
133
+ const deadStmt = ctx.src.prepare(
134
+ `SELECT id, name, placetype, latitude, longitude, min_latitude, min_longitude, max_latitude, max_longitude
135
+ FROM spr
136
+ WHERE country = ? AND placetype IN (${deadPlacetypes.map(() => "?").join(", ")})
137
+ AND is_current = 0 AND is_deprecated = 1 AND is_superseded = 0`
138
+ )
139
+
140
+ const liveStmt = ctx.src.prepare(
141
+ `SELECT latitude, longitude, placetype FROM spr WHERE country = ? AND name = ? AND is_current != 0`
142
+ )
143
+
144
+ let total = 0
145
+
146
+ for (const country of ctx.countries) {
147
+ const cc = country.toUpperCase()
148
+ const dumpPath = resolvePath(ctx.geonamesDir, `${cc}.txt`)
149
+
150
+ const report: CurrencyBackfillCountryReport = {
151
+ country: cc,
152
+ deadPlacetypes,
153
+ dumpPresent: await pathExists(dumpPath),
154
+ ...emptyOutcomes(),
155
+ byDeadPlacetype: {},
156
+ sample: [],
157
+ }
158
+
159
+ if (!report.dumpPresent) {
160
+ ctx.progress("currency-backfill", `${cc}: no GeoNames dump at ${dumpPath} — holes stay dead`)
161
+ ctx.onCountry?.(report)
162
+
163
+ continue
164
+ }
165
+
166
+ // Dead rows FIRST: a country with no deprecated-no-successor localities needs no attestors at all, and
167
+ // loading a national dump to judge zero rows is pure heap pressure on a build already near its ceiling
168
+ // (the first live run OOM'd in a later pass with JP/KR dumps loaded for 0 dead names each).
169
+ const dead = deadStmt.all(cc, ...deadPlacetypes)
170
+
171
+ if (!dead.length) {
172
+ ctx.progress("currency-backfill", `${cc}: 0 dead names — dump not loaded`)
173
+ ctx.onCountry?.(report)
174
+
175
+ continue
176
+ }
177
+
178
+ // Only the dead names' own folded keys can ever be probed, so only those keys are worth holding —
179
+ // the rest of the national dump streams through without residency.
180
+ const deadKeys = new Set<string>()
181
+
182
+ for (const d of dead) {
183
+ const k = normalizeLocalityForKey(String(d.name ?? ""))
184
+
185
+ if (k) {
186
+ deadKeys.add(k)
187
+ }
188
+ }
189
+
190
+ // Folded name → P-class attestors. GeoNames columns by index: 1 name, 2 ascii, 4 lat, 5 lon,
191
+ // 6 feature_class, 14 population.
192
+ const attestors = new Map<string, { lat: number; lon: number; pop: number }[]>()
193
+
194
+ for await (const f of TSVSpliterator.fromAsync(dumpPath, { header: false })) {
195
+ if (f[6] !== "P") continue
196
+
197
+ const keys = [normalizeLocalityForKey(String(f[1] ?? "")), normalizeLocalityForKey(String(f[2] ?? ""))].filter(
198
+ (key) => key && deadKeys.has(key)
199
+ )
200
+
201
+ if (!keys.length) continue
202
+
203
+ const row = { lat: Number(f[4]), lon: Number(f[5]), pop: Number(f[14]) || 0 }
204
+
205
+ for (const key of new Set(keys)) {
206
+ const bag = attestors.get(key)
207
+
208
+ if (bag) {
209
+ bag.push(row)
210
+ } else {
211
+ attestors.set(key, [row])
212
+ }
213
+ }
214
+ }
215
+
216
+ const seen = new Set<string>()
217
+
218
+ const count = (deadPlacetype: string, outcome: keyof CurrencyBackfillOutcomes): void => {
219
+ report[outcome] += 1
220
+ const bucket = (report.byDeadPlacetype[deadPlacetype] ??= emptyOutcomes())
221
+
222
+ bucket[outcome] += 1
223
+ }
224
+
225
+ if (!dryRun) {
226
+ ctx.tx!.exec("BEGIN")
227
+ }
228
+
229
+ for (const d of dead) {
230
+ const name = String(d.name ?? "")
231
+ const pkey = normalizeLocalityForKey(name)
232
+
233
+ if (!pkey || seen.has(pkey)) continue
234
+ seen.add(pkey)
235
+ // Read from the row: the query admits whatever `deadPlacetypes` names, and the rank comparison below must
236
+ // judge each candidate against ITS dead rung, never a hardcoded one.
237
+ const deadPlacetype = String(d.placetype ?? "locality")
238
+
239
+ count(deadPlacetype, "judged")
240
+ const dLat = Number(d.latitude)
241
+ const dLon = Number(d.longitude)
242
+
243
+ // A live row blocks only when it is AT LEAST AS COARSE as the dead one. The original check compared name and
244
+ // distance alone, on the premise that a nearby same-name row means "the place is alive under another
245
+ // placetype" — true for a place recorded twice, false for a placetype DEMOTION, which is the shape that
246
+ // actually occurs: WOF retired `Gillingham` the locality (pop 101,187) and kept `Gillingham` the
247
+ // neighbourhood 3.2 km away, and the check read the surviving CHILD as covering its own dead parent.
248
+ // Sixteen of seventeen GB refusals had exactly that shape (#1746).
249
+ //
250
+ // An UNRANKED placetype blocks, which is the conservative direction: this check's failure mode is inventing
251
+ // a place, so a row we cannot rank is treated as covering rather than waved through.
252
+ const liveNear = liveStmt.all(cc, name).some((row) => {
253
+ if (haversineKm(dLat, dLon, Number(row.latitude), Number(row.longitude)) > CURRENCY_BACKFILL_RADIUS_KM) {
254
+ return false
255
+ }
256
+
257
+ // Blocks UNLESS the live row is strictly finer. The equal rung must still block — a live `locality`
258
+ // covers a dead `locality` — and an unranked placetype blocks too, since this check's failure mode
259
+ // is inventing a place.
260
+ return isStrictlyFiner(String(row.placetype ?? ""), deadPlacetype) !== true
261
+ })
262
+
263
+ if (liveNear) {
264
+ count(deadPlacetype, "blocked")
265
+
266
+ continue
267
+ }
268
+
269
+ const near = (attestors.get(pkey) ?? []).filter(
270
+ (g) => haversineKm(dLat, dLon, g.lat, g.lon) <= CURRENCY_BACKFILL_RADIUS_KM
271
+ )
272
+
273
+ if (!near.length) {
274
+ count(deadPlacetype, "unattested")
275
+
276
+ continue
277
+ }
278
+
279
+ const pop = Math.max(...near.map((g) => g.pop))
280
+
281
+ if (pop < CURRENCY_BACKFILL_POP_FLOOR) {
282
+ count(deadPlacetype, "floored")
283
+
284
+ continue
285
+ }
286
+
287
+ count(deadPlacetype, "resurrected")
288
+
289
+ if (report.sample.length < REPORT_SAMPLE_SIZE) {
290
+ report.sample.push(`${deadPlacetype}:${name}`)
291
+ }
292
+
293
+ if (dryRun) continue
294
+
295
+ const sid = Number(d.id)
296
+
297
+ const a: PlaceAttrs = {
298
+ cid: ctx.ccID(cc),
299
+ rid: ctx.regionOf.get(sid) ?? 0,
300
+ ptid: ctx.ptID(deadPlacetype),
301
+ name,
302
+ lat: dLat,
303
+ lon: dLon,
304
+ mnLat: Number(d.min_latitude),
305
+ mnLon: Number(d.min_longitude),
306
+ mxLat: Number(d.max_latitude),
307
+ mxLon: Number(d.max_longitude),
308
+ pop,
309
+ neg: -Math.log10(pop + 1),
310
+ pkey,
311
+ imp: ctx.importance?.find(name, cc, deadPlacetype, dLat, dLon) ?? null,
312
+ }
313
+
314
+ ctx.attrs.set(sid, a)
315
+ ctx.stageRow(pkey, a, sid, 1)
316
+ }
317
+
318
+ if (!dryRun) {
319
+ ctx.tx!.exec("COMMIT")
320
+ }
321
+
322
+ ctx.progress(
323
+ "currency-backfill",
324
+ `${cc}: ${report.resurrected} resurrected of ${report.judged} dead names ` +
325
+ `(${report.blocked} blocked by a live near row, ${report.unattested} unattested, ` +
326
+ `${report.floored} under the population floor)${dryRun ? " — dry run, nothing staged" : ""}`
327
+ )
328
+
329
+ ctx.onCountry?.(report)
330
+ total += report.resurrected
331
+ }
332
+
333
+ return total
334
+ }