@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
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The OWN-NAME VARIANT predicate (#1882): is an alias surface the holder's own primary name in
7
+ * another orthography — a romanization (`Брэст` → `brest`), a spacing/diacritic variant
8
+ * (`George Town` → `georgetown`), an abbreviation expansion (`St. George's` → `Saint George's`)
9
+ * — rather than a different name that merely shares the folded key?
10
+ *
11
+ * The comparator is `levenshteinSimilarity`, NOT Jaro-Winkler: JW's common-prefix bonus scores
12
+ * the required NEGATIVE case (`chanchun` vs `cancun`, 0.925) above real positives
13
+ * (`saint george s` vs `st georges` expanded, 0.914), so no JW threshold separates them.
14
+ * Measured on the #1882 census contests, edit similarity separates every case with margin:
15
+ *
16
+ * - IN: `brest`/`brest` 1.0 · `george town`/`georgetown` 0.909 · `saint george s`/`saint georges`
17
+ * 0.929 · `adamovka`/`adamowka` 0.875
18
+ * - OUT: `lievin`/`levin` 0.833 (Liévin FR vs Levin NZ — different places with near-identical
19
+ * names; the panel's `41 Weraroa Road, Levin` row needs this side) · `chanchun`/`cancun` 0.75 ·
20
+ * `augsburg`/`augusta` 0.375 · `west bay`/`west end` 0.625 · `derry`/`londonderry` 0.455
21
+ * (Derry/Londonderry is a DUAL NAME, not a variant — its own follow-up on #1882)
22
+ *
23
+ * An unhandled script (Arabic, Hebrew, CJK — the romanizer covers Cyrillic only) answers NULL,
24
+ * never "different name": absence of a verdict must not stamp anything (the meaning-of-zero rule).
25
+ * The #1882 census counted 396 demoted Arabic/Hebrew-primary aliases left unclassified by this.
26
+ */
27
+
28
+ import { levenshteinSimilarity } from "@mailwoman/match/comparators"
29
+
30
+ /**
31
+ * Edit-similarity floor for the variant verdict. The measured band: nearest admitted pair 0.875
32
+ * (`adamovka`/`adamowka`), nearest refused pair 0.833 (`lievin`/`levin`).
33
+ */
34
+ export const VARIANT_SIMILARITY_MIN = 0.85
35
+
36
+ /**
37
+ * BGN/PCGN-flavored Cyrillic romanization, folded to the name-key alphabet. Digraph outputs (zh, kh, ts, ch, sh, shch,
38
+ * yu, ya) match the dominant transliteration conventions the gazetteer's Latin aliases actually use; the `w`/`v` and
39
+ * `kh`/`h` style variance between systems is what the edit-similarity threshold absorbs.
40
+ */
41
+ const CYRILLIC_TO_LATIN: Record<string, string> = {
42
+ а: "a",
43
+ б: "b",
44
+ в: "v",
45
+ г: "g",
46
+ д: "d",
47
+ е: "e",
48
+ ё: "e",
49
+ ж: "zh",
50
+ з: "z",
51
+ и: "i",
52
+ і: "i",
53
+ й: "i",
54
+ к: "k",
55
+ л: "l",
56
+ м: "m",
57
+ н: "n",
58
+ о: "o",
59
+ п: "p",
60
+ р: "r",
61
+ с: "s",
62
+ т: "t",
63
+ у: "u",
64
+ ў: "u",
65
+ ф: "f",
66
+ х: "kh",
67
+ ц: "ts",
68
+ ч: "ch",
69
+ ш: "sh",
70
+ щ: "shch",
71
+ ъ: "",
72
+ ы: "y",
73
+ ь: "",
74
+ э: "e",
75
+ ю: "yu",
76
+ я: "ya",
77
+ ґ: "g",
78
+ є: "e",
79
+ ї: "i",
80
+ }
81
+
82
+ /**
83
+ * Leading-word abbreviations expanded BEFORE comparison, so `st georges` meets `saint george s` inside the edit
84
+ * threshold. Whole-word only — `st` inside `stanley` never expands.
85
+ */
86
+ const NAME_ABBREVIATIONS: ReadonlyArray<[RegExp, string]> = [
87
+ [/\bst\b/g, "saint"],
88
+ [/\bste\b/g, "sainte"],
89
+ [/\bmt\b/g, "mount"],
90
+ [/\bft\b/g, "fort"],
91
+ ]
92
+
93
+ /**
94
+ * Expand the whole-word abbreviations above.
95
+ */
96
+ export function expandNameAbbreviations(key: string): string {
97
+ let out = key
98
+
99
+ for (const [pattern, replacement] of NAME_ABBREVIATIONS) {
100
+ out = out.replaceAll(pattern, replacement)
101
+ }
102
+
103
+ return out
104
+ }
105
+
106
+ /**
107
+ * Romanize a folded name key to the a–z0–9/space alphabet. `null` when characters outside the covered scripts remain —
108
+ * an unhandled script is NO VERDICT, not a mismatch.
109
+ */
110
+ export function romanizeNameKey(key: string): string | null {
111
+ let out = ""
112
+
113
+ for (const ch of key.toLowerCase()) {
114
+ out += CYRILLIC_TO_LATIN[ch] ?? ch
115
+ }
116
+
117
+ out = out
118
+ .normalize("NFD")
119
+ .replaceAll(/[̀-ͯ]/g, "")
120
+ .replaceAll(/\s+/g, " ")
121
+ .trim()
122
+
123
+ return /^[a-z0-9 ]*$/.test(out) ? out : null
124
+ }
125
+
126
+ /**
127
+ * Edit similarity between a holder's primary name key and one of its alias keys, both romanized and
128
+ * abbreviation-expanded. `null` when either side's script is uncovered.
129
+ */
130
+ export function ownNameSimilarity(primaryKey: string, aliasKey: string): number | null {
131
+ const primary = romanizeNameKey(primaryKey)
132
+ const alias = romanizeNameKey(aliasKey)
133
+
134
+ if (primary == null || alias == null || !primary || !alias) return null
135
+
136
+ return levenshteinSimilarity(expandNameAbbreviations(primary), expandNameAbbreviations(alias))
137
+ }
138
+
139
+ /**
140
+ * The stamp predicate: the alias surface is the holder's own name in another orthography.
141
+ */
142
+ export function isOwnNameVariant(primaryKey: string, aliasKey: string): boolean {
143
+ const similarity = ownNameSimilarity(primaryKey, aliasKey)
144
+
145
+ return similarity != null && similarity >= VARIANT_SIMILARITY_MIN
146
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The per-place record every candidate-staging pass writes its rows from.
6
+ *
7
+ * Pass 1 reduces each current `spr` row to one {@link PlaceAttrs}; every later pass (alias bags,
8
+ * region abbreviations, country display names, the currency backfill, the extract folds) discovers
9
+ * ADDITIONAL name keys for a place already in that map and stages a row against the same record.
10
+ * That is what keeps each candidate row denormalized without re-reading the source, and it is why
11
+ * a pass needs exactly four things to stage: the key it found, the place, the id the row hangs on,
12
+ * and whether the key is the place's canonical name.
13
+ */
14
+
15
+ export interface PlaceAttrs {
16
+ cid: number
17
+ rid: number
18
+ ptid: number
19
+ name: string
20
+ lat: number
21
+ lon: number
22
+ mnLat: number
23
+ mnLon: number
24
+ mxLat: number
25
+ mxLon: number
26
+ pop: number
27
+ neg: number
28
+ pkey: string
29
+ /**
30
+ * The place's toponym-fame score, or null when the score source has no measurement for it (#28). A property of the
31
+ * PLACE, so it rides {@link StageRow} onto the alias and abbrev rows too — that is how a bare `Moscow` reaches
32
+ * Москва's score through the alias row that carries the key.
33
+ */
34
+ imp: number | null
35
+ }
36
+
37
+ /**
38
+ * Stage one candidate row: a normalized name key, the place it belongs to, the source id the row hangs on, and whether
39
+ * the key is that place's canonical name (`is_primary`).
40
+ *
41
+ * `sid` is passed separately rather than read off the place because a extract fold and the alias pass stage rows for
42
+ * ids the admin `attrs` map never held.
43
+ */
44
+ export type StageRow = (k: string, a: PlaceAttrs, sid: number, isPrimary: number) => void
@@ -0,0 +1,195 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for the candidate gazetteer's ANCESTORS sidecar (`candidate_ancestor` +
7
+ * `candidate_interval`) — the containment lineage the candidate table itself cannot answer, built
8
+ * into the same `candidate.db` by `build-candidate.ts` and read by
9
+ * {@link WOFCandidateTableLookup.ancestors}.
10
+ *
11
+ * ENCODING: closure-lite rows, denormalized — one row per (place, ancestor) edge carrying the
12
+ * parent's placetype, display name and folded key — rather than a fixed-slot id chain on the
13
+ * candidate row. Decided by the two consumers:
14
+ *
15
+ * 1. The admin-coherence check needs the WINNER's chain as (placetype, name) pairs in one probe.
16
+ * A fixed-slot `[id;8]` chain answers with ids, and every id then needs a name lookup the
17
+ * artifact has no per-id table for — up to 8 indirections where the closure row has zero.
18
+ * 2. The account layer needs EVERY candidate under a `name_key` enumerable WITH its chain from
19
+ * one artifact probe ("present-but-outranked, discriminated by containment"). That is the
20
+ * candidate probe (contiguous) followed by one `spr_id`-clustered closure probe per candidate
21
+ * — each a handful of adjacent pages.
22
+ *
23
+ * Denormalizing the parent onto the edge is the same discipline as the candidate table itself:
24
+ * this artifact is read over HTTP byte ranges, where a join to a dimension table scatters page
25
+ * fetches, and a `WITHOUT ROWID` B-tree clustered on `(spr_id, depth)` keeps a whole chain in
26
+ * 1-2 pages. The repeated parent strings are the price of the zero-join read, paid at build time.
27
+ *
28
+ * `depth` is 1 for the NEAREST ancestor (deepest containment tier), increasing outward to the
29
+ * country — the same nearest-first order `ancestry.ts` serves for the FTS backend, so the two
30
+ * backends' `ancestors()` agree by construction. The order within a place is deterministic:
31
+ * containment depth descending (`placetypeDepth`), then ancestor id ascending, capped at
32
+ * {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the SHARED {@link normalizeLocalityForKey}
33
+ * fold — the same fold `candidate.name_key` is built with, so a chain entry and a candidate key
34
+ * compare under one normalizer.
35
+ *
36
+ * `candidate_interval` carries pre/post-order labels over the CANONICAL-PARENT FOREST, assigned
37
+ * at build time: `a` contains `d` ⟺ `a.pre <= d.pre AND d.post <= a.post` — O(1) in either
38
+ * direction with no chain scan and no knowledge of either side's tier — and the descendants of
39
+ * `a` are the contiguous range `pre BETWEEN a.pre AND a.post`. Interval labels are classically
40
+ * avoided for their relabel-on-update cost; this database is a sealed read-only artifact rebuilt
41
+ * whole, which is exactly the regime where that cost is void.
42
+ *
43
+ * THE DAG CAVEAT, and the recorded choice: WOF places can carry more than one parent (multiple
44
+ * hierarchies, ambiguous boundaries). `candidate_ancestor` keeps EVERY parent — the closure rows
45
+ * are the complete containment record. A single interval pair can only encode a tree, so the
46
+ * interval forest links each place to ONE canonical parent: its depth-1 edge — the finest
47
+ * containment tier, lowest ancestor id — the same MIN-stability convention the candidate table's
48
+ * `region_id` stamp uses. A containment question about a NON-canonical hierarchy must consult the
49
+ * closure rows; the interval answer for it is `false`, which is why interval verdicts are
50
+ * "contained along the canonical hierarchy", never "not contained at all".
51
+ *
52
+ * ABSENCE SEMANTICS (meaning-of-zero): a place with no `candidate_interval` row has no recorded
53
+ * ancestry in the source (extract-fed postcodes and localities, isolated places, cycle-skipped
54
+ * rows). Absence is UNVERIFIABLE, never a containment verdict.
55
+ */
56
+
57
+ import { sql, type Kysely } from "kysely"
58
+
59
+ // Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's
60
+ // DB parameter is invariant, so the DDL functions must take the FULL database type their caller
61
+ // holds. Erased at runtime.
62
+ import type { CandidateDatabase } from "#candidate-schema"
63
+ import type { NameKey } from "#street/normalize"
64
+
65
+ /**
66
+ * The deepest chain the sidecar stores per place. WOF containment within the resolvable placetypes (country …
67
+ * microhood) never legitimately exceeds this; anything past it is source noise the build drops (and counts) rather than
68
+ * stores.
69
+ */
70
+ export const MAX_ANCESTOR_DEPTH = 8
71
+
72
+ /**
73
+ * The closure-row table's name — probed with `hasTable` by the reader, so an artifact predating the sidecar degrades to
74
+ * "no ancestors capability" rather than `no such table`.
75
+ */
76
+ export const CANDIDATE_ANCESTOR_TABLE = "candidate_ancestor"
77
+
78
+ /**
79
+ * The interval-label table's name — the closure table's seal-time sibling; existence-restricted the same way.
80
+ */
81
+ export const CANDIDATE_INTERVAL_TABLE = "candidate_interval"
82
+
83
+ /**
84
+ * One (place, ancestor) edge, denormalized so a chain read is a single clustered probe (no join).
85
+ */
86
+ export interface CandidateAncestorTable {
87
+ /**
88
+ * WOF id of the place whose chain this row belongs to — the same `spr_id` the candidate table resolves to.
89
+ */
90
+ spr_id: number
91
+ /**
92
+ * 1 = nearest ancestor, increasing outward to the country. Deterministic within a place (containment depth
93
+ * descending, then ancestor id ascending), so `(spr_id, depth)` is a stable primary key across rebuilds.
94
+ */
95
+ depth: number
96
+ /**
97
+ * WOF id of the ancestor.
98
+ */
99
+ parent_spr_id: number
100
+ /**
101
+ * Small int from the shared `placetype_codes` dictionary (the same one the candidate table uses).
102
+ */
103
+ parent_placetype_id: number
104
+ /**
105
+ * The ancestor's canonical display name — what {@link Ancestor.name} serves, matching the FTS backend's register.
106
+ */
107
+ parent_name: string
108
+ /**
109
+ * The SHARED {@link normalizeLocalityForKey} fold of `parent_name` — comparable against `candidate.name_key` and
110
+ * against a query-side fold under one normalizer, by construction.
111
+ */
112
+ parent_name_key: NameKey
113
+ }
114
+
115
+ /**
116
+ * Pre/post-order labels over the canonical-parent forest — one row per place WITH recorded ancestry (see the module
117
+ * docstring for absence semantics). `pre < post` always; labels are unique across the artifact.
118
+ */
119
+ export interface CandidateIntervalTable {
120
+ spr_id: number
121
+ pre: number
122
+ post: number
123
+ }
124
+
125
+ /**
126
+ * The sidecar tables, for a `Kysely` view over the candidate DB. `CandidateDatabase` (candidate-schema.ts) extends
127
+ * this, so the builder's one typed client sees both families.
128
+ */
129
+ export interface CandidateAncestorsDatabase {
130
+ candidate_ancestor: CandidateAncestorTable
131
+ candidate_interval: CandidateIntervalTable
132
+ }
133
+
134
+ /**
135
+ * The `candidate_ancestor` columns in clustered-key order — the first two ARE the primary key, and the builder's
136
+ * positional `INSERT` binds by this order. Keep in sync with {@link CandidateAncestorTable}.
137
+ */
138
+ export const CANDIDATE_ANCESTOR_COLUMNS = [
139
+ "spr_id",
140
+ "depth",
141
+ "parent_spr_id",
142
+ "parent_placetype_id",
143
+ "parent_name",
144
+ "parent_name_key",
145
+ ] as const
146
+
147
+ /**
148
+ * Create the clustered closure table. The builder inserts in `(spr_id, depth)` order so the B-tree leaves are
149
+ * contiguous per place — the byte-range read discipline.
150
+ */
151
+ export async function createCandidateAncestorTable(db: Kysely<CandidateDatabase>): Promise<void> {
152
+ await db.schema
153
+ .createTable(CANDIDATE_ANCESTOR_TABLE)
154
+ .addColumn("spr_id", "integer", (c) => c.notNull())
155
+ .addColumn("depth", "integer", (c) => c.notNull())
156
+ .addColumn("parent_spr_id", "integer", (c) => c.notNull())
157
+ .addColumn("parent_placetype_id", "integer", (c) => c.notNull())
158
+ .addColumn("parent_name", "text", (c) => c.notNull())
159
+ .addColumn("parent_name_key", "text", (c) => c.notNull())
160
+ .addPrimaryKeyConstraint("candidate_ancestor_pk", ["spr_id", "depth"])
161
+ // `WITHOUT ROWID` has no first-class builder; the raw modifier is the idiomatic fallback.
162
+ .modifyEnd(sql`without rowid`)
163
+ .execute()
164
+ }
165
+
166
+ /**
167
+ * Create the interval-label table. Small rows probed by primary key — the `WITHOUT ROWID` win case.
168
+ */
169
+ export async function createCandidateIntervalTable(db: Kysely<CandidateDatabase>): Promise<void> {
170
+ await db.schema
171
+ .createTable(CANDIDATE_INTERVAL_TABLE)
172
+ .addColumn("spr_id", "integer", (c) => c.primaryKey())
173
+ .addColumn("pre", "integer", (c) => c.notNull())
174
+ .addColumn("post", "integer", (c) => c.notNull())
175
+ .modifyEnd(sql`without rowid`)
176
+ .execute()
177
+ }
178
+
179
+ /**
180
+ * One place's interval label — the shape both sides of a containment comparison read.
181
+ */
182
+ export interface IntervalLabel {
183
+ pre: number
184
+ post: number
185
+ }
186
+
187
+ /**
188
+ * Does `outer` contain `inner` along the canonical hierarchy? Reflexive: a place contains itself (containment
189
+ * degenerates to identity, the same reading the admin-coherence check gives a self-match). The shared FUNCTION for
190
+ * every consumer of the labels — a re-derived comparison risks disagreeing at exactly the strict/inclusive boundary
191
+ * this line settles.
192
+ */
193
+ export function intervalContains(outer: IntervalLabel, inner: IntervalLabel): boolean {
194
+ return outer.pre <= inner.pre && inner.post <= outer.post
195
+ }
@@ -9,8 +9,10 @@
9
9
  * raw `name`), so a diacritic-stripped query (`munchen`) trigram-matches the stored `munchen`
10
10
  * rather than missing a raw `München`. The trigram tokenizer makes MATCH a substring/fuzzy
11
11
  * operation; the reader ({@link WOFCandidateTableLookup}) OR's the query's trigrams to fetch a
12
- * loose set, then re-ranks by the SAME `trigramJaccard` the admin/FTS backend uses, so a typo
13
- * resolves identically on either.
12
+ * loose set, then re-ranks it with a WORD-level similarity. The trigram index is the candidate
13
+ * GENERATOR, not the scorer — trigram Jaccard scores a true transposition correction BELOW a wrong
14
+ * answer, because shared generic suffixes count as evidence and transpositions count against it.
15
+ * The receipts are on `candidate-lookup.ts`'s `FUZZY_FETCH` and `WORD_FUZZY_MIN`.
14
16
  *
15
17
  * This is what unifies the two gazetteers: the candidate B-tree stays the common,
16
18
  * byte-range-optimal fast path (the browser's contiguous probe), and FTS5 is consulted ONLY on an
@@ -23,10 +25,10 @@
23
25
  * fallback only needs to recover the name_key, then re-probes the B-tree for its rows).
24
26
  */
25
27
 
26
- import type { DatabaseSync } from "node:sqlite"
28
+ import type { DatabaseClient } from "@mailwoman/sqlite/client"
27
29
 
28
30
  /**
29
- * Name of the FTS5 trigram virtual table this module owns. The reader gates its fuzzy fallback on it.
31
+ * Name of the FTS5 trigram virtual table this module owns. The reader conditions its fuzzy fallback on it.
30
32
  */
31
33
  export const CANDIDATE_FTS_TABLE = "candidate_fts"
32
34
 
@@ -34,7 +36,7 @@ export const CANDIDATE_FTS_TABLE = "candidate_fts"
34
36
  * Build (or rebuild) {@link CANDIDATE_FTS_TABLE} from the materialized `candidate` table. Call after the candidate
35
37
  * B-tree is populated (build pipeline) or against an existing candidate DB (migration).
36
38
  */
37
- export function createCandidateFTS(db: DatabaseSync): void {
39
+ export function createCandidateFTS<DB>(db: DatabaseClient<DB>): void {
38
40
  db.exec(`DROP TABLE IF EXISTS ${CANDIDATE_FTS_TABLE}`)
39
41
  db.exec(`CREATE VIRTUAL TABLE ${CANDIDATE_FTS_TABLE} USING fts5(name_key, tokenize='trigram')`)
40
42
 
@@ -27,7 +27,7 @@
27
27
  * Warwick in America the fame of Warwick, Rhode Island, which is the fan-out defect
28
28
  * `importance-fanout.ts` documents one layer up, re-introduced at the join. So the group is
29
29
  * disambiguated GEOGRAPHICALLY: the nearest centroid wins, and only within
30
- * {@link IMPORTANCE_JOIN_GATE_KM}. Two artifacts describing the same settlement put its centroid in
30
+ * {@link IMPORTANCE_JOIN_RADIUS_KM}. Two artifacts describing the same settlement put its centroid in
31
31
  * almost the same place; two same-named towns in one country do not.
32
32
  *
33
33
  * ## What lands in the column
@@ -41,10 +41,11 @@
41
41
  * (`resolver/toponym-prior.ts`) leaves an unmeasured candidate exactly where population put it.
42
42
  */
43
43
 
44
- import { DatabaseSync } from "node:sqlite"
44
+ import { haversineKm } from "@mailwoman/spatial"
45
+ import { DatabaseClient } from "@mailwoman/sqlite/client"
45
46
 
46
- import { haversineKm } from "./geo.ts"
47
- import { normalizeLocalityForKey } from "./street-normalize.ts"
47
+ import type { CandidateDatabase } from "#candidate-schema"
48
+ import { normalizeLocalityForKey } from "#street/normalize"
48
49
 
49
50
  /**
50
51
  * How far apart two artifacts may put the same place's centroid and still be read as the same place.
@@ -54,18 +55,18 @@ import { normalizeLocalityForKey } from "./street-normalize.ts"
54
55
  * country, placetype)`). The nearest-centroid distance is **exactly 0.00 km for 577,080 of them (56.6%)** — the two
55
56
  * snapshots agree to the bit — and 656,755 (64.4%) are inside 500 m.
56
57
  *
57
- * What sets the gate is where that mode ENDS, and the per-kilometre density says so plainly. It falls from 1,076
58
- * places/km over 3–5 km to a trough of **441 places/km over 7–10 km**, then climbs back and flattens onto a plateau of
59
- * 760–780 places/km from 30 km out to 100 km and beyond. That plateau is the background rate of two DIFFERENT towns
60
- * wearing one name in one country, and it does not decay with distance because there is no reason it should. 10 km is
61
- * the floor between the two populations. Admitting it scores 679,163 places (66.6% of the matched set); pushing the
62
- * gate to 25 km buys 8,722 more, and by then better than half of each additional kilometre is the wrong town.
58
+ * What sets the radius is where that mode ENDS, and the per-kilometre density states. It falls from 1,076 places/km
59
+ * over 3–5 km to a trough of **441 places/km over 7–10 km**, then climbs back and flattens onto a plateau of 760–780
60
+ * places/km from 30 km out to 100 km and beyond. That plateau is the background rate of two DIFFERENT towns wearing one
61
+ * name in one country, and it does not decay with distance because there is no reason it should. 10 km is the floor
62
+ * between the two populations. Admitting it scores 679,163 places (66.6% of the matched set); pushing the radius to 25
63
+ * km buys 8,722 more, and by then better than half of each additional kilometre is the wrong town.
63
64
  *
64
65
  * The four-row bare-GB board is insensitive across this whole range — 5 km and 25 km were both measured and select
65
- * identical rows — so the value is chosen by what the join MEANS, not by what it scores. The gate is the definition of
66
- * "this is the same place"; widening it past the floor starts handing one town's fame to another.
66
+ * identical rows — so the value is chosen by what the join MEANS, not by what it scores. The radius is the definition
67
+ * of "this is the same place"; widening it past the floor starts handing one town's fame to another.
67
68
  */
68
- export const IMPORTANCE_JOIN_GATE_KM = 10
69
+ export const IMPORTANCE_JOIN_RADIUS_KM = 10
69
70
 
70
71
  /**
71
72
  * One scored place from the source: where it is, and what it scored.
@@ -110,17 +111,17 @@ export class ImportanceIndex {
110
111
  readonly #groups: Map<string, ScoredPlace[]>
111
112
  readonly stats: ImportanceIndexStats
112
113
  /**
113
- * Places {@link find} matched inside the gate.
114
+ * Places {@link find} matched inside the radius.
114
115
  */
115
116
  matched = 0
116
117
  /**
117
- * Places {@link find} REFUSED — the key matched a scored group, but the nearest member of it was outside the gate, so
118
- * it is a different place wearing the same name.
118
+ * Places {@link find} REFUSED — the key matched a scored group, but the nearest member of it was outside the radius,
119
+ * so it is a different place wearing the same name.
119
120
  *
120
121
  * This is the number worth watching across rebuilds. A jump means the score source and the admin source have drifted
121
- * apart and the join is being asked to guess; it does not mean the gate is too tight.
122
+ * apart and the join is being asked to guess; it does not mean the radius is too tight.
122
123
  */
123
- gated = 0
124
+ refused = 0
124
125
 
125
126
  constructor(groups: Map<string, ScoredPlace[]>, stats: ImportanceIndexStats) {
126
127
  this.#groups = groups
@@ -129,7 +130,7 @@ export class ImportanceIndex {
129
130
 
130
131
  /**
131
132
  * The importance of the scored place nearest `(lat, lon)` sharing `name`'s folded key, `country` and `placetype`, or
132
- * null when there is no such place within {@link IMPORTANCE_JOIN_GATE_KM}.
133
+ * null when there is no such place within {@link IMPORTANCE_JOIN_RADIUS_KM}.
133
134
  *
134
135
  * Null is UNMEASURED. Never substitute a zero, and never fall back to a population-derived value here — the source
135
136
  * column already carries that fallback where it has one, and inventing a second one would make an absence
@@ -156,8 +157,8 @@ export class ImportanceIndex {
156
157
  }
157
158
  }
158
159
 
159
- if (!best || bestKm > IMPORTANCE_JOIN_GATE_KM) {
160
- this.gated++
160
+ if (!best || bestKm > IMPORTANCE_JOIN_RADIUS_KM) {
161
+ this.refused++
161
162
 
162
163
  return null
163
164
  }
@@ -181,47 +182,43 @@ export class ImportanceIndex {
181
182
  * 25 s.
182
183
  */
183
184
  export function loadImportanceIndex(databasePath: string): ImportanceIndex {
184
- const db = new DatabaseSync(databasePath, { readOnly: true })
185
-
186
- try {
187
- const groups = new Map<string, ScoredPlace[]>()
188
- let places = 0
189
- let unkeyable = 0
190
-
191
- for (const row of db
192
- .prepare(
193
- `SELECT s.name AS name, s.country AS country, s.placetype AS placetype,
194
- s.latitude AS latitude, s.longitude AS longitude, i.importance AS importance
195
- FROM place_importance i JOIN spr s ON s.id = i.id
196
- WHERE s.is_current != 0 AND s.is_deprecated = 0`
197
- )
198
- .iterate()) {
199
- const importance = Number(row.importance)
200
-
201
- if (!Number.isFinite(importance)) continue
202
- const nameKey = normalizeLocalityForKey(String(row.name ?? ""))
203
-
204
- if (!nameKey) {
205
- unkeyable++
206
-
207
- continue
208
- }
185
+ using db = new DatabaseClient<CandidateDatabase>(databasePath, { readOnly: true })
209
186
 
210
- const key = groupKey(nameKey, row.country as string | null, row.placetype as string | null)
211
- let group = groups.get(key)
187
+ const groups = new Map<string, ScoredPlace[]>()
188
+ let places = 0
189
+ let unkeyable = 0
212
190
 
213
- if (!group) {
214
- group = []
215
- groups.set(key, group)
216
- }
191
+ for (const row of db
192
+ .prepare(
193
+ `SELECT s.name AS name, s.country AS country, s.placetype AS placetype,
194
+ s.latitude AS latitude, s.longitude AS longitude, i.importance AS importance
195
+ FROM place_importance i JOIN spr s ON s.id = i.id
196
+ WHERE s.is_current != 0 AND s.is_deprecated = 0`
197
+ )
198
+ .iterate()) {
199
+ const importance = Number(row.importance)
200
+
201
+ if (!Number.isFinite(importance)) continue
202
+ const nameKey = normalizeLocalityForKey(String(row.name ?? ""))
203
+
204
+ if (!nameKey) {
205
+ unkeyable++
217
206
 
218
- group.push({ lat: Number(row.latitude), lon: Number(row.longitude), importance })
207
+ continue
208
+ }
209
+
210
+ const key = groupKey(nameKey, row.country as string | null, row.placetype as string | null)
211
+ let group = groups.get(key)
219
212
 
220
- places++
213
+ if (!group) {
214
+ group = []
215
+ groups.set(key, group)
221
216
  }
222
217
 
223
- return new ImportanceIndex(groups, { places, keys: groups.size, unkeyable })
224
- } finally {
225
- db.close()
218
+ group.push({ lat: Number(row.latitude), lon: Number(row.longitude), importance })
219
+
220
+ places++
226
221
  }
222
+
223
+ return new ImportanceIndex(groups, { places, keys: groups.size, unkeyable })
227
224
  }