@mailwoman/resolver-wof-sqlite 8.0.0 → 8.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.
- package/address-point-interpolation.ts +9 -3
- package/address-point-schema.ts +32 -10
- package/address-point.ts +3 -0
- package/ancestry-backfill.ts +18 -5
- package/ancestry.ts +7 -2
- package/build-candidate.ts +29 -6
- package/build-slim.ts +40 -11
- package/candidate-fts.ts +1 -0
- package/candidate-lookup.ts +49 -15
- package/candidate-schema.ts +35 -11
- package/coincident-roles.ts +28 -6
- package/convention.ts +3 -1
- package/coverage-manifest-schema.ts +243 -0
- package/fst-autocomplete.ts +15 -9
- package/fst-builder.ts +65 -9
- package/fst-deserialize-web.ts +46 -9
- package/fst-matcher.ts +12 -5
- package/fst-serialize.ts +83 -9
- package/fst-types.ts +43 -0
- package/fts-query.ts +84 -0
- package/fts.ts +35 -9
- package/geo.ts +9 -3
- package/geonames-aliases.ts +116 -79
- package/geonames-postal.ts +25 -5
- package/index.ts +19 -0
- package/interpolation.ts +59 -55
- package/lookup.ts +103 -292
- package/name-score.ts +76 -0
- package/out/address-point-interpolation.d.ts.map +1 -1
- package/out/address-point-interpolation.js +4 -2
- package/out/address-point-interpolation.js.map +1 -1
- package/out/address-point-schema.d.ts +30 -10
- package/out/address-point-schema.d.ts.map +1 -1
- package/out/address-point-schema.js +6 -2
- package/out/address-point-schema.js.map +1 -1
- package/out/address-point.d.ts.map +1 -1
- package/out/address-point.js.map +1 -1
- package/out/ancestry-backfill.d.ts +6 -2
- package/out/ancestry-backfill.d.ts.map +1 -1
- package/out/ancestry-backfill.js +7 -3
- package/out/ancestry-backfill.js.map +1 -1
- package/out/ancestry.d.ts +6 -2
- package/out/ancestry.d.ts.map +1 -1
- package/out/ancestry.js +3 -1
- package/out/ancestry.js.map +1 -1
- package/out/build-candidate.d.ts +9 -3
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +5 -3
- package/out/build-candidate.js.map +1 -1
- package/out/build-slim.d.ts +15 -5
- package/out/build-slim.d.ts.map +1 -1
- package/out/build-slim.js +11 -5
- package/out/build-slim.js.map +1 -1
- package/out/candidate-fts.d.ts.map +1 -1
- package/out/candidate-fts.js.map +1 -1
- package/out/candidate-lookup.d.ts +16 -3
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +27 -11
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +33 -11
- package/out/candidate-schema.d.ts.map +1 -1
- package/out/candidate-schema.js.map +1 -1
- package/out/coincident-roles.d.ts +16 -4
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +9 -3
- package/out/coincident-roles.js.map +1 -1
- package/out/convention.d.ts +3 -1
- package/out/convention.d.ts.map +1 -1
- package/out/convention.js.map +1 -1
- package/out/coverage-manifest-schema.d.ts +112 -0
- package/out/coverage-manifest-schema.d.ts.map +1 -0
- package/out/coverage-manifest-schema.js +154 -0
- package/out/coverage-manifest-schema.js.map +1 -0
- package/out/fst-autocomplete.d.ts +1 -1
- package/out/fst-autocomplete.d.ts.map +1 -1
- package/out/fst-autocomplete.js +11 -9
- package/out/fst-autocomplete.js.map +1 -1
- package/out/fst-builder.d.ts.map +1 -1
- package/out/fst-builder.js +42 -9
- package/out/fst-builder.js.map +1 -1
- package/out/fst-deserialize-web.d.ts.map +1 -1
- package/out/fst-deserialize-web.js +34 -9
- package/out/fst-deserialize-web.js.map +1 -1
- package/out/fst-matcher.d.ts +6 -2
- package/out/fst-matcher.d.ts.map +1 -1
- package/out/fst-matcher.js +9 -5
- package/out/fst-matcher.js.map +1 -1
- package/out/fst-serialize.d.ts.map +1 -1
- package/out/fst-serialize.js +62 -9
- package/out/fst-serialize.js.map +1 -1
- package/out/fst-types.d.ts +43 -0
- package/out/fst-types.d.ts.map +1 -1
- package/out/fts-query.d.ts +41 -0
- package/out/fts-query.d.ts.map +1 -0
- package/out/fts-query.js +75 -0
- package/out/fts-query.js.map +1 -0
- package/out/fts.d.ts +21 -7
- package/out/fts.d.ts.map +1 -1
- package/out/fts.js +10 -4
- package/out/fts.js.map +1 -1
- package/out/geo.d.ts +6 -2
- package/out/geo.d.ts.map +1 -1
- package/out/geo.js +3 -1
- package/out/geo.js.map +1 -1
- package/out/geonames-aliases.d.ts +12 -4
- package/out/geonames-aliases.d.ts.map +1 -1
- package/out/geonames-aliases.js +72 -67
- package/out/geonames-aliases.js.map +1 -1
- package/out/geonames-postal.d.ts +9 -3
- package/out/geonames-postal.d.ts.map +1 -1
- package/out/geonames-postal.js +7 -2
- package/out/geonames-postal.js.map +1 -1
- package/out/index.d.ts +2 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +1 -0
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +24 -6
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +32 -40
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +3 -97
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +52 -184
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +28 -0
- package/out/name-score.d.ts.map +1 -0
- package/out/name-score.js +67 -0
- package/out/name-score.js.map +1 -0
- package/out/poi-lookup.d.ts +24 -8
- package/out/poi-lookup.d.ts.map +1 -1
- package/out/poi-lookup.js +27 -13
- package/out/poi-lookup.js.map +1 -1
- package/out/poi-schema.d.ts +42 -13
- package/out/poi-schema.d.ts.map +1 -1
- package/out/poi-schema.js +12 -3
- package/out/poi-schema.js.map +1 -1
- package/out/postal-city-alias-lookup.d.ts +18 -6
- package/out/postal-city-alias-lookup.d.ts.map +1 -1
- package/out/postal-city-alias-lookup.js.map +1 -1
- package/out/postal-city-alias-schema.d.ts +27 -9
- package/out/postal-city-alias-schema.d.ts.map +1 -1
- package/out/postal-city-alias-schema.js +3 -1
- package/out/postal-city-alias-schema.js.map +1 -1
- package/out/postal-city-candidate-schema.d.ts +15 -5
- package/out/postal-city-candidate-schema.d.ts.map +1 -1
- package/out/postal-city-candidate-schema.js +3 -1
- package/out/postal-city-candidate-schema.js.map +1 -1
- package/out/postcode-point-lookup.d.ts +6 -2
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +6 -2
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/ranking-weights.d.ts +118 -0
- package/out/ranking-weights.d.ts.map +1 -0
- package/out/ranking-weights.js +44 -0
- package/out/ranking-weights.js.map +1 -0
- package/out/reverse.d.ts +9 -3
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +20 -6
- package/out/reverse.js.map +1 -1
- package/out/sharding.d.ts +3 -1
- package/out/sharding.d.ts.map +1 -1
- package/out/sharding.js +7 -5
- package/out/sharding.js.map +1 -1
- package/out/sqlite-convention-source.d.ts.map +1 -1
- package/out/sqlite-convention-source.js +3 -1
- package/out/sqlite-convention-source.js.map +1 -1
- package/out/street-centroid-schema.d.ts +33 -11
- package/out/street-centroid-schema.d.ts.map +1 -1
- package/out/street-centroid-schema.js +3 -1
- package/out/street-centroid-schema.js.map +1 -1
- package/out/street-centroid.d.ts.map +1 -1
- package/out/street-centroid.js +6 -2
- package/out/street-centroid.js.map +1 -1
- package/out/street-morphology-fst-builder.d.ts +6 -2
- package/out/street-morphology-fst-builder.d.ts.map +1 -1
- package/out/street-morphology-fst-builder.js +8 -7
- package/out/street-morphology-fst-builder.js.map +1 -1
- package/out/street-morphology-fst-loader.d.ts +67 -0
- package/out/street-morphology-fst-loader.d.ts.map +1 -0
- package/out/street-morphology-fst-loader.js +59 -0
- package/out/street-morphology-fst-loader.js.map +1 -0
- package/out/street-name-lookup.d.ts +9 -3
- package/out/street-name-lookup.d.ts.map +1 -1
- package/out/street-name-lookup.js +9 -7
- package/out/street-name-lookup.js.map +1 -1
- package/out/street-normalize.d.ts +3 -1
- package/out/street-normalize.d.ts.map +1 -1
- package/out/street-normalize.js +23 -13
- package/out/street-normalize.js.map +1 -1
- package/out/street-segment-schema.d.ts +68 -13
- package/out/street-segment-schema.d.ts.map +1 -1
- package/out/street-segment-schema.js +21 -2
- package/out/street-segment-schema.js.map +1 -1
- package/out/types.d.ts +18 -6
- package/out/types.d.ts.map +1 -1
- package/out/unified-schema.d.ts +1 -1
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +2 -2
- package/out/unified-schema.js.map +1 -1
- package/package.json +13 -5
- package/poi-lookup.ts +53 -21
- package/poi-schema.ts +43 -13
- package/postal-city-alias-lookup.ts +20 -6
- package/postal-city-alias-schema.ts +28 -9
- package/postal-city-candidate-schema.ts +15 -5
- package/postcode-point-lookup.ts +6 -2
- package/ranking-weights.ts +148 -0
- package/reverse.ts +47 -10
- package/sharding.ts +13 -6
- package/sqlite-convention-source.ts +4 -1
- package/street-centroid-schema.ts +35 -11
- package/street-centroid.ts +10 -3
- package/street-morphology-fst-builder.ts +25 -9
- package/street-morphology-fst-loader.ts +103 -0
- package/street-name-lookup.ts +19 -7
- package/street-normalize.ts +28 -13
- package/street-segment-schema.ts +83 -13
- package/types.ts +18 -6
- package/unified-schema.ts +11 -2
package/street-name-lookup.ts
CHANGED
|
@@ -39,9 +39,13 @@ function hasColumn(db: DatabaseSync, table: string, column: string): boolean {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface SQLiteStreetNameLookupOpts {
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* ISO-2 (upper-case) countries this index answers for. Default `["FR"]` (the BAN street-centroids instance).
|
|
44
|
+
*/
|
|
43
45
|
countries?: Iterable<string>
|
|
44
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Table name. Default `street_centroid`.
|
|
48
|
+
*/
|
|
45
49
|
table?: string
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -68,9 +72,11 @@ export class SQLiteStreetNameLookup implements StreetLocalityEvidence {
|
|
|
68
72
|
// `name_key` MUST match `foldStreetSurface` here (the fold-parity contract).
|
|
69
73
|
const keyCol = hasColumn(this.#db, table, "name_key") ? "name_key" : "street_norm"
|
|
70
74
|
this.#byName = this.#db.prepare(`SELECT 1 FROM ${table} WHERE ${keyCol} = ? LIMIT 1`)
|
|
75
|
+
|
|
71
76
|
this.#byNameLocality = this.#db.prepare(
|
|
72
77
|
`SELECT 1 FROM ${table} WHERE ${keyCol} = ? AND locality_base = ? LIMIT 1`
|
|
73
78
|
)
|
|
79
|
+
|
|
74
80
|
this.#byNamePostcode = this.#db.prepare(`SELECT 1 FROM ${table} WHERE ${keyCol} = ? AND postcode = ? LIMIT 1`)
|
|
75
81
|
}
|
|
76
82
|
}
|
|
@@ -83,18 +89,24 @@ export class SQLiteStreetNameLookup implements StreetLocalityEvidence {
|
|
|
83
89
|
|
|
84
90
|
// Scoped lookups tighten precision when the hypothesis carries a locality/postcode; a scoped MISS falls back to the
|
|
85
91
|
// unscoped probe (index incompleteness in the scope column is not evidence of absence — positive-evidence rule).
|
|
86
|
-
if (
|
|
87
|
-
|
|
92
|
+
if (
|
|
93
|
+
scope?.locality &&
|
|
94
|
+
this.#byNameLocality &&
|
|
95
|
+
this.#byNameLocality.get(norm, foldStreetSurface(scope.locality)) !== undefined
|
|
96
|
+
) {
|
|
97
|
+
return true
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
if (scope?.postcode && this.#byNamePostcode) {
|
|
91
|
-
|
|
100
|
+
if (scope?.postcode && this.#byNamePostcode && this.#byNamePostcode.get(norm, scope.postcode) !== undefined) {
|
|
101
|
+
return true
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
return this.#byName.get(norm) !== undefined
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Close the underlying handle.
|
|
109
|
+
*/
|
|
98
110
|
close(): void {
|
|
99
111
|
this.#db.close()
|
|
100
112
|
}
|
package/street-normalize.ts
CHANGED
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
|
|
25
25
|
import { AbbreviationToDirectional, US_STREET_SUFFIX_LOOKUP } from "@mailwoman/codex/us"
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Token count a street must exceed before its trailing pair is merged. At or below it the pair IS the whole street
|
|
29
|
+
* name, and merging would leave nothing to match on.
|
|
30
|
+
*/
|
|
31
|
+
const MIN_TOKENS_FOR_TAIL_MERGE = 3
|
|
32
|
+
|
|
27
33
|
/**
|
|
28
34
|
* Spelled ordinal street names → their digit-ordinal form ("tenth" → "10th"), applied ONLY when a street-type suffix
|
|
29
35
|
* follows (#723 admin-tail) — so the ordinal cross-streets common in grid cities ("Tenth Street", "Fifth Avenue") match
|
|
@@ -63,14 +69,16 @@ const SPELLED_ORDINAL_TO_DIGIT = new Map<string, string>([
|
|
|
63
69
|
["hundredth", "100th"],
|
|
64
70
|
])
|
|
65
71
|
|
|
66
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Lowercase + diacritic-fold + punctuation strip + whitespace collapse.
|
|
74
|
+
*/
|
|
67
75
|
function fold(input: string): string {
|
|
68
76
|
return input
|
|
69
77
|
.normalize("NFKD")
|
|
70
|
-
.
|
|
78
|
+
.replaceAll(/[̀-ͯ]/g, "")
|
|
71
79
|
.toLowerCase()
|
|
72
|
-
.
|
|
73
|
-
.
|
|
80
|
+
.replaceAll(/[.,'’]/g, "")
|
|
81
|
+
.replaceAll(/\s+/g, " ")
|
|
74
82
|
.trim()
|
|
75
83
|
}
|
|
76
84
|
|
|
@@ -81,7 +89,7 @@ function fold(input: string): string {
|
|
|
81
89
|
export function normalizeStreetForKey(street: string): string {
|
|
82
90
|
const tokens = fold(street).split(" ")
|
|
83
91
|
|
|
84
|
-
if (tokens.length
|
|
92
|
+
if (!tokens.length) return ""
|
|
85
93
|
|
|
86
94
|
// Spelled-ordinal street names → digit form when a street suffix follows ("Tenth Street" →
|
|
87
95
|
// "10th street", #723). Gated on the next token being a suffix so ordinal-WORD names are untouched.
|
|
@@ -99,6 +107,7 @@ export function normalizeStreetForKey(street: string): string {
|
|
|
99
107
|
// ("southeast"), and also merge an already-written two-token pair ("South East …").
|
|
100
108
|
const edgeDirectional = (raw: string) =>
|
|
101
109
|
AbbreviationToDirectional.get(raw.toUpperCase())?.toLowerCase().replace(" ", "")
|
|
110
|
+
|
|
102
111
|
const mergePair = (a?: string, b?: string) =>
|
|
103
112
|
a && b && /^(north|south)$/.test(a) && /^(east|west)$/.test(b) ? a + b : undefined
|
|
104
113
|
|
|
@@ -107,20 +116,21 @@ export function normalizeStreetForKey(street: string): string {
|
|
|
107
116
|
if (leadPair && tokens.length > 2) {
|
|
108
117
|
tokens.splice(0, 2, leadPair)
|
|
109
118
|
}
|
|
119
|
+
|
|
110
120
|
const first = edgeDirectional(tokens[0]!)
|
|
111
121
|
|
|
112
122
|
if (first && tokens.length > 1) {
|
|
113
123
|
tokens[0] = first
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
const tailPair = mergePair(tokens
|
|
126
|
+
const tailPair = mergePair(tokens.at(-2), tokens.at(-1))
|
|
117
127
|
|
|
118
|
-
if (tailPair && tokens.length >
|
|
119
|
-
tokens.splice(
|
|
128
|
+
if (tailPair && tokens.length > MIN_TOKENS_FOR_TAIL_MERGE) {
|
|
129
|
+
tokens.splice(-2, 2, tailPair)
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
if (tokens.length > 2) {
|
|
123
|
-
const last = edgeDirectional(tokens
|
|
133
|
+
const last = edgeDirectional(tokens.at(-1)!)
|
|
124
134
|
|
|
125
135
|
if (last) {
|
|
126
136
|
tokens[tokens.length - 1] = last
|
|
@@ -136,6 +146,7 @@ export function normalizeStreetForKey(street: string): string {
|
|
|
136
146
|
|
|
137
147
|
if (canonical) {
|
|
138
148
|
tokens[at] = canonical.toLowerCase()
|
|
149
|
+
|
|
139
150
|
break
|
|
140
151
|
}
|
|
141
152
|
}
|
|
@@ -196,9 +207,9 @@ export function normalizeStreetForKeyLocale(street: string, locale: StreetLocale
|
|
|
196
207
|
// hyphen ("Champs-Élysées", "St-Honoré") or a space — both sides fold identically, so this is pure
|
|
197
208
|
// robustness. It also splits a hyphenated abbreviation ("St-Honoré" → "st honore") into tokens the
|
|
198
209
|
// per-locale type/Saint map can see.
|
|
199
|
-
const tokens = fold(street).
|
|
210
|
+
const tokens = fold(street).replaceAll("ß", "ss").replaceAll("-", " ").split(/\s+/).filter(Boolean)
|
|
200
211
|
|
|
201
|
-
if (tokens.length
|
|
212
|
+
if (!tokens.length) return ""
|
|
202
213
|
|
|
203
214
|
switch (locale) {
|
|
204
215
|
case "fr":
|
|
@@ -229,7 +240,9 @@ export function normalizeStreetForKeyLocale(street: string, locale: StreetLocale
|
|
|
229
240
|
return tokens.join(" ")
|
|
230
241
|
}
|
|
231
242
|
|
|
232
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* Normalize a locality name for address-point keying (fold only — no street semantics).
|
|
245
|
+
*/
|
|
233
246
|
export function normalizeLocalityForKey(locality: string): string {
|
|
234
247
|
return fold(locality)
|
|
235
248
|
}
|
|
@@ -268,7 +281,9 @@ export function stripLocalityQualifier(locality: string): string {
|
|
|
268
281
|
|
|
269
282
|
if (s.includes("/")) {
|
|
270
283
|
s = s.split("/")[0]!.trim()
|
|
271
|
-
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// "Kraubath/Mur", "St.Kanzian/Klopeiner See"
|
|
272
287
|
s = s.replace(/\s+[a-zà-ÿ]\.\s*\S.*$/iu, "") // abbreviated " b.Graz" / " o.Bleiburg" / " a.d. …"
|
|
273
288
|
s = s.replace(/\s+(im|an der|ob|bei|in der|unter|vor)\s+\S.*$/iu, "") // " im Simmental", " bei Graz"
|
|
274
289
|
s = s.replace(/\s+(S|N|E|W|V|Ø|Sø|Fyn|Thy|Sjælland|Jylland|[A-ZÅÄÖ]{2})$/u, "") // " S", " VD", " Thy"
|
package/street-segment-schema.ts
CHANGED
|
@@ -23,34 +23,79 @@ import type { Kysely } from "kysely"
|
|
|
23
23
|
* `odd`/`even`/`mixed`.
|
|
24
24
|
*/
|
|
25
25
|
export interface StreetSegmentTable {
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Shared {@link normalizeStreetForKey} of the street — the build/query-consistent probe key.
|
|
28
|
+
*/
|
|
27
29
|
street_norm: string
|
|
28
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* `L` or `R` — the TIGER side the address range sits on.
|
|
32
|
+
*/
|
|
29
33
|
side: string
|
|
30
34
|
from_hn: number
|
|
31
35
|
to_hn: number
|
|
32
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Sorted lower bound of `(from_hn, to_hn)` — the probe filters `min_hn <= n <= max_hn`.
|
|
38
|
+
*/
|
|
33
39
|
min_hn: number
|
|
34
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Sorted upper bound of `(from_hn, to_hn)`.
|
|
42
|
+
*/
|
|
35
43
|
max_hn: number
|
|
36
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* `odd` | `even` | `mixed` — the house-number parity along the range.
|
|
46
|
+
*/
|
|
37
47
|
parity: string
|
|
38
48
|
postcode: string | null
|
|
39
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* 5-digit state+county FIPS the edge came from.
|
|
51
|
+
*/
|
|
40
52
|
county_fips: string
|
|
41
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* The street as it appeared in TIGER (kept for display / debugging).
|
|
55
|
+
*/
|
|
42
56
|
street_raw: string
|
|
43
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* GeoJSON LineString text (no SpatiaLite — read back with `JSON.parse`).
|
|
59
|
+
*/
|
|
44
60
|
geometry: string
|
|
45
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Provenance: the dataset this edge came from (e.g. `tiger:edges`).
|
|
63
|
+
*/
|
|
46
64
|
source: string
|
|
47
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* The pinned TIGER release the edge was ingested from.
|
|
67
|
+
*/
|
|
48
68
|
release: string
|
|
49
69
|
}
|
|
50
70
|
|
|
51
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* The shard's single-row calibration metadata (#374 doctrine, 2026-07-26): the conformal radius multiplier is a
|
|
73
|
+
* property of the CALIBRATION SET the artifact was built against — so it ships IN the artifact (the pair-index δ
|
|
74
|
+
* precedent, `neural/pair-index-resolver.ts`), not in caller code. Written once by the builder; read at open time by
|
|
75
|
+
* {@link StreetInterpolator}. Shards built before this table exists simply lack it — the reader degrades to `undefined`
|
|
76
|
+
* and callers fall back to the in-code per-region table (never patch shipped DBs — rebuild).
|
|
77
|
+
*/
|
|
78
|
+
export interface InterpCalibrationRow {
|
|
79
|
+
/**
|
|
80
|
+
* Conformal multiplier for the raw half-segment `uncertainty_m` radius (#374/#584) — ×Q̂ for a ~90% bound.
|
|
81
|
+
*/
|
|
82
|
+
radius_multiplier: number
|
|
83
|
+
/**
|
|
84
|
+
* Provenance of the multiplier (e.g. `split-conformal:2026-06-14`).
|
|
85
|
+
*/
|
|
86
|
+
method: string
|
|
87
|
+
/**
|
|
88
|
+
* The calibration-table key the multiplier was selected by (a USPS region code, or `default` for unmeasured).
|
|
89
|
+
*/
|
|
90
|
+
region: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The street-segment database schema for `new DatabaseClient<StreetSegmentDatabase>(...)`.
|
|
95
|
+
*/
|
|
52
96
|
export interface StreetSegmentDatabase {
|
|
53
97
|
street_segment: StreetSegmentTable
|
|
98
|
+
interp_calibration: InterpCalibrationRow
|
|
54
99
|
}
|
|
55
100
|
|
|
56
101
|
/**
|
|
@@ -73,7 +118,9 @@ export const STREET_SEGMENT_COLUMNS = [
|
|
|
73
118
|
"release",
|
|
74
119
|
] as const
|
|
75
120
|
|
|
76
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Create the `street_segment` table — called before the streaming bulk load.
|
|
123
|
+
*/
|
|
77
124
|
export async function createStreetSegmentTable(db: Kysely<StreetSegmentDatabase>): Promise<void> {
|
|
78
125
|
await db.schema
|
|
79
126
|
.createTable("street_segment")
|
|
@@ -93,12 +140,35 @@ export async function createStreetSegmentTable(db: Kysely<StreetSegmentDatabase>
|
|
|
93
140
|
.execute()
|
|
94
141
|
}
|
|
95
142
|
|
|
96
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Create + populate the single-row `interp_calibration` metadata table (see {@link InterpCalibrationRow}) — called once
|
|
145
|
+
* by the shard builder, after the value is selected from the calibration source of record. Build-time only (async
|
|
146
|
+
* Kysely is fine here); the READ side is the raw sync probe in {@link StreetInterpolator}'s constructor, per the
|
|
147
|
+
* sync-by-interface doctrine.
|
|
148
|
+
*/
|
|
149
|
+
export async function writeInterpCalibration(
|
|
150
|
+
db: Kysely<StreetSegmentDatabase>,
|
|
151
|
+
row: InterpCalibrationRow
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
await db.schema
|
|
154
|
+
.createTable("interp_calibration")
|
|
155
|
+
.addColumn("radius_multiplier", "real", (c) => c.notNull())
|
|
156
|
+
.addColumn("method", "text", (c) => c.notNull())
|
|
157
|
+
.addColumn("region", "text", (c) => c.notNull())
|
|
158
|
+
.execute()
|
|
159
|
+
|
|
160
|
+
await db.insertInto("interp_calibration").values(row).execute()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create the two probe indexes the reader relies on (postcode-scope, street-scope).
|
|
165
|
+
*/
|
|
97
166
|
export async function createStreetSegmentIndexes(db: Kysely<StreetSegmentDatabase>): Promise<void> {
|
|
98
167
|
await db.schema
|
|
99
168
|
.createIndex("idx_seg_postcode")
|
|
100
169
|
.on("street_segment")
|
|
101
170
|
.columns(["postcode", "street_norm", "min_hn"])
|
|
102
171
|
.execute()
|
|
172
|
+
|
|
103
173
|
await db.schema.createIndex("idx_seg_street").on("street_segment").columns(["street_norm", "min_hn"]).execute()
|
|
104
174
|
}
|
package/types.ts
CHANGED
|
@@ -49,7 +49,9 @@ export interface PlaceCandidate {
|
|
|
49
49
|
id: number
|
|
50
50
|
name: string
|
|
51
51
|
placetype: WOFPlacetype
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* ISO 3166-1 alpha-2 country code.
|
|
54
|
+
*/
|
|
53
55
|
country: string
|
|
54
56
|
lat: number
|
|
55
57
|
lon: number
|
|
@@ -125,9 +127,13 @@ export interface GeoBbox {
|
|
|
125
127
|
export interface FindPlaceQuery {
|
|
126
128
|
text: string
|
|
127
129
|
placetype?: WOFPlacetype | WOFPlacetype[]
|
|
128
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* ISO 3166-1 alpha-2 — narrows to one country.
|
|
132
|
+
*/
|
|
129
133
|
country?: string
|
|
130
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* WOF place id — narrows to descendants of this place.
|
|
136
|
+
*/
|
|
131
137
|
parentID?: number
|
|
132
138
|
/**
|
|
133
139
|
* Sibling postcode. When set on a `locality` query AND a `postcode_locality` table is present, triggers the
|
|
@@ -136,7 +142,9 @@ export interface FindPlaceQuery {
|
|
|
136
142
|
* postcode_locality shard is present.
|
|
137
143
|
*/
|
|
138
144
|
postcode?: string
|
|
139
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Proximity hint — candidates close to this point get a ranking boost.
|
|
147
|
+
*/
|
|
140
148
|
near?: GeoPoint & { maxDistanceKm?: number }
|
|
141
149
|
/**
|
|
142
150
|
* Ordered proximity-bias points (viewport center, user location, …), each optionally weighted (default 1.0, first
|
|
@@ -147,9 +155,13 @@ export interface FindPlaceQuery {
|
|
|
147
155
|
* back-compat.
|
|
148
156
|
*/
|
|
149
157
|
bias?: Array<GeoPoint & { weight?: number }>
|
|
150
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Bounding-box filter — only candidates whose bbox intersects this box are returned.
|
|
160
|
+
*/
|
|
151
161
|
bbox?: GeoBbox
|
|
152
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* Default 10.
|
|
164
|
+
*/
|
|
153
165
|
limit?: number
|
|
154
166
|
}
|
|
155
167
|
|
package/unified-schema.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `place_search` FTS5 + `place_bbox` R*Tree are built separately by `build-fts` (fts.ts).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { DatabaseSync } from "node:sqlite"
|
|
15
|
+
import type { DatabaseSync } from "node:sqlite"
|
|
16
16
|
|
|
17
17
|
import { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
18
18
|
|
|
@@ -109,11 +109,13 @@ export async function createUnifiedSchema(db: DatabaseSync): Promise<void> {
|
|
|
109
109
|
*/
|
|
110
110
|
export function populateAncestors(db: DatabaseSync): number {
|
|
111
111
|
db.exec("DELETE FROM ancestors")
|
|
112
|
+
|
|
112
113
|
const rows = db.prepare("SELECT id, parent_id, placetype FROM spr").all() as Array<{
|
|
113
114
|
id: number
|
|
114
115
|
parent_id: number
|
|
115
116
|
placetype: string
|
|
116
117
|
}>
|
|
118
|
+
|
|
117
119
|
const byID = new Map<number, { parent: number; placetype: string }>()
|
|
118
120
|
|
|
119
121
|
for (const r of rows) {
|
|
@@ -125,7 +127,9 @@ export function populateAncestors(db: DatabaseSync): number {
|
|
|
125
127
|
let count = 0
|
|
126
128
|
|
|
127
129
|
for (const r of rows) {
|
|
128
|
-
insert.run(r.id, r.id, r.placetype)
|
|
130
|
+
insert.run(r.id, r.id, r.placetype)
|
|
131
|
+
|
|
132
|
+
// self
|
|
129
133
|
count++
|
|
130
134
|
const seen = new Set<number>([r.id])
|
|
131
135
|
let cur = r.parent_id
|
|
@@ -135,11 +139,13 @@ export function populateAncestors(db: DatabaseSync): number {
|
|
|
135
139
|
|
|
136
140
|
if (!node) break
|
|
137
141
|
insert.run(r.id, cur, node.placetype)
|
|
142
|
+
|
|
138
143
|
count++
|
|
139
144
|
seen.add(cur)
|
|
140
145
|
cur = node.parent
|
|
141
146
|
}
|
|
142
147
|
}
|
|
148
|
+
|
|
143
149
|
db.exec("COMMIT")
|
|
144
150
|
|
|
145
151
|
return count
|
|
@@ -152,18 +158,21 @@ export async function createUnifiedIndexes(db: DatabaseSync): Promise<void> {
|
|
|
152
158
|
await kdb.schema.createIndex("spr_by_parent").ifNotExists().on("spr").column("parent_id").execute()
|
|
153
159
|
await kdb.schema.createIndex("names_by_id").ifNotExists().on("names").column("id").execute()
|
|
154
160
|
await kdb.schema.createIndex("names_by_name").ifNotExists().on("names").column("name").execute()
|
|
161
|
+
|
|
155
162
|
await kdb.schema
|
|
156
163
|
.createIndex("concordances_by_id")
|
|
157
164
|
.ifNotExists()
|
|
158
165
|
.on("concordances")
|
|
159
166
|
.columns(["id", "lastmodified"])
|
|
160
167
|
.execute()
|
|
168
|
+
|
|
161
169
|
await kdb.schema
|
|
162
170
|
.createIndex("concordances_by_other_id")
|
|
163
171
|
.ifNotExists()
|
|
164
172
|
.on("concordances")
|
|
165
173
|
.columns(["other_source", "other_id"])
|
|
166
174
|
.execute()
|
|
175
|
+
|
|
167
176
|
// ancestor_id is the hot column (parent-constraint queries `WHERE ancestor_id = ?`); id supports
|
|
168
177
|
// the reverse lookup.
|
|
169
178
|
await kdb.schema.createIndex("ancestors_by_ancestor").ifNotExists().on("ancestors").column("ancestor_id").execute()
|