@mailwoman/resolver-wof-sqlite 2.1.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 (158) hide show
  1. package/README.md +250 -0
  2. package/out/address-point-interpolation.d.ts +48 -0
  3. package/out/address-point-interpolation.d.ts.map +1 -0
  4. package/out/address-point-interpolation.js +164 -0
  5. package/out/address-point-interpolation.js.map +1 -0
  6. package/out/address-point-schema.d.ts +58 -0
  7. package/out/address-point-schema.d.ts.map +1 -0
  8. package/out/address-point-schema.js +67 -0
  9. package/out/address-point-schema.js.map +1 -0
  10. package/out/address-point.d.ts +29 -0
  11. package/out/address-point.d.ts.map +1 -0
  12. package/out/address-point.js +62 -0
  13. package/out/address-point.js.map +1 -0
  14. package/out/ancestry.d.ts +40 -0
  15. package/out/ancestry.d.ts.map +1 -0
  16. package/out/ancestry.js +53 -0
  17. package/out/ancestry.js.map +1 -0
  18. package/out/build-candidate-cli.d.ts +16 -0
  19. package/out/build-candidate-cli.d.ts.map +1 -0
  20. package/out/build-candidate-cli.js +80 -0
  21. package/out/build-candidate-cli.js.map +1 -0
  22. package/out/build-candidate.d.ts +54 -0
  23. package/out/build-candidate.d.ts.map +1 -0
  24. package/out/build-candidate.js +230 -0
  25. package/out/build-candidate.js.map +1 -0
  26. package/out/build-coincident-roles-cli.d.ts +16 -0
  27. package/out/build-coincident-roles-cli.d.ts.map +1 -0
  28. package/out/build-coincident-roles-cli.js +94 -0
  29. package/out/build-coincident-roles-cli.js.map +1 -0
  30. package/out/build-fts-cli.d.ts +23 -0
  31. package/out/build-fts-cli.d.ts.map +1 -0
  32. package/out/build-fts-cli.js +117 -0
  33. package/out/build-fts-cli.js.map +1 -0
  34. package/out/build-slim-cli.d.ts +14 -0
  35. package/out/build-slim-cli.d.ts.map +1 -0
  36. package/out/build-slim-cli.js +130 -0
  37. package/out/build-slim-cli.js.map +1 -0
  38. package/out/build-slim.d.ts +71 -0
  39. package/out/build-slim.d.ts.map +1 -0
  40. package/out/build-slim.js +267 -0
  41. package/out/build-slim.js.map +1 -0
  42. package/out/candidate-lookup.d.ts +43 -0
  43. package/out/candidate-lookup.d.ts.map +1 -0
  44. package/out/candidate-lookup.js +191 -0
  45. package/out/candidate-lookup.js.map +1 -0
  46. package/out/candidate-schema.d.ts +86 -0
  47. package/out/candidate-schema.d.ts.map +1 -0
  48. package/out/candidate-schema.js +109 -0
  49. package/out/candidate-schema.js.map +1 -0
  50. package/out/coincident-roles.d.ts +86 -0
  51. package/out/coincident-roles.d.ts.map +1 -0
  52. package/out/coincident-roles.js +160 -0
  53. package/out/coincident-roles.js.map +1 -0
  54. package/out/convention.d.ts +109 -0
  55. package/out/convention.d.ts.map +1 -0
  56. package/out/convention.js +94 -0
  57. package/out/convention.js.map +1 -0
  58. package/out/fst-autocomplete.d.ts +49 -0
  59. package/out/fst-autocomplete.d.ts.map +1 -0
  60. package/out/fst-autocomplete.js +124 -0
  61. package/out/fst-autocomplete.js.map +1 -0
  62. package/out/fst-builder.d.ts +20 -0
  63. package/out/fst-builder.d.ts.map +1 -0
  64. package/out/fst-builder.js +219 -0
  65. package/out/fst-builder.js.map +1 -0
  66. package/out/fst-deserialize-web.d.ts +16 -0
  67. package/out/fst-deserialize-web.d.ts.map +1 -0
  68. package/out/fst-deserialize-web.js +133 -0
  69. package/out/fst-deserialize-web.js.map +1 -0
  70. package/out/fst-matcher.d.ts +33 -0
  71. package/out/fst-matcher.d.ts.map +1 -0
  72. package/out/fst-matcher.js +117 -0
  73. package/out/fst-matcher.js.map +1 -0
  74. package/out/fst-serialize.d.ts +30 -0
  75. package/out/fst-serialize.d.ts.map +1 -0
  76. package/out/fst-serialize.js +261 -0
  77. package/out/fst-serialize.js.map +1 -0
  78. package/out/fst-types.d.ts +60 -0
  79. package/out/fst-types.d.ts.map +1 -0
  80. package/out/fst-types.js +11 -0
  81. package/out/fst-types.js.map +1 -0
  82. package/out/fts.d.ts +158 -0
  83. package/out/fts.d.ts.map +1 -0
  84. package/out/fts.js +261 -0
  85. package/out/fts.js.map +1 -0
  86. package/out/geo.d.ts +74 -0
  87. package/out/geo.d.ts.map +1 -0
  88. package/out/geo.js +88 -0
  89. package/out/geo.js.map +1 -0
  90. package/out/index.d.ts +27 -0
  91. package/out/index.d.ts.map +1 -0
  92. package/out/index.js +22 -0
  93. package/out/index.js.map +1 -0
  94. package/out/interpolation.d.ts +84 -0
  95. package/out/interpolation.d.ts.map +1 -0
  96. package/out/interpolation.js +150 -0
  97. package/out/interpolation.js.map +1 -0
  98. package/out/lookup.d.ts +156 -0
  99. package/out/lookup.d.ts.map +1 -0
  100. package/out/lookup.js +876 -0
  101. package/out/lookup.js.map +1 -0
  102. package/out/postal-city-alias-lookup.d.ts +50 -0
  103. package/out/postal-city-alias-lookup.d.ts.map +1 -0
  104. package/out/postal-city-alias-lookup.js +66 -0
  105. package/out/postal-city-alias-lookup.js.map +1 -0
  106. package/out/postal-city-alias-schema.d.ts +51 -0
  107. package/out/postal-city-alias-schema.d.ts.map +1 -0
  108. package/out/postal-city-alias-schema.js +47 -0
  109. package/out/postal-city-alias-schema.js.map +1 -0
  110. package/out/postal-city-candidate-schema.d.ts +58 -0
  111. package/out/postal-city-candidate-schema.d.ts.map +1 -0
  112. package/out/postal-city-candidate-schema.js +56 -0
  113. package/out/postal-city-candidate-schema.js.map +1 -0
  114. package/out/postcode-point-lookup.d.ts +38 -0
  115. package/out/postcode-point-lookup.d.ts.map +1 -0
  116. package/out/postcode-point-lookup.js +46 -0
  117. package/out/postcode-point-lookup.js.map +1 -0
  118. package/out/reverse.d.ts +99 -0
  119. package/out/reverse.d.ts.map +1 -0
  120. package/out/reverse.js +290 -0
  121. package/out/reverse.js.map +1 -0
  122. package/out/schema.d.ts +163 -0
  123. package/out/schema.d.ts.map +1 -0
  124. package/out/schema.js +18 -0
  125. package/out/schema.js.map +1 -0
  126. package/out/sharding.d.ts +96 -0
  127. package/out/sharding.d.ts.map +1 -0
  128. package/out/sharding.js +129 -0
  129. package/out/sharding.js.map +1 -0
  130. package/out/sqlite-convention-source.d.ts +29 -0
  131. package/out/sqlite-convention-source.d.ts.map +1 -0
  132. package/out/sqlite-convention-source.js +53 -0
  133. package/out/sqlite-convention-source.js.map +1 -0
  134. package/out/sqlite-utils.d.ts +17 -0
  135. package/out/sqlite-utils.d.ts.map +1 -0
  136. package/out/sqlite-utils.js +24 -0
  137. package/out/sqlite-utils.js.map +1 -0
  138. package/out/street-morphology-fst-builder.d.ts +59 -0
  139. package/out/street-morphology-fst-builder.d.ts.map +1 -0
  140. package/out/street-morphology-fst-builder.js +174 -0
  141. package/out/street-morphology-fst-builder.js.map +1 -0
  142. package/out/street-normalize.d.ts +66 -0
  143. package/out/street-normalize.d.ts.map +1 -0
  144. package/out/street-normalize.js +176 -0
  145. package/out/street-normalize.js.map +1 -0
  146. package/out/street-segment-schema.d.ts +61 -0
  147. package/out/street-segment-schema.d.ts.map +1 -0
  148. package/out/street-segment-schema.js +64 -0
  149. package/out/street-segment-schema.js.map +1 -0
  150. package/out/types.d.ts +137 -0
  151. package/out/types.d.ts.map +1 -0
  152. package/out/types.js +13 -0
  153. package/out/types.js.map +1 -0
  154. package/out/unified-schema.d.ts +25 -0
  155. package/out/unified-schema.d.ts.map +1 -0
  156. package/out/unified-schema.js +142 -0
  157. package/out/unified-schema.js.map +1 -0
  158. package/package.json +54 -0
