@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,39 +3,37 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `buildCoincidentRoles` — derives the **coincident-roles relation** (#403, epic #402) into the
7
- * unified gazetteer.
6
+ * `buildCoincidentRoles` derives the coincident-roles relation into the unified gazetteer.
8
7
  *
9
- * Many places occupy MULTIPLE admin tiers under one name: German city-states (Berlin/Hamburg/Bremen
10
- * = city == state), Italian provinces named after their capital (Milano, Varese…), Spanish
11
- * provinces-after-capitals, UK unitary authorities, JP prefectures, NL province-capitals
12
- * (Utrecht/Groningen), Shanghai. When an address surfaces only the admin role (the parser drops
13
- * the locality span), the resolver has no locality to place. The hierarchy-completion step (#405)
14
- * repairs that by consulting THIS relation; the table replaces #387's hardcoded 15 km constant
15
- * with the gazetteer's own structure, so the runtime is an O(1) membership lookup with no
16
- * distance math.
8
+ * Many places occupy multiple admin tiers under one name: German city-states (Berlin, Hamburg,
9
+ * Bremen), Italian provinces whose names match their capital (Milano, Varese), Spanish
10
+ * provinces-after-capitals, UK unitary authorities, JP prefectures, NL province-capitals (Utrecht,
11
+ * Groningen), and Shanghai. When an address surfaces only the admin role because the parser
12
+ * dropped the locality span, the resolver has no locality to place. The hierarchy-completion step
13
+ * consults this relation. The table preserves the gazetteer's own structure, so the runtime is an
14
+ * O(1) membership lookup with no distance math.
17
15
  *
18
- * V1 is REGION-tier only (admin.placetype = `region`): the ~124 places matching the census across 9
19
- * countries (IT/ES/GB/JP/KR/FR/DE/NL/CN). County-tier same-name coincidences are deliberately
20
- * excluded — they're dominated by French cantons and JP counties (admin subdivisions named after
21
- * a seat town, not dual-role cities) that don't hit the parser-drops-locality failure; genuine
22
- * consolidated city-counties (US SF/Denver) are a separate follow-up needing a relative-size
23
- * filter.
16
+ * V1 is region-tier only (admin.placetype = `region`), the ~124 places matching the census across
17
+ * nine countries. County-tier same-name coincidences are excluded because they are dominated by
18
+ * French cantons and JP counties, admin subdivisions whose names match a seat town that do not hit the
19
+ * parser-drops-locality failure. Consolidated city-counties (US SF, Denver) are a separate
20
+ * follow-up needing a relative-size filter.
24
21
  *
25
- * A pair `(admin, locality)` is recorded when all hold: same `name` (case-insensitive), the
26
- * locality is a `descendant` of the admin (via the `ancestors` table), and their centroids are
27
- * within a RELATIVE tolerance — `toleranceFraction × admin-bbox-diagonal`, floored at
28
- * `minToleranceKm`. The relative term lets a large Italian province admit a city ~tens of km from
29
- * its centroid while a tiny city-state stays tight; the floor catches city-states whose bbox is
30
- * small (Bremen's centroids sit 9.3 km apart). The tolerance lives ONLY here at build time — it
31
- * never enters the resolver hot path.
22
+ * A pair `(admin, locality)` is recorded when the names match case-insensitively, the locality descends from the admin in `ancestors`,
23
+ * and their centroids fall within a
24
+ * relative tolerance of `toleranceFraction × admin-bbox-diagonal`, floored at `minToleranceKm`.
25
+ * The relative term lets a large Italian province admit a city tens of km from its centroid while
26
+ * a tiny city-state stays tight. The floor catches city-states whose bbox is small (Bremen's
27
+ * centroids sit 9.3 km apart). The tolerance lives only here at build time and never enters the
28
+ * resolver hot path.
32
29
  *
33
- * `relationship_type` is recorded for debuggability / deferred per-type behavior; v1 completion is
34
- * uniform (see #405). It's a coarse classification, not critical.
30
+ * `relationship_type` is recorded for debuggability and deferred per-type behavior. V1 completion
31
+ * is uniform. It is a coarse classification.
35
32
  *
36
- * Mirrors the derived-table builder pattern in `fts.ts` (`buildPlaceSearchFTS`). Run incrementally
37
- * against an existing `admin-global-priority.db` via `build-coincident-roles-cli.ts`; should also
38
- * be wired as a post-step of the main `scripts/build-unified-wof.ts`.
33
+ * This mirrors the derived-table builder pattern in `fts.ts` (`buildPlaceSearchFTS`). It runs
34
+ * incrementally against an existing `admin-global-priority.db` via
35
+ * `build-coincident-roles-cli.ts`, and it should also be wired as a post-step of the main
36
+ * `scripts/build-unified-wof.ts`.
39
37
  */
40
38
 
41
39
  import type { CoincidentLocality } from "@mailwoman/core/resolver"
@@ -46,13 +44,15 @@ import { tableExists } from "@mailwoman/sqlite/introspection"
46
44
 
47
45
  import type { WOFDatabase } from "#schema"
48
46
  /**
49
- * Table of places that hold more than one admin role — a locality that is also its county seat. Written by the
50
- * gazetteer build, read by the resolver when a coincident locality has to be chosen.
47
+ * Table of places that hold more than one admin role.
48
+ * A locality that is also its county seat.
49
+ *
50
+ * Written by the gazetteer build, read by the resolver when a coincident locality has to be chosen.
51
51
  */
52
52
  export const COINCIDENT_ROLES_TABLE = "coincident_roles"
53
53
 
54
54
  /**
55
- * A place that plays multiple admin roles — one row of the relation, keyed by `admin_id`.
55
+ * A place that plays multiple admin roles, one row of the relation keyed by `admin_id`.
56
56
  */
57
57
  export interface CoincidentRole {
58
58
  localityID: number
@@ -64,19 +64,27 @@ export interface CoincidentRole {
64
64
 
65
65
  export interface BuildCoincidentRolesOpts {
66
66
  /**
67
- * Drop + rebuild the table if it already exists. Default true (the build is cheap + idempotent).
67
+ * Drop + rebuild the table if it already exists.
68
+ *
69
+ * @defaultValue `true` (the build is cheap + idempotent)
68
70
  */
69
71
  drop?: boolean
70
72
  /**
71
- * Relative tolerance: a pair is kept when centroid distance ≤ `toleranceFraction × bbox-diagonal`. Default 0.15.
73
+ * Relative tolerance: a pair is kept when centroid distance ≤ `toleranceFraction × bbox-diagonal`.
74
+ *
75
+ * @defaultValue `0.15`
72
76
  */
73
77
  toleranceFraction?: number
74
78
  /**
75
- * Floor (km) under the relative tolerance, so small-bbox city-states still qualify. Default 12.
79
+ * Floor (km) under the relative tolerance, so small-bbox city-states still qualify.
80
+ *
81
+ * @defaultValue `12`
76
82
  */
77
83
  minToleranceKm?: number
78
84
  /**
79
- * Centroid distance (km) below which a region-tier pair is classed `city-state` (metadata only). Default 2.
85
+ * Centroid distance (km) below which a region-tier pair is classed `city-state` (metadata only).
86
+ *
87
+ * @defaultValue `2`
80
88
  */
81
89
  cityStateMaxKm?: number
82
90
  onProgress?: (phase: string, detail?: string) => void
@@ -106,8 +114,11 @@ interface CandidateRow {
106
114
  }
107
115
 
108
116
  /**
109
- * Derive the coincident-roles relation into `db`. Additive — only creates/replaces the `coincident_roles` table; never
110
- * touches `spr`/`names`/`ancestors`. Idempotent.
117
+ * Derive the coincident-roles relation into `db`.
118
+ *
119
+ * Additive, only creating or replacing the `coincident_roles` table.
120
+ * It never touches `spr`, `names`, or `ancestors`.
121
+ * Idempotent.
111
122
  */
112
123
  export function buildCoincidentRoles(
113
124
  db: DatabaseClient<WOFDatabase>,
@@ -127,10 +138,9 @@ export function buildCoincidentRoles(
127
138
 
128
139
  onProgress("creating", COINCIDENT_ROLES_TABLE)
129
140
 
130
- // Raw DDL by design: this is a sync builder consumed by a sync CLI (build-coincident-roles-cli) and
131
- // 6 sync unit tests, so routing one table through async Kysely would cascade async through all of
132
- // them for no real gain. See AGENTS.md "Database / inline SQL". (The SELECT + INSERT loop below are
133
- // likewise the raw hot path.)
141
+ // Raw DDL by design: this is a sync builder consumed by a sync CLI and six sync unit tests,
142
+ // so routing one table through async Kysely would cascade async through all of them.
143
+ // See agents.md "Database / inline SQL".
134
144
  db.exec(`
135
145
  CREATE TABLE IF NOT EXISTS ${COINCIDENT_ROLES_TABLE} (
136
146
  admin_id INTEGER NOT NULL,
@@ -145,9 +155,11 @@ export function buildCoincidentRoles(
145
155
 
146
156
  onProgress("scanning")
147
157
 
148
- // Admin (region/county tier) ⋈ same-name DESCENDANT locality. `place_population` is optional (LEFT
149
- // JOIN → 0 when absent). The relative-tolerance filter + relationship classification happen in JS so
150
- // the SQL stays a plain join. `spr` exposes the bbox columns we need for the diagonal.
158
+ // Admin (region or county tier) joined to a same-name descendant locality.
159
+ // `place_population` is optional, so the left join yields 0 when absent.
160
+ // JavaScript applies the relative-tolerance filter and classifies the relationship.
161
+ // SQL stays a plain join.
162
+ // `spr` exposes the bbox columns needed for the diagonal.
151
163
  const candidates = allRows<CandidateRow>(
152
164
  db.prepare(
153
165
  `SELECT r.id AS admin_id, r.placetype AS admin_placetype, r.country AS country, l.id AS locality_id,
@@ -183,11 +195,12 @@ export function buildCoincidentRoles(
183
195
  const tolerance = Math.max(toleranceFraction * diag, minToleranceKm)
184
196
 
185
197
  if (dist > tolerance) continue
186
- // v1 is region-tier only: a place is a `city-state` when its centroid coincides with the
187
- // region's (Berlin/Hamburg), else `capital-seat` (a region named after its principal city, e.g.
188
- // Milano province → Milano comune). `consolidated-county` is reserved for a future county-tier
189
- // pass (US SF/Denver) — excluded from v1 because county-tier same-name coincidences are
190
- // dominated by French cantons / JP counties that don't hit the parser-drops-locality failure.
198
+ // v1 is region-tier only: a place is `city-state` when its centroid coincides with the
199
+ // region's (Berlin, Hamburg), otherwise `capital-seat` (a region whose name matches its
200
+ // principal city, such as Milano province and Milano comune). `consolidated-county` is
201
+ // reserved for a future county-tier pass (US SF, Denver), excluded from v1 because
202
+ // county-tier same-name coincidences are dominated by French cantons and JP counties
203
+ // that do not hit the parser-drops-locality failure.
191
204
  const relationshipType = dist <= cityStateMaxKm ? "city-state" : "capital-seat"
192
205
  insert.run(c.admin_id, c.locality_id, relationshipType, c.admin_placetype, dist, c.pop)
193
206
 
@@ -209,16 +222,20 @@ export function buildCoincidentRoles(
209
222
  }
210
223
 
211
224
  /**
212
- * True iff the relation table exists. Used by the resolver to decide whether completion can run.
225
+ * True iff the relation table exists.
226
+ *
227
+ * Used by the resolver to decide whether completion can run.
213
228
  */
214
229
  export function coincidentRolesExists<DB>(db: DatabaseClient<DB>): boolean {
215
230
  return tableExists(db, COINCIDENT_ROLES_TABLE)
216
231
  }
217
232
 
218
233
  /**
219
- * Load the relation into an in-memory map keyed by `admin_id` for O(1) runtime lookup (#405). Each admin may map to
220
- * MULTIPLE same-name descendants; the consumer disambiguates (min distance → population → abstain). Returns an empty
221
- * map when the table is absent.
234
+ * Load the relation into an in-memory map keyed by `admin_id` for an O(1) runtime lookup.
235
+ *
236
+ * Each admin may map to multiple same-name descendants.
237
+ * The consumer disambiguates by minimum distance, then population, then abstains.
238
+ * Returns an empty map when the table is absent.
222
239
  */
223
240
  export function loadCoincidentRoles<DB>(db: DatabaseClient<DB>): Map<number, CoincidentRole[]> {
224
241
  const map = new Map<number, CoincidentRole[]>()
@@ -261,8 +278,10 @@ export function loadCoincidentRoles<DB>(db: DatabaseClient<DB>): Map<number, Coi
261
278
  }
262
279
 
263
280
  /**
264
- * The relation joined with `spr`, as an in-memory map keyed by `admin_id` — the resolver-facing view of
265
- * {@link loadCoincidentRoles}, carrying the canonical name and coordinates each coincident locality resolves to.
281
+ * The relation joined with `spr`, as an in-memory map keyed by `admin_id`.
282
+ *
283
+ * The resolver-facing view of {@link loadCoincidentRoles}, carrying the canonical name
284
+ * and coordinates each coincident locality resolves to.
266
285
  * Returns an empty map when the relation table is absent.
267
286
  */
268
287
  export function loadCoincidentLocalities<DB>(db: DatabaseClient<DB>): Map<number, CoincidentLocality[]> {
@@ -301,6 +320,17 @@ export function loadCoincidentLocalities<DB>(db: DatabaseClient<DB>): Map<number
301
320
  relationshipType: r.relationshipType,
302
321
  population: r.population,
303
322
  distanceKm: r.distanceKm,
323
+ parent_id: null,
324
+ prominence: null,
325
+ referential: null,
326
+ encyclopedic: null,
327
+ importance: null,
328
+ exactMatch: null,
329
+ mismatch: null,
330
+ resolutionQuality: null,
331
+ containedByQualifier: null,
332
+ regionScopeMiss: null,
333
+ variantAliasExempted: null,
304
334
  }
305
335
 
306
336
  const list = map.get(r.adminID)
@@ -4,14 +4,15 @@
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
6
  * Typed schema for the convention asset — the `address_convention` table `SqliteConventionSource` probes, plus the
7
- * `meta` provenance row every sealed artifact here carries. The interface is the read/write contract and
7
+ * `meta` provenance row every sealed artifact here includes. The interface is the read/write interface and
8
8
  * {@link createAddressConventionTable} creates the table, so a column added to one is a compile error against the
9
9
  * other.
10
10
  */
11
11
 
12
+ import type { KeyValueMetaTable } from "@mailwoman/sqlite/database-schema"
12
13
  import type { Kysely } from "kysely"
13
14
 
14
- import { ADDRESS_CONVENTION_TABLE } from "#convention/index"
15
+ import { ADDRESS_CONVENTION_TABLE } from "#convention"
15
16
 
16
17
  /**
17
18
  * One convention profile, keyed by the WOF admin polygon it attaches to.
@@ -19,7 +20,9 @@ import { ADDRESS_CONVENTION_TABLE } from "#convention/index"
19
20
  export interface AddressConventionTable {
20
21
  wof_id: number
21
22
  /**
22
- * The `Convention` record, JSON-encoded. Parsed at read time by `SqliteConventionSource`.
23
+ * The `Convention` record, JSON-encoded.
24
+ *
25
+ * Parsed at read time by `SqliteConventionSource`.
23
26
  */
24
27
  convention: string
25
28
  /**
@@ -28,28 +31,24 @@ export interface AddressConventionTable {
28
31
  source: string
29
32
  }
30
33
 
31
- /**
32
- * Key/value provenance for the sealed artifact.
33
- */
34
- export interface ConventionMetaTable {
35
- key: string
36
- value: string | null
37
- }
38
-
39
34
  export interface ConventionDatabase {
40
35
  address_convention: AddressConventionTable
41
- meta: ConventionMetaTable
36
+ meta: KeyValueMetaTable
42
37
  }
43
38
 
44
39
  /**
45
- * The slice of a Kysely handle the convention DDL touches. Kysely is invariant in its schema parameter, so naming only
46
- * `schema` lets a builder holding a wider handle pass it without a cast.
40
+ * The subset of a Kysely handle the convention DDL touches.
41
+ *
42
+ * Kysely is invariant in its schema parameter, so naming only `schema` lets a
43
+ * builder holding a wider handle pass it without a cast.
47
44
  */
48
45
  export type ConventionSchemaHandle = Pick<Kysely<ConventionDatabase>, "schema">
49
46
 
50
47
  /**
51
- * Create `address_convention`. The table name comes from {@link ADDRESS_CONVENTION_TABLE} so the build script, the
52
- * runtime source, and the extract auto-detect cannot drift apart.
48
+ * Create `address_convention`.
49
+ *
50
+ * The table name comes from {@link ADDRESS_CONVENTION_TABLE} so the build script, the runtime source.
51
+ * The extract auto-detect stays aligned with it.
53
52
  */
54
53
  export async function createAddressConventionTable(db: ConventionSchemaHandle): Promise<void> {
55
54
  await db.schema
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Defines resolution conventions as per-polygon profiles merged along a place's ancestor chain.
6
+ *
7
+ * The strategy implementations live in `lookup.ts` and are registered by name.
8
+ */
9
+
10
+ import type { FindPlaceQuery, PlaceCandidate } from "#types"
11
+
12
+ /**
13
+ * Soft-scoring weights for the `postcode_area_resolution` strategy: `pc·S_pc + name·S_name + pop·S_pop`.
14
+ */
15
+ export interface ScoringWeights {
16
+ pc: number
17
+ name: number
18
+ pop: number
19
+ }
20
+
21
+ /**
22
+ * A resolution profile attached to a Who's On First admin polygon.
23
+ */
24
+ export interface Convention {
25
+ /**
26
+ * The strategies that the dispatcher runs in order, by registered name.
27
+ * The first strategy that returns a non-null result wins.
28
+ */
29
+ candidateStrategies?: string[]
30
+ /**
31
+ * Weights for the `postcode_area_resolution` soft score.
32
+ *
33
+ * A layer may set some weights and inherit the rest from the layers below it.
34
+ */
35
+ scoringWeights?: Partial<ScoringWeights>
36
+ }
37
+
38
+ /**
39
+ * A convention with every field set, as `resolveConvention` returns it to strategies.
40
+ */
41
+ export interface ResolvedConvention {
42
+ candidateStrategies: string[]
43
+ scoringWeights: ScoringWeights
44
+ }
45
+
46
+ /**
47
+ * The base layer that every ancestor chain starts from.
48
+ *
49
+ * It tries `postcode_area_resolution` and then falls back to fuzzy name matching.
50
+ * Every place without a convention override uses these values, so a change here
51
+ * changes resolution in every such country.
52
+ */
53
+ export const WORLD_DEFAULT: ResolvedConvention = {
54
+ candidateStrategies: ["postcode_area_resolution", "fallback_fuzzy_name_match"],
55
+ scoringWeights: { pc: 0.6, name: 0.3, pop: 0.1 },
56
+ }
57
+
58
+ /**
59
+ * The names of the strategies that the backend registers.
60
+ *
61
+ * The dispatch registry and the build-time validator share this list, so the build
62
+ * rejects a convention that lists an unknown strategy.
63
+ */
64
+ export const BUILTIN_STRATEGY_NAMES = ["postcode_area_resolution", "fallback_fuzzy_name_match"] as const
65
+
66
+ /**
67
+ * The table name for the convention asset, shared by the build script,
68
+ * the runtime source and extract detection.
69
+ */
70
+ export const ADDRESS_CONVENTION_TABLE = "address_convention"
71
+
72
+ /**
73
+ * A resolution strategy selected by name.
74
+ *
75
+ * @returns `null` to abstain, so the dispatcher tries the next strategy,
76
+ * or an array, possibly empty, to claim the result.
77
+ */
78
+ export type Strategy = (query: FindPlaceQuery, convention: ResolvedConvention) => Promise<PlaceCandidate[] | null>
79
+
80
+ /**
81
+ * Looks up a convention by WOF polygon id.
82
+ * `get` returns `null` when the polygon has no override.
83
+ */
84
+ export interface ConventionSource {
85
+ get(wofID: number): Convention | null
86
+ }
87
+
88
+ /**
89
+ * An in-memory convention source built from a `{ wofID: Convention }` map.
90
+ */
91
+ export class SeedConventionSource implements ConventionSource {
92
+ readonly #rows: Map<number, Convention>
93
+
94
+ constructor(rows: Record<number, Convention> = {}) {
95
+ this.#rows = new Map(Object.entries(rows).map(([k, v]) => [Number(k), v]))
96
+ }
97
+
98
+ get(wofID: number): Convention | null {
99
+ return this.#rows.get(wofID) ?? null
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Merges convention layers, with later, more specific layers winning per field.
105
+ *
106
+ * A layer's `candidateStrategies` replaces the whole list.
107
+ * `scoringWeights` merges key by key, so a layer can change one weight without restating the others.
108
+ */
109
+ export function mergeConventions(base: Convention, ...overrides: Array<Convention | null | undefined>): Convention {
110
+ const out: Convention = {
111
+ candidateStrategies: base.candidateStrategies ? [...base.candidateStrategies] : undefined,
112
+ scoringWeights: base.scoringWeights ? { ...base.scoringWeights } : undefined,
113
+ }
114
+
115
+ for (const o of overrides) {
116
+ if (!o) continue
117
+
118
+ if (o.candidateStrategies) {
119
+ out.candidateStrategies = [...o.candidateStrategies]
120
+ }
121
+
122
+ if (o.scoringWeights) {
123
+ out.scoringWeights = { ...(out.scoringWeights ?? WORLD_DEFAULT.scoringWeights), ...o.scoringWeights }
124
+ }
125
+ }
126
+
127
+ return out
128
+ }
129
+
130
+ /**
131
+ * Resolves the effective convention for a place from its ancestor ids,
132
+ * ordered from most general to most specific.
133
+ *
134
+ * The merge starts from `WORLD_DEFAULT`, so every field is defined.
135
+ */
136
+ export function resolveConvention(source: ConventionSource, ancestorIDs: readonly number[]): ResolvedConvention {
137
+ const layers = ancestorIDs.map((id) => source.get(id))
138
+ const merged = mergeConventions(WORLD_DEFAULT, ...layers)
139
+
140
+ return {
141
+ candidateStrategies: merged.candidateStrategies ?? WORLD_DEFAULT.candidateStrategies,
142
+ scoringWeights: { ...WORLD_DEFAULT.scoringWeights, ...merged.scoringWeights },
143
+ }
144
+ }
@@ -2,25 +2,11 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
+ * @file Schema and read/write helpers for the candidate gazetteer's coverage manifest.
5
6
  *
6
- * Typed schema + read/write helpers for the candidate gazetteer's COVERAGE MANIFEST — the two
7
- * country-keyed tables through which the artifact declares facts about ITSELF, so those facts are
8
- * updated at gazetteer REBUILD, not by a hand-edited code PR after someone remembers:
9
- *
10
- * - `country_coverage`: the hard-country-filter coverage record (#743/#194) — per-country
11
- * promotion-eval verdicts + the measured hard-resolve rates that used to live in a code comment on
12
- * `HARD_PLACE_COUNTRY_SAFELIST`. Presence = measured; `hard_filter_safe = 0` = measured and
13
- * FAILED the check (FI 69.5%, PL 77.8%) — distinguishable from a country never measured at all
14
- * (the meaning-of-zero rule, `docs/engineering/reference/layer-contract.mdx`).
15
- * - `country_bbox`: the coarse guard-B plausibility boxes that used to live in
16
- * `resolver/plausibility.ts`'s `COUNTRY_BBOX`. An absent row fails open (never trips the guard),
17
- * exactly like an absent key in the constant.
18
- *
19
- * Emission happens at build time (`mailwoman/gazetteer-pipeline/coverage-manifest.ts` owns the
20
- * measured record and calls {@link writeGazetteerCoverageManifest} before the DB is sealed — never
21
- * patch a shipped DB, rebuild). The read happens at open time ({@link WOFCandidateTableLookup}
22
- * calls {@link readGazetteerCoverageManifest} in its constructor); an artifact that predates the
23
- * manifest returns `undefined` and every consumer falls back to the code constants byte-identically.
7
+ * The manifest holds two country-keyed tables. `country_coverage` records each measured country's hard-filter
8
+ * verdict. A missing row means the country was never measured. `hard_filter_safe = 0` means measurement found the
9
+ * filter unsafe. `country_bbox` holds coarse plausibility boxes. A missing bbox row never trips the guard.
24
10
  */
25
11
 
26
12
  import {
@@ -36,41 +22,45 @@ import { sql, type Kysely } from "kysely"
36
22
  import { hasTable } from "#sqlite-utils"
37
23
 
38
24
  /**
39
- * One country's hard-filter coverage measurement — the storage form of {@link CountryCoverageFact}.
25
+ * The stored form of {@link CountryCoverageFact}, one row per country.
40
26
  */
41
27
  export interface CountryCoverageTable {
42
28
  /**
43
- * ISO 3166-1 alpha-2, uppercase (PK).
29
+ * The upper-case ISO 3166-1 alpha-2 code.
30
+ * This field is the primary key.
44
31
  */
45
32
  country: string
46
33
  /**
47
- * 0/1 — the promotion-eval verdict (a verdict column, NOT re-derived from the rate; see the fact type's docstring).
34
+ * The promotion-eval verdict as 0 or 1.
35
+ *
36
+ * It is stored directly because the rate by itself does not decide it.
48
37
  */
49
38
  hard_filter_safe: number
50
39
  /**
51
- * Measured hard-resolve rate 0..1 on the panel named in `source`; NULL when the receipt recorded none.
40
+ * The hard-resolve rate from 0 to 1 on the panel given in `source`, or NULL when none was recorded.
52
41
  */
53
42
  hard_resolve_rate: number | null
54
43
  /**
55
- * Panel size behind `hard_resolve_rate`; NULL when unrecorded.
44
+ * The panel size behind `hard_resolve_rate`, or NULL when none was recorded.
56
45
  */
57
46
  sample_size: number | null
58
47
  /**
59
- * ISO-8601 date of the measurement / promote eval.
48
+ * The ISO 8601 date of the measurement.
60
49
  */
61
50
  measured_at: string
62
51
  /**
63
- * The receipt: which panel/check produced this row.
52
+ * The panel or check that produced this row.
64
53
  */
65
54
  source: string
66
55
  }
67
56
 
68
57
  /**
69
- * One country's coarse guard-B bounding box — the storage form of {@link CountryBBoxFact}.
58
+ * The stored form of {@link CountryBBoxFact}, one coarse bounding box per country.
70
59
  */
71
60
  export interface CountryBBoxTable {
72
61
  /**
73
- * ISO 3166-1 alpha-2, uppercase (PK).
62
+ * The upper-case ISO 3166-1 alpha-2 code.
63
+ * This field is the primary key.
74
64
  */
75
65
  country: string
76
66
  lat_min: number
@@ -78,7 +68,7 @@ export interface CountryBBoxTable {
78
68
  lon_min: number
79
69
  lon_max: number
80
70
  /**
81
- * Provenance of the box (harness + date).
71
+ * The provenance of the box.
82
72
  */
83
73
  source: string
84
74
  }
@@ -92,16 +82,17 @@ export interface GazetteerCoverageDatabase {
92
82
  }
93
83
 
94
84
  /**
95
- * Table names the lookup probes (existence-restricted, so a candidate.db built before the manifest is byte-stable).
85
+ * The name of the per-country coverage table.
86
+ * Readers check that it exists before querying it.
96
87
  */
97
88
  export const COUNTRY_COVERAGE_TABLE = "country_coverage"
98
89
  /**
99
- * Table of per-country bounding boxes, used to reject a placement that fell outside its own country.
90
+ * The name of the per-country bounding-box table, used to reject a placement outside its own country.
100
91
  */
101
92
  export const COUNTRY_BBOX_TABLE = "country_bbox"
102
93
 
103
94
  /**
104
- * Create `country_coverage` — a handful of small PK-probed rows, the WITHOUT ROWID sweet spot.
95
+ * Creates the `country_coverage` table as a `WITHOUT ROWID` table keyed by country.
105
96
  */
106
97
  export async function createCountryCoverageTable(db: Kysely<GazetteerCoverageDatabase>): Promise<void> {
107
98
  await db.schema
@@ -113,13 +104,13 @@ export async function createCountryCoverageTable(db: Kysely<GazetteerCoverageDat
113
104
  .addColumn("sample_size", "integer")
114
105
  .addColumn("measured_at", "text", (c) => c.notNull())
115
106
  .addColumn("source", "text", (c) => c.notNull())
116
- // `WITHOUT ROWID` has no first-class builder; the raw modifier is the idiomatic fallback.
107
+ // Kysely has no builder method for `without rowid`.
117
108
  .modifyEnd(sql`without rowid`)
118
109
  .execute()
119
110
  }
120
111
 
121
112
  /**
122
- * Create `country_bbox` — same shape discipline as {@link createCountryCoverageTable}.
113
+ * Creates the `country_bbox` table as a `WITHOUT ROWID` table keyed by country.
123
114
  */
124
115
  export async function createCountryBBoxTable(db: Kysely<GazetteerCoverageDatabase>): Promise<void> {
125
116
  await db.schema
@@ -136,8 +127,10 @@ export async function createCountryBBoxTable(db: Kysely<GazetteerCoverageDatabas
136
127
  }
137
128
 
138
129
  /**
139
- * Write the coverage manifest into a candidate DB UNDER CONSTRUCTION (pre-seal — a shipped DB is never patched, rebuild
140
- * instead). Creates both tables and inserts the facts; call exactly once, from the gazetteer build.
130
+ * Creates both manifest tables in a candidate database under construction and inserts the facts.
131
+ *
132
+ * The gazetteer build calls it once.
133
+ * A shipped database is rebuilt instead of patched.
141
134
  */
142
135
  export async function writeGazetteerCoverageManifest(
143
136
  db: Kysely<GazetteerCoverageDatabase>,
@@ -153,8 +146,8 @@ export async function writeGazetteerCoverageManifest(
153
146
  facts.coverage.map((f) => ({
154
147
  country: f.country.toUpperCase(),
155
148
  hard_filter_safe: f.hardFilterSafe ? 1 : 0,
156
- hard_resolve_rate: f.hardResolveRate ?? null,
157
- sample_size: f.sampleSize ?? null,
149
+ hard_resolve_rate: f.hardResolveRate,
150
+ sample_size: f.sampleSize,
158
151
  measured_at: f.measuredAt,
159
152
  source: f.source,
160
153
  }))
@@ -180,16 +173,18 @@ export async function writeGazetteerCoverageManifest(
180
173
  }
181
174
 
182
175
  /**
183
- * Read the coverage manifest from an OPEN candidate DB, or `undefined` when the artifact predates it (neither table
184
- * present) — the signal for consumers to fall back to the code constants byte-identically. Synchronous raw reads on
185
- * purpose: this runs inside {@link WOFCandidateTableLookup}'s synchronous constructor (the sync-reader carve-out in
186
- * `AGENTS.md`), and the tables are a few dozen rows read once per open.
176
+ * Reads the coverage manifest from an open candidate database.
177
+ *
178
+ * It returns `undefined` when neither table exists.
179
+ * Consumers then fall back to the code constants.
180
+ *
181
+ * The reads are synchronous because the candidate lookup calls this from its constructor.
187
182
  */
188
- export function readGazetteerCoverageManifest<DB>(db: DatabaseClient<DB>): GazetteerArtifactCoverage | undefined {
183
+ export function readGazetteerCoverageManifest<DB>(db: DatabaseClient<DB>): GazetteerArtifactCoverage | null {
189
184
  const hasCoverage = hasTable(db, COUNTRY_COVERAGE_TABLE)
190
185
  const hasBBox = hasTable(db, COUNTRY_BBOX_TABLE)
191
186
 
192
- if (!hasCoverage && !hasBBox) return undefined
187
+ if (!hasCoverage && !hasBBox) return null
193
188
 
194
189
  const countryCoverage = new Map<string, CountryCoverageFact>()
195
190
 
@@ -206,8 +201,8 @@ export function readGazetteerCoverageManifest<DB>(db: DatabaseClient<DB>): Gazet
206
201
  countryCoverage.set(country, {
207
202
  country,
208
203
  hardFilterSafe: Number(row.hard_filter_safe) !== 0,
209
- ...(row.hard_resolve_rate === null ? {} : { hardResolveRate: Number(row.hard_resolve_rate) }),
210
- ...(row.sample_size === null ? {} : { sampleSize: Number(row.sample_size) }),
204
+ hardResolveRate: row.hard_resolve_rate === null ? null : Number(row.hard_resolve_rate),
205
+ sampleSize: row.sample_size === null ? null : Number(row.sample_size),
211
206
  measuredAt: String(row.measured_at),
212
207
  source: String(row.source),
213
208
  })