@mailwoman/resolver-wof-sqlite 9.0.0 → 9.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 (297) hide show
  1. package/README.md +28 -9
  2. package/address-point-interpolation.ts +18 -8
  3. package/address-point-schema.ts +18 -6
  4. package/address-point.ts +111 -18
  5. package/ancestry.ts +9 -6
  6. package/build-candidate.ts +287 -157
  7. package/build-slim.ts +3 -3
  8. package/candidate/alias-bags.ts +54 -0
  9. package/candidate/ancestors-sidecar.ts +206 -0
  10. package/candidate/country-display-names.ts +79 -0
  11. package/candidate/name-roles.ts +237 -0
  12. package/candidate/own-name.ts +146 -0
  13. package/candidate/place-attrs.ts +44 -0
  14. package/candidate/shard-fold.ts +137 -0
  15. package/candidate-ancestors-schema.ts +195 -0
  16. package/candidate-fts.ts +4 -2
  17. package/candidate-importance.ts +228 -0
  18. package/candidate-lookup.ts +564 -174
  19. package/candidate-schema.ts +60 -3
  20. package/candidate-scoring.ts +268 -0
  21. package/capital-schema.ts +90 -0
  22. package/capitals.ts +148 -0
  23. package/coincident-roles.ts +69 -10
  24. package/convention-schema.ts +72 -0
  25. package/convention.ts +2 -2
  26. package/coverage-manifest-schema.ts +7 -7
  27. package/currency-backfill.ts +249 -0
  28. package/exact-match.ts +104 -0
  29. package/fst-autocomplete.ts +105 -122
  30. package/fst-builder.ts +39 -47
  31. package/fst-deserialize-web.ts +43 -7
  32. package/fst-freshness.ts +2 -2
  33. package/fst-serialize.ts +68 -12
  34. package/fst-types.ts +35 -1
  35. package/fts-query.ts +1 -1
  36. package/fts.ts +16 -4
  37. package/geonames-postal.ts +2 -2
  38. package/index.ts +26 -14
  39. package/interpolation.ts +113 -19
  40. package/lookup.ts +118 -560
  41. package/name-score.ts +6 -4
  42. package/out/address-point-interpolation.d.ts.map +1 -1
  43. package/out/address-point-interpolation.js +13 -7
  44. package/out/address-point-interpolation.js.map +1 -1
  45. package/out/address-point-schema.d.ts +16 -6
  46. package/out/address-point-schema.d.ts.map +1 -1
  47. package/out/address-point-schema.js.map +1 -1
  48. package/out/address-point.d.ts.map +1 -1
  49. package/out/address-point.js +70 -14
  50. package/out/address-point.js.map +1 -1
  51. package/out/ancestry.d.ts +2 -2
  52. package/out/ancestry.d.ts.map +1 -1
  53. package/out/ancestry.js +5 -6
  54. package/out/ancestry.js.map +1 -1
  55. package/out/build-candidate.d.ts +108 -0
  56. package/out/build-candidate.d.ts.map +1 -1
  57. package/out/build-candidate.js +151 -120
  58. package/out/build-candidate.js.map +1 -1
  59. package/out/build-slim.d.ts +1 -1
  60. package/out/build-slim.js +3 -3
  61. package/out/build-slim.js.map +1 -1
  62. package/out/candidate/alias-bags.d.ts +17 -0
  63. package/out/candidate/alias-bags.d.ts.map +1 -0
  64. package/out/candidate/alias-bags.js +39 -0
  65. package/out/candidate/alias-bags.js.map +1 -0
  66. package/out/candidate/ancestors-sidecar.d.ts +33 -0
  67. package/out/candidate/ancestors-sidecar.d.ts.map +1 -0
  68. package/out/candidate/ancestors-sidecar.js +140 -0
  69. package/out/candidate/ancestors-sidecar.js.map +1 -0
  70. package/out/candidate/country-display-names.d.ts +35 -0
  71. package/out/candidate/country-display-names.d.ts.map +1 -0
  72. package/out/candidate/country-display-names.js +59 -0
  73. package/out/candidate/country-display-names.js.map +1 -0
  74. package/out/candidate/name-roles.d.ts +55 -0
  75. package/out/candidate/name-roles.d.ts.map +1 -0
  76. package/out/candidate/name-roles.js +165 -0
  77. package/out/candidate/name-roles.js.map +1 -0
  78. package/out/candidate/own-name.d.ts +50 -0
  79. package/out/candidate/own-name.d.ts.map +1 -0
  80. package/out/candidate/own-name.js +132 -0
  81. package/out/candidate/own-name.js.map +1 -0
  82. package/out/candidate/place-attrs.d.ts +43 -0
  83. package/out/candidate/place-attrs.d.ts.map +1 -0
  84. package/out/candidate/place-attrs.js +15 -0
  85. package/out/candidate/place-attrs.js.map +1 -0
  86. package/out/candidate/shard-fold.d.ts +31 -0
  87. package/out/candidate/shard-fold.d.ts.map +1 -0
  88. package/out/candidate/shard-fold.js +104 -0
  89. package/out/candidate/shard-fold.js.map +1 -0
  90. package/out/candidate-ancestors-schema.d.ts +150 -0
  91. package/out/candidate-ancestors-schema.d.ts.map +1 -0
  92. package/out/candidate-ancestors-schema.js +123 -0
  93. package/out/candidate-ancestors-schema.js.map +1 -0
  94. package/out/candidate-fts.d.ts +4 -2
  95. package/out/candidate-fts.d.ts.map +1 -1
  96. package/out/candidate-fts.js +4 -2
  97. package/out/candidate-fts.js.map +1 -1
  98. package/out/candidate-importance.d.ts +132 -0
  99. package/out/candidate-importance.d.ts.map +1 -0
  100. package/out/candidate-importance.js +174 -0
  101. package/out/candidate-importance.js.map +1 -0
  102. package/out/candidate-lookup.d.ts +22 -37
  103. package/out/candidate-lookup.d.ts.map +1 -1
  104. package/out/candidate-lookup.js +446 -132
  105. package/out/candidate-lookup.js.map +1 -1
  106. package/out/candidate-schema.d.ts +52 -4
  107. package/out/candidate-schema.d.ts.map +1 -1
  108. package/out/candidate-schema.js +8 -0
  109. package/out/candidate-schema.js.map +1 -1
  110. package/out/candidate-scoring.d.ts +34 -0
  111. package/out/candidate-scoring.d.ts.map +1 -0
  112. package/out/candidate-scoring.js +200 -0
  113. package/out/candidate-scoring.js.map +1 -0
  114. package/out/capital-schema.d.ts +51 -0
  115. package/out/capital-schema.d.ts.map +1 -0
  116. package/out/capital-schema.js +63 -0
  117. package/out/capital-schema.js.map +1 -0
  118. package/out/capitals.d.ts +69 -0
  119. package/out/capitals.d.ts.map +1 -0
  120. package/out/capitals.js +98 -0
  121. package/out/capitals.js.map +1 -0
  122. package/out/coincident-roles.d.ts +7 -0
  123. package/out/coincident-roles.d.ts.map +1 -1
  124. package/out/coincident-roles.js +42 -8
  125. package/out/coincident-roles.js.map +1 -1
  126. package/out/convention-schema.d.ts +51 -0
  127. package/out/convention-schema.d.ts.map +1 -0
  128. package/out/convention-schema.js +34 -0
  129. package/out/convention-schema.js.map +1 -0
  130. package/out/convention.d.ts +1 -1
  131. package/out/convention.js +2 -2
  132. package/out/coverage-manifest-schema.js +3 -7
  133. package/out/coverage-manifest-schema.js.map +1 -1
  134. package/out/currency-backfill.d.ts +46 -0
  135. package/out/currency-backfill.d.ts.map +1 -0
  136. package/out/currency-backfill.js +180 -0
  137. package/out/currency-backfill.js.map +1 -0
  138. package/out/exact-match.d.ts +25 -0
  139. package/out/exact-match.d.ts.map +1 -0
  140. package/out/exact-match.js +89 -0
  141. package/out/exact-match.js.map +1 -0
  142. package/out/fst-autocomplete.d.ts +24 -14
  143. package/out/fst-autocomplete.d.ts.map +1 -1
  144. package/out/fst-autocomplete.js +84 -100
  145. package/out/fst-autocomplete.js.map +1 -1
  146. package/out/fst-builder.d.ts.map +1 -1
  147. package/out/fst-builder.js +32 -40
  148. package/out/fst-builder.js.map +1 -1
  149. package/out/fst-deserialize-web.d.ts.map +1 -1
  150. package/out/fst-deserialize-web.js +36 -7
  151. package/out/fst-deserialize-web.js.map +1 -1
  152. package/out/fst-freshness.d.ts +2 -2
  153. package/out/fst-freshness.js +2 -2
  154. package/out/fst-serialize.d.ts +14 -4
  155. package/out/fst-serialize.d.ts.map +1 -1
  156. package/out/fst-serialize.js +60 -12
  157. package/out/fst-serialize.js.map +1 -1
  158. package/out/fst-types.d.ts +35 -1
  159. package/out/fst-types.d.ts.map +1 -1
  160. package/out/fts-query.js +1 -1
  161. package/out/fts-query.js.map +1 -1
  162. package/out/fts.d.ts +15 -4
  163. package/out/fts.d.ts.map +1 -1
  164. package/out/fts.js +15 -4
  165. package/out/fts.js.map +1 -1
  166. package/out/geonames-postal.d.ts +2 -2
  167. package/out/geonames-postal.js +2 -2
  168. package/out/index.d.ts +4 -2
  169. package/out/index.d.ts.map +1 -1
  170. package/out/index.js +3 -2
  171. package/out/index.js.map +1 -1
  172. package/out/interpolation.d.ts +8 -0
  173. package/out/interpolation.d.ts.map +1 -1
  174. package/out/interpolation.js +91 -19
  175. package/out/interpolation.js.map +1 -1
  176. package/out/lookup.d.ts +4 -5
  177. package/out/lookup.d.ts.map +1 -1
  178. package/out/lookup.js +102 -444
  179. package/out/lookup.js.map +1 -1
  180. package/out/name-score.d.ts +0 -10
  181. package/out/name-score.d.ts.map +1 -1
  182. package/out/name-score.js +6 -4
  183. package/out/name-score.js.map +1 -1
  184. package/out/place-importance-schema.d.ts +226 -0
  185. package/out/place-importance-schema.d.ts.map +1 -0
  186. package/out/place-importance-schema.js +288 -0
  187. package/out/place-importance-schema.js.map +1 -0
  188. package/out/poi-lookup.d.ts +1 -1
  189. package/out/poi-lookup.d.ts.map +1 -1
  190. package/out/poi-lookup.js +12 -13
  191. package/out/poi-lookup.js.map +1 -1
  192. package/out/poi-schema.d.ts +7 -3
  193. package/out/poi-schema.d.ts.map +1 -1
  194. package/out/poi-schema.js.map +1 -1
  195. package/out/polygon-schema.d.ts +37 -0
  196. package/out/polygon-schema.d.ts.map +1 -0
  197. package/out/polygon-schema.js +23 -0
  198. package/out/polygon-schema.js.map +1 -0
  199. package/out/postal-city-alias-lookup.d.ts +1 -1
  200. package/out/postal-city-alias-lookup.js +1 -1
  201. package/out/postal-city-candidate-schema.d.ts +2 -1
  202. package/out/postal-city-candidate-schema.d.ts.map +1 -1
  203. package/out/postal-city-candidate-schema.js.map +1 -1
  204. package/out/postcode-point-lookup.d.ts +1 -1
  205. package/out/postcode-point-lookup.js +1 -1
  206. package/out/primary-preference.d.ts +125 -0
  207. package/out/primary-preference.d.ts.map +1 -0
  208. package/out/primary-preference.js +138 -0
  209. package/out/primary-preference.js.map +1 -0
  210. package/out/proximity-rerank.d.ts +77 -0
  211. package/out/proximity-rerank.d.ts.map +1 -0
  212. package/out/proximity-rerank.js +86 -0
  213. package/out/proximity-rerank.js.map +1 -0
  214. package/out/region-keys.d.ts +47 -0
  215. package/out/region-keys.d.ts.map +1 -0
  216. package/out/region-keys.js +121 -0
  217. package/out/region-keys.js.map +1 -0
  218. package/out/reverse.d.ts.map +1 -1
  219. package/out/reverse.js +6 -9
  220. package/out/reverse.js.map +1 -1
  221. package/out/schema.d.ts +1 -1
  222. package/out/search-fetch.d.ts +57 -0
  223. package/out/search-fetch.d.ts.map +1 -0
  224. package/out/search-fetch.js +183 -0
  225. package/out/search-fetch.js.map +1 -0
  226. package/out/sharding.d.ts +3 -3
  227. package/out/sharding.js +1 -1
  228. package/out/sqlite-convention-source.d.ts +1 -1
  229. package/out/sqlite-convention-source.js +1 -1
  230. package/out/sqlite-utils.d.ts +31 -1
  231. package/out/sqlite-utils.d.ts.map +1 -1
  232. package/out/sqlite-utils.js +38 -0
  233. package/out/sqlite-utils.js.map +1 -1
  234. package/out/street-centroid-schema.d.ts +7 -2
  235. package/out/street-centroid-schema.d.ts.map +1 -1
  236. package/out/street-centroid-schema.js.map +1 -1
  237. package/out/street-centroid.d.ts.map +1 -1
  238. package/out/street-centroid.js +7 -7
  239. package/out/street-centroid.js.map +1 -1
  240. package/out/street-morphology-fst-builder.d.ts.map +1 -1
  241. package/out/street-morphology-fst-builder.js +5 -4
  242. package/out/street-morphology-fst-builder.js.map +1 -1
  243. package/out/street-normalize.d.ts +83 -9
  244. package/out/street-normalize.d.ts.map +1 -1
  245. package/out/street-normalize.js +177 -10
  246. package/out/street-normalize.js.map +1 -1
  247. package/out/street-segment-schema.d.ts +6 -2
  248. package/out/street-segment-schema.d.ts.map +1 -1
  249. package/out/street-segment-schema.js.map +1 -1
  250. package/out/types.d.ts +74 -1
  251. package/out/types.d.ts.map +1 -1
  252. package/out/unified-schema.d.ts +1 -1
  253. package/out/unified-schema.js +1 -1
  254. package/out/uprn-lookup.d.ts +85 -0
  255. package/out/uprn-lookup.d.ts.map +1 -0
  256. package/out/uprn-lookup.js +152 -0
  257. package/out/uprn-lookup.js.map +1 -0
  258. package/out/uprn-schema.d.ts +93 -0
  259. package/out/uprn-schema.d.ts.map +1 -0
  260. package/out/uprn-schema.js +78 -0
  261. package/out/uprn-schema.js.map +1 -0
  262. package/out/weights-overlay-linker.d.ts +141 -0
  263. package/out/weights-overlay-linker.d.ts.map +1 -0
  264. package/out/weights-overlay-linker.js +259 -0
  265. package/out/weights-overlay-linker.js.map +1 -0
  266. package/package.json +296 -16
  267. package/place-importance-schema.ts +402 -0
  268. package/poi-lookup.ts +12 -13
  269. package/poi-schema.ts +8 -3
  270. package/polygon-schema.ts +47 -0
  271. package/postal-city-alias-lookup.ts +1 -1
  272. package/postal-city-candidate-schema.ts +3 -1
  273. package/postcode-point-lookup.ts +1 -1
  274. package/primary-preference.ts +207 -0
  275. package/proximity-rerank.ts +120 -0
  276. package/region-keys.ts +144 -0
  277. package/reverse.ts +17 -16
  278. package/schema.ts +1 -1
  279. package/search-fetch.ts +256 -0
  280. package/sharding.ts +3 -3
  281. package/sqlite-convention-source.ts +1 -1
  282. package/sqlite-utils.ts +63 -1
  283. package/street-centroid-schema.ts +8 -2
  284. package/street-centroid.ts +13 -8
  285. package/street-morphology-fst-builder.ts +5 -4
  286. package/street-normalize.ts +254 -24
  287. package/street-segment-schema.ts +7 -2
  288. package/types.ts +74 -1
  289. package/unified-schema.ts +1 -1
  290. package/uprn-lookup.ts +210 -0
  291. package/uprn-schema.ts +124 -0
  292. package/weights-overlay-linker.ts +377 -0
  293. package/geo.ts +0 -121
  294. package/out/geo.d.ts +0 -74
  295. package/out/geo.d.ts.map +0 -1
  296. package/out/geo.js +0 -71
  297. package/out/geo.js.map +0 -1
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The OWN-NAME VARIANT predicate (#1882): is an alias surface the holder's own primary name in
7
+ * another orthography — a romanization (`Брэст` → `brest`), a spacing/diacritic variant
8
+ * (`George Town` → `georgetown`), an abbreviation expansion (`St. George's` → `Saint George's`)
9
+ * — rather than a different name that merely shares the folded key?
10
+ *
11
+ * The comparator is `levenshteinSimilarity`, NOT Jaro-Winkler: JW's common-prefix bonus scores
12
+ * the load-bearing NEGATIVE case (`chanchun` vs `cancun`, 0.925) above real positives
13
+ * (`saint george s` vs `st georges` expanded, 0.914), so no JW threshold separates them.
14
+ * Measured on the #1882 census contests, edit similarity separates every case with margin:
15
+ *
16
+ * - IN: `brest`/`brest` 1.0 · `george town`/`georgetown` 0.909 · `saint george s`/`saint georges`
17
+ * 0.929 · `adamovka`/`adamowka` 0.875
18
+ * - OUT: `lievin`/`levin` 0.833 (Liévin FR vs Levin NZ — different places with near-identical
19
+ * names; the panel's `41 Weraroa Road, Levin` row needs this side) · `chanchun`/`cancun` 0.75 ·
20
+ * `augsburg`/`augusta` 0.375 · `west bay`/`west end` 0.625 · `derry`/`londonderry` 0.455
21
+ * (Derry/Londonderry is a DUAL NAME, not a variant — its own follow-up on #1882)
22
+ *
23
+ * An unhandled script (Arabic, Hebrew, CJK — the romanizer covers Cyrillic only) answers NULL,
24
+ * never "different name": absence of a verdict must not stamp anything (the meaning-of-zero rule).
25
+ * The #1882 census counted 396 demoted Arabic/Hebrew-primary aliases left unclassified by this.
26
+ */
27
+ import { levenshteinSimilarity } from "@mailwoman/match/comparators";
28
+ /**
29
+ * Edit-similarity floor for the variant verdict. The measured band: nearest admitted pair 0.875
30
+ * (`adamovka`/`adamowka`), nearest refused pair 0.833 (`lievin`/`levin`).
31
+ */
32
+ export const VARIANT_SIMILARITY_MIN = 0.85;
33
+ /**
34
+ * BGN/PCGN-flavored Cyrillic romanization, folded to the name-key alphabet. Digraph outputs (zh, kh, ts, ch, sh, shch,
35
+ * yu, ya) match the dominant transliteration conventions the gazetteer's Latin aliases actually use; the `w`/`v` and
36
+ * `kh`/`h` style variance between systems is what the edit-similarity threshold absorbs.
37
+ */
38
+ const CYRILLIC_TO_LATIN = {
39
+ а: "a",
40
+ б: "b",
41
+ в: "v",
42
+ г: "g",
43
+ д: "d",
44
+ е: "e",
45
+ ё: "e",
46
+ ж: "zh",
47
+ з: "z",
48
+ и: "i",
49
+ і: "i",
50
+ й: "i",
51
+ к: "k",
52
+ л: "l",
53
+ м: "m",
54
+ н: "n",
55
+ о: "o",
56
+ п: "p",
57
+ р: "r",
58
+ с: "s",
59
+ т: "t",
60
+ у: "u",
61
+ ў: "u",
62
+ ф: "f",
63
+ х: "kh",
64
+ ц: "ts",
65
+ ч: "ch",
66
+ ш: "sh",
67
+ щ: "shch",
68
+ ъ: "",
69
+ ы: "y",
70
+ ь: "",
71
+ э: "e",
72
+ ю: "yu",
73
+ я: "ya",
74
+ ґ: "g",
75
+ є: "e",
76
+ ї: "i",
77
+ };
78
+ /**
79
+ * Leading-word abbreviations expanded BEFORE comparison, so `st georges` meets `saint george s` inside the edit
80
+ * threshold. Whole-word only — `st` inside `stanley` never expands.
81
+ */
82
+ const NAME_ABBREVIATIONS = [
83
+ [/\bst\b/g, "saint"],
84
+ [/\bste\b/g, "sainte"],
85
+ [/\bmt\b/g, "mount"],
86
+ [/\bft\b/g, "fort"],
87
+ ];
88
+ /**
89
+ * Expand the whole-word abbreviations above.
90
+ */
91
+ export function expandNameAbbreviations(key) {
92
+ let out = key;
93
+ for (const [pattern, replacement] of NAME_ABBREVIATIONS) {
94
+ out = out.replaceAll(pattern, replacement);
95
+ }
96
+ return out;
97
+ }
98
+ /**
99
+ * Romanize a folded name key to the a–z0–9/space alphabet. `null` when characters outside the covered scripts remain —
100
+ * an unhandled script is NO VERDICT, not a mismatch.
101
+ */
102
+ export function romanizeNameKey(key) {
103
+ let out = "";
104
+ for (const ch of key.toLowerCase()) {
105
+ out += CYRILLIC_TO_LATIN[ch] ?? ch;
106
+ }
107
+ out = out
108
+ .normalize("NFD")
109
+ .replaceAll(/[̀-ͯ]/g, "")
110
+ .replaceAll(/\s+/g, " ")
111
+ .trim();
112
+ return /^[a-z0-9 ]*$/.test(out) ? out : null;
113
+ }
114
+ /**
115
+ * Edit similarity between a holder's primary name key and one of its alias keys, both romanized and
116
+ * abbreviation-expanded. `null` when either side's script is uncovered.
117
+ */
118
+ export function ownNameSimilarity(primaryKey, aliasKey) {
119
+ const primary = romanizeNameKey(primaryKey);
120
+ const alias = romanizeNameKey(aliasKey);
121
+ if (primary == null || alias == null || !primary || !alias)
122
+ return null;
123
+ return levenshteinSimilarity(expandNameAbbreviations(primary), expandNameAbbreviations(alias));
124
+ }
125
+ /**
126
+ * The stamp predicate: the alias surface is the holder's own name in another orthography.
127
+ */
128
+ export function isOwnNameVariant(primaryKey, aliasKey) {
129
+ const similarity = ownNameSimilarity(primaryKey, aliasKey);
130
+ return similarity != null && similarity >= VARIANT_SIMILARITY_MIN;
131
+ }
132
+ //# sourceMappingURL=own-name.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"own-name.js","sourceRoot":"","sources":["../../candidate/own-name.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The per-place record every candidate-staging pass writes its rows from.
6
+ *
7
+ * Pass 1 reduces each current `spr` row to one {@link PlaceAttrs}; every later pass (alias bags,
8
+ * region abbreviations, country display names, the currency backfill, the shard folds) discovers
9
+ * ADDITIONAL name keys for a place already in that map and stages a row against the same record.
10
+ * That is what keeps each candidate row denormalized without re-reading the source, and it is why
11
+ * a pass needs exactly four things to stage: the key it found, the place, the id the row hangs on,
12
+ * and whether the key is the place's canonical name.
13
+ */
14
+ export interface PlaceAttrs {
15
+ cid: number;
16
+ rid: number;
17
+ ptid: number;
18
+ name: string;
19
+ lat: number;
20
+ lon: number;
21
+ mnLat: number;
22
+ mnLon: number;
23
+ mxLat: number;
24
+ mxLon: number;
25
+ pop: number;
26
+ neg: number;
27
+ pkey: string;
28
+ /**
29
+ * The place's toponym-fame score, or null when the score source has no measurement for it (#28). A property of the
30
+ * PLACE, so it rides {@link StageRow} onto the alias and abbrev rows too — that is how a bare `Moscow` reaches
31
+ * Москва's score through the alias row that carries the key.
32
+ */
33
+ imp: number | null;
34
+ }
35
+ /**
36
+ * Stage one candidate row: a normalized name key, the place it belongs to, the source id the row hangs on, and whether
37
+ * the key is that place's canonical name (`is_primary`).
38
+ *
39
+ * `sid` is passed separately rather than read off the place because a shard fold and the alias pass stage rows for ids
40
+ * the admin `attrs` map never held.
41
+ */
42
+ export type StageRow = (k: string, a: PlaceAttrs, sid: number, isPrimary: number) => void;
43
+ //# sourceMappingURL=place-attrs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"place-attrs.d.ts","sourceRoot":"","sources":["../../candidate/place-attrs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file The per-place record every candidate-staging pass writes its rows from.
6
+ *
7
+ * Pass 1 reduces each current `spr` row to one {@link PlaceAttrs}; every later pass (alias bags,
8
+ * region abbreviations, country display names, the currency backfill, the shard folds) discovers
9
+ * ADDITIONAL name keys for a place already in that map and stages a row against the same record.
10
+ * That is what keeps each candidate row denormalized without re-reading the source, and it is why
11
+ * a pass needs exactly four things to stage: the key it found, the place, the id the row hangs on,
12
+ * and whether the key is the place's canonical name.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=place-attrs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"place-attrs.js","sourceRoot":"","sources":["../../candidate/place-attrs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Pass 4 of the candidate build — fold a postcode or locality shard into the staging table.
6
+ */
7
+ import { DatabaseSync } from "node:sqlite";
8
+ import type { StageRow } from "./place-attrs.ts";
9
+ /**
10
+ * Fold ONE shard (`spr` rows at `shardPlacetype` carrying real coordinates) in, then pass 4b: the alias names hanging
11
+ * off that same shard's `names` table.
12
+ *
13
+ * Self-contained by construction — it shares only the staging statement and the code dictionaries with the admin passes
14
+ * above it, and nothing downstream reads anything it produces except the two counters it returns.
15
+ */
16
+ export declare function foldShard(ctx: {
17
+ /**
18
+ * The staging connection. The shard itself is opened read-only here and closed before returning.
19
+ */
20
+ out: DatabaseSync;
21
+ shardPath: string;
22
+ shardPlacetype: "postalcode" | "locality";
23
+ ccID: (code: string | null) => number;
24
+ ptID: (pt: string | null) => number;
25
+ stageRow: StageRow;
26
+ progress: (phase: string, message: string) => void;
27
+ }): {
28
+ primaries: number;
29
+ aliases: number;
30
+ };
31
+ //# sourceMappingURL=shard-fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shard-fold.d.ts","sourceRoot":"","sources":["../../candidate/shard-fold.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG1C,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE5D;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE;IAC9B;;OAEG;IACH,GAAG,EAAE,YAAY,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,YAAY,GAAG,UAAU,CAAA;IACzC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAA;IACrC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAA;IACnC,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClD,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA0GzC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Pass 4 of the candidate build — fold a postcode or locality shard into the staging table.
6
+ */
7
+ import { DatabaseSync } from "node:sqlite";
8
+ import { normalizeLocalityForKey } from "../street-normalize.js";
9
+ /**
10
+ * Fold ONE shard (`spr` rows at `shardPlacetype` carrying real coordinates) in, then pass 4b: the alias names hanging
11
+ * off that same shard's `names` table.
12
+ *
13
+ * Self-contained by construction — it shares only the staging statement and the code dictionaries with the admin passes
14
+ * above it, and nothing downstream reads anything it produces except the two counters it returns.
15
+ */
16
+ export function foldShard(ctx) {
17
+ const { out, shardPath, shardPlacetype, ccID, ptID, stageRow, progress } = ctx;
18
+ progress(shardPlacetype === "postalcode" ? "postcodes" : "localities", `reading ${shardPath}`);
19
+ const pc = new DatabaseSync(shardPath, { readOnly: true });
20
+ const pcPtid = ptID(shardPlacetype);
21
+ // Per-shard, not the admin `attrs` map: pass 1 only ever sees the admin DB, so the alias pass
22
+ // below has nothing to join against unless this primary loop records what it staged.
23
+ const pcAttrs = new Map();
24
+ let primaries = 0;
25
+ let aliases = 0;
26
+ out.exec("BEGIN");
27
+ for (const r of pc
28
+ .prepare(`SELECT id, name, country, latitude, longitude,
29
+ min_latitude AS mnlat, min_longitude AS mnlon, max_latitude AS mxlat, max_longitude AS mxlon
30
+ FROM spr WHERE placetype = ? AND latitude != 0 AND longitude != 0`)
31
+ .iterate(shardPlacetype)) {
32
+ const name = String(r.name ?? "");
33
+ const key = normalizeLocalityForKey(name);
34
+ if (!key)
35
+ continue;
36
+ const lat = r.latitude;
37
+ const lon = r.longitude;
38
+ // region_id 0 (a postcode is unique by name+country — no same-name disambiguation); neg_rank 0
39
+ // (no population). bbox = the postcode's own min/max (falls back to the centroid point).
40
+ const a = {
41
+ cid: ccID(r.country),
42
+ rid: 0,
43
+ ptid: pcPtid,
44
+ name,
45
+ lat,
46
+ lon,
47
+ mnLat: r.mnlat || lat,
48
+ mnLon: r.mnlon || lon,
49
+ mxLat: r.mxlat || lat,
50
+ mxLon: r.mxlon || lon,
51
+ pop: 0,
52
+ neg: 0,
53
+ pkey: key,
54
+ // A postcode has no toponym fame — nobody writes an encyclopedia article about SW1A 2AA — and
55
+ // the score source carries no `postalcode` rows to join against anyway. NULL is the truthful
56
+ // value: unmeasured, so the ranking key leaves postcode rows exactly where they were.
57
+ imp: null,
58
+ };
59
+ pcAttrs.set(Number(r.id), a);
60
+ stageRow(key, a, Number(r.id), 1);
61
+ primaries++;
62
+ }
63
+ out.exec("COMMIT");
64
+ // --- pass 4b: postcode ALIAS names (#1495) ---
65
+ //
66
+ // The delivery-city names GeoNames supplies for a ZIP ("Brooklyn" for 11201) are written into
67
+ // the shard's `names` table by `postcode/centroid-fills.ts`'s `geonamesNameFill`. Everything
68
+ // downstream of `names` picked them up EXCEPT this build: `fts.ts` unions `spr.name` with every
69
+ // `names` row into `place_search.alt_names`, so the FTS backend resolved "Brooklyn" → 11201
70
+ // while the candidate backend — whose every row IS an exact-tier row — had no key for it at
71
+ // all. Pass 2 does the equivalent fold for admin places, but reads the ADMIN `place_search`,
72
+ // and `attrs` holds admin ids only, so a postcode shard could never reach it.
73
+ //
74
+ // Same discipline as pass 2: `is_primary = 0` (so `rankByPrimaryPreference` treats it as an
75
+ // alias, not a canonical postcode name), the row stays denormalized onto the POSTCODE's own
76
+ // spr_id/coords/bbox, and the display `name` stays the postcode — resolving "brooklyn" answers
77
+ // with place 11201, it does not rename the place to its delivery city.
78
+ const hasNames = pc.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='names'").get() !== undefined;
79
+ if (hasNames) {
80
+ out.exec("BEGIN");
81
+ for (const r of pc.prepare("SELECT id, name FROM names").iterate()) {
82
+ const a = pcAttrs.get(Number(r.id));
83
+ if (!a)
84
+ continue;
85
+ const k = normalizeLocalityForKey(String(r.name ?? ""));
86
+ // The postcode's own key is already staged as the primary; `INSERT OR IGNORE` at
87
+ // materialization dedupes repeats, so this only skips the obvious self-alias.
88
+ if (!k || k === a.pkey)
89
+ continue;
90
+ stageRow(k, a, Number(r.id), 0);
91
+ aliases++;
92
+ }
93
+ out.exec("COMMIT");
94
+ }
95
+ else {
96
+ // Never a silent zero: real shards come from `createUnifiedSchema`, which always creates
97
+ // `names`. A shard without it has no alias surface to lose, but say so rather than reporting
98
+ // "0 aliases" from a table that was never read.
99
+ progress("postcode-aliases", `${shardPath} has no \`names\` table — no delivery-city aliases to fold`);
100
+ }
101
+ pc.close();
102
+ return { primaries, aliases };
103
+ }
104
+ //# sourceMappingURL=shard-fold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shard-fold.js","sourceRoot":"","sources":["../../candidate/shard-fold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for the candidate gazetteer's ANCESTORS sidecar (`candidate_ancestor` +
7
+ * `candidate_interval`) — the containment lineage the candidate table itself cannot answer, built
8
+ * into the same `candidate.db` by `build-candidate.ts` and read by
9
+ * {@link WOFCandidateTableLookup.ancestors}.
10
+ *
11
+ * ENCODING: closure-lite rows, denormalized — one row per (place, ancestor) edge carrying the
12
+ * parent's placetype, display name and folded key — rather than a fixed-slot id chain on the
13
+ * candidate row. Decided by the two consumers:
14
+ *
15
+ * 1. The admin-coherence check needs the WINNER's chain as (placetype, name) pairs in one probe.
16
+ * A fixed-slot `[id;8]` chain answers with ids, and every id then needs a name lookup the
17
+ * artifact has no per-id table for — up to 8 indirections where the closure row has zero.
18
+ * 2. The account layer needs EVERY candidate under a `name_key` enumerable WITH its chain from
19
+ * one artifact probe ("present-but-outranked, discriminated by containment"). That is the
20
+ * candidate probe (contiguous) followed by one `spr_id`-clustered closure probe per candidate
21
+ * — each a handful of adjacent pages.
22
+ *
23
+ * Denormalizing the parent onto the edge is the same discipline as the candidate table itself:
24
+ * this artifact is read over HTTP byte ranges, where a join to a dimension table scatters page
25
+ * fetches, and a `WITHOUT ROWID` B-tree clustered on `(spr_id, depth)` keeps a whole chain in
26
+ * 1-2 pages. The repeated parent strings are the price of the zero-join read, paid at build time.
27
+ *
28
+ * `depth` is 1 for the NEAREST ancestor (deepest containment tier), increasing outward to the
29
+ * country — the same nearest-first order `ancestry.ts` serves for the FTS backend, so the two
30
+ * backends' `ancestors()` agree by construction. The order within a place is deterministic:
31
+ * containment depth descending (`placetypeDepth`), then ancestor id ascending, capped at
32
+ * {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the SHARED {@link normalizeLocalityForKey}
33
+ * fold — the same fold `candidate.name_key` is built with, so a chain entry and a candidate key
34
+ * compare under one normalizer.
35
+ *
36
+ * `candidate_interval` carries pre/post-order labels over the CANONICAL-PARENT FOREST, assigned
37
+ * at build time: `a` contains `d` ⟺ `a.pre <= d.pre AND d.post <= a.post` — O(1) in either
38
+ * direction with no chain scan and no knowledge of either side's tier — and the descendants of
39
+ * `a` are the contiguous range `pre BETWEEN a.pre AND a.post`. Interval labels are classically
40
+ * avoided for their relabel-on-update cost; this database is a sealed read-only artifact rebuilt
41
+ * whole, which is exactly the regime where that cost is void.
42
+ *
43
+ * THE DAG CAVEAT, and the recorded choice: WOF places can carry more than one parent (multiple
44
+ * hierarchies, ambiguous boundaries). `candidate_ancestor` keeps EVERY parent — the closure rows
45
+ * are the complete containment record. A single interval pair can only encode a tree, so the
46
+ * interval forest links each place to ONE canonical parent: its depth-1 edge — the finest
47
+ * containment tier, lowest ancestor id — the same MIN-stability convention the candidate table's
48
+ * `region_id` stamp uses. A containment question about a NON-canonical hierarchy must consult the
49
+ * closure rows; the interval answer for it is `false`, which is why interval verdicts are
50
+ * "contained along the canonical hierarchy", never "not contained at all".
51
+ *
52
+ * ABSENCE SEMANTICS (meaning-of-zero): a place with no `candidate_interval` row has no recorded
53
+ * ancestry in the source (shard-fed postcodes and localities, isolated places, cycle-skipped
54
+ * rows). Absence is UNVERIFIABLE, never a containment verdict.
55
+ */
56
+ import { type Kysely } from "kysely";
57
+ import type { CandidateDatabase } from "./candidate-schema.ts";
58
+ import type { NameKey } from "./street-normalize.ts";
59
+ /**
60
+ * The deepest chain the sidecar stores per place. WOF containment within the resolvable placetypes (country …
61
+ * microhood) never legitimately exceeds this; anything past it is source noise the build drops (and counts) rather than
62
+ * stores.
63
+ */
64
+ export declare const MAX_ANCESTOR_DEPTH = 8;
65
+ /**
66
+ * The closure-row table's name — probed with `hasTable` by the reader, so an artifact predating the sidecar degrades to
67
+ * "no ancestors capability" rather than `no such table`.
68
+ */
69
+ export declare const CANDIDATE_ANCESTOR_TABLE = "candidate_ancestor";
70
+ /**
71
+ * The interval-label table's name — the closure table's seal-time sibling; existence-gated the same way.
72
+ */
73
+ export declare const CANDIDATE_INTERVAL_TABLE = "candidate_interval";
74
+ /**
75
+ * One (place, ancestor) edge, denormalized so a chain read is a single clustered probe (no join).
76
+ */
77
+ export interface CandidateAncestorTable {
78
+ /**
79
+ * WOF id of the place whose chain this row belongs to — the same `spr_id` the candidate table resolves to.
80
+ */
81
+ spr_id: number;
82
+ /**
83
+ * 1 = nearest ancestor, increasing outward to the country. Deterministic within a place (containment depth
84
+ * descending, then ancestor id ascending), so `(spr_id, depth)` is a stable primary key across rebuilds.
85
+ */
86
+ depth: number;
87
+ /**
88
+ * WOF id of the ancestor.
89
+ */
90
+ parent_spr_id: number;
91
+ /**
92
+ * Small int from the shared `placetype_codes` dictionary (the same one the candidate table uses).
93
+ */
94
+ parent_placetype_id: number;
95
+ /**
96
+ * The ancestor's canonical display name — what {@link Ancestor.name} serves, matching the FTS backend's register.
97
+ */
98
+ parent_name: string;
99
+ /**
100
+ * The SHARED {@link normalizeLocalityForKey} fold of `parent_name` — comparable against `candidate.name_key` and
101
+ * against a query-side fold under one normalizer, by construction.
102
+ */
103
+ parent_name_key: NameKey;
104
+ }
105
+ /**
106
+ * Pre/post-order labels over the canonical-parent forest — one row per place WITH recorded ancestry (see the module
107
+ * docstring for absence semantics). `pre < post` always; labels are unique across the artifact.
108
+ */
109
+ export interface CandidateIntervalTable {
110
+ spr_id: number;
111
+ pre: number;
112
+ post: number;
113
+ }
114
+ /**
115
+ * The sidecar tables, for a `Kysely` view over the candidate DB. `CandidateDatabase` (candidate-schema.ts) extends
116
+ * this, so the builder's one typed client sees both families.
117
+ */
118
+ export interface CandidateAncestorsDatabase {
119
+ candidate_ancestor: CandidateAncestorTable;
120
+ candidate_interval: CandidateIntervalTable;
121
+ }
122
+ /**
123
+ * The `candidate_ancestor` columns in clustered-key order — the first two ARE the primary key, and the builder's
124
+ * positional `INSERT` binds by this order. Keep in sync with {@link CandidateAncestorTable}.
125
+ */
126
+ export declare const CANDIDATE_ANCESTOR_COLUMNS: readonly ["spr_id", "depth", "parent_spr_id", "parent_placetype_id", "parent_name", "parent_name_key"];
127
+ /**
128
+ * Create the clustered closure table. The builder inserts in `(spr_id, depth)` order so the B-tree leaves are
129
+ * contiguous per place — the byte-range read discipline.
130
+ */
131
+ export declare function createCandidateAncestorTable(db: Kysely<CandidateDatabase>): Promise<void>;
132
+ /**
133
+ * Create the interval-label table. Small rows probed by primary key — the `WITHOUT ROWID` win case.
134
+ */
135
+ export declare function createCandidateIntervalTable(db: Kysely<CandidateDatabase>): Promise<void>;
136
+ /**
137
+ * One place's interval label — the shape both sides of a containment comparison read.
138
+ */
139
+ export interface IntervalLabel {
140
+ pre: number;
141
+ post: number;
142
+ }
143
+ /**
144
+ * Does `outer` contain `inner` along the canonical hierarchy? Reflexive: a place contains itself (containment
145
+ * degenerates to identity, the same reading the admin-coherence check gives a self-match). The shared FUNCTION for
146
+ * every consumer of the labels — a re-derived comparison risks disagreeing at exactly the strict/inclusive boundary
147
+ * this line settles.
148
+ */
149
+ export declare function intervalContains(outer: IntervalLabel, inner: IntervalLabel): boolean;
150
+ //# sourceMappingURL=candidate-ancestors-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidate-ancestors-schema.d.ts","sourceRoot":"","sources":["../candidate-ancestors-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,EAAO,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAA;AAKzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAEpD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC;;;GAGG;AACH,eAAO,MAAM,wBAAwB,uBAAuB,CAAA;AAE5D;;GAEG;AACH,eAAO,MAAM,wBAAwB,uBAAuB,CAAA;AAE5D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACtC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;OAEG;IACH,aAAa,EAAE,MAAM,CAAA;IACrB;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAA;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACZ;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IAC1C,kBAAkB,EAAE,sBAAsB,CAAA;IAC1C,kBAAkB,EAAE,sBAAsB,CAAA;CAC1C;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,wGAO7B,CAAA;AAEV;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,EAAE,EAAE,MAAM,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAa/F;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,EAAE,EAAE,MAAM,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ/F;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACZ;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAEpF"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Typed schema for the candidate gazetteer's ANCESTORS sidecar (`candidate_ancestor` +
7
+ * `candidate_interval`) — the containment lineage the candidate table itself cannot answer, built
8
+ * into the same `candidate.db` by `build-candidate.ts` and read by
9
+ * {@link WOFCandidateTableLookup.ancestors}.
10
+ *
11
+ * ENCODING: closure-lite rows, denormalized — one row per (place, ancestor) edge carrying the
12
+ * parent's placetype, display name and folded key — rather than a fixed-slot id chain on the
13
+ * candidate row. Decided by the two consumers:
14
+ *
15
+ * 1. The admin-coherence check needs the WINNER's chain as (placetype, name) pairs in one probe.
16
+ * A fixed-slot `[id;8]` chain answers with ids, and every id then needs a name lookup the
17
+ * artifact has no per-id table for — up to 8 indirections where the closure row has zero.
18
+ * 2. The account layer needs EVERY candidate under a `name_key` enumerable WITH its chain from
19
+ * one artifact probe ("present-but-outranked, discriminated by containment"). That is the
20
+ * candidate probe (contiguous) followed by one `spr_id`-clustered closure probe per candidate
21
+ * — each a handful of adjacent pages.
22
+ *
23
+ * Denormalizing the parent onto the edge is the same discipline as the candidate table itself:
24
+ * this artifact is read over HTTP byte ranges, where a join to a dimension table scatters page
25
+ * fetches, and a `WITHOUT ROWID` B-tree clustered on `(spr_id, depth)` keeps a whole chain in
26
+ * 1-2 pages. The repeated parent strings are the price of the zero-join read, paid at build time.
27
+ *
28
+ * `depth` is 1 for the NEAREST ancestor (deepest containment tier), increasing outward to the
29
+ * country — the same nearest-first order `ancestry.ts` serves for the FTS backend, so the two
30
+ * backends' `ancestors()` agree by construction. The order within a place is deterministic:
31
+ * containment depth descending (`placetypeDepth`), then ancestor id ascending, capped at
32
+ * {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the SHARED {@link normalizeLocalityForKey}
33
+ * fold — the same fold `candidate.name_key` is built with, so a chain entry and a candidate key
34
+ * compare under one normalizer.
35
+ *
36
+ * `candidate_interval` carries pre/post-order labels over the CANONICAL-PARENT FOREST, assigned
37
+ * at build time: `a` contains `d` ⟺ `a.pre <= d.pre AND d.post <= a.post` — O(1) in either
38
+ * direction with no chain scan and no knowledge of either side's tier — and the descendants of
39
+ * `a` are the contiguous range `pre BETWEEN a.pre AND a.post`. Interval labels are classically
40
+ * avoided for their relabel-on-update cost; this database is a sealed read-only artifact rebuilt
41
+ * whole, which is exactly the regime where that cost is void.
42
+ *
43
+ * THE DAG CAVEAT, and the recorded choice: WOF places can carry more than one parent (multiple
44
+ * hierarchies, ambiguous boundaries). `candidate_ancestor` keeps EVERY parent — the closure rows
45
+ * are the complete containment record. A single interval pair can only encode a tree, so the
46
+ * interval forest links each place to ONE canonical parent: its depth-1 edge — the finest
47
+ * containment tier, lowest ancestor id — the same MIN-stability convention the candidate table's
48
+ * `region_id` stamp uses. A containment question about a NON-canonical hierarchy must consult the
49
+ * closure rows; the interval answer for it is `false`, which is why interval verdicts are
50
+ * "contained along the canonical hierarchy", never "not contained at all".
51
+ *
52
+ * ABSENCE SEMANTICS (meaning-of-zero): a place with no `candidate_interval` row has no recorded
53
+ * ancestry in the source (shard-fed postcodes and localities, isolated places, cycle-skipped
54
+ * rows). Absence is UNVERIFIABLE, never a containment verdict.
55
+ */
56
+ import { sql } from "kysely";
57
+ /**
58
+ * The deepest chain the sidecar stores per place. WOF containment within the resolvable placetypes (country …
59
+ * microhood) never legitimately exceeds this; anything past it is source noise the build drops (and counts) rather than
60
+ * stores.
61
+ */
62
+ export const MAX_ANCESTOR_DEPTH = 8;
63
+ /**
64
+ * The closure-row table's name — probed with `hasTable` by the reader, so an artifact predating the sidecar degrades to
65
+ * "no ancestors capability" rather than `no such table`.
66
+ */
67
+ export const CANDIDATE_ANCESTOR_TABLE = "candidate_ancestor";
68
+ /**
69
+ * The interval-label table's name — the closure table's seal-time sibling; existence-gated the same way.
70
+ */
71
+ export const CANDIDATE_INTERVAL_TABLE = "candidate_interval";
72
+ /**
73
+ * The `candidate_ancestor` columns in clustered-key order — the first two ARE the primary key, and the builder's
74
+ * positional `INSERT` binds by this order. Keep in sync with {@link CandidateAncestorTable}.
75
+ */
76
+ export const CANDIDATE_ANCESTOR_COLUMNS = [
77
+ "spr_id",
78
+ "depth",
79
+ "parent_spr_id",
80
+ "parent_placetype_id",
81
+ "parent_name",
82
+ "parent_name_key",
83
+ ];
84
+ /**
85
+ * Create the clustered closure table. The builder inserts in `(spr_id, depth)` order so the B-tree leaves are
86
+ * contiguous per place — the byte-range read discipline.
87
+ */
88
+ export async function createCandidateAncestorTable(db) {
89
+ await db.schema
90
+ .createTable(CANDIDATE_ANCESTOR_TABLE)
91
+ .addColumn("spr_id", "integer", (c) => c.notNull())
92
+ .addColumn("depth", "integer", (c) => c.notNull())
93
+ .addColumn("parent_spr_id", "integer", (c) => c.notNull())
94
+ .addColumn("parent_placetype_id", "integer", (c) => c.notNull())
95
+ .addColumn("parent_name", "text", (c) => c.notNull())
96
+ .addColumn("parent_name_key", "text", (c) => c.notNull())
97
+ .addPrimaryKeyConstraint("candidate_ancestor_pk", ["spr_id", "depth"])
98
+ // `WITHOUT ROWID` has no first-class builder; the raw modifier is the idiomatic fallback.
99
+ .modifyEnd(sql `without rowid`)
100
+ .execute();
101
+ }
102
+ /**
103
+ * Create the interval-label table. Small rows probed by primary key — the `WITHOUT ROWID` win case.
104
+ */
105
+ export async function createCandidateIntervalTable(db) {
106
+ await db.schema
107
+ .createTable(CANDIDATE_INTERVAL_TABLE)
108
+ .addColumn("spr_id", "integer", (c) => c.primaryKey())
109
+ .addColumn("pre", "integer", (c) => c.notNull())
110
+ .addColumn("post", "integer", (c) => c.notNull())
111
+ .modifyEnd(sql `without rowid`)
112
+ .execute();
113
+ }
114
+ /**
115
+ * Does `outer` contain `inner` along the canonical hierarchy? Reflexive: a place contains itself (containment
116
+ * degenerates to identity, the same reading the admin-coherence check gives a self-match). The shared FUNCTION for
117
+ * every consumer of the labels — a re-derived comparison risks disagreeing at exactly the strict/inclusive boundary
118
+ * this line settles.
119
+ */
120
+ export function intervalContains(outer, inner) {
121
+ return outer.pre <= inner.pre && inner.post <= outer.post;
122
+ }
123
+ //# sourceMappingURL=candidate-ancestors-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidate-ancestors-schema.js","sourceRoot":"","sources":["../candidate-ancestors-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,EAAE,GAAG,EAAe,MAAM,QAAQ,CAAA;AAQzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAA;AAE5D;;GAEG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAA;AAqD5D;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACzC,QAAQ;IACR,OAAO;IACP,eAAe;IACf,qBAAqB;IACrB,aAAa;IACb,iBAAiB;CACR,CAAA;AAEV;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EAA6B;IAC/E,MAAM,EAAE,CAAC,MAAM;SACb,WAAW,CAAC,wBAAwB,CAAC;SACrC,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAClD,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACjD,SAAS,CAAC,eAAe,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACzD,SAAS,CAAC,qBAAqB,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/D,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACpD,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SACxD,uBAAuB,CAAC,uBAAuB,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACtE,0FAA0F;SACzF,SAAS,CAAC,GAAG,CAAA,eAAe,CAAC;SAC7B,OAAO,EAAE,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,EAA6B;IAC/E,MAAM,EAAE,CAAC,MAAM;SACb,WAAW,CAAC,wBAAwB,CAAC;SACrC,SAAS,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;SACrD,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/C,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAChD,SAAS,CAAC,GAAG,CAAA,eAAe,CAAC;SAC7B,OAAO,EAAE,CAAA;AACZ,CAAC;AAUD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAoB,EAAE,KAAoB;IAC1E,OAAO,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAA;AAC1D,CAAC"}