@@ -0,0 +1,62 @@
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) or locality scope, against a per-state shard built by
8
+ * `scripts/build-address-point-shard.ts`. Query-side normalization is THE shared normalizer
9
+ * (`street-normalize.ts`) — identical to build-side, by construction.
10
+ *
11
+ * Matching is exact-after-normalization only — no fuzzy street matching in this tier (measure how
12
+ * far exact gets first; fuzz is a later, separate decision). Postcode scope is attempted first
13
+ * (cheapest, most selective); locality scope is the fallback. Multiple hits (same number,
14
+ * units/duplicates) return the first by rowid — coordinates of unit siblings are the same
15
+ * building for tier purposes.
16
+ */
17
+ import { DatabaseSync } from "node:sqlite";
18
+ import { hasTable } from "./sqlite-utils.js";
19
+ import { normalizeLocalityForKey, normalizeStreetForKey } from "./street-normalize.js";
20
+ /**
21
+ * The 4 columns the reader SELECTs, in the schema's order — referenced by the prepared SELECTs so
22
+ * the projected `AddressPointRow` stays in lockstep with the shared schema.
23
+ */
24
+ const SELECT_COLS = "lat, lon, source, release";
25
+ export class AddressPointSqliteLookup {
26
+ #db;
27
+ #byPostcode;
28
+ #byLocality;
29
+ constructor(dbPath) {
30
+ this.#db = new DatabaseSync(dbPath, { readOnly: true });
31
+ // Degrade gracefully on an empty/tableless shard (interrupted build, stray 0-byte file): with no
32
+ // `address_point` table this lookup is a no-op miss, not a crash that loses the whole state (#568).
33
+ if (hasTable(this.#db, "address_point")) {
34
+ this.#byPostcode = this.#db.prepare(`SELECT ${SELECT_COLS} FROM address_point
35
+ WHERE postcode = ? AND street_norm = ? AND number = ? LIMIT 1`);
36
+ this.#byLocality = this.#db.prepare(`SELECT ${SELECT_COLS} FROM address_point
37
+ WHERE locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`);
38
+ }
39
+ }
40
+ find(query) {
41
+ if (!this.#byPostcode || !this.#byLocality)
42
+ return null;
43
+ const streetNorm = normalizeStreetForKey(query.street);
44
+ const number = query.number.trim().toLowerCase();
45
+ if (!streetNorm || !number)
46
+ return null;
47
+ let row;
48
+ if (query.postcode) {
49
+ row = this.#byPostcode.get(query.postcode.trim(), streetNorm, number);
50
+ }
51
+ if (!row && query.locality) {
52
+ row = this.#byLocality.get(normalizeLocalityForKey(query.locality), streetNorm, number);
53
+ }
54
+ if (!row)
55
+ return null;
56
+ return { lat: row.lat, lon: row.lon, source: row.source, release: row.release };
57
+ }
58
+ close() {
59
+ this.#db.close();
60
+ }
61
+ }
62
+ //# sourceMappingURL=address-point.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-point.js","sourceRoot":"","sources":["../address-point.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAK1C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAStF;;;GAGG;AACH,MAAM,WAAW,GAAG,2BAA2B,CAAA;AAE/C,MAAM,OAAO,wBAAwB;IAC3B,GAAG,CAAc;IACjB,WAAW,CAAiD;IAC5D,WAAW,CAAiD;IAErE,YAAY,MAAc;QACzB,IAAI,CAAC,GAAG,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;QACvD,iGAAiG;QACjG,oGAAoG;QACpG,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAClC,UAAU,WAAW;mEAC0C,CAC/D,CAAA;YACD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAClC,UAAU,WAAW;wEAC+C,CACpE,CAAA;QACF,CAAC;IACF,CAAC;IAED,IAAI,CAAC,KAA+E;QACnF,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAA;QACvD,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACtD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAChD,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAEvC,IAAI,GAAgC,CAAA;QACpC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpB,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,CAAgC,CAAA;QACrG,CAAC;QACD,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC5B,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,uBAAuB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,MAAM,CAE1E,CAAA;QACb,CAAC;QACD,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QACrB,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IAChF,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;IACjB,CAAC;CACD"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The shared ancestor-lineage walk over the WOF `ancestors` table — one place's containment chain
7
+ * joined with `spr` for canonical names + centroids, ordered NEAREST-FIRST (deepest placetype
8
+ * first, country last).
9
+ *
10
+ * Factored out of `WofSqlitePlaceLookup.ancestors()` (#404) so the reverse geocoder (`reverse.ts`,
11
+ * #484) reuses the SAME walk instead of growing a second one. The placetype-specificity ordering
12
+ * lives here as `PLACETYPE_DEPTH` — a single TS map instead of the previous SQL CASE, and
13
+ * extended below `localadmin` (locality/borough/neighbourhood/microhood now rank correctly
14
+ * instead of sorting last; forward resolution rarely saw those as ANCESTOR placetypes, reverse
15
+ * geocoding always does).
16
+ */
17
+ import type { DatabaseSync } from "node:sqlite";
18
+ /**
19
+ * WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve
20
+ * (continent, empire, …) map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER`
21
+ * (fst-serialize.ts) — that one is a serialization order, this one is containment depth.
22
+ */
23
+ export declare const PLACETYPE_DEPTH: Readonly<Record<string, number>>;
24
+ /** Containment depth for a placetype — 0 (sorts coarsest) when unknown. */
25
+ export declare function placetypeDepth(placetype: string): number;
26
+ /** One ancestor row, enriched with the `spr` columns both consumers need. */
27
+ export interface AncestorPlaceRow {
28
+ id: number;
29
+ placetype: string;
30
+ name: string;
31
+ country: string;
32
+ lat: number;
33
+ lon: number;
34
+ }
35
+ /**
36
+ * The ancestor lineage of `id` — self excluded, nearest-first. Returns `[]` when the place has no
37
+ * recorded ancestry. NOT memoized here; `WofSqlitePlaceLookup` keeps its own per-id cache.
38
+ */
39
+ export declare function ancestorLineage(db: DatabaseSync, id: number, schemaName?: string): AncestorPlaceRow[];
40
+ //# sourceMappingURL=ancestry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ancestry.d.ts","sourceRoot":"","sources":["../ancestry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAY5D,CAAA;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACX;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,SAAS,GAAG,gBAAgB,EAAE,CAWrG"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The shared ancestor-lineage walk over the WOF `ancestors` table — one place's containment chain
7
+ * joined with `spr` for canonical names + centroids, ordered NEAREST-FIRST (deepest placetype
8
+ * first, country last).
9
+ *
10
+ * Factored out of `WofSqlitePlaceLookup.ancestors()` (#404) so the reverse geocoder (`reverse.ts`,
11
+ * #484) reuses the SAME walk instead of growing a second one. The placetype-specificity ordering
12
+ * lives here as `PLACETYPE_DEPTH` — a single TS map instead of the previous SQL CASE, and
13
+ * extended below `localadmin` (locality/borough/neighbourhood/microhood now rank correctly
14
+ * instead of sorting last; forward resolution rarely saw those as ANCESTOR placetypes, reverse
15
+ * geocoding always does).
16
+ */
17
+ /**
18
+ * WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve
19
+ * (continent, empire, …) map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER`
20
+ * (fst-serialize.ts) — that one is a serialization order, this one is containment depth.
21
+ */
22
+ export const PLACETYPE_DEPTH = {
23
+ country: 1,
24
+ macroregion: 2,
25
+ region: 3,
26
+ macrocounty: 4,
27
+ county: 5,
28
+ localadmin: 6,
29
+ locality: 7,
30
+ borough: 8,
31
+ macrohood: 9,
32
+ neighbourhood: 10,
33
+ microhood: 11,
34
+ };
35
+ /** Containment depth for a placetype — 0 (sorts coarsest) when unknown. */
36
+ export function placetypeDepth(placetype) {
37
+ return PLACETYPE_DEPTH[placetype] ?? 0;
38
+ }
39
+ /**
40
+ * The ancestor lineage of `id` — self excluded, nearest-first. Returns `[]` when the place has no
41
+ * recorded ancestry. NOT memoized here; `WofSqlitePlaceLookup` keeps its own per-id cache.
42
+ */
43
+ export function ancestorLineage(db, id, schemaName = "main") {
44
+ const rows = db
45
+ .prepare(`SELECT a.ancestor_id AS id, a.ancestor_placetype AS placetype, s.name AS name,
46
+ s.country AS country, s.latitude AS lat, s.longitude AS lon
47
+ FROM ${schemaName}.ancestors a JOIN ${schemaName}.spr s ON s.id = a.ancestor_id
48
+ WHERE a.id = ? AND a.ancestor_id != a.id`)
49
+ .all(id);
50
+ rows.sort((a, b) => placetypeDepth(b.placetype) - placetypeDepth(a.placetype));
51
+ return rows;
52
+ }
53
+ //# sourceMappingURL=ancestry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ancestry.js","sourceRoot":"","sources":["../ancestry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAqC;IAChE,OAAO,EAAE,CAAC;IACV,WAAW,EAAE,CAAC;IACd,MAAM,EAAE,CAAC;IACT,WAAW,EAAE,CAAC;IACd,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,SAAS,EAAE,CAAC;IACZ,aAAa,EAAE,EAAE;IACjB,SAAS,EAAE,EAAE;CACb,CAAA;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC/C,OAAO,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;AACvC,CAAC;AAYD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,EAAgB,EAAE,EAAU,EAAE,UAAU,GAAG,MAAM;IAChF,MAAM,IAAI,GAAG,EAAE;SACb,OAAO,CACP;;UAEO,UAAU,qBAAqB,UAAU;4CACP,CACzC;SACA,GAAG,CAAC,EAAE,CAAkC,CAAA;IAC1C,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAC9E,OAAO,IAAI,CAAA;AACZ,CAAC"}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @copyright Sister Software
4
+ * @license AGPL-3.0
5
+ * @author Teffen Ellis, et al.
6
+ *
7
+ * `mailwoman-wof-build-candidate --in <admin.db> --out <candidate.db>`
8
+ *
9
+ * Operator-side one-shot CLI that builds the global byte-range "candidate" lookup DB for the
10
+ * browser demo (the FTS-free, single-probe gazetteer). Delegates to
11
+ * {@linkcode buildCandidateTable}; the CLI is just argument parsing + stderr progress. The
12
+ * TypeScript port of the 2026-06-20 prototype — it lives in the package (not /scripts) so the
13
+ * name_key normalizer is the SAME shared `normalizeLocalityForKey` the query-side resolver uses.
14
+ */
15
+ export declare function main(rawArgv: string[]): Promise<number>;
16
+ //# sourceMappingURL=build-candidate-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-candidate-cli.d.ts","sourceRoot":"","sources":["../build-candidate-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAiDH,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB7D"}
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @copyright Sister Software
4
+ * @license AGPL-3.0
5
+ * @author Teffen Ellis, et al.
6
+ *
7
+ * `mailwoman-wof-build-candidate --in <admin.db> --out <candidate.db>`
8
+ *
9
+ * Operator-side one-shot CLI that builds the global byte-range "candidate" lookup DB for the
10
+ * browser demo (the FTS-free, single-probe gazetteer). Delegates to
11
+ * {@linkcode buildCandidateTable}; the CLI is just argument parsing + stderr progress. The
12
+ * TypeScript port of the 2026-06-20 prototype — it lives in the package (not /scripts) so the
13
+ * name_key normalizer is the SAME shared `normalizeLocalityForKey` the query-side resolver uses.
14
+ */
15
+ import { exit, stderr } from "node:process";
16
+ import { buildCandidateTable } from "./build-candidate.js";
17
+ function printUsageAndExit(code) {
18
+ stderr.write([
19
+ "usage: mailwoman-wof-build-candidate --in <admin.db> --out <candidate.db> [--postcodes <pc.db>]...",
20
+ "",
21
+ "Builds the global candidate lookup DB (FTS-free, single B-tree probe per resolve) from a",
22
+ "unified admin WOF DB (needs spr, place_population, place_search, place_abbr, ancestors).",
23
+ "--postcodes folds a postcode shard (spr placetype='postalcode' + coords, e.g. postalcode-us.db)",
24
+ "in as postalcode rows so findPlace(postalcode) resolves a ZIP. Name keys use the shared",
25
+ "normalizeLocalityForKey — the query side MUST match.",
26
+ "",
27
+ ].join("\n"));
28
+ exit(code);
29
+ }
30
+ function parseArgs(argv) {
31
+ let input;
32
+ let output;
33
+ const postcodes = [];
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a === "--in" || a === "--input")
37
+ input = argv[++i];
38
+ else if (a === "--out" || a === "--output")
39
+ output = argv[++i];
40
+ else if (a === "--postcodes") {
41
+ const v = argv[++i];
42
+ if (v)
43
+ postcodes.push(v);
44
+ }
45
+ else if (a === "-h" || a === "--help")
46
+ printUsageAndExit(0);
47
+ else {
48
+ stderr.write(`unknown argument: ${a}\n`);
49
+ printUsageAndExit(1);
50
+ }
51
+ }
52
+ if (!input || !output)
53
+ printUsageAndExit(1);
54
+ return { input, output, postcodes };
55
+ }
56
+ export async function main(rawArgv) {
57
+ const args = parseArgs(rawArgv);
58
+ const t0 = Date.now();
59
+ const result = await buildCandidateTable({
60
+ input: args.input,
61
+ output: args.output,
62
+ postcodes: args.postcodes,
63
+ onProgress: (phase, message) => stderr.write(` [${phase}] ${message}\n`),
64
+ });
65
+ const secs = ((Date.now() - t0) / 1000).toFixed(1);
66
+ stderr.write(`done in ${secs}s: ${result.rows.toLocaleString()} rows ` +
67
+ `(${result.primaries.toLocaleString()} primary, ${result.aliases.toLocaleString()} alias, ` +
68
+ `${result.abbrevs} abbr, ${result.postcodes.toLocaleString()} postcode) ` +
69
+ `from ${result.places.toLocaleString()} places → ${args.output}\n`);
70
+ return 0;
71
+ }
72
+ if (import.meta.url === `file://${process.argv[1]}`) {
73
+ main(process.argv.slice(2))
74
+ .then((code) => exit(code))
75
+ .catch((err) => {
76
+ stderr.write(`${err instanceof Error ? err.stack : String(err)}\n`);
77
+ exit(1);
78
+ });
79
+ }
80
+ //# sourceMappingURL=build-candidate-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-candidate-cli.js","sourceRoot":"","sources":["../build-candidate-cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Build the global "candidate" lookup DB from a unified admin WOF DB — the byte-range-optimal
7
+ * gazetteer the browser demo resolves against. Instead of FTS5 (whose postings for a common name
8
+ * scatter across a multi-GB file → hundreds of serial range fetches), this materializes one
9
+ * `WITHOUT ROWID` B-tree keyed `(name_key, country_id, region_id, placetype_id, neg_rank,
10
+ * spr_id)`: every place's normalized name + distinct aliases + region abbreviations become rows,
11
+ * population rank is precomputed into `neg_rank`, and the rows are bulk-loaded PRE-SORTED so a
12
+ * resolve is one contiguous B-tree probe (a handful of pages → 1-2 chunk fetches, regardless of
13
+ * global volume).
14
+ *
15
+ * Each row is DENORMALIZED — it carries the place's display `name`, centroid (`latitude`/
16
+ * `longitude`), and `min/max` bbox — so a resolve is one statement, no FTS, no join to spr:
17
+ * SELECT spr_id, name, latitude, longitude, min_lat, ... FROM candidate WHERE name_key = ? AND
18
+ * country_id = ? AND placetype_id IN (...) [AND latitude BETWEEN ...] ORDER BY neg_rank ASC LIMIT
19
+ * K; The demo cascade resolves a parsed region first (its bbox), then constrains the locality to
20
+ * that bbox; `region_id` (the place's region-tier ancestor) is also carried for a future region
21
+ * 2-step.
22
+ *
23
+ * The name_key normalizer is the SHARED {@link normalizeLocalityForKey} — the query side (the demo
24
+ * resolver {@link WofCandidateTableLookup}) MUST use the same function, the one-normalizer
25
+ * discipline the address-point shard uses, so build/query stay consistent by construction.
26
+ *
27
+ * Measured (2026-06-20, vs the 2.6 GB full-DB FTS): ~5 M rows; ~12 range fetches per 8-query
28
+ * session (the full DB needs 243); US locality 96.8% (region bbox), EU coord parity 88.6%.
29
+ */
30
+ export interface BuildCandidateOptions {
31
+ /** Source unified admin DB — needs spr, place_population, place_search, place_abbr, ancestors. */
32
+ input: string;
33
+ /** Output candidate DB path (overwritten if present). */
34
+ output: string;
35
+ /**
36
+ * Optional postcode shards (`spr` rows with `placetype='postalcode'` + real coords, e.g.
37
+ * postalcode-us.db) — folded in as `postalcode` candidate rows so `findPlace(postalcode)`
38
+ * resolves a ZIP directly (the demo's primary postcode path; the postcode-*.bin anchor stays the
39
+ * fallback). Matches the slim wof-hot.db, which took one such postcode DB.
40
+ */
41
+ postcodes?: string[];
42
+ /** Optional progress callback for CLI / test introspection. */
43
+ onProgress?: (phase: string, message: string) => void;
44
+ }
45
+ export interface BuildCandidateResult {
46
+ rows: number;
47
+ places: number;
48
+ primaries: number;
49
+ aliases: number;
50
+ abbrevs: number;
51
+ postcodes: number;
52
+ }
53
+ export declare function buildCandidateTable(opts: BuildCandidateOptions): Promise<BuildCandidateResult>;
54
+ //# sourceMappingURL=build-candidate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-candidate.d.ts","sourceRoot":"","sources":["../build-candidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAgBH,MAAM,WAAW,qBAAqB;IACrC,kGAAkG;IAClG,KAAK,EAAE,MAAM,CAAA;IACb,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACrD;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CACjB;AAkBD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyOpG"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Build the global "candidate" lookup DB from a unified admin WOF DB — the byte-range-optimal
7
+ * gazetteer the browser demo resolves against. Instead of FTS5 (whose postings for a common name
8
+ * scatter across a multi-GB file → hundreds of serial range fetches), this materializes one
9
+ * `WITHOUT ROWID` B-tree keyed `(name_key, country_id, region_id, placetype_id, neg_rank,
10
+ * spr_id)`: every place's normalized name + distinct aliases + region abbreviations become rows,
11
+ * population rank is precomputed into `neg_rank`, and the rows are bulk-loaded PRE-SORTED so a
12
+ * resolve is one contiguous B-tree probe (a handful of pages → 1-2 chunk fetches, regardless of
13
+ * global volume).
14
+ *
15
+ * Each row is DENORMALIZED — it carries the place's display `name`, centroid (`latitude`/
16
+ * `longitude`), and `min/max` bbox — so a resolve is one statement, no FTS, no join to spr:
17
+ * SELECT spr_id, name, latitude, longitude, min_lat, ... FROM candidate WHERE name_key = ? AND
18
+ * country_id = ? AND placetype_id IN (...) [AND latitude BETWEEN ...] ORDER BY neg_rank ASC LIMIT
19
+ * K; The demo cascade resolves a parsed region first (its bbox), then constrains the locality to
20
+ * that bbox; `region_id` (the place's region-tier ancestor) is also carried for a future region
21
+ * 2-step.
22
+ *
23
+ * The name_key normalizer is the SHARED {@link normalizeLocalityForKey} — the query side (the demo
24
+ * resolver {@link WofCandidateTableLookup}) MUST use the same function, the one-normalizer
25
+ * discipline the address-point shard uses, so build/query stay consistent by construction.
26
+ *
27
+ * Measured (2026-06-20, vs the 2.6 GB full-DB FTS): ~5 M rows; ~12 range fetches per 8-query
28
+ * session (the full DB needs 243); US locality 96.8% (region bbox), EU coord parity 88.6%.
29
+ */
30
+ import { DatabaseClient } from "@mailwoman/core/kysley/client";
31
+ import { existsSync, rmSync } from "node:fs";
32
+ import { DatabaseSync } from "node:sqlite";
33
+ import { CANDIDATE_COLUMNS, createCandidateStagingTables, createCandidateTable, } from "./candidate-schema.js";
34
+ import { normalizeLocalityForKey } from "./street-normalize.js";
35
+ /** Boundary-preserving alias-bag separator (#523, U+E000). */
36
+ const ALIAS_SEP = "\u{E000}";
37
+ export async function buildCandidateTable(opts) {
38
+ const progress = opts.onProgress ?? (() => { });
39
+ if (existsSync(opts.output))
40
+ rmSync(opts.output);
41
+ const src = new DatabaseSync(opts.input, { readOnly: true });
42
+ const out = new DatabaseSync(opts.output);
43
+ // Build-tuning pragmas (raw — Kysely doesn't model PRAGMA). The code dictionaries + the transient
44
+ // staging table come from the SHARED schema DDL, so they can't drift from {@link CandidateDatabase}.
45
+ out.exec("PRAGMA page_size=8192; PRAGMA journal_mode=OFF; PRAGMA synchronous=OFF; PRAGMA cache_size=-2000000;");
46
+ const kdb = new DatabaseClient({ database: out });
47
+ await createCandidateStagingTables(kdb);
48
+ // --- compact code maps (country/placetype → small int, shrinks the clustered key). The ids are
49
+ // assigned here; the rows are bulk-inserted via kdb once the passes have discovered every code. ---
50
+ const ccodes = new Map();
51
+ const ptcodes = new Map();
52
+ const ccId = (code) => {
53
+ const c = (code || "??").toUpperCase();
54
+ let id = ccodes.get(c);
55
+ if (id === undefined) {
56
+ id = ccodes.size;
57
+ ccodes.set(c, id);
58
+ }
59
+ return id;
60
+ };
61
+ const ptId = (pt) => {
62
+ const p = pt || "";
63
+ let id = ptcodes.get(p);
64
+ if (id === undefined) {
65
+ id = ptcodes.size;
66
+ ptcodes.set(p, id);
67
+ }
68
+ return id;
69
+ };
70
+ // --- region_id per place (its region-tier ancestor) for same-name disambiguation ---
71
+ progress("region", "loading region ancestry");
72
+ const regionOf = new Map();
73
+ for (const r of src.prepare("SELECT id, ancestor_id FROM ancestors WHERE ancestor_placetype='region'").iterate()) {
74
+ regionOf.set(Number(r.id), Number(r.ancestor_id));
75
+ }
76
+ progress("region", `${regionOf.size.toLocaleString()} places carry a region`);
77
+ // The hot path — millions of clustered rows. Kept a single positional prepared statement (the fastest
78
+ // node:sqlite insert) rather than a per-row query builder. Placeholders come from CANDIDATE_COLUMNS so
79
+ // the column COUNT can't drift; the positional run() args below MUST stay in CANDIDATE_COLUMNS order.
80
+ const insStage = out.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`);
81
+ // --- pass 1: primaries (and the per-place attrs the alias/abbrev passes reuse) ---
82
+ progress("primaries", "indexing place names");
83
+ const attrs = new Map();
84
+ let nPrim = 0;
85
+ out.exec("BEGIN");
86
+ for (const r of src
87
+ .prepare(`SELECT s.id AS id, s.name AS name, s.placetype AS placetype, s.country AS country,
88
+ s.latitude AS lat, s.longitude AS lon,
89
+ s.min_latitude AS mnlat, s.min_longitude AS mnlon, s.max_latitude AS mxlat, s.max_longitude AS mxlon,
90
+ COALESCE(pp.population,0) AS pop
91
+ FROM spr s LEFT JOIN place_population pp ON pp.id = s.id
92
+ WHERE s.is_current != 0 AND s.is_deprecated = 0`)
93
+ .iterate()) {
94
+ const sid = Number(r.id);
95
+ const cid = ccId(r.country);
96
+ const ptid = ptId(r.placetype);
97
+ const rid = regionOf.get(sid) ?? 0;
98
+ const pop = Number(r.pop) || 0;
99
+ const neg = -Math.log10(pop + 1);
100
+ const name = String(r.name ?? "");
101
+ const pkey = normalizeLocalityForKey(name);
102
+ const a = {
103
+ cid,
104
+ rid,
105
+ ptid,
106
+ name,
107
+ lat: r.lat,
108
+ lon: r.lon,
109
+ mnLat: r.mnlat,
110
+ mnLon: r.mnlon,
111
+ mxLat: r.mxlat,
112
+ mxLon: r.mxlon,
113
+ pop,
114
+ neg,
115
+ pkey,
116
+ };
117
+ attrs.set(sid, a);
118
+ if (pkey) {
119
+ insStage.run(pkey, cid, rid, ptid, neg, sid, name, a.lat, a.lon, a.mnLat, a.mnLon, a.mxLat, a.mxLon, pop, 1);
120
+ nPrim++;
121
+ }
122
+ }
123
+ out.exec("COMMIT");
124
+ progress("primaries", `${nPrim.toLocaleString()} primaries; ${attrs.size.toLocaleString()} places`);
125
+ const stageRow = (k, a, sid, isPrimary) => {
126
+ insStage.run(k, a.cid, a.rid, a.ptid, a.neg, sid, a.name, a.lat, a.lon, a.mnLat, a.mnLon, a.mxLat, a.mxLon, a.pop, isPrimary);
127
+ };
128
+ // --- pass 2: distinct normalized aliases from place_search.alt_names ---
129
+ progress("aliases", "exploding alias bags");
130
+ let nAlias = 0;
131
+ out.exec("BEGIN");
132
+ for (const r of src.prepare("SELECT wof_id, alt_names FROM place_search").iterate()) {
133
+ const a = attrs.get(Number(r.wof_id));
134
+ const alt = r.alt_names;
135
+ if (!a || !alt)
136
+ continue;
137
+ const seen = new Set([a.pkey]);
138
+ for (const piece of alt.split(ALIAS_SEP)) {
139
+ const k = normalizeLocalityForKey(piece);
140
+ if (!k || seen.has(k))
141
+ continue;
142
+ seen.add(k);
143
+ stageRow(k, a, Number(r.wof_id), 0);
144
+ nAlias++;
145
+ }
146
+ }
147
+ out.exec("COMMIT");
148
+ progress("aliases", `${nAlias.toLocaleString()} aliases`);
149
+ // --- pass 3: region abbreviations (place_abbr) ---
150
+ let nAbbr = 0;
151
+ out.exec("BEGIN");
152
+ for (const r of src.prepare("SELECT id, abbr FROM place_abbr").iterate()) {
153
+ const a = attrs.get(Number(r.id));
154
+ if (!a)
155
+ continue;
156
+ const k = normalizeLocalityForKey(String(r.abbr ?? ""));
157
+ if (!k)
158
+ continue;
159
+ stageRow(k, a, Number(r.id), 1);
160
+ nAbbr++;
161
+ }
162
+ out.exec("COMMIT");
163
+ progress("abbrevs", `${nAbbr.toLocaleString()} abbrevs`);
164
+ // --- pass 4: postcodes (separate shards: spr placetype='postalcode' with real coords) ---
165
+ let nPostcode = 0;
166
+ for (const pcDb of opts.postcodes ?? []) {
167
+ progress("postcodes", `reading ${pcDb}`);
168
+ const pc = new DatabaseSync(pcDb, { readOnly: true });
169
+ const pcPtid = ptId("postalcode");
170
+ out.exec("BEGIN");
171
+ for (const r of pc
172
+ .prepare(`SELECT id, name, country, latitude, longitude,
173
+ min_latitude AS mnlat, min_longitude AS mnlon, max_latitude AS mxlat, max_longitude AS mxlon
174
+ FROM spr WHERE placetype='postalcode' AND latitude != 0 AND longitude != 0`)
175
+ .iterate()) {
176
+ const name = String(r.name ?? "");
177
+ const key = normalizeLocalityForKey(name);
178
+ if (!key)
179
+ continue;
180
+ const lat = r.latitude;
181
+ const lon = r.longitude;
182
+ // region_id 0 (a postcode is unique by name+country — no same-name disambiguation); neg_rank 0
183
+ // (no population). bbox = the postcode's own min/max (falls back to the centroid point).
184
+ insStage.run(key, ccId(r.country), 0, pcPtid, 0, Number(r.id), name, lat, lon, r.mnlat || lat, r.mnlon || lon, r.mxlat || lat, r.mxlon || lon, 0, 1);
185
+ nPostcode++;
186
+ }
187
+ out.exec("COMMIT");
188
+ pc.close();
189
+ }
190
+ if (nPostcode > 0)
191
+ progress("postcodes", `${nPostcode.toLocaleString()} postcodes`);
192
+ // --- code dictionaries: typed batch inserts via kdb (a few hundred rows — Kysely is clean here) ---
193
+ if (ccodes.size > 0) {
194
+ await kdb
195
+ .insertInto("country_codes")
196
+ .values([...ccodes].map(([code, id]) => ({ id, code })))
197
+ .execute();
198
+ }
199
+ if (ptcodes.size > 0) {
200
+ await kdb
201
+ .insertInto("placetype_codes")
202
+ .values([...ptcodes].map(([placetype, id]) => ({ id, placetype })))
203
+ .execute();
204
+ }
205
+ // --- materialize the clustered WITHOUT ROWID table (sorted insert → contiguous leaves) ---
206
+ progress("cluster", "building clustered candidate table + VACUUM");
207
+ // Column list + clustered-key order are sourced from CANDIDATE_COLUMNS (the first 6 ARE the PRIMARY
208
+ // KEY) so the SELECT, the ORDER BY, and the table can't drift. The table comes from the shared
209
+ // createCandidateTable().
210
+ const cols = CANDIDATE_COLUMNS.join(", ");
211
+ const keyOrder = CANDIDATE_COLUMNS.slice(0, 6).join(", ");
212
+ await createCandidateTable(kdb);
213
+ // OR IGNORE: an abbrev/alias can normalize to a place's primary key (same place, same rank) → any one
214
+ // row. The bulk sorted INSERT…SELECT (clustered materialization) stays raw — a single hot bulk statement.
215
+ out.exec(`INSERT OR IGNORE INTO candidate (${cols}) SELECT ${cols} FROM cand_stage ORDER BY ${keyOrder};`);
216
+ await kdb.schema.dropTable("cand_stage").execute();
217
+ // page_size MUST be set right before VACUUM: node:sqlite initializes the file at the 4096 default on
218
+ // `new DatabaseSync`, so the creation-time pragma is a no-op — only a VACUUM rebuilds at the new size.
219
+ // 8192 matches the sql.js-httpvfs 64 KiB request chunk cleanly (8 pages) and shallows the B-tree.
220
+ out.exec("PRAGMA page_size=8192");
221
+ out.exec("VACUUM");
222
+ const { n: rows } = await kdb
223
+ .selectFrom("candidate")
224
+ .select((eb) => eb.fn.countAll().as("n"))
225
+ .executeTakeFirstOrThrow();
226
+ src.close();
227
+ await kdb.destroy(); // closes the underlying `out` connection
228
+ return { rows, places: attrs.size, primaries: nPrim, aliases: nAlias, abbrevs: nAbbr, postcodes: nPostcode };
229
+ }
230
+ //# sourceMappingURL=build-candidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-candidate.js","sourceRoot":"","sources":["../build-candidate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @copyright Sister Software
4
+ * @license AGPL-3.0
5
+ * @author Teffen Ellis, et al.
6
+ *
7
+ * `mailwoman-wof-build-coincident-roles <path-to-admin.db>... [--drop]`
8
+ *
9
+ * Operator-side one-shot CLI (#403, epic #402): derives the `coincident_roles` relation into an
10
+ * existing admin gazetteer — the dual-role places (city-states, capital-seat provinces,
11
+ * consolidated city-counties) the hierarchy-completion step (#405) consults. Additive +
12
+ * idempotent; re-run after refreshing `spr`/`ancestors`. Mirrors `build-fts-cli.ts`. Should also
13
+ * be invoked as a post-step of the main `scripts/build-unified-wof.ts`.
14
+ */
15
+ export declare function main(argv: readonly string[]): number;
16
+ //# sourceMappingURL=build-coincident-roles-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-coincident-roles-cli.d.ts","sourceRoot":"","sources":["../build-coincident-roles-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAuDH,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAqBpD"}