@mailwoman/resolver-wof-sqlite 10.0.0 → 10.2.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 (477) hide show
  1. package/README.md +9 -8
  2. package/lib/address/point/interpolation.ts +45 -39
  3. package/lib/address/point/schema.ts +17 -40
  4. package/lib/address/point.ts +316 -0
  5. package/lib/{address/index.ts → address.ts} +1 -1
  6. package/lib/ancestry/backfill.ts +20 -86
  7. package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
  8. package/lib/build/candidate.ts +122 -174
  9. package/lib/build/population-corrections.ts +64 -0
  10. package/lib/build/slim.ts +44 -114
  11. package/lib/candidate/alias-bags.ts +16 -17
  12. package/lib/candidate/ancestors/schema.ts +77 -61
  13. package/lib/candidate/ancestors/sidecar.ts +15 -22
  14. package/lib/candidate/country-display-names.ts +6 -20
  15. package/lib/candidate/extract-fold.ts +10 -55
  16. package/lib/candidate/fts.ts +5 -23
  17. package/lib/candidate/importance.ts +65 -68
  18. package/lib/candidate/lookup.ts +124 -415
  19. package/lib/candidate/name-roles.ts +15 -33
  20. package/lib/candidate/own-name.ts +35 -32
  21. package/lib/candidate/place-attrs.ts +20 -13
  22. package/lib/candidate/schema.ts +29 -65
  23. package/lib/candidate/scoring.ts +54 -93
  24. package/lib/capital-schema.ts +21 -17
  25. package/lib/capitals.ts +29 -27
  26. package/lib/coincident-roles.ts +85 -55
  27. package/lib/convention/schema.ts +15 -16
  28. package/lib/convention.ts +144 -0
  29. package/lib/coverage-manifest-schema.ts +40 -45
  30. package/lib/currency-backfill.ts +34 -79
  31. package/lib/env.ts +1 -1
  32. package/lib/exact-match.ts +32 -21
  33. package/lib/extracts.ts +97 -74
  34. package/lib/fst/autocomplete.ts +54 -37
  35. package/lib/fst/builder.ts +37 -41
  36. package/lib/fst/deserialize-web.ts +16 -17
  37. package/lib/fst/format.ts +41 -23
  38. package/lib/fst/freshness.ts +43 -120
  39. package/lib/fst/matcher.ts +7 -5
  40. package/lib/fst/serialize.ts +54 -74
  41. package/lib/fst/types.ts +78 -75
  42. package/lib/{fst/index.ts → fst.ts} +1 -0
  43. package/lib/fts/query.ts +5 -36
  44. package/lib/fts.ts +280 -0
  45. package/lib/geonames/aliases.ts +73 -144
  46. package/lib/geonames/postal.ts +162 -71
  47. package/lib/index.ts +9 -8
  48. package/lib/interpolation.ts +48 -100
  49. package/lib/lookup.ts +155 -270
  50. package/lib/name-score.ts +9 -7
  51. package/lib/nsul/lookup.ts +37 -54
  52. package/lib/nsul/schema.ts +16 -43
  53. package/lib/paths.ts +159 -0
  54. package/lib/place-importance-schema.ts +62 -169
  55. package/lib/poi/lookup.ts +104 -121
  56. package/lib/poi/schema.ts +42 -26
  57. package/lib/polygon-schema.ts +10 -6
  58. package/lib/postal/city/alias/lookup.ts +22 -40
  59. package/lib/postal/city/alias/schema.ts +14 -10
  60. package/lib/postal/city/candidate-schema.ts +26 -20
  61. package/lib/postcode-point-lookup.ts +8 -27
  62. package/lib/primary-preference.ts +46 -126
  63. package/lib/proximity-rerank.ts +44 -33
  64. package/lib/ranking-weights.ts +105 -78
  65. package/lib/region-keys.ts +70 -42
  66. package/lib/reverse.ts +149 -86
  67. package/lib/schema.ts +73 -50
  68. package/lib/search-fetch.ts +90 -68
  69. package/lib/sqlite/convention-source.ts +15 -23
  70. package/lib/sqlite/utils.ts +19 -22
  71. package/lib/street/centroid/schema.ts +37 -42
  72. package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
  73. package/lib/street/morphology/fst/builder.ts +60 -42
  74. package/lib/street/morphology/fst/loader.ts +40 -32
  75. package/lib/street/name-lookup.ts +45 -41
  76. package/lib/street/normalize.ts +88 -219
  77. package/lib/street/segment-schema.ts +116 -43
  78. package/lib/{street/index.ts → street.ts} +1 -1
  79. package/lib/types.ts +109 -150
  80. package/lib/unified-schema.ts +29 -29
  81. package/lib/uprn/existence.ts +30 -19
  82. package/lib/uprn/lookup.ts +29 -68
  83. package/lib/uprn/schema.ts +18 -40
  84. package/lib/weights-overlay-linker.ts +236 -252
  85. package/out/address/point/interpolation.d.ts +20 -23
  86. package/out/address/point/interpolation.d.ts.map +1 -1
  87. package/out/address/point/interpolation.js +42 -39
  88. package/out/address/point/interpolation.js.map +1 -1
  89. package/out/address/point/schema.d.ts +12 -35
  90. package/out/address/point/schema.d.ts.map +1 -1
  91. package/out/address/point/schema.js +3 -19
  92. package/out/address/point/schema.js.map +1 -1
  93. package/out/address/point.d.ts +57 -0
  94. package/out/address/point.d.ts.map +1 -0
  95. package/out/address/point.js +206 -0
  96. package/out/address/point.js.map +1 -0
  97. package/out/{address/index.js → address.d.ts} +2 -2
  98. package/out/address.d.ts.map +1 -0
  99. package/out/{address/index.d.ts → address.js} +2 -2
  100. package/out/address.js.map +1 -0
  101. package/out/ancestry/backfill.d.ts +11 -64
  102. package/out/ancestry/backfill.d.ts.map +1 -1
  103. package/out/ancestry/backfill.js +14 -74
  104. package/out/ancestry/backfill.js.map +1 -1
  105. package/out/ancestry.d.ts +49 -0
  106. package/out/ancestry.d.ts.map +1 -0
  107. package/out/ancestry.js +59 -0
  108. package/out/ancestry.js.map +1 -0
  109. package/out/build/candidate.d.ts +82 -112
  110. package/out/build/candidate.d.ts.map +1 -1
  111. package/out/build/candidate.js +17 -100
  112. package/out/build/candidate.js.map +1 -1
  113. package/out/build/population-corrections.d.ts +52 -0
  114. package/out/build/population-corrections.d.ts.map +1 -0
  115. package/out/build/population-corrections.js +40 -0
  116. package/out/build/population-corrections.js.map +1 -0
  117. package/out/build/slim.d.ts +8 -44
  118. package/out/build/slim.d.ts.map +1 -1
  119. package/out/build/slim.js +33 -94
  120. package/out/build/slim.js.map +1 -1
  121. package/out/candidate/alias-bags.d.ts +6 -6
  122. package/out/candidate/alias-bags.d.ts.map +1 -1
  123. package/out/candidate/alias-bags.js +16 -17
  124. package/out/candidate/alias-bags.js.map +1 -1
  125. package/out/candidate/ancestors/schema.d.ts +73 -57
  126. package/out/candidate/ancestors/schema.d.ts.map +1 -1
  127. package/out/candidate/ancestors/schema.js +56 -48
  128. package/out/candidate/ancestors/schema.js.map +1 -1
  129. package/out/candidate/ancestors/sidecar.d.ts +3 -8
  130. package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
  131. package/out/candidate/ancestors/sidecar.js +15 -22
  132. package/out/candidate/ancestors/sidecar.js.map +1 -1
  133. package/out/candidate/country-display-names.d.ts +5 -15
  134. package/out/candidate/country-display-names.d.ts.map +1 -1
  135. package/out/candidate/country-display-names.js +6 -20
  136. package/out/candidate/country-display-names.js.map +1 -1
  137. package/out/candidate/extract-fold.d.ts +7 -15
  138. package/out/candidate/extract-fold.d.ts.map +1 -1
  139. package/out/candidate/extract-fold.js +5 -47
  140. package/out/candidate/extract-fold.js.map +1 -1
  141. package/out/candidate/fts.d.ts +5 -23
  142. package/out/candidate/fts.d.ts.map +1 -1
  143. package/out/candidate/fts.js +5 -23
  144. package/out/candidate/fts.js.map +1 -1
  145. package/out/candidate/importance.d.ts +60 -65
  146. package/out/candidate/importance.d.ts.map +1 -1
  147. package/out/candidate/importance.js +56 -63
  148. package/out/candidate/importance.js.map +1 -1
  149. package/out/candidate/lookup.d.ts +18 -55
  150. package/out/candidate/lookup.d.ts.map +1 -1
  151. package/out/candidate/lookup.js +82 -385
  152. package/out/candidate/lookup.js.map +1 -1
  153. package/out/candidate/name-roles.d.ts +9 -21
  154. package/out/candidate/name-roles.d.ts.map +1 -1
  155. package/out/candidate/name-roles.js +15 -33
  156. package/out/candidate/name-roles.js.map +1 -1
  157. package/out/candidate/own-name.d.ts +23 -25
  158. package/out/candidate/own-name.d.ts.map +1 -1
  159. package/out/candidate/own-name.js +35 -32
  160. package/out/candidate/own-name.js.map +1 -1
  161. package/out/candidate/place-attrs.d.ts +20 -13
  162. package/out/candidate/place-attrs.d.ts.map +1 -1
  163. package/out/candidate/place-attrs.js +4 -5
  164. package/out/candidate/place-attrs.js.map +1 -1
  165. package/out/candidate/schema.d.ts +27 -62
  166. package/out/candidate/schema.d.ts.map +1 -1
  167. package/out/candidate/schema.js +10 -22
  168. package/out/candidate/schema.js.map +1 -1
  169. package/out/candidate/scoring.d.ts +7 -8
  170. package/out/candidate/scoring.d.ts.map +1 -1
  171. package/out/candidate/scoring.js +52 -89
  172. package/out/candidate/scoring.js.map +1 -1
  173. package/out/capital-schema.d.ts +21 -17
  174. package/out/capital-schema.d.ts.map +1 -1
  175. package/out/capital-schema.js +17 -15
  176. package/out/capital-schema.js.map +1 -1
  177. package/out/capitals.d.ts +27 -25
  178. package/out/capitals.d.ts.map +1 -1
  179. package/out/capitals.js +23 -21
  180. package/out/capitals.js.map +1 -1
  181. package/out/coincident-roles.d.ts +60 -43
  182. package/out/coincident-roles.d.ts.map +1 -1
  183. package/out/coincident-roles.js +72 -50
  184. package/out/coincident-roles.js.map +1 -1
  185. package/out/convention/schema.d.ts +14 -14
  186. package/out/convention/schema.d.ts.map +1 -1
  187. package/out/convention/schema.js +6 -4
  188. package/out/convention/schema.js.map +1 -1
  189. package/out/convention.d.ts +97 -0
  190. package/out/convention.d.ts.map +1 -0
  191. package/out/convention.js +81 -0
  192. package/out/convention.js.map +1 -0
  193. package/out/coverage-manifest-schema.d.ts +34 -39
  194. package/out/coverage-manifest-schema.d.ts.map +1 -1
  195. package/out/coverage-manifest-schema.js +25 -34
  196. package/out/coverage-manifest-schema.js.map +1 -1
  197. package/out/currency-backfill.d.ts +16 -38
  198. package/out/currency-backfill.d.ts.map +1 -1
  199. package/out/currency-backfill.js +19 -46
  200. package/out/currency-backfill.js.map +1 -1
  201. package/out/env.d.ts +1 -1
  202. package/out/env.js +1 -1
  203. package/out/exact-match.d.ts +19 -10
  204. package/out/exact-match.d.ts.map +1 -1
  205. package/out/exact-match.js +32 -21
  206. package/out/exact-match.js.map +1 -1
  207. package/out/extracts.d.ts +66 -55
  208. package/out/extracts.d.ts.map +1 -1
  209. package/out/extracts.js +63 -55
  210. package/out/extracts.js.map +1 -1
  211. package/out/fst/autocomplete.d.ts +29 -21
  212. package/out/fst/autocomplete.d.ts.map +1 -1
  213. package/out/fst/autocomplete.js +39 -28
  214. package/out/fst/autocomplete.js.map +1 -1
  215. package/out/fst/builder.d.ts.map +1 -1
  216. package/out/fst/builder.js +37 -41
  217. package/out/fst/builder.js.map +1 -1
  218. package/out/fst/deserialize-web.d.ts +4 -4
  219. package/out/fst/deserialize-web.d.ts.map +1 -1
  220. package/out/fst/deserialize-web.js +15 -16
  221. package/out/fst/deserialize-web.js.map +1 -1
  222. package/out/fst/format.d.ts +41 -23
  223. package/out/fst/format.d.ts.map +1 -1
  224. package/out/fst/format.js +41 -23
  225. package/out/fst/format.js.map +1 -1
  226. package/out/fst/freshness.d.ts +19 -81
  227. package/out/fst/freshness.d.ts.map +1 -1
  228. package/out/fst/freshness.js +31 -94
  229. package/out/fst/freshness.js.map +1 -1
  230. package/out/fst/matcher.d.ts +5 -4
  231. package/out/fst/matcher.d.ts.map +1 -1
  232. package/out/fst/matcher.js +2 -2
  233. package/out/fst/matcher.js.map +1 -1
  234. package/out/fst/serialize.d.ts +28 -28
  235. package/out/fst/serialize.d.ts.map +1 -1
  236. package/out/fst/serialize.js +53 -71
  237. package/out/fst/serialize.js.map +1 -1
  238. package/out/fst/types.d.ts +78 -74
  239. package/out/fst/types.d.ts.map +1 -1
  240. package/out/fst/types.js +1 -4
  241. package/out/fst/types.js.map +1 -1
  242. package/out/{fst/index.js → fst.d.ts} +2 -1
  243. package/out/fst.d.ts.map +1 -0
  244. package/out/{fst/index.d.ts → fst.js} +2 -1
  245. package/out/fst.js.map +1 -0
  246. package/out/fts/query.d.ts +2 -27
  247. package/out/fts/query.d.ts.map +1 -1
  248. package/out/fts/query.js +5 -36
  249. package/out/fts/query.js.map +1 -1
  250. package/out/fts.d.ts +126 -0
  251. package/out/fts.d.ts.map +1 -0
  252. package/out/fts.js +188 -0
  253. package/out/fts.js.map +1 -0
  254. package/out/geonames/aliases.d.ts +26 -62
  255. package/out/geonames/aliases.d.ts.map +1 -1
  256. package/out/geonames/aliases.js +45 -117
  257. package/out/geonames/aliases.js.map +1 -1
  258. package/out/geonames/postal.d.ts +84 -44
  259. package/out/geonames/postal.d.ts.map +1 -1
  260. package/out/geonames/postal.js +115 -73
  261. package/out/geonames/postal.js.map +1 -1
  262. package/out/{geonames/index.js → geonames.d.ts} +1 -1
  263. package/out/geonames.d.ts.map +1 -0
  264. package/out/{geonames/index.d.ts → geonames.js} +1 -1
  265. package/out/geonames.js.map +1 -0
  266. package/out/index.d.ts +9 -8
  267. package/out/index.d.ts.map +1 -1
  268. package/out/index.js +6 -5
  269. package/out/index.js.map +1 -1
  270. package/out/interpolation.d.ts +18 -55
  271. package/out/interpolation.d.ts.map +1 -1
  272. package/out/interpolation.js +27 -69
  273. package/out/interpolation.js.map +1 -1
  274. package/out/lookup.d.ts +23 -51
  275. package/out/lookup.d.ts.map +1 -1
  276. package/out/lookup.js +136 -234
  277. package/out/lookup.js.map +1 -1
  278. package/out/name-score.d.ts +2 -2
  279. package/out/name-score.d.ts.map +1 -1
  280. package/out/name-score.js +9 -7
  281. package/out/name-score.js.map +1 -1
  282. package/out/nsul/lookup.d.ts +33 -34
  283. package/out/nsul/lookup.d.ts.map +1 -1
  284. package/out/nsul/lookup.js +26 -40
  285. package/out/nsul/lookup.js.map +1 -1
  286. package/out/nsul/schema.d.ts +16 -43
  287. package/out/nsul/schema.d.ts.map +1 -1
  288. package/out/nsul/schema.js +9 -35
  289. package/out/nsul/schema.js.map +1 -1
  290. package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
  291. package/out/nsul.d.ts.map +1 -0
  292. package/out/{nsul/index.js → nsul.js} +1 -1
  293. package/out/nsul.js.map +1 -0
  294. package/out/paths.d.ts +124 -0
  295. package/out/paths.d.ts.map +1 -0
  296. package/out/paths.js +104 -0
  297. package/out/paths.js.map +1 -0
  298. package/out/place-importance-schema.d.ts +51 -143
  299. package/out/place-importance-schema.d.ts.map +1 -1
  300. package/out/place-importance-schema.js +28 -143
  301. package/out/place-importance-schema.js.map +1 -1
  302. package/out/poi/lookup.d.ts +40 -47
  303. package/out/poi/lookup.d.ts.map +1 -1
  304. package/out/poi/lookup.js +69 -96
  305. package/out/poi/lookup.js.map +1 -1
  306. package/out/poi/schema.d.ts +41 -25
  307. package/out/poi/schema.d.ts.map +1 -1
  308. package/out/poi/schema.js +25 -17
  309. package/out/poi/schema.js.map +1 -1
  310. package/out/{poi/index.js → poi.d.ts} +1 -1
  311. package/out/poi.d.ts.map +1 -0
  312. package/out/{poi/index.d.ts → poi.js} +1 -1
  313. package/out/poi.js.map +1 -0
  314. package/out/polygon-schema.d.ts +10 -6
  315. package/out/polygon-schema.d.ts.map +1 -1
  316. package/out/polygon-schema.js +2 -2
  317. package/out/polygon-schema.js.map +1 -1
  318. package/out/postal/city/alias/lookup.d.ts +20 -23
  319. package/out/postal/city/alias/lookup.d.ts.map +1 -1
  320. package/out/postal/city/alias/lookup.js +17 -30
  321. package/out/postal/city/alias/lookup.js.map +1 -1
  322. package/out/postal/city/alias/schema.d.ts +14 -10
  323. package/out/postal/city/alias/schema.d.ts.map +1 -1
  324. package/out/postal/city/alias/schema.js +10 -8
  325. package/out/postal/city/alias/schema.js.map +1 -1
  326. package/out/postal/city/candidate-schema.d.ts +25 -19
  327. package/out/postal/city/candidate-schema.d.ts.map +1 -1
  328. package/out/postal/city/candidate-schema.js +21 -17
  329. package/out/postal/city/candidate-schema.js.map +1 -1
  330. package/out/{postal/index.d.ts → postal.d.ts} +1 -1
  331. package/out/postal.d.ts.map +1 -0
  332. package/out/{postal/index.js → postal.js} +1 -1
  333. package/out/postal.js.map +1 -0
  334. package/out/postcode-point-lookup.d.ts +6 -24
  335. package/out/postcode-point-lookup.d.ts.map +1 -1
  336. package/out/postcode-point-lookup.js +5 -17
  337. package/out/postcode-point-lookup.js.map +1 -1
  338. package/out/primary-preference.d.ts +38 -92
  339. package/out/primary-preference.d.ts.map +1 -1
  340. package/out/primary-preference.js +15 -99
  341. package/out/primary-preference.js.map +1 -1
  342. package/out/proximity-rerank.d.ts +41 -30
  343. package/out/proximity-rerank.d.ts.map +1 -1
  344. package/out/proximity-rerank.js +38 -29
  345. package/out/proximity-rerank.js.map +1 -1
  346. package/out/ranking-weights.d.ts +98 -64
  347. package/out/ranking-weights.d.ts.map +1 -1
  348. package/out/ranking-weights.js +26 -25
  349. package/out/ranking-weights.js.map +1 -1
  350. package/out/region-keys.d.ts +50 -31
  351. package/out/region-keys.d.ts.map +1 -1
  352. package/out/region-keys.js +68 -40
  353. package/out/region-keys.js.map +1 -1
  354. package/out/reverse.d.ts +78 -50
  355. package/out/reverse.d.ts.map +1 -1
  356. package/out/reverse.js +98 -61
  357. package/out/reverse.js.map +1 -1
  358. package/out/schema.d.ts +72 -49
  359. package/out/schema.d.ts.map +1 -1
  360. package/out/schema.js +4 -5
  361. package/out/schema.js.map +1 -1
  362. package/out/search-fetch.d.ts +16 -12
  363. package/out/search-fetch.d.ts.map +1 -1
  364. package/out/search-fetch.js +83 -63
  365. package/out/search-fetch.js.map +1 -1
  366. package/out/sqlite/convention-source.d.ts +12 -16
  367. package/out/sqlite/convention-source.d.ts.map +1 -1
  368. package/out/sqlite/convention-source.js +13 -22
  369. package/out/sqlite/convention-source.js.map +1 -1
  370. package/out/sqlite/utils.d.ts +19 -19
  371. package/out/sqlite/utils.d.ts.map +1 -1
  372. package/out/sqlite/utils.js +14 -15
  373. package/out/sqlite/utils.js.map +1 -1
  374. package/out/street/centroid/schema.d.ts +37 -42
  375. package/out/street/centroid/schema.d.ts.map +1 -1
  376. package/out/street/centroid/schema.js +9 -21
  377. package/out/street/centroid/schema.js.map +1 -1
  378. package/out/street/centroid.d.ts +30 -0
  379. package/out/street/centroid.d.ts.map +1 -0
  380. package/out/street/{centroid/index.js → centroid.js} +13 -24
  381. package/out/street/centroid.js.map +1 -0
  382. package/out/street/morphology/fst/builder.d.ts +20 -10
  383. package/out/street/morphology/fst/builder.d.ts.map +1 -1
  384. package/out/street/morphology/fst/builder.js +44 -34
  385. package/out/street/morphology/fst/builder.js.map +1 -1
  386. package/out/street/morphology/fst/loader.d.ts +33 -26
  387. package/out/street/morphology/fst/loader.d.ts.map +1 -1
  388. package/out/street/morphology/fst/loader.js +23 -22
  389. package/out/street/morphology/fst/loader.js.map +1 -1
  390. package/out/street/name-lookup.d.ts +24 -18
  391. package/out/street/name-lookup.d.ts.map +1 -1
  392. package/out/street/name-lookup.js +34 -35
  393. package/out/street/name-lookup.js.map +1 -1
  394. package/out/street/normalize.d.ts +35 -136
  395. package/out/street/normalize.d.ts.map +1 -1
  396. package/out/street/normalize.js +77 -194
  397. package/out/street/normalize.js.map +1 -1
  398. package/out/street/segment-schema.d.ts +79 -39
  399. package/out/street/segment-schema.d.ts.map +1 -1
  400. package/out/street/segment-schema.js +71 -21
  401. package/out/street/segment-schema.js.map +1 -1
  402. package/out/{street/index.js → street.d.ts} +2 -2
  403. package/out/street.d.ts.map +1 -0
  404. package/out/{street/index.d.ts → street.js} +2 -2
  405. package/out/street.js.map +1 -0
  406. package/out/types.d.ts +84 -149
  407. package/out/types.d.ts.map +1 -1
  408. package/out/types.js +0 -6
  409. package/out/types.js.map +1 -1
  410. package/out/unified-schema.d.ts +8 -12
  411. package/out/unified-schema.d.ts.map +1 -1
  412. package/out/unified-schema.js +29 -29
  413. package/out/unified-schema.js.map +1 -1
  414. package/out/uprn/existence.d.ts +28 -17
  415. package/out/uprn/existence.d.ts.map +1 -1
  416. package/out/uprn/existence.js +26 -16
  417. package/out/uprn/existence.js.map +1 -1
  418. package/out/uprn/lookup.d.ts +14 -44
  419. package/out/uprn/lookup.d.ts.map +1 -1
  420. package/out/uprn/lookup.js +21 -57
  421. package/out/uprn/lookup.js.map +1 -1
  422. package/out/uprn/schema.d.ts +18 -40
  423. package/out/uprn/schema.d.ts.map +1 -1
  424. package/out/uprn/schema.js +10 -32
  425. package/out/uprn/schema.js.map +1 -1
  426. package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
  427. package/out/uprn.d.ts.map +1 -0
  428. package/out/{uprn/index.js → uprn.js} +1 -1
  429. package/out/uprn.js.map +1 -0
  430. package/out/weights-overlay-linker.d.ts +138 -144
  431. package/out/weights-overlay-linker.d.ts.map +1 -1
  432. package/out/weights-overlay-linker.js +105 -159
  433. package/out/weights-overlay-linker.js.map +1 -1
  434. package/package.json +91 -596
  435. package/lib/address/point/index.ts +0 -328
  436. package/lib/convention/index.ts +0 -154
  437. package/lib/fts/index.ts +0 -361
  438. package/out/address/index.d.ts.map +0 -1
  439. package/out/address/index.js.map +0 -1
  440. package/out/address/point/index.d.ts +0 -53
  441. package/out/address/point/index.d.ts.map +0 -1
  442. package/out/address/point/index.js +0 -222
  443. package/out/address/point/index.js.map +0 -1
  444. package/out/ancestry/index.d.ts +0 -45
  445. package/out/ancestry/index.d.ts.map +0 -1
  446. package/out/ancestry/index.js +0 -55
  447. package/out/ancestry/index.js.map +0 -1
  448. package/out/convention/index.d.ts +0 -106
  449. package/out/convention/index.d.ts.map +0 -1
  450. package/out/convention/index.js +0 -93
  451. package/out/convention/index.js.map +0 -1
  452. package/out/fst/index.d.ts.map +0 -1
  453. package/out/fst/index.js.map +0 -1
  454. package/out/fts/index.d.ts +0 -184
  455. package/out/fts/index.d.ts.map +0 -1
  456. package/out/fts/index.js +0 -277
  457. package/out/fts/index.js.map +0 -1
  458. package/out/geonames/index.d.ts.map +0 -1
  459. package/out/geonames/index.js.map +0 -1
  460. package/out/nsul/index.d.ts.map +0 -1
  461. package/out/nsul/index.js.map +0 -1
  462. package/out/poi/index.d.ts.map +0 -1
  463. package/out/poi/index.js.map +0 -1
  464. package/out/postal/index.d.ts.map +0 -1
  465. package/out/postal/index.js.map +0 -1
  466. package/out/street/centroid/index.d.ts +0 -40
  467. package/out/street/centroid/index.d.ts.map +0 -1
  468. package/out/street/centroid/index.js.map +0 -1
  469. package/out/street/index.d.ts.map +0 -1
  470. package/out/street/index.js.map +0 -1
  471. package/out/uprn/index.d.ts.map +0 -1
  472. package/out/uprn/index.js.map +0 -1
  473. /package/lib/{geonames/index.ts → geonames.ts} +0 -0
  474. /package/lib/{nsul/index.ts → nsul.ts} +0 -0
  475. /package/lib/{poi/index.ts → poi.ts} +0 -0
  476. /package/lib/{postal/index.ts → postal.ts} +0 -0
  477. /package/lib/{uprn/index.ts → uprn.ts} +0 -0
