@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
@@ -1,328 +0,0 @@
1
- /**
2
- * @copyright Sister Software
3
- * @license AGPL-3.0
4
- * @author Teffen Ellis, et al.
5
- *
6
- * SQLite implementation of core's `AddressPointLookup` (#476): exact `(street, number)` within a
7
- * postcode (preferred), locality, or — for extracts whose points carry no scope tag (OSM, #247) —
8
- * the resolved locality's BBOX. Query-side normalization is THE shared normalizer
9
- * (`street-normalize.ts`), selected per the extract's `streetLocale` so build-side and probe-side
10
- * stay identical by construction (US delegates to the USPS pipeline; FR/DE/NL use the locale rules).
11
- *
12
- * Matching is exact-after-normalization only — no fuzzy street matching in this tier (measure how
13
- * far exact gets first; fuzz is a later, separate decision). Scope order is most-selective first:
14
- * postcode, then locality, then the bbox fall-through (only when a bbox is supplied AND the prior
15
- * scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
16
- */
17
-
18
- import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
19
- import { DatabaseClient } from "@mailwoman/sqlite/client"
20
-
21
- import type { AddressPointDatabase, AddressPointTable } from "#address/point/schema"
22
- import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
23
- import {
24
- normalizeHouseNumberForKey,
25
- normalizeLocalityForKey,
26
- normalizeLocalityForKeyLocale,
27
- type NameKey,
28
- type StreetKey,
29
- streetKeyVariants,
30
- streetLocaleForSurface,
31
- stripArrondissement,
32
- type StreetLocale,
33
- } from "#street/normalize"
34
-
35
- /**
36
- * The columns this lookup projects — a typed slice of the SHARED {@link AddressPointTable}, so a column rename in
37
- * `build-address-point-extract.ts` (the writer) is a compile error here (the reader).
38
- */
39
- type AddressPointRow = Pick<AddressPointTable, "lat" | "lon" | "source" | "release" | "locality_norm" | "postcode">
40
-
41
- /**
42
- * The 4 columns the reader SELECTs, in the schema's order — referenced by the prepared SELECTs so the projected
43
- * `AddressPointRow` stays in lockstep with the shared schema.
44
- */
45
- const SELECT_COLS = "lat, lon, source, release, locality_norm, postcode"
46
-
47
- export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressPointDatabase>
48
- implements AddressPointLookup, Disposable
49
- {
50
- readonly #db: DatabaseClient<DB>
51
- readonly #locale: StreetLocale
52
- readonly #localityKeys: "full" | "abbreviated"
53
- readonly #byPostcode: PreparedGet<[postcode: string, street: StreetKey, number: string], AddressPointRow> | undefined
54
- readonly #byPostcodeLocality:
55
- | PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
56
- | undefined
57
- readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
58
- /**
59
- * The scope key matched by its TAIL — a `zh` query that names the 鄉鎮市區 without its 縣市. Narrowed by the (street,
60
- * number) index first, so the LIKE walks the handful of rows that share the pair.
61
- */
62
- readonly #byLocalityTail:
63
- | PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
64
- | undefined
65
- readonly #byBbox:
66
- | PreparedGet<
67
- [street: StreetKey, number: string, minLat: number, maxLat: number, minLon: number, maxLon: number],
68
- AddressPointRow
69
- >
70
- | undefined
71
-
72
- /**
73
- * @param dbPath Extract path.
74
- * @param opts.streetLocale The street-normalization locale this extract was BUILT with — must match, or every key
75
- * misses. Defaults to `"us"` (the situs tier), so existing callers are unchanged.
76
- * @param opts.localityKeys Whether the extract's `locality_norm` is a FULL place name a query can be held to. The BAN
77
- * and OSM extracts write the commune or `addr:city` in full; the US situs extract writes the NAD city field, which
78
- * several counties abbreviate (`addi` for Addison on 5,174 Texas rows, 327,264 Texas rows at four characters or
79
- * fewer) or give as the parent town (`easton` for North Easton). A key like that can steer WHICH row answers but
80
- * cannot refuse one, so it never contradicts. Defaults from the street locale: `"us"` is abbreviated, the rest
81
- * full.
82
- */
83
- constructor(dbPath: string, opts: { streetLocale?: StreetLocale; localityKeys?: "full" | "abbreviated" } = {}) {
84
- this.#db = new DatabaseClient<DB>(dbPath, { readOnly: true })
85
- this.#locale = opts.streetLocale ?? "us"
86
- this.#localityKeys = opts.localityKeys ?? (this.#locale === "us" ? "abbreviated" : "full")
87
-
88
- // Degrade gracefully on an empty/tableless extract (interrupted build, stray 0-byte file): with no
89
- // `address_point` table this lookup is a no-op miss, not a crash that loses the whole state (#568).
90
- if (hasTable(this.#db, "address_point")) {
91
- this.#byPostcode = prepareGet(
92
- this.#db,
93
- `SELECT ${SELECT_COLS} FROM address_point
94
- WHERE postcode = ? AND street_norm = ? AND number = ? LIMIT 1`
95
- )
96
-
97
- this.#byPostcodeLocality = prepareGet(
98
- this.#db,
99
- `SELECT ${SELECT_COLS} FROM address_point
100
- WHERE postcode = ? AND locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
101
- )
102
-
103
- this.#byLocality = prepareGet(
104
- this.#db,
105
- `SELECT ${SELECT_COLS} FROM address_point
106
- WHERE locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
107
- )
108
-
109
- this.#byBbox = prepareGet(
110
- this.#db,
111
- `SELECT ${SELECT_COLS} FROM address_point
112
- WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
113
- )
114
-
115
- this.#byLocalityTail = prepareGet(
116
- this.#db,
117
- `SELECT ${SELECT_COLS} FROM address_point
118
- WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
119
- )
120
- }
121
- }
122
-
123
- find(query: {
124
- street: string
125
- number: string
126
- postcode?: string
127
- locality?: string
128
- region?: string
129
- subregion?: string
130
- bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
131
- }): AddressPointHit | null {
132
- if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
133
- const number = normalizeHouseNumberForKey(query.number, this.#locale)
134
-
135
- if (!number) return null
136
-
137
- // A `zh` extract scopes a point by 縣市 + 鄉鎮市區, the pair the parse tags `region` + `subregion`; the Taiwanese
138
- // register carries no postcode and the parse no `locality`. The pair becomes the locality key here, on the
139
- // reader that was built with the same fold, and a Latin extract never sees the two fields. A line that names
140
- // only the 鄉鎮市區 (`中和區中興街281號`, 14.7% of the TW board) matches the stored pair by its tail instead.
141
- const scoped =
142
- this.#locale === "zh" && !query.locality && query.subregion
143
- ? query.region
144
- ? { ...query, locality: `${query.region}${query.subregion}` }
145
- : { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
146
- : query
147
-
148
- // Key-variant ladder (see `streetKeyVariants`): the literal key first, then the doubled-type
149
- // collapse and the saint↔st register swap — each variant runs the FULL number ladder below, and
150
- // the first variant to answer wins, so an attested literal key is never second-guessed.
151
- let row: AddressPointRow | undefined
152
-
153
- for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
154
- row = this.#findForKey(streetNorm, number, scoped)
155
-
156
- if (row) break
157
- }
158
-
159
- if (!row) return null
160
-
161
- return {
162
- lat: row.lat,
163
- lon: row.lon,
164
- source: row.source,
165
- release: row.release,
166
- ...(row.locality_norm ? { localityNorm: row.locality_norm } : {}),
167
- ...(row.postcode ? { postcode: row.postcode } : {}),
168
- }
169
- }
170
-
171
- /**
172
- * The full number ladder for ONE normalized street key: exact, then the range low-end, then letter-suffix spacing
173
- * with the base-number fall — see each rung's note in place.
174
- */
175
- #findForKey(
176
- streetNorm: StreetKey,
177
- number: string,
178
- query: {
179
- street: string
180
- number: string
181
- postcode?: string
182
- locality?: string
183
- localityTail?: NameKey
184
- bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
185
- }
186
- ) {
187
- let row = this.#probe(streetNorm, number, query)
188
-
189
- // Range-surface fallback: every register this reader serves stores ONE number per point
190
- // (G-NAF `NUMBER_FIRST`, BAN, OA, OSM `addr:housenumber`), but the attested surface is often
191
- // a range — "385-387 Esplanade" keys `385`. Null-only: an exact range key that matched above
192
- // (some OSM points DO carry "385-387" verbatim) is never second-guessed.
193
- if (!row) {
194
- const low = /^(\d+[a-z]?)-\d+[a-z]?$/.exec(number)?.[1]
195
-
196
- if (low) {
197
- row = this.#probe(streetNorm, low, query)
198
- }
199
- }
200
-
201
- // Letter-suffix spacing fallback: the registers disagree on the joint — BAN stores "3 a"
202
- // (space-separated), G-NAF and most OA sources store "3a" — and the parsed surface can arrive
203
- // either way. On a miss, retry the OTHER spacing; on a double miss, the BASE number (the
204
- // register attests no 3A but does attest 3 — the adjacent-parcel approximation, priced the
205
- // same as the range fallback's low end). Null-only throughout, and only for the
206
- // digits+single-letter shape (never touches "12 1/2" or unit-bearing forms).
207
- if (!row) {
208
- const joined = /^(\d+)\s+([a-z])$/.exec(number)
209
- const spaced = /^(\d+)([a-z])$/.exec(number)
210
-
211
- if (joined) {
212
- row = this.#probe(streetNorm, `${joined[1]}${joined[2]}`, query) ?? this.#probe(streetNorm, joined[1]!, query)
213
- } else if (spaced) {
214
- row = this.#probe(streetNorm, `${spaced[1]} ${spaced[2]}`, query) ?? this.#probe(streetNorm, spaced[1]!, query)
215
- }
216
- }
217
-
218
- // Sub-number fallback for the Taiwanese register: `14之12` is building 12 off number 14, `30附40` an attached
219
- // number, stored as number `14` / `30` with the rest in `unit`. A query that kept the pair on the number span
220
- // falls to the base number — the same adjacent-parcel approximation the letter-suffix rung makes, priced the
221
- // same: on the 2,000-row served read the rows answered this way sit a median 256 m and at most 1.0 km from the
222
- // building.
223
- if (!row && this.#locale === "zh") {
224
- const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
225
-
226
- if (base) {
227
- row = this.#probe(streetNorm, base, query)
228
- }
229
- }
230
-
231
- return row
232
- }
233
-
234
- /**
235
- * The scope ladder for one (street, number) key: postcode, then locality, then the bbox fall-through — each rung only
236
- * when the prior missed.
237
- */
238
- #probe(
239
- streetNorm: StreetKey,
240
- number: string,
241
- query: {
242
- postcode?: string
243
- locality?: string
244
- localityTail?: NameKey
245
- bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
246
- }
247
- ): AddressPointRow | undefined {
248
- let row: AddressPointRow | undefined
249
-
250
- if (query.postcode) {
251
- const postcode = query.postcode.trim()
252
- const localityKey = query.locality ? this.#localityKey(query.locality) : undefined
253
-
254
- // A postcode can span several places — DE 04509 covers Schönwölkau and Werlitzsch, both with a Teichstraße 3 —
255
- // so when the query names a locality the row whose own locality agrees is asked for FIRST. Only when no such
256
- // row exists does the postcode-only row answer, and then only if its locality does not name a different place
257
- // (see `#scopeContradicts` for what "different" tolerates): a query naming a third village under the postcode
258
- // falls through to the locality rung rather than answering the wrong rooftop.
259
- const agreeing = localityKey ? this.#byPostcodeLocality!(postcode, localityKey, streetNorm, number) : undefined
260
- const candidate = agreeing ?? this.#byPostcode!(postcode, streetNorm, number)
261
-
262
- row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
263
- }
264
-
265
- if (!row && query.locality) {
266
- // FR extracts key arrondissement communes at the base city (both-sides fold, see the BAN
267
- // builder + stripArrondissement) — fold the probe too so "Paris 13e Arrondissement" and
268
- // "Paris" both hit. No-op for "us" extracts and every non-arrondissement commune.
269
- row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
270
- }
271
-
272
- if (!row && query.localityTail && this.#byLocalityTail) {
273
- // A Han key carries no `%` or `_`, so the tail is a literal suffix pattern.
274
- row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
275
- }
276
-
277
- // Bbox fall-through (#247): the point carries no postcode/locality of its own, but its coordinate falls
278
- // inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
279
- if (!row && query.bbox) {
280
- const b = query.bbox
281
- const candidate = this.#byBbox!(streetNorm, number, b.minLat, b.maxLat, b.minLon, b.maxLon)
282
-
283
- // A register row that carries its own scope and was NOT found by the scoped rungs is a different address that
284
- // happens to share the street and number inside the box: `10 rue de la République, 75008 Paris` reached
285
- // Servon's `10 rue de la République` (postcode 77170) 26 km away this way, at rooftop tier and 1 m uncertainty.
286
- // The rung exists for points with no scope of their own; a point whose scope disagrees with the query is a miss.
287
- row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
288
- }
289
-
290
- return row
291
- }
292
-
293
- /**
294
- * The query's locality folded the way the extract's builder folded its `locality_norm`. FR extracts key
295
- * arrondissement communes at the base city (both-sides fold, see the BAN builder + stripArrondissement), so "Paris
296
- * 13e Arrondissement" and "Paris" both hit; a no-op for every other locale.
297
- */
298
- #localityKey(locality: string): NameKey {
299
- if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
300
-
301
- return normalizeLocalityForKeyLocale(locality, this.#locale)
302
- }
303
-
304
- /**
305
- * Whether a row's OWN postcode or locality names a different place than the query did. Absent scope on the row is not
306
- * a contradiction — it is the case the bbox rung was built for — and a rung that matched ON a field cannot contradict
307
- * it, so at the postcode rung only the locality can disagree and at the bbox rung either can.
308
- *
309
- * The locality is consulted only on an extract whose keys are full names (the constructor's `localityKeys`). Under
310
- * exact comparison against the US extract's abbreviated keys, the postcode rung refused `4900 Airport Pkwy, Addison
311
- * TX 75001`'s own rooftop row (`addi`) and `678 Depot St, North Easton, MA 02356`'s (`easton`), and both `pass` board
312
- * rows fell to interpolation 144–198 m away — invisible to a 1 km grade (#2155). `servon` against `paris` and
313
- * `werlitzsch` against `krensitz`, on the BAN and OSM extracts, are different places and still refuse.
314
- */
315
- #scopeContradicts(row: AddressPointRow, query: { postcode?: string; locality?: string }): boolean {
316
- if (query.postcode && row.postcode && row.postcode.trim() !== query.postcode.trim()) return true
317
-
318
- if (this.#localityKeys === "full" && query.locality && row.locality_norm) {
319
- return row.locality_norm !== this.#localityKey(query.locality)
320
- }
321
-
322
- return false
323
- }
324
-
325
- [Symbol.dispose](): void {
326
- this.#db[Symbol.dispose]()
327
- }
328
- }
@@ -1,154 +0,0 @@
1
- /**
2
- * @copyright Sister Software
3
- * @license AGPL-3.0
4
- * @author Teffen Ellis, et al.
5
- *
6
- * The **Geographic Rule Engine** convention model (Direction E, #289 — see
7
- * `docs/articles/plan/2026-06-05-geographic-rule-engine.md` and epic #288).
8
- *
9
- * A `Convention` is a declarative resolution profile attached to a Who's-On-First admin polygon.
10
- * The engine deep-merges the conventions along a resolved place's ancestor chain — country →
11
- * region → … → locality, most-specific winning — and the backend dispatches the named strategies
12
- * in `candidateStrategies`, first to return candidates wins.
13
- *
14
- * This module is the backend-agnostic core: the convention TYPES, the deep-merge, and the seed
15
- * source. The strategy IMPLEMENTATIONS are SQL-bound and live in `lookup.ts`, registered by
16
- * name.
17
- *
18
- * For the existing EU locales (DE/FR/GB/NL) the seed source is empty, so every query resolves to
19
- * `WORLD_DEFAULT` and the dispatch is byte-identical to the pre-engine coordinate-first path. JP
20
- * / KR / TW add rows here (and #290 swaps the seed map for a build-from-source sqlite-backed
21
- * source).
22
- */
23
-
24
- import type { FindPlaceQuery, PlaceCandidate } from "#types"
25
-
26
- /**
27
- * Soft-scoring weights for the `postcode_area_resolution` strategy: `pc·S_pc + name·S_name + pop·S_pop`.
28
- */
29
- export interface ScoringWeights {
30
- pc: number
31
- name: number
32
- pop: number
33
- }
34
-
35
- /**
36
- * A geographically-scoped resolution profile. Namespaced sections grow per phase; #289 ships the dispatch + scoring
37
- * slice (`candidateStrategies` + `scoringWeights`). Later phases add `fieldMapping` (locale semantics for `locator[]`),
38
- * `tokenNormalization`, etc.
39
- */
40
- export interface Convention {
41
- /**
42
- * Ordered strategy names the dispatcher runs; the first to return a non-null result wins.
43
- */
44
- candidateStrategies?: string[]
45
- /**
46
- * Weights for `postcode_area_resolution`'s soft-score. Partial — a layer may nudge one weight and inherit the rest
47
- * from the layers below it (`resolveConvention` fills any gaps from WORLD_DEFAULT).
48
- */
49
- scoringWeights?: Partial<ScoringWeights>
50
- }
51
-
52
- /**
53
- * A fully-resolved convention: every field present, weights complete. What `resolveConvention` returns and what
54
- * strategies consume.
55
- */
56
- export interface ResolvedConvention {
57
- candidateStrategies: string[]
58
- scoringWeights: ScoringWeights
59
- }
60
-
61
- /**
62
- * The base layer every ancestor chain starts from. Reproduces the pre-engine coordinate-first behavior exactly: try
63
- * `postcode_area_resolution`, else fall back to fuzzy name match; soft-score weights 0.6 / 0.3 / 0.1. Changing these
64
- * changes EU behavior — don't, without a byte-stability run.
65
- */
66
- export const WORLD_DEFAULT: ResolvedConvention = {
67
- candidateStrategies: ["postcode_area_resolution", "fallback_fuzzy_name_match"],
68
- scoringWeights: { pc: 0.6, name: 0.3, pop: 0.1 },
69
- }
70
-
71
- /**
72
- * The strategy names the backend registers. The single source of truth shared by the dispatch registry and the
73
- * build-time validator, so an authored convention that names a non-existent strategy is caught at build (loud) rather
74
- * than silently skipped at runtime.
75
- */
76
- export const BUILTIN_STRATEGY_NAMES = ["postcode_area_resolution", "fallback_fuzzy_name_match"] as const
77
-
78
- /**
79
- * Table name for the convention asset (#290). Carried here so the build script, the runtime source, and the extract
80
- * auto-detect all agree.
81
- */
82
- export const ADDRESS_CONVENTION_TABLE = "address_convention"
83
-
84
- /**
85
- * A named resolution primitive. Returns `null` to abstain (condition unmet / no data) → the dispatcher tries the next
86
- * strategy; returns an array (possibly empty) to claim the result.
87
- */
88
- export type Strategy = (query: FindPlaceQuery, convention: ResolvedConvention) => Promise<PlaceCandidate[] | null>
89
-
90
- /**
91
- * Look up a convention record by WOF polygon id. Returns `undefined` when the polygon has no override.
92
- */
93
- export interface ConventionSource {
94
- get(wofID: number): Convention | undefined
95
- }
96
-
97
- /**
98
- * In-memory convention source seeded from a `{ wofID: Convention }` map. Empty for the EU locales (they ride
99
- * `WORLD_DEFAULT`); JP / KR / TW add rows. #290 replaces this with a sqlite-backed source built from source, same
100
- * distributable-asset discipline as `postcode-locality-intl.db`.
101
- */
102
- export class SeedConventionSource implements ConventionSource {
103
- readonly #rows: Map<number, Convention>
104
-
105
- constructor(rows: Record<number, Convention> = {}) {
106
- this.#rows = new Map(Object.entries(rows).map(([k, v]) => [Number(k), v]))
107
- }
108
-
109
- get(wofID: number): Convention | undefined {
110
- return this.#rows.get(wofID)
111
- }
112
- }
113
-
114
- /**
115
- * Deep-merge convention layers, later (more-specific) layers winning per field. `candidateStrategies` is replaced
116
- * wholesale — a convention names its full ordered list, it does not append. `scoringWeights` is merged key-by-key so a
117
- * locality can nudge one weight without restating the others.
118
- */
119
- export function mergeConventions(base: Convention, ...overrides: Array<Convention | undefined>): Convention {
120
- const out: Convention = {
121
- candidateStrategies: base.candidateStrategies ? [...base.candidateStrategies] : undefined,
122
- scoringWeights: base.scoringWeights ? { ...base.scoringWeights } : undefined,
123
- }
124
-
125
- for (const o of overrides) {
126
- if (!o) continue
127
-
128
- if (o.candidateStrategies !== undefined) {
129
- out.candidateStrategies = [...o.candidateStrategies]
130
- }
131
-
132
- if (o.scoringWeights !== undefined) {
133
- out.scoringWeights = { ...(out.scoringWeights ?? WORLD_DEFAULT.scoringWeights), ...o.scoringWeights }
134
- }
135
- }
136
-
137
- return out
138
- }
139
-
140
- /**
141
- * Resolve the effective convention for a place given its ancestor chain, ordered MOST-GENERAL → MOST-SPECIFIC (country,
142
- * region, …, locality). Starts from `WORLD_DEFAULT` so every field is defined regardless of which (if any) ancestors
143
- * carry an override.
144
- */
145
- export function resolveConvention(source: ConventionSource, ancestorIDs: readonly number[]): ResolvedConvention {
146
- const layers = ancestorIDs.map((id) => source.get(id))
147
- const merged = mergeConventions(WORLD_DEFAULT, ...layers)
148
-
149
- return {
150
- candidateStrategies: merged.candidateStrategies ?? WORLD_DEFAULT.candidateStrategies,
151
- // Fill any weight gaps from the base so strategies always see a complete set.
152
- scoringWeights: { ...WORLD_DEFAULT.scoringWeights, ...merged.scoringWeights },
153
- }
154
- }