@@ -3,23 +3,24 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * The proximity re-rank (#938): with bias hints — the demo's map viewport, a user location — re-order exact-match
7
- * candidates by population and nearness on one additive scale, so an in-view namesake wins a tie without a hard
8
- * filter. Byte-identical to plain population order when no bias is passed.
6
+ * Re-order exact-match candidates by population and nearness on one additive scale when bias hints
7
+ * are present (the demo's map viewport, a user location), so an in-view namesake wins a tie
8
+ * without a hard filter. With no bias the order is plain population order, byte-identical.
9
9
  *
10
- * This lives in its own platform-free module because it has to run identically in two places: the Node candidate
11
- * reader and the browser byte-range twin. That is the #861 server↔demo parity contract and the second thing
12
- * here held by construction rather than by comment (`primary-preference.ts` was the first). Constants alone were not
13
- * enough — the two copies agreed on every literal and still diverged on which field the population term reads and on
14
- * whether the combined value is written back, which is the half that actually decides the answer.
10
+ * This lives in its own platform-free module because it has to run identically in two places, the
11
+ * Node candidate reader and the browser byte-range twin. That is the server and demo parity
12
+ * interface held by construction. Constants by themselves were not enough, since the two copies agreed on
13
+ * every literal and still diverged on which field the population term reads and on whether the
14
+ * combined value is written back.
15
15
  *
16
16
  * Two properties are required and easy to lose when transcribing:
17
17
  *
18
- * 1. The population base is `prominence ?? score`, NOT `score`. `prominence` carries the bounded cross-country
19
- * primary preference, so reading raw score lets a coincidental foreign alias ride population back over a primary
20
- * whenever a viewport hint happens to be present.
21
- * 2. The combined value is PERSISTED into `prominence`. The resolver walk re-sorts by `prominence ?? score`, so a
22
- * caller that only returns the array in bias order has its ordering silently discarded downstream.
18
+ * 1. The population base is `prominence ?? score`. `prominence` contains the bounded cross-country
19
+ * primary preference, so reading raw score lets a coincidental foreign alias ride population
20
+ * back over a primary whenever a viewport hint happens to be present.
21
+ * 2. The combined value is persisted into `prominence`. The resolver walk re-sorts by
22
+ * `prominence ?? score`, so a caller that only returns the array in bias order has its
23
+ * ordering silently discarded downstream.
23
24
  */
24
25
 
25
26
  import { haversineKm } from "@mailwoman/spatial"
@@ -35,24 +36,27 @@ export const BIAS_BOOST = 4
35
36
  export const POP_BOOST = 4
36
37
 
37
38
  /**
38
- * `log10(population + 1)` at which the population term saturates — 6 means a population of one million earns the whole
39
- * {@link POP_BOOST}, and larger populations earn no more.
39
+ * `log10(population + 1)` at which the population term saturates.
40
+ *
41
+ * A value of 6 means a population of one million warrants the whole {@link POP_BOOST},
42
+ * and larger populations warrant no more.
40
43
  */
41
44
  export const POP_SCALE_LOG10 = 6
42
45
 
43
46
  /**
44
47
  * Distance at which the nearness term halves.
45
48
  *
46
- * SHARPER than the FTS reader's 100 km on purpose: the candidate backend's score is log-population ALONE, with no bm25
47
- * document term, so the population signal is weaker relative to the bias and a gentle 100 km decay let a 230 km-distant
48
- * alias-exact township ("Paris Township", OH) edge out a global city ("Paris", FR) from a nearby view. At ~30 km the
49
- * boost reaches only candidates the user is actually looking at: an in-view namesake still wins (Dublin, OH from an
50
- * Ohio view), a distant one no longer does (Paris stays FR from a Michigan view).
49
+ * Sharper than the FTS reader's 100 km on purpose, because the candidate backend's
50
+ * score is log-population by itself with no bm25 document term.
51
+ * This weakens the population signal relative to the bias.
52
+ *
53
+ * At around 30 km the boost reaches only candidates the user is looking at,
54
+ * so an in-view namesake still wins and a distant one does not.
51
55
  */
52
56
  export const PROX_SCALE_KM = 30
53
57
 
54
58
  /**
55
- * One bias hint — a coordinate the user is looking at or standing on, optionally weighted.
59
+ * One bias hint, a coordinate the user is looking at or standing on, optionally weighted.
56
60
  */
57
61
  export interface ProximityBias {
58
62
  lat: number
@@ -61,27 +65,31 @@ export interface ProximityBias {
61
65
  }
62
66
 
63
67
  /**
64
- * The candidate fields the re-rank reads and writes. Structural rather than a concrete candidate type, so the Node
65
- * reader's `PlaceCandidate` and the browser twin's row shape both satisfy it without an adapter.
68
+ * The candidate fields the re-rank reads and writes.
69
+ *
70
+ * This is structural, so the Node reader's `PlaceCandidate` and the browser twin's
71
+ * row shape both satisfy it without an adapter.
66
72
  */
67
73
  export interface ProximityRerankable {
68
74
  lat: number
69
75
  lon: number
70
76
  score: number
71
- prominence?: number
77
+ prominence?: number | null
72
78
  }
73
79
 
74
80
  /**
75
- * Population plus nearness on one additive scale. Exported for tests and for a caller that wants the value without the
76
- * sort; ordinary callers want {@link applyProximityRerank}.
81
+ * Population plus nearness on one additive scale.
82
+ *
83
+ * Exported for tests and for a caller that wants the value without the sort.
84
+ * Ordinary callers want {@link applyProximityRerank}.
77
85
  */
78
86
  export function combinedProminence(candidate: ProximityRerankable, bias: readonly ProximityBias[]): number {
79
87
  const popBase = candidate.prominence ?? candidate.score
80
88
  const popTerm = POP_BOOST * Math.min(1, Math.max(0, popBase) / POP_SCALE_LOG10)
81
89
  let proxTerm = 0
82
90
 
83
- // A candidate at the null island has no coordinate, not a coordinate at 0,0 — it earns no nearness term rather
84
- // than an enormous one.
91
+ // A candidate at the null island has no coordinate, so it warrants no nearness term.
92
+ // A 0,0 pair interpreted as a real coordinate would receive an enormous score.
85
93
  if (!(candidate.lat === 0 && candidate.lon === 0)) {
86
94
  for (const b of bias) {
87
95
  const d = haversineKm(b.lat, b.lon, candidate.lat, candidate.lon)
@@ -97,10 +105,13 @@ export function combinedProminence(candidate: ProximityRerankable, bias: readonl
97
105
  }
98
106
 
99
107
  /**
100
- * Re-order `candidates` in place by {@link combinedProminence}, persisting each combined value into `prominence` so the
101
- * resolver walk's own `prominence ?? score` sort carries the bias order rather than undoing it. Stable within equal
102
- * prominence, preserving the population order the index already gave. A caller with no bias hints must not call this —
103
- * the no-bias path is plain population order by construction.
108
+ * Re-order `candidates` in place by {@link combinedProminence}, persisting each
109
+ * combined value into `prominence` so the resolver walk's own `prominence ?? score`
110
+ * sort preserves the bias order rather than undoing it.
111
+ *
112
+ * Stable within equal prominence, preserving the population order the index already gave.
113
+ * A caller with no bias hints must not call this.
114
+ * The no-bias path is plain population order by construction.
104
115
  */
105
116
  export function applyProximityRerank<T extends ProximityRerankable>(
106
117
  candidates: T[],
@@ -112,7 +123,7 @@ export function applyProximityRerank<T extends ProximityRerankable>(
112
123
 
113
124
  return { c, i, p: c.prominence }
114
125
  })
115
- // oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array; toSorted would double-allocate on a hot path
126
+ // oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array. toSorted would double-allocate on a hot path
116
127
  .sort((a, b) => b.p - a.p || a.i - b.i)
117
128
  .forEach((x, j) => (candidates[j] = x.c))
118
129
 
@@ -3,14 +3,15 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * The locality-ranking weights and their shipped defaults. Its own module because every value here is
7
- * a measured tuning decision with its rationale attached — the block reads as a reference table, not
8
- * as part of the lookup's control flow, and the tests import it directly to pin one change at a time.
6
+ * The locality-ranking weights and their shipped defaults live here because each value is a measured tuning decision
7
+ * with its rationale attached. This block reads as a reference table outside the lookup's control flow.
8
+ * Tests import it directly to pin one change at a time.
9
9
  */
10
10
 
11
11
  /**
12
- * Ranking weights for `findPlace`. Tweakable per-instance but defaults match the values declared in the Phase 4.2 plan
13
- * doc.
12
+ * The weights used to rank `findPlace` results.
13
+ *
14
+ * Tweakable per-instance but defaults match the values declared in the Phase 4.2 plan doc.
14
15
  */
15
16
  export interface RankingWeights {
16
17
  /**
@@ -38,83 +39,110 @@ export interface RankingWeights {
38
39
  */
39
40
  lengthPenaltyWeight: number
40
41
  /**
41
- * Magnitude of the proximity boost when the query carries `near`. The contribution is `proximityBoost / (1 +
42
- * distanceKm / proximityScaleKm)` — at distance 0 the boost is full magnitude, at `proximityScaleKm` it's half,
43
- * decaying further with distance. Default tuned so proximity can overcome a typical FTS rank tie but not dominate a
44
- * strong text match.
42
+ * Magnitude of the proximity boost when the query includes `near`.
43
+ *
44
+ * The contribution is `proximityBoost / (1 + distanceKm / proximityScaleKm)`.
45
+ * At distance 0 the boost has full magnitude.
46
+ *
47
+ * At `proximityScaleKm` it is half magnitude.
48
+ * It decays further as distance increases.
49
+ *
50
+ * Its default is tuned so proximity can overcome a typical FTS rank tie
51
+ * but not dominate a strong text match.
45
52
  */
46
53
  proximityBoost: number
47
54
  /**
48
- * Magnitude of the bias-hint term inside the exact-tier PROMINENCE sort (the `bias`/viewport path). Deliberately
49
- * population-scale (default = populationBoost) so a candidate near the map view / the user beats a distant-but-bigger
50
- * namesake — "the map view wins" is the feature; same-region ties (all candidates far from every hint) still fall to
51
- * population.
55
+ * Magnitude of the bias-hint term inside the exact-tier prominence sort (the `bias`/viewport path).
56
+ *
57
+ * Deliberately population-scale, so a candidate near the map view
58
+ * or the user beats a distant-but-bigger namesake.
59
+ * "The map view wins" is the feature.
60
+ *
61
+ * Same-region ties, where all candidates are far from every hint, still fall to population.
62
+ *
63
+ * @defaultValue the same value as `populationBoost`
52
64
  */
53
65
  biasBoost: number
54
66
  /**
55
- * Distance (km) at which the proximity boost halves. Tune to the typical query radius.
67
+ * Distance (km) at which the proximity boost halves.
68
+ *
69
+ * Tune to the typical query radius.
56
70
  */
57
71
  proximityScaleKm: number
58
72
  /**
59
- * Magnitude of the population boost when the candidate has a known `wof:population`. The contribution is
60
- * `populationBoost * log10(1 + population) / populationScaleLog10`, capped at `populationBoost`. WOF only carries
61
- * population for ~15% of localities (mostly larger ones); places without it get +0 (never a penalty). Default tuned
62
- * so the famous Springfield, IL (pop ~112k) gets ~0.42 boost — enough to nudge past tiny same-name peers.
73
+ * Magnitude of the population boost when the candidate has a known `wof:population`.
74
+ *
75
+ * The contribution is `populationBoost * log10(1 + population) / populationScaleLog10`,
76
+ * capped at `populationBoost`.
77
+ * WOF records population for about 15% of localities (mostly larger ones),
78
+ * and places without it get +0, never a penalty.
79
+ *
80
+ * Its default is tuned so the famous Springfield, IL (pop ~112k) gets about a 0.42 boost,
81
+ * enough to nudge past tiny same-name peers.
63
82
  */
64
83
  populationBoost: number
65
84
  /**
66
- * Population (in log10) at which the boost reaches its full magnitude. Default 6 — i.e. a population of 1,000,000
67
- * gives `populationBoost` exactly. Larger populations cap at the same value (no compounding effect for megacities).
85
+ * Population (in log10) at which the boost reaches its full magnitude.
86
+ *
87
+ * Larger populations cap at the same value (no compounding effect for megacities).
88
+ *
89
+ * @defaultValue `6`, so a population of 1,000,000 gives `populationBoost` exactly
68
90
  */
69
91
  populationScaleLog10: number
70
92
  /**
71
- * Tier candidates with an EXACT name/alias match above candidates that only match partially, BEFORE the weighted-sum
72
- * score is consulted. Default true.
73
- *
74
- * Why this is needed (and why it ALIGNS with — rather than overrides — the population/importance signal): the
75
- * weighted sum adds population as a large additive boost (`populationBoost`, up to +4) so that famous places surface
76
- * for unambiguous full-name queries. But population is a _prominence prior_ — its job is to break ties among
77
- * candidates that match the query EQUALLY WELL (e.g. "Springfield" → Springfield IL over Springfield MA, both exact
78
- * name matches). It was never meant to promote a place that matches the query WORSE. For a 2-letter region
79
- * abbreviation that backfires: querying "ME" returns Maine (which has the exact alias `ME`) AND Missouri/
80
- * Michigan/etc. (which do not), and Missouri's larger population (+4) overcomes Maine's bm25 edge — so "Portland, ME"
81
- * resolves its region to Missouri and the locality then cascades to the wrong state. Tiering restores the intended
82
- * ordering: **match quality is the primary key, prominence (population) the secondary key WITHIN a tier.**
83
- * Springfield-IL-over-MA still works (both exact → same tier → population decides); ME→Maine now works (only Maine is
84
- * exact → higher tier → population never gets to override it). See
85
- * docs/articles/evals/resolver-geo/2026-05-30-resolver-exact-match.md.
86
- *
87
- * Note: tiering re-ranks within the over-fetched candidate window (`limit * 4`); a pathological exact match that
88
- * falls outside that window is not rescued. For the region-abbrev case the window is comfortably sufficient (a
89
- * handful of states match a 2-letter query).
93
+ * Tier candidates with an exact name/alias match above candidates that only match
94
+ * partially, before the weighted-sum score is consulted.
95
+ *
96
+ * The weighted sum adds population as a large additive boost, so famous places
97
+ * surface for unambiguous full-name queries.
98
+ * Population is a prominence prior.
99
+ *
100
+ * Its job is to break ties among candidates that match the query equally well,
101
+ * as in "Springfield" → Springfield IL over Springfield MA.
102
+ *
103
+ * It does not promote a place that matches the query worse.
104
+ * Tiers keep match quality as the primary key and prominence as the secondary key within each tier.
105
+ *
106
+ * Note: tiering re-ranks within the over-fetched candidate window (`limit * 4`),
107
+ * so a pathological exact match outside that window is not rescued.
108
+ *
109
+ * @defaultValue `true`
90
110
  */
91
111
  exactMatchTiering: boolean
92
112
  /**
93
- * #936 option 3 — official-language names ARE names. When true, a candidate holding the query as an OFFICIAL name
94
- * (`names.official = 1`: a preferred-form name in an official language of its country, stamped at ingest) joins the
95
- * NAME-exact sub-tier rather than the alias-exact one, provided its population clears {@link officialNameExactFloor}.
96
- * Fixes unscoped "Åbo" → Turku (its official Swedish name) over a hamlet literally named Åbo; population still orders
97
- * within the sub-tier, so Paris → Paris FR is untouched.
98
- *
99
- * Default true (operator-promoted 2026-07-03 after the pre-registered eval battery: four intended exonym flips —
100
- * Berne→Bern, Bruges→Brugge, Roma→Rome, Åbo→Turku — with the namesake/abbreviation rows and the US/FI panels
101
- * byte-identical). Requires a gazetteer carrying the #940 ingest bit — on older DBs without the `official` column the
102
- * probe fails soft and behavior is identical to the flag being off.
113
+ * Official-language names count as names.
114
+ *
115
+ * When true, a candidate holding the query as an official name (`names.official = 1`,
116
+ * a preferred-form name in an official language of its country, stamped at ingest)
117
+ * joins the name-exact sub-tier rather than the alias-exact one, provided its
118
+ * population clears {@link officialNameExactFloor}.
119
+ *
120
+ * Fixes unscoped "Åbo" → Turku (its official Swedish name) over a hamlet literally called Åbo.
121
+ * Population still orders within the sub-tier, so Paris → Paris FR is untouched.
122
+ *
123
+ * It requires a gazetteer carrying the `official` ingest bit.
124
+ *
125
+ * On older DBs without that column, the probe fails soft and behavior matches the flag being off.
126
+ *
127
+ * @defaultValue `true`
103
128
  */
104
129
  officialNameExact: boolean
105
130
  /**
106
- * Minimum population for a candidate's official names to join the name-exact sub-tier. The #936 review's no-floor
107
- * census measured the boundary: ≥100k holders are the famous-exonym class (757 flips, intent-correct; 7 collisions,
108
- * none harmful) while 10k–100k holders are junk-dominated (3,481 flips led by short-form mis-tags — Villeneuve-Loubet
109
- * carrying "villeneuve" would bury five real villages of that name). Rank-time knob: tunable without re-ingest;
110
- * below-floor official names simply stay alias-tier (today's behavior).
131
+ * Minimum population for a candidate's official names to join the name-exact sub-tier.
132
+ *
133
+ * The floor separates the famous-exonym class from a junk-dominated one led by short-form
134
+ * mis-tags, where a place carrying a short form would bury real villages of that name.
135
+ * This is a rank-time knob, tunable without re-ingest.
136
+ * Below-floor official names stay in the alias tier.
111
137
  */
112
138
  officialNameExactFloor: number
113
139
  }
114
140
 
115
141
  /**
116
- * The shipped weights. Every value is a measured decision — change one and re-run the resolver eval; the per-field docs
117
- * on {@link RankingWeights} say what each change moves and what motivated its current value.
142
+ * The shipped weights.
143
+ *
144
+ * Every value is a measured decision, so change one and re-run the resolver eval.
145
+ * The per-field docs on {@link RankingWeights} say what each change moves.
118
146
  */
119
147
  export const DEFAULT_WEIGHTS: RankingWeights = {
120
148
  placetypeMatchBoost: 0.5,
@@ -126,34 +154,31 @@ export const DEFAULT_WEIGHTS: RankingWeights = {
126
154
  proximityBoost: 0.8,
127
155
  proximityScaleKm: 100,
128
156
  biasBoost: 4,
129
- // populationBoost is intentionally large — empirical tuning against real WOF showed BM25 gaps
130
- // of 1.5-3.0 between famous places and tiny same-name peers (because the famous ones have
131
- // hundreds of alt-name entries that hurt their FTS document score). To consistently surface
132
- // "the famous one" for unambiguous queries like "New York" or "Chicago", the population signal
133
- // needs to dominate. Callers wanting a more conservative balance can drop this in the
134
- // RankingWeights override.
135
- //
136
- // Note: this resolver uses `place_population` directly. The separate `place_importance` table
137
- // (Wikipedia-derived) is consumed by the FST layer, not here. See
138
- // docs/articles/concepts/importance-vs-population.md for the two-signal contract.
157
+ // populationBoost is intentionally large.
158
+ // Real WOF showed BM25 gaps of 1.5 to 3.0 between famous places and tiny same-name peers,
159
+ // because the famous ones have hundreds of alt-name entries that hurt their FTS document score.
160
+ // This resolver reads `place_population` directly.
161
+ // The separate Wikipedia-derived `place_importance` table is consumed by the FST layer.
139
162
  populationBoost: 4,
140
163
  populationScaleLog10: 6,
141
- // Exact name/alias match outranks partial match before the weighted sum (incl. population) is
142
- // consulted — keeps population as an intra-tier prominence tiebreaker, not a cross-tier promoter.
143
- // Fixes the 2-letter-region-abbrev bug ("ME" → Maine, not the more-populous Missouri).
164
+ // Exact name and alias matches outrank partial matches before the weighted sum.
165
+ // This keeps population as an intra-tier prominence tiebreaker instead of a cross-tier promoter.
144
166
  exactMatchTiering: true,
145
- // #936 option 3 — promoted default-ON 2026-07-03 (eval battery PASS; see the RankingWeights docstring).
146
167
  officialNameExact: true,
147
168
  officialNameExactFloor: 100_000,
148
169
  }
149
170
 
150
171
  /**
151
- * The population contribution as a 0..1 fraction: `min(1, log10(1 + population) / populationScaleLog10)`. Zero for an
152
- * absent or non-positive population — and zero for a non-positive scale, so a magnitude never carries its own absence.
153
- * The coordinate-first locality path consumes this fraction directly; {@link populationBoostTerm} scales it.
172
+ * The population contribution as a 0..1 fraction: `min(1, log10(1 + population) / populationScaleLog10)`.
173
+ *
174
+ * Returns zero for an absent or non-positive population.
175
+ * It also returns zero for a non-positive scale.
176
+ *
177
+ * A magnitude never represents its own absence.
178
+ * The coordinate-first locality path consumes this fraction directly. {@link populationBoostTerm} scales it.
154
179
  */
155
180
  export function populationScaleTerm(
156
- population: number | null | undefined,
181
+ population: number | null,
157
182
  weights: Pick<RankingWeights, "populationScaleLog10">
158
183
  ): number {
159
184
  if (population == null || population <= 0 || weights.populationScaleLog10 <= 0) return 0
@@ -162,12 +187,14 @@ export function populationScaleTerm(
162
187
  }
163
188
 
164
189
  /**
165
- * The additive population boost: `populationBoost * populationScaleTerm(...)`, capped at `populationBoost` magnitude at
166
- * `10^populationScaleLog10` people. Missing population contributes 0 — never a penalty. The one formula behind the Node
167
- * weighted sum and the WASM re-rank, so the two backends cannot drift.
190
+ * The additive population boost: `populationBoost * populationScaleTerm(...)`,
191
+ * capped at `populationBoost` magnitude at `10^populationScaleLog10` people.
192
+ *
193
+ * A missing population contributes 0, without a penalty.
194
+ * The one formula behind the Node weighted sum and the wasm re-rank, so the two backends cannot drift.
168
195
  */
169
196
  export function populationBoostTerm(
170
- population: number | null | undefined,
197
+ population: number | null,
171
198
  weights: Pick<RankingWeights, "populationBoost" | "populationScaleLog10">
172
199
  ): number {
173
200
  return weights.populationBoost * populationScaleTerm(population, weights)
@@ -3,15 +3,13 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * The comparable-key expansion for a parsed REGION qualifier — one function, both consumers (the
7
- * #861 rule). The admin-coherence verdicts (`mailwoman/admin-coherence.ts`) fold a qualifier and a
8
- * winner-ancestry name through this to decide `confirmed`/`contradicted`; the candidate backend's
9
- * admin-containment re-rank (#1717 stage 2, `candidate-lookup.ts`) folds the same qualifier through
10
- * it to find the qualifier's own region-class rows in the candidate table. A check that says
11
- * `contradicted` and a re-rank that cannot find the qualifier would otherwise be two readings of
12
- * one string that silently disagree — the exact drift the shared-function rule exists to prevent.
13
- *
14
- * Lives HERE rather than in `mailwoman` because the dependency points this way: `mailwoman`
6
+ * Both consumers use this expansion to compare parsed region qualifiers with stored keys.
7
+ * Admin-coherence verdicts (`mailwoman/admin-coherence.ts`) fold a qualifier and the winner's ancestry
8
+ * to decide `confirmed` or `contradicted`. The candidate backend's admin-containment re-rank
9
+ * (`candidate-lookup.ts`) uses the same fold to find region-class rows. The shared function keeps
10
+ * both decisions aligned.
11
+ *
12
+ * Lives here rather than in `mailwoman` because the dependency points this way. `mailwoman`
15
13
  * depends on `@mailwoman/resolver-wof-sqlite` (which owns the fold and already depends on codex),
16
14
  * never the reverse.
17
15
  */
@@ -21,26 +19,39 @@ import { matchSubdivision, matchSubdivisionIn } from "@mailwoman/codex/country"
21
19
  import { normalizeLocalityForKey } from "#street/normalize"
22
20
 
23
21
  /**
24
- * The ancestry placetypes that answer for a parsed `region` qualifier — WOF's admin band between country and locality.
25
- * Deliberately the whole band: a qualifier stated at any grain ("Lancashire", a ceremonial county; "Thüringen", a Land)
26
- * may confirm against whichever level the backend stored, and `contradicted` requires the entire band to miss, so
27
- * widening the band only ever makes the check more conservative.
22
+ * The ancestry placetypes that answer for a parsed `region` qualifier,
23
+ * WOF's admin band between country and locality.
24
+ *
25
+ * The full band covers qualifiers at any grain, such as "Lancashire"
26
+ * (a ceremonial county) or "Thüringen" (a Land).
27
+ * A qualifier may confirm against any level the backend stored.
28
+ *
29
+ * `contradicted` requires every level in the band to miss.
30
+ * A wider band makes the check more conservative.
28
31
  */
29
32
  export const REGION_CLASS_PLACETYPES: ReadonlySet<string> = new Set(["region", "macroregion", "county", "macrocounty"])
30
33
 
31
34
  /**
32
- * County-style qualifier prefixes stripped to produce a comparison VARIANT. Ireland writes `Co. Westmeath` where WOF
33
- * stores `Westmeath`, so the prefix defeats the fold and every Irish county qualifier read `contradicted` on the first
34
- * board census (2026-08-17, five rows). The stripped form is ADDED to the key set, never substituted — `County Durham`
35
- * is a real name whose stripped variant simply also matches, and a set union can only widen confirmation, so the
36
- * closure is monotone: `contradicted → confirmed` is the only movement it can cause.
35
+ * County-style qualifier prefixes stripped to produce a comparison variant.
36
+ *
37
+ * Ireland writes `Co. Westmeath` where WOF stores `Westmeath`, so the prefix defeats the fold
38
+ * and every Irish county qualifier would read `contradicted`.
39
+ * The stripped form is added to the key set, never substituted.
40
+ *
41
+ * `County Durham` is a real name that also matches after prefix removal.
42
+ * Set union can only widen confirmation, so the closure is monotone.
43
+ *
44
+ * `contradicted → confirmed` is the only movement it can cause.
37
45
  */
38
46
  const COUNTY_QUALIFIER_PREFIXES = ["county", "co.", "co"] as const
39
47
 
40
48
  /**
41
- * Trailing admin-qualifier words, the suffix sibling of the prefix above: `San José Province` (CR board row) folds
42
- * against stored `San José` only with the word removed. Same monotone rule — the stripped form joins the set, never
43
- * replaces the original.
49
+ * Admin-qualifier words at the end, the suffix form of the prefix above.
50
+ *
51
+ * `San José Province` (CR board row) folds against stored `San José` only with the word removed.
52
+ *
53
+ * Same monotone rule.
54
+ * The stripped form joins the set, never replaces the original.
44
55
  */
45
56
  const ADMIN_QUALIFIER_SUFFIXES = ["province", "prov.", "prov"] as const
46
57
 
@@ -83,14 +94,20 @@ function withoutSuffix(value: string, suffixes: readonly string[]): string {
83
94
  }
84
95
 
85
96
  /**
86
- * The comparable keys a region string expands to: its own fold (the shared candidate.db `name_key` normalizer,
87
- * {@link normalizeLocalityForKey} — build side and check side agree by construction); a county-prefix-stripped variant;
88
- * the codex subdivision expansions — the disjoint US+CA table always, plus the COUNTRY-SCOPED table when the caller
89
- * knows a country (`WA` under AU is Western Australia; under US, Washington — the collision that keeps AU out of the
90
- * unscoped table). Every expansion lands the canonical name and code folds in the set, so `IL`/`Illinois` and
91
- * `WA`/`Western Australia` meet from either side.
97
+ * The comparable keys a region string expands to: its own fold (the shared candidate.db `name_key`
98
+ * normalizer, {@link normalizeLocalityForKey}, so build side and check side agree by construction).
99
+ *
100
+ * A county-prefix-stripped variant.
101
+ * The codex subdivision expansions.
102
+ *
103
+ * The disjoint US and CA table always, plus the country-scoped table when the caller knows a country.
104
+ * `WA` under AU is Western Australia, while under US it is Washington,
105
+ * the collision that keeps AU out of the unscoped table.
106
+ *
107
+ * Every expansion lands the canonical name and code folds in the set, so `IL`
108
+ * and `Illinois`, or `WA` and `Western Australia`, meet from either side.
92
109
  */
93
- export function regionKeys(value: string, countryAlpha2?: string): Set<string> {
110
+ export function regionKeys(value: string, countryAlpha2?: string | null): Set<string> {
94
111
  const keys = new Set([normalizeLocalityForKey(value)])
95
112
 
96
113
  for (const stripped of [
@@ -111,25 +128,36 @@ export function regionKeys(value: string, countryAlpha2?: string): Set<string> {
111
128
  }
112
129
  }
113
130
 
114
- // The empty fold stays IN the set on purpose — the admin-coherence verdicts have always compared
115
- // the empty key (two empty-folding strings intersect → `confirmed`), and this move must not shift
116
- // a verdict. A consumer probing a table by key filters the empty string out itself.
131
+ // The empty fold stays in the set on purpose.
132
+ // The admin-coherence verdicts have always compared the empty key
133
+ // (two empty-folding strings intersect → `confirmed`), and this move must not shift a verdict.
134
+ // A consumer probing a table by key filters the empty string out itself.
117
135
  return keys
118
136
  }
119
137
 
120
138
  /**
121
- * The PROBE-side expansion for a region qualifier — {@link regionKeys} plus the county-PREFIXED variant of every key.
122
- *
123
- * The verdict implementation intersects two {@link regionKeys} SETS, so `Co. Donegal` meets stored `County Donegal` at
124
- * the shared stripped key `donegal`. A table probe is one-sided: it matches the STORED fold verbatim, and WOF stores
125
- * Irish counties under `county donegal` with no bare `donegal` key (measured on the shipped candidate.db — the
126
- * qualifier probe missed every Irish county until this variant landed). Adding `county <key>` restores the
127
- * two-sidedness for the one stored-form family with an evidenced case; the union is monotone (a wider qualifier set can
128
- * only find more BEARERS, each of which must still genuinely contain a candidate before anything moves). The suffix
129
- * sibling (`<key> province`) is deliberately absent — no stored-form case has been evidenced, and a change without a
130
- * board does not get built.
139
+ * The probe-side expansion for a region qualifier, {@link regionKeys} plus the
140
+ * county-prefixed variant of every key.
141
+ *
142
+ * The verdict implementation intersects two {@link regionKeys} sets, so `Co. Donegal`
143
+ * meets stored `County Donegal` at the shared stripped key `donegal`.
144
+ * A table probe matches the stored fold verbatim in one direction.
145
+ *
146
+ * WOF stores Irish counties under `county donegal`.
147
+ * The table has no bare `donegal` key.
148
+ *
149
+ * The qualifier probe missed every Irish county until this variant landed.
150
+ *
151
+ * The `county <key>` form restores two-sidedness for the one stored-form family with an evidenced case.
152
+ * The union is monotone because a wider qualifier set can only find more bearers.
153
+ *
154
+ * Each bearer must still contain a candidate before anything moves.
155
+ *
156
+ * The suffix sibling (`<key> province`) is deliberately absent.
157
+ * The board has no case for this stored form.
158
+ * Add one before building a change for this suffix.
131
159
  */
132
- export function regionQualifierProbeKeys(value: string, countryAlpha2?: string): Set<string> {
160
+ export function regionQualifierProbeKeys(value: string, countryAlpha2?: string | null): Set<string> {
133
161
  const keys = regionKeys(value, countryAlpha2)
134
162
  // Snapshot before widening: the loop adds `county <key>` members that must not themselves be revisited.
135
163
  const bare = [...keys]