@mailwoman/resolver-wof-sqlite 9.3.0 → 10.0.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/lib/address/index.ts +3 -3
- package/lib/address/{point.ts → point/index.ts} +55 -7
- package/lib/address/{point-interpolation.ts → point/interpolation.ts} +2 -3
- package/lib/ancestry/backfill.ts +4 -2
- package/lib/ancestry/index.ts +1 -2
- package/lib/{build-candidate.ts → build/candidate.ts} +62 -31
- package/lib/candidate/alias-bags.ts +68 -7
- package/lib/{candidate-ancestors-schema.ts → candidate/ancestors/schema.ts} +1 -1
- package/lib/candidate/{ancestors-sidecar.ts → ancestors/sidecar.ts} +2 -2
- package/lib/candidate/extract-fold.ts +91 -10
- package/lib/{candidate-importance.ts → candidate/importance.ts} +1 -1
- package/lib/{candidate-lookup.ts → candidate/lookup.ts} +13 -16
- package/lib/candidate/name-roles.ts +2 -2
- package/lib/candidate/own-name.ts +1 -0
- package/lib/{candidate-schema.ts → candidate/schema.ts} +2 -2
- package/lib/{candidate-scoring.ts → candidate/scoring.ts} +1 -1
- package/lib/coincident-roles.ts +2 -3
- package/lib/coverage-manifest-schema.ts +2 -1
- package/lib/currency-backfill.ts +2 -2
- package/lib/fst/builder.ts +1 -1
- package/lib/fst/deserialize-web.ts +2 -2
- package/lib/fst/freshness.ts +0 -1
- package/lib/fst/serialize.ts +9 -9
- package/lib/geonames/aliases.ts +1 -7
- package/lib/geonames/postal.ts +1 -7
- package/lib/index.ts +18 -25
- package/lib/interpolation.ts +1 -1
- package/lib/lookup.ts +6 -5
- package/lib/place-importance-schema.ts +1 -9
- package/lib/poi/lookup.ts +1 -2
- package/lib/postal/{city-alias-lookup.ts → city/alias/lookup.ts} +1 -1
- package/lib/postal/index.ts +3 -3
- package/lib/primary-preference.ts +15 -5
- package/lib/reverse.ts +1 -2
- package/lib/search-fetch.ts +1 -2
- package/lib/{sqlite-utils.ts → sqlite/utils.ts} +0 -1
- package/lib/street/{centroid.ts → centroid/index.ts} +2 -2
- package/lib/street/index.ts +4 -4
- package/lib/street/{morphology-fst-builder.ts → morphology/fst/builder.ts} +8 -2
- package/lib/street/{morphology-fst-loader.ts → morphology/fst/loader.ts} +1 -1
- package/lib/street/normalize.ts +42 -1
- package/lib/uprn/lookup.ts +1 -2
- package/lib/weights-overlay-linker.ts +5 -4
- package/out/address/index.d.ts +3 -3
- package/out/address/index.d.ts.map +1 -1
- package/out/address/index.js +3 -3
- package/out/address/index.js.map +1 -1
- package/out/address/{point.d.ts → point/index.d.ts} +5 -3
- package/out/address/point/index.d.ts.map +1 -0
- package/out/address/{point.js → point/index.js} +38 -7
- package/out/address/point/index.js.map +1 -0
- package/out/address/{point-interpolation.d.ts → point/interpolation.d.ts} +3 -3
- package/out/address/point/interpolation.d.ts.map +1 -0
- package/out/address/{point-interpolation.js → point/interpolation.js} +1 -1
- package/out/address/point/interpolation.js.map +1 -0
- package/out/address/{point-schema.d.ts → point/schema.d.ts} +1 -1
- package/out/address/point/schema.d.ts.map +1 -0
- package/out/address/{point-schema.js → point/schema.js} +1 -1
- package/out/address/point/schema.js.map +1 -0
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +4 -2
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry/index.d.ts.map +1 -1
- package/out/ancestry/index.js +1 -1
- package/out/ancestry/index.js.map +1 -1
- package/out/{build-candidate.d.ts → build/candidate.d.ts} +12 -4
- package/out/build/candidate.d.ts.map +1 -0
- package/out/{build-candidate.js → build/candidate.js} +39 -24
- package/out/build/candidate.js.map +1 -0
- package/out/{build-slim.d.ts → build/slim.d.ts} +1 -1
- package/out/build/slim.d.ts.map +1 -0
- package/out/{build-slim.js → build/slim.js} +1 -1
- package/out/build/slim.js.map +1 -0
- package/out/candidate/alias-bags.d.ts +10 -2
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +49 -5
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/{candidate-ancestors-schema.d.ts → candidate/ancestors/schema.d.ts} +2 -2
- package/out/candidate/ancestors/schema.d.ts.map +1 -0
- package/out/{candidate-ancestors-schema.js → candidate/ancestors/schema.js} +1 -1
- package/out/candidate/ancestors/schema.js.map +1 -0
- package/out/candidate/{ancestors-sidecar.d.ts → ancestors/sidecar.d.ts} +2 -2
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -0
- package/out/candidate/{ancestors-sidecar.js → ancestors/sidecar.js} +2 -2
- package/out/candidate/ancestors/sidecar.js.map +1 -0
- package/out/candidate/extract-fold.d.ts +12 -3
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +57 -8
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/{candidate-fts.d.ts → candidate/fts.d.ts} +1 -1
- package/out/candidate/fts.d.ts.map +1 -0
- package/out/{candidate-fts.js → candidate/fts.js} +1 -1
- package/out/candidate/fts.js.map +1 -0
- package/out/{candidate-importance.d.ts → candidate/importance.d.ts} +1 -1
- package/out/candidate/importance.d.ts.map +1 -0
- package/out/{candidate-importance.js → candidate/importance.js} +1 -1
- package/out/candidate/importance.js.map +1 -0
- package/out/{candidate-lookup.d.ts → candidate/lookup.d.ts} +7 -7
- package/out/candidate/lookup.d.ts.map +1 -0
- package/out/{candidate-lookup.js → candidate/lookup.js} +13 -11
- package/out/candidate/lookup.js.map +1 -0
- package/out/candidate/name-roles.d.ts +1 -1
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +1 -1
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +1 -0
- package/out/candidate/own-name.js.map +1 -1
- package/out/{candidate-schema.d.ts → candidate/schema.d.ts} +3 -3
- package/out/candidate/schema.d.ts.map +1 -0
- package/out/{candidate-schema.js → candidate/schema.js} +2 -2
- package/out/candidate/schema.js.map +1 -0
- package/out/{candidate-scoring.d.ts → candidate/scoring.d.ts} +1 -1
- package/out/candidate/scoring.d.ts.map +1 -0
- package/out/{candidate-scoring.js → candidate/scoring.js} +2 -2
- package/out/candidate/scoring.js.map +1 -0
- package/out/capitals.d.ts.map +1 -1
- package/out/coincident-roles.d.ts +1 -1
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +1 -1
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/index.d.ts.map +1 -1
- package/out/convention/index.js.map +1 -1
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +2 -1
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +2 -2
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/env.d.ts +1 -1
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +1 -1
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.js +2 -2
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/freshness.d.ts +0 -1
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +0 -1
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/serialize.js +9 -9
- package/out/fst/serialize.js.map +1 -1
- package/out/fts/index.js.map +1 -1
- package/out/geonames/aliases.d.ts +0 -5
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +1 -6
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +0 -6
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +1 -6
- package/out/geonames/postal.js.map +1 -1
- package/out/index.d.ts +18 -19
- package/out/index.d.ts.map +1 -1
- package/out/index.js +13 -14
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +1 -1
- package/out/interpolation.d.ts.map +1 -1
- package/out/lookup.d.ts +2 -2
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +4 -4
- package/out/lookup.js.map +1 -1
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/place-importance-schema.d.ts +0 -1
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +1 -2
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +1 -1
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts.map +1 -1
- package/out/postal/{city-alias-lookup.d.ts → city/alias/lookup.d.ts} +2 -2
- package/out/postal/city/alias/lookup.d.ts.map +1 -0
- package/out/postal/{city-alias-lookup.js → city/alias/lookup.js} +1 -1
- package/out/postal/city/alias/lookup.js.map +1 -0
- package/out/postal/{city-alias-schema.d.ts → city/alias/schema.d.ts} +1 -1
- package/out/postal/city/alias/schema.d.ts.map +1 -0
- package/out/postal/{city-alias-schema.js → city/alias/schema.js} +1 -1
- package/out/postal/city/alias/schema.js.map +1 -0
- package/out/postal/{city-candidate-schema.d.ts → city/candidate-schema.d.ts} +1 -1
- package/out/postal/city/candidate-schema.d.ts.map +1 -0
- package/out/postal/{city-candidate-schema.js → city/candidate-schema.js} +1 -1
- package/out/postal/city/candidate-schema.js.map +1 -0
- package/out/postal/index.d.ts +3 -3
- package/out/postal/index.js +3 -3
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/primary-preference.d.ts +3 -3
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +12 -4
- package/out/primary-preference.js.map +1 -1
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +1 -1
- package/out/reverse.js.map +1 -1
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +1 -1
- package/out/search-fetch.js.map +1 -1
- package/out/{sqlite-convention-source.d.ts → sqlite/convention-source.d.ts} +1 -1
- package/out/sqlite/convention-source.d.ts.map +1 -0
- package/out/{sqlite-convention-source.js → sqlite/convention-source.js} +1 -1
- package/out/sqlite/convention-source.js.map +1 -0
- package/out/{sqlite-utils.d.ts → sqlite/utils.d.ts} +1 -2
- package/out/sqlite/utils.d.ts.map +1 -0
- package/out/{sqlite-utils.js → sqlite/utils.js} +1 -4
- package/out/sqlite/utils.js.map +1 -0
- package/out/street/{centroid.d.ts → centroid/index.d.ts} +2 -2
- package/out/street/centroid/index.d.ts.map +1 -0
- package/out/street/{centroid.js → centroid/index.js} +1 -1
- package/out/street/centroid/index.js.map +1 -0
- package/out/street/{centroid-schema.d.ts → centroid/schema.d.ts} +1 -1
- package/out/street/centroid/schema.d.ts.map +1 -0
- package/out/street/{centroid-schema.js → centroid/schema.js} +1 -1
- package/out/street/centroid/schema.js.map +1 -0
- package/out/street/index.d.ts +4 -4
- package/out/street/index.d.ts.map +1 -1
- package/out/street/index.js +4 -4
- package/out/street/index.js.map +1 -1
- package/out/street/{morphology-fst-builder.d.ts → morphology/fst/builder.d.ts} +1 -1
- package/out/street/morphology/fst/builder.d.ts.map +1 -0
- package/out/street/{morphology-fst-builder.js → morphology/fst/builder.js} +8 -3
- package/out/street/morphology/fst/builder.js.map +1 -0
- package/out/street/{morphology-fst-loader.d.ts → morphology/fst/loader.d.ts} +1 -1
- package/out/street/morphology/fst/loader.d.ts.map +1 -0
- package/out/street/{morphology-fst-loader.js → morphology/fst/loader.js} +2 -2
- package/out/street/morphology/fst/loader.js.map +1 -0
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +13 -1
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +37 -0
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +1 -1
- package/out/uprn/lookup.js.map +1 -1
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +4 -3
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +64 -64
- package/out/address/point-interpolation.d.ts.map +0 -1
- package/out/address/point-interpolation.js.map +0 -1
- package/out/address/point-schema.d.ts.map +0 -1
- package/out/address/point-schema.js.map +0 -1
- package/out/address/point.d.ts.map +0 -1
- package/out/address/point.js.map +0 -1
- package/out/build-candidate.d.ts.map +0 -1
- package/out/build-candidate.js.map +0 -1
- package/out/build-slim.d.ts.map +0 -1
- package/out/build-slim.js.map +0 -1
- package/out/candidate/ancestors-sidecar.d.ts.map +0 -1
- package/out/candidate/ancestors-sidecar.js.map +0 -1
- package/out/candidate-ancestors-schema.d.ts.map +0 -1
- package/out/candidate-ancestors-schema.js.map +0 -1
- package/out/candidate-fts.d.ts.map +0 -1
- package/out/candidate-fts.js.map +0 -1
- package/out/candidate-importance.d.ts.map +0 -1
- package/out/candidate-importance.js.map +0 -1
- package/out/candidate-lookup.d.ts.map +0 -1
- package/out/candidate-lookup.js.map +0 -1
- package/out/candidate-schema.d.ts.map +0 -1
- package/out/candidate-schema.js.map +0 -1
- package/out/candidate-scoring.d.ts.map +0 -1
- package/out/candidate-scoring.js.map +0 -1
- package/out/postal/city-alias-lookup.d.ts.map +0 -1
- package/out/postal/city-alias-lookup.js.map +0 -1
- package/out/postal/city-alias-schema.d.ts.map +0 -1
- package/out/postal/city-alias-schema.js.map +0 -1
- package/out/postal/city-candidate-schema.d.ts.map +0 -1
- package/out/postal/city-candidate-schema.js.map +0 -1
- package/out/sqlite-convention-source.d.ts.map +0 -1
- package/out/sqlite-convention-source.js.map +0 -1
- package/out/sqlite-utils.d.ts.map +0 -1
- package/out/sqlite-utils.js.map +0 -1
- package/out/street/centroid-schema.d.ts.map +0 -1
- package/out/street/centroid-schema.js.map +0 -1
- package/out/street/centroid.d.ts.map +0 -1
- package/out/street/centroid.js.map +0 -1
- package/out/street/morphology-fst-builder.d.ts.map +0 -1
- package/out/street/morphology-fst-builder.js.map +0 -1
- package/out/street/morphology-fst-loader.d.ts.map +0 -1
- package/out/street/morphology-fst-loader.js.map +0 -1
- /package/lib/address/{point-schema.ts → point/schema.ts} +0 -0
- /package/lib/{build-slim.ts → build/slim.ts} +0 -0
- /package/lib/{candidate-fts.ts → candidate/fts.ts} +0 -0
- /package/lib/postal/{city-alias-schema.ts → city/alias/schema.ts} +0 -0
- /package/lib/postal/{city-candidate-schema.ts → city/candidate-schema.ts} +0 -0
- /package/lib/{sqlite-convention-source.ts → sqlite/convention-source.ts} +0 -0
- /package/lib/street/{centroid-schema.ts → centroid/schema.ts} +0 -0
package/lib/address/index.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export * from "#address/point"
|
|
8
|
-
export * from "#address/point
|
|
9
|
-
export * from "#address/point
|
|
7
|
+
export * from "#address/point/index"
|
|
8
|
+
export * from "#address/point/interpolation"
|
|
9
|
+
export * from "#address/point/schema"
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
* scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/resolver"
|
|
18
|
+
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
|
|
19
19
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
20
20
|
|
|
21
|
-
import type { AddressPointDatabase, AddressPointTable } from "#address/point
|
|
21
|
+
import type { AddressPointDatabase, AddressPointTable } from "#address/point/schema"
|
|
22
22
|
import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
|
|
23
23
|
import {
|
|
24
|
+
normalizeHouseNumberForKey,
|
|
24
25
|
normalizeLocalityForKey,
|
|
26
|
+
normalizeLocalityForKeyLocale,
|
|
25
27
|
type NameKey,
|
|
26
28
|
type StreetKey,
|
|
27
29
|
streetKeyVariants,
|
|
@@ -53,6 +55,13 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
53
55
|
| PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
|
|
54
56
|
| undefined
|
|
55
57
|
readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
|
|
58
|
+
/**
|
|
59
|
+
* The scope key matched by its TAIL — a `zh` query that names the 鄉鎮市區 without its 縣市. Narrowed by the (street,
|
|
60
|
+
* number) index first, so the LIKE walks the handful of rows that share the pair.
|
|
61
|
+
*/
|
|
62
|
+
readonly #byLocalityTail:
|
|
63
|
+
| PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
|
|
64
|
+
| undefined
|
|
56
65
|
readonly #byBbox:
|
|
57
66
|
| PreparedGet<
|
|
58
67
|
[street: StreetKey, number: string, minLat: number, maxLat: number, minLon: number, maxLon: number],
|
|
@@ -102,6 +111,12 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
102
111
|
`SELECT ${SELECT_COLS} FROM address_point
|
|
103
112
|
WHERE street_norm = ? AND number = ? AND lat BETWEEN ? AND ? AND lon BETWEEN ? AND ? LIMIT 1`
|
|
104
113
|
)
|
|
114
|
+
|
|
115
|
+
this.#byLocalityTail = prepareGet(
|
|
116
|
+
this.#db,
|
|
117
|
+
`SELECT ${SELECT_COLS} FROM address_point
|
|
118
|
+
WHERE street_norm = ? AND number = ? AND locality_norm LIKE ? LIMIT 1`
|
|
119
|
+
)
|
|
105
120
|
}
|
|
106
121
|
}
|
|
107
122
|
|
|
@@ -110,20 +125,33 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
110
125
|
number: string
|
|
111
126
|
postcode?: string
|
|
112
127
|
locality?: string
|
|
128
|
+
region?: string
|
|
129
|
+
subregion?: string
|
|
113
130
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
114
131
|
}): AddressPointHit | null {
|
|
115
132
|
if (!this.#byPostcode || !this.#byLocality || !this.#byBbox) return null
|
|
116
|
-
const number = query.number
|
|
133
|
+
const number = normalizeHouseNumberForKey(query.number, this.#locale)
|
|
117
134
|
|
|
118
135
|
if (!number) return null
|
|
119
136
|
|
|
137
|
+
// A `zh` extract scopes a point by 縣市 + 鄉鎮市區, the pair the parse tags `region` + `subregion`; the Taiwanese
|
|
138
|
+
// register carries no postcode and the parse no `locality`. The pair becomes the locality key here, on the
|
|
139
|
+
// reader that was built with the same fold, and a Latin extract never sees the two fields. A line that names
|
|
140
|
+
// only the 鄉鎮市區 (`中和區中興街281號`, 14.7% of the TW board) matches the stored pair by its tail instead.
|
|
141
|
+
const scoped =
|
|
142
|
+
this.#locale === "zh" && !query.locality && query.subregion
|
|
143
|
+
? query.region
|
|
144
|
+
? { ...query, locality: `${query.region}${query.subregion}` }
|
|
145
|
+
: { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
|
|
146
|
+
: query
|
|
147
|
+
|
|
120
148
|
// Key-variant ladder (see `streetKeyVariants`): the literal key first, then the doubled-type
|
|
121
149
|
// collapse and the saint↔st register swap — each variant runs the FULL number ladder below, and
|
|
122
150
|
// the first variant to answer wins, so an attested literal key is never second-guessed.
|
|
123
151
|
let row: AddressPointRow | undefined
|
|
124
152
|
|
|
125
153
|
for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
|
|
126
|
-
row = this.#findForKey(streetNorm, number,
|
|
154
|
+
row = this.#findForKey(streetNorm, number, scoped)
|
|
127
155
|
|
|
128
156
|
if (row) break
|
|
129
157
|
}
|
|
@@ -152,6 +180,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
152
180
|
number: string
|
|
153
181
|
postcode?: string
|
|
154
182
|
locality?: string
|
|
183
|
+
localityTail?: NameKey
|
|
155
184
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
156
185
|
}
|
|
157
186
|
) {
|
|
@@ -186,6 +215,19 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
186
215
|
}
|
|
187
216
|
}
|
|
188
217
|
|
|
218
|
+
// Sub-number fallback for the Taiwanese register: `14之12` is building 12 off number 14, `30附40` an attached
|
|
219
|
+
// number, stored as number `14` / `30` with the rest in `unit`. A query that kept the pair on the number span
|
|
220
|
+
// falls to the base number — the same adjacent-parcel approximation the letter-suffix rung makes, priced the
|
|
221
|
+
// same: on the 2,000-row served read the rows answered this way sit a median 256 m and at most 1.0 km from the
|
|
222
|
+
// building.
|
|
223
|
+
if (!row && this.#locale === "zh") {
|
|
224
|
+
const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
|
|
225
|
+
|
|
226
|
+
if (base) {
|
|
227
|
+
row = this.#probe(streetNorm, base, query)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
189
231
|
return row
|
|
190
232
|
}
|
|
191
233
|
|
|
@@ -199,6 +241,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
199
241
|
query: {
|
|
200
242
|
postcode?: string
|
|
201
243
|
locality?: string
|
|
244
|
+
localityTail?: NameKey
|
|
202
245
|
bbox?: { minLat: number; maxLat: number; minLon: number; maxLon: number }
|
|
203
246
|
}
|
|
204
247
|
): AddressPointRow | undefined {
|
|
@@ -226,6 +269,11 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
226
269
|
row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
|
|
227
270
|
}
|
|
228
271
|
|
|
272
|
+
if (!row && query.localityTail && this.#byLocalityTail) {
|
|
273
|
+
// A Han key carries no `%` or `_`, so the tail is a literal suffix pattern.
|
|
274
|
+
row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
|
|
275
|
+
}
|
|
276
|
+
|
|
229
277
|
// Bbox fall-through (#247): the point carries no postcode/locality of its own, but its coordinate falls
|
|
230
278
|
// inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
|
|
231
279
|
if (!row && query.bbox) {
|
|
@@ -248,9 +296,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
248
296
|
* 13e Arrondissement" and "Paris" both hit; a no-op for every other locale.
|
|
249
297
|
*/
|
|
250
298
|
#localityKey(locality: string): NameKey {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
299
|
+
if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
|
|
300
|
+
|
|
301
|
+
return normalizeLocalityForKeyLocale(locality, this.#locale)
|
|
254
302
|
}
|
|
255
303
|
|
|
256
304
|
/**
|
|
@@ -33,15 +33,14 @@
|
|
|
33
33
|
* Standalone like the segment tier — core wiring rides the Phase 2 ordered `spatialTiers` list.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import type { InterpolationLookup } from "@mailwoman/resolver"
|
|
36
|
+
import type { InterpolationLookup } from "@mailwoman/core/resolver"
|
|
37
37
|
import { haversineKm } from "@mailwoman/spatial"
|
|
38
38
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
39
39
|
|
|
40
|
-
import type { AddressPointDatabase } from "#address/point
|
|
40
|
+
import type { AddressPointDatabase } from "#address/point/schema"
|
|
41
41
|
import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#interpolation"
|
|
42
42
|
import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
|
|
43
43
|
import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
|
|
44
|
-
|
|
45
44
|
/**
|
|
46
45
|
* Extrapolation cap for a single-sided bracket: at most one pair-span beyond the nearest known point (`t = 2`). Past
|
|
47
46
|
* it, the two-point line carries no evidence about the query number.
|
package/lib/ancestry/backfill.ts
CHANGED
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
* nothing.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
import { readDirectoryEntries } from "@mailwoman/core/fs/readers"
|
|
49
48
|
import { readWOFFeature } from "@mailwoman/core/resources/whosonfirst"
|
|
50
49
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
51
50
|
import { join, resolvePath, type PathBuilderLike } from "path-ts"
|
|
51
|
+
import { Globerator } from "spliterator/node/fs"
|
|
52
52
|
|
|
53
53
|
import type { WOFDatabase } from "#schema"
|
|
54
54
|
|
|
@@ -88,7 +88,9 @@ export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promis
|
|
|
88
88
|
let names: string[]
|
|
89
89
|
|
|
90
90
|
try {
|
|
91
|
-
names = (await
|
|
91
|
+
names = (await Globerator.from("*", { cwd: dir, withFileTypes: true, onlyFiles: false }).toArray())
|
|
92
|
+
.filter((entry) => entry.isDirectory())
|
|
93
|
+
.map((entry) => entry.name)
|
|
92
94
|
} catch {
|
|
93
95
|
return
|
|
94
96
|
}
|
package/lib/ancestry/index.ts
CHANGED
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
* geocoding always does).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
18
19
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
19
20
|
|
|
20
|
-
import { allRows } from "#sqlite-utils"
|
|
21
|
-
|
|
22
21
|
/**
|
|
23
22
|
* WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve (continent, empire, …)
|
|
24
23
|
* map to 0 and sort last. NOT the same table as the FST's `PLACETYPE_ORDER` (fst-serialize.ts) — that one is a
|
|
@@ -44,20 +44,20 @@ import { pathExists } from "@mailwoman/core/fs/readers"
|
|
|
44
44
|
import { removePath } from "@mailwoman/core/fs/writers"
|
|
45
45
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
46
46
|
|
|
47
|
-
import { createCandidateFTS } from "#candidate-fts"
|
|
48
|
-
import { IMPORTANCE_JOIN_RADIUS_KM, loadImportanceIndex } from "#candidate-importance"
|
|
49
|
-
import {
|
|
50
|
-
CANDIDATE_COLUMNS,
|
|
51
|
-
createCandidateStagingTables,
|
|
52
|
-
createCandidateTable,
|
|
53
|
-
type CandidateDatabase,
|
|
54
|
-
} from "#candidate-schema"
|
|
55
47
|
import { explodeAliasBags } from "#candidate/alias-bags"
|
|
56
|
-
import { buildAncestorsSidecar } from "#candidate/ancestors
|
|
48
|
+
import { buildAncestorsSidecar } from "#candidate/ancestors/sidecar"
|
|
57
49
|
import { stageCountryDisplayNames } from "#candidate/country-display-names"
|
|
58
50
|
import { foldExtract } from "#candidate/extract-fold"
|
|
51
|
+
import { createCandidateFTS } from "#candidate/fts"
|
|
52
|
+
import { IMPORTANCE_JOIN_RADIUS_KM, loadImportanceIndex } from "#candidate/importance"
|
|
59
53
|
import { GLOSS_KEY_THRESHOLD, stampNameRoles } from "#candidate/name-roles"
|
|
60
54
|
import type { PlaceAttrs } from "#candidate/place-attrs"
|
|
55
|
+
import {
|
|
56
|
+
CANDIDATE_COLUMNS,
|
|
57
|
+
createCandidateStagingTables,
|
|
58
|
+
createCandidateTable,
|
|
59
|
+
type CandidateDatabase,
|
|
60
|
+
} from "#candidate/schema"
|
|
61
61
|
import { createCapitalTable } from "#capital-schema"
|
|
62
62
|
import type { CapitalPoint } from "#capitals"
|
|
63
63
|
import { resurrectCurrencyHoles } from "#currency-backfill"
|
|
@@ -97,9 +97,11 @@ export interface BuildCandidateOptions {
|
|
|
97
97
|
/**
|
|
98
98
|
* Optional LOCALITY extracts (`spr` rows with `placetype='locality'` + real coords, e.g. localities-nz-linz.db — the
|
|
99
99
|
* #1564 NZ suburb tier) — folded through the same extract loop as the postcode extracts, staged as `locality`
|
|
100
|
-
* candidate rows with
|
|
101
|
-
*
|
|
102
|
-
* `
|
|
100
|
+
* candidate rows with UNMEASURED population (`neg_rank 0`: a extract row ranks behind any populated namesake and wins
|
|
101
|
+
* only where its key is the answer). Each extract's `names` table folds as aliases, `is_primary = 0`, same as the
|
|
102
|
+
* delivery-city pass. An extract whose `ancestors` table names an admin region for a row gives that row the region's
|
|
103
|
+
* scope (`region_id`) plus closure rows for the region and the region's own chain above it; an extract without one
|
|
104
|
+
* stays unscoped, as before.
|
|
103
105
|
*/
|
|
104
106
|
localities?: string[]
|
|
105
107
|
/**
|
|
@@ -151,6 +153,12 @@ export interface BuildCandidateResult {
|
|
|
151
153
|
places: number
|
|
152
154
|
primaries: number
|
|
153
155
|
aliases: number
|
|
156
|
+
/**
|
|
157
|
+
* Region aliases refused because another same-country region holds the name as its official name (three pairs in the
|
|
158
|
+
* admin artifact: `新竹市`, `嘉義市`, `충청남도`). Zero on a source without a `names` table means the rule had nothing to read,
|
|
159
|
+
* not that no pair exists.
|
|
160
|
+
*/
|
|
161
|
+
regionOfficialRefused: number
|
|
154
162
|
abbrevs: number
|
|
155
163
|
postcodes: number
|
|
156
164
|
/**
|
|
@@ -223,7 +231,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
223
231
|
|
|
224
232
|
await createCandidateStagingTables(kdb)
|
|
225
233
|
|
|
226
|
-
//
|
|
234
|
+
// Build compact country and placetype code maps for clustered keys.
|
|
227
235
|
// assigned here; the rows are bulk-inserted via kdb once the passes have discovered every code. ---
|
|
228
236
|
const ccodes = new Map<string, number>()
|
|
229
237
|
const ptcodes = new Map<string, number>()
|
|
@@ -252,7 +260,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
252
260
|
return id
|
|
253
261
|
}
|
|
254
262
|
|
|
255
|
-
//
|
|
263
|
+
// Load the importance source before the only pass that sees each place.
|
|
256
264
|
// name/country/placetype/centroid together. Absent → every row's `importance` stays NULL. ---
|
|
257
265
|
let importance: ReturnType<typeof loadImportanceIndex> | undefined
|
|
258
266
|
|
|
@@ -271,7 +279,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
271
279
|
progress("importance", "no score source given — `importance` will be NULL on every row")
|
|
272
280
|
}
|
|
273
281
|
|
|
274
|
-
//
|
|
282
|
+
// Derive each place's region ancestor for same-name disambiguation.
|
|
275
283
|
progress("region", "loading region ancestry")
|
|
276
284
|
const regionOf = new Map<number, number>()
|
|
277
285
|
let multiRegion = 0
|
|
@@ -307,7 +315,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
307
315
|
// the column COUNT can't drift; the positional run() args below MUST stay in CANDIDATE_COLUMNS order.
|
|
308
316
|
const insStage = kdb.prepare(`INSERT INTO cand_stage VALUES (${CANDIDATE_COLUMNS.map(() => "?").join(", ")})`)
|
|
309
317
|
|
|
310
|
-
//
|
|
318
|
+
// Stage primary names and attributes reused by later alias passes.
|
|
311
319
|
progress("primaries", "indexing place names")
|
|
312
320
|
const attrs = new Map<number, PlaceAttrs>()
|
|
313
321
|
let nPrim = 0
|
|
@@ -416,7 +424,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
416
424
|
)
|
|
417
425
|
}
|
|
418
426
|
|
|
419
|
-
//
|
|
427
|
+
// Stage country display names across scripts.
|
|
420
428
|
// Never a silent zero: a runtime whose ICU lacks these locales degrades to fewer surfaces, and the count is how a
|
|
421
429
|
// reader tells that apart from the pass not having run.
|
|
422
430
|
progress(
|
|
@@ -430,7 +438,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
430
438
|
}).toLocaleString()} country surfaces`
|
|
431
439
|
)
|
|
432
440
|
|
|
433
|
-
//
|
|
441
|
+
// Backfill cross-source currency before later candidate passes.
|
|
434
442
|
// the alias pass so a resurrected place's alt names explode like any primary's. ---
|
|
435
443
|
if (opts.currencyBackfill) {
|
|
436
444
|
const nBackfill = await resurrectCurrencyHoles({
|
|
@@ -455,12 +463,23 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
455
463
|
progress("currency-backfill", "not configured — deprecated-no-successor holes stay dead (#1737)")
|
|
456
464
|
}
|
|
457
465
|
|
|
458
|
-
//
|
|
466
|
+
// Stage distinct normalized aliases from alternate names.
|
|
459
467
|
progress("aliases", "exploding alias bags")
|
|
460
|
-
const { nAlias, keyCounts } = explodeAliasBags(src, kdb, attrs, stageRow)
|
|
461
|
-
progress("aliases", `${nAlias.toLocaleString()} aliases`)
|
|
462
468
|
|
|
463
|
-
|
|
469
|
+
const { nAlias, keyCounts, regionOfficialRefused } = explodeAliasBags(src, kdb, attrs, stageRow, {
|
|
470
|
+
regionPlacetypeID: ptID("region"),
|
|
471
|
+
ccID,
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
progress(
|
|
475
|
+
"aliases",
|
|
476
|
+
`${nAlias.toLocaleString()} aliases` +
|
|
477
|
+
(regionOfficialRefused
|
|
478
|
+
? `; ${regionOfficialRefused.toLocaleString()} region aliases refused as another region's official name`
|
|
479
|
+
: "")
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
// Stage region abbreviations.
|
|
464
483
|
let nAbbr = 0
|
|
465
484
|
kdb.exec("BEGIN")
|
|
466
485
|
|
|
@@ -479,7 +498,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
479
498
|
kdb.exec("COMMIT")
|
|
480
499
|
progress("abbrevs", `${nAbbr.toLocaleString()} abbrevs`)
|
|
481
500
|
|
|
482
|
-
//
|
|
501
|
+
// Stamp each name's semantic role.
|
|
483
502
|
// Independent of the sidecar below: this writes `cand_stage.name_role`, that writes the ancestor and
|
|
484
503
|
// interval tables, and neither reads the other's output. Ordered by label only.
|
|
485
504
|
const roles = stampNameRoles({
|
|
@@ -493,10 +512,10 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
493
512
|
progress,
|
|
494
513
|
})
|
|
495
514
|
|
|
496
|
-
//
|
|
515
|
+
// Stage the encoded ancestors sidecar.
|
|
497
516
|
const sidecar = await buildAncestorsSidecar({ src, out: kdb, attrs, ptID, progress })
|
|
498
517
|
|
|
499
|
-
//
|
|
518
|
+
// Fold postcode and locality extracts into candidate records.
|
|
500
519
|
let nPostcode = 0
|
|
501
520
|
let nPostcodeAlias = 0
|
|
502
521
|
|
|
@@ -520,6 +539,8 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
520
539
|
}
|
|
521
540
|
|
|
522
541
|
let nLocality = 0
|
|
542
|
+
let nLocalityScoped = 0
|
|
543
|
+
let nLocalityAncestor = 0
|
|
523
544
|
|
|
524
545
|
for (const locDB of opts.localities ?? []) {
|
|
525
546
|
const folded = foldExtract({
|
|
@@ -529,17 +550,26 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
529
550
|
ccID,
|
|
530
551
|
ptID,
|
|
531
552
|
stageRow,
|
|
553
|
+
attrs,
|
|
532
554
|
progress,
|
|
533
555
|
})
|
|
534
556
|
|
|
535
557
|
nLocality += folded.primaries
|
|
558
|
+
nLocalityScoped += folded.scoped
|
|
559
|
+
nLocalityAncestor += folded.ancestorRows
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
if (nLocality > 0) {
|
|
539
|
-
progress(
|
|
563
|
+
progress(
|
|
564
|
+
"localities",
|
|
565
|
+
`${nLocality.toLocaleString()} extract localities folded` +
|
|
566
|
+
(nLocalityScoped
|
|
567
|
+
? `; ${nLocalityScoped.toLocaleString()} carry a region scope (${nLocalityAncestor.toLocaleString()} closure rows)`
|
|
568
|
+
: "")
|
|
569
|
+
)
|
|
540
570
|
}
|
|
541
571
|
|
|
542
|
-
//
|
|
572
|
+
// Insert the small typed code dictionaries through Kysely.
|
|
543
573
|
if (ccodes.size) {
|
|
544
574
|
await kdb
|
|
545
575
|
.insertInto("country_codes")
|
|
@@ -554,7 +584,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
554
584
|
.execute()
|
|
555
585
|
}
|
|
556
586
|
|
|
557
|
-
//
|
|
587
|
+
// Insert the small typed capital-status reference table.
|
|
558
588
|
if (opts.capitals?.length) {
|
|
559
589
|
await createCapitalTable<CandidateDatabase>(kdb)
|
|
560
590
|
|
|
@@ -574,7 +604,7 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
574
604
|
progress("capitals", `${opts.capitals.length.toLocaleString()} capital-reference rows carried in-artifact`)
|
|
575
605
|
}
|
|
576
606
|
|
|
577
|
-
//
|
|
607
|
+
// Materialize the clustered WITHOUT ROWID table in sorted order.
|
|
578
608
|
progress("cluster", "building clustered candidate table + VACUUM")
|
|
579
609
|
// Column list + clustered-key order are sourced from CANDIDATE_COLUMNS (the first 6 ARE the PRIMARY
|
|
580
610
|
// KEY) so the SELECT, the ORDER BY, and the table can't drift. The table comes from the shared
|
|
@@ -607,11 +637,12 @@ export async function buildCandidateTable(opts: BuildCandidateOptions): Promise<
|
|
|
607
637
|
places: attrs.size,
|
|
608
638
|
primaries: nPrim,
|
|
609
639
|
aliases: nAlias,
|
|
640
|
+
regionOfficialRefused,
|
|
610
641
|
abbrevs: nAbbr,
|
|
611
642
|
postcodes: nPostcode,
|
|
612
643
|
postcodeAliases: nPostcodeAlias,
|
|
613
|
-
ancestorRows: sidecar.ancestorRows,
|
|
614
|
-
ancestorPlaces: sidecar.ancestorPlaces,
|
|
644
|
+
ancestorRows: sidecar.ancestorRows + nLocalityAncestor,
|
|
645
|
+
ancestorPlaces: sidecar.ancestorPlaces + nLocalityScoped,
|
|
615
646
|
intervalPlaces: sidecar.intervalPlaces,
|
|
616
647
|
...roles,
|
|
617
648
|
...(importance ? { importanceScored: importance.matched, importanceFiltered: importance.refused } : {}),
|
|
@@ -6,33 +6,83 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
9
|
+
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
9
10
|
|
|
10
|
-
import type { CandidateDatabase } from "#candidate-schema"
|
|
11
11
|
import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
|
|
12
|
+
import type { CandidateDatabase } from "#candidate/schema"
|
|
12
13
|
import { ALIAS_SEPARATOR } from "#fts/index"
|
|
13
14
|
import type { WOFDatabase } from "#schema"
|
|
14
15
|
import { normalizeLocalityForKey } from "#street/normalize"
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The official name of every current region, keyed `${country id}\0${name key}` → region id, from the source `names`
|
|
19
|
+
* table's `official` bit. Empty when the source carries no `names` table (a fixture, or an extract shape).
|
|
20
|
+
*
|
|
21
|
+
* This is the positive evidence behind the one alias refusal below: a name is refused from a region's bag ONLY when
|
|
22
|
+
* another region of the same country holds it as its official name. Three such pairs exist in the admin artifact —
|
|
23
|
+
* `新竹市` on Hsinchu County (the city's official name), `嘉義市` on Chiayi County, `충청남도` on Sejong — and each let the more
|
|
24
|
+
* populous holder of the variant outrank the place the name officially is, so a `新竹市` region node resolved the county
|
|
25
|
+
* and every district scoped to the city contradicted it.
|
|
26
|
+
*/
|
|
27
|
+
function officialNameHoldersByRegion(
|
|
28
|
+
src: DatabaseClient<WOFDatabase>,
|
|
29
|
+
ccID: (code: string | null) => number
|
|
30
|
+
): Map<string, number> {
|
|
31
|
+
const keys = new Map<string, number>()
|
|
32
|
+
|
|
33
|
+
if (!tableExists(src, "names")) return keys
|
|
34
|
+
|
|
35
|
+
for (const r of src
|
|
36
|
+
.prepare(
|
|
37
|
+
`SELECT n.id AS id, n.name AS name, s.country AS country
|
|
38
|
+
FROM names n JOIN spr s ON s.id = n.id
|
|
39
|
+
WHERE s.placetype = 'region' AND s.is_current != 0 AND s.is_deprecated = 0 AND n.official = 1`
|
|
40
|
+
)
|
|
41
|
+
.iterate()) {
|
|
42
|
+
const k = normalizeLocalityForKey(String(r.name ?? ""))
|
|
43
|
+
|
|
44
|
+
if (k) {
|
|
45
|
+
keys.set(`${ccID(r.country as string | null)}\0${k}`, Number(r.id))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return keys
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
/**
|
|
17
53
|
* Pass 2 — explode each place's `place_search.alt_names` bag into distinct-key alias rows (`is_primary = 0`), and count
|
|
18
54
|
* each place's distinct staged keys (primary included) — the gloss detector's key-count signal (#1730).
|
|
55
|
+
*
|
|
56
|
+
* With `regionPlacetypeID` and `ccID` given, a REGION's alias that is another same-country region's official name is
|
|
57
|
+
* refused and counted rather than staged (see {@link officialNameHoldersByRegion}); without them the pass stages every
|
|
58
|
+
* alias.
|
|
19
59
|
*/
|
|
20
60
|
export function explodeAliasBags(
|
|
21
61
|
src: DatabaseClient<WOFDatabase>,
|
|
22
62
|
out: DatabaseClient<CandidateDatabase>,
|
|
23
63
|
attrs: Map<number, PlaceAttrs>,
|
|
24
|
-
stageRow: StageRow
|
|
25
|
-
|
|
64
|
+
stageRow: StageRow,
|
|
65
|
+
opts: { regionPlacetypeID?: number; ccID?: (code: string | null) => number } = {}
|
|
66
|
+
): { nAlias: number; keyCounts: Map<number, number>; regionOfficialRefused: number } {
|
|
26
67
|
let nAlias = 0
|
|
68
|
+
let regionOfficialRefused = 0
|
|
27
69
|
const keyCounts = new Map<number, number>()
|
|
70
|
+
|
|
71
|
+
const official =
|
|
72
|
+
typeof opts.regionPlacetypeID === "number" && opts.ccID
|
|
73
|
+
? officialNameHoldersByRegion(src, opts.ccID)
|
|
74
|
+
: new Map<string, number>()
|
|
75
|
+
|
|
28
76
|
out.exec("BEGIN")
|
|
29
77
|
|
|
30
78
|
for (const r of src.prepare("SELECT wof_id, alt_names FROM place_search").iterate()) {
|
|
31
|
-
const
|
|
79
|
+
const id = Number(r.wof_id)
|
|
80
|
+
const a = attrs.get(id)
|
|
32
81
|
const alt = r.alt_names as string | null
|
|
33
82
|
|
|
34
83
|
if (!a || !alt) continue
|
|
35
84
|
const seen = new Set<string>([a.pkey])
|
|
85
|
+
const isRegion = a.ptid === opts.regionPlacetypeID
|
|
36
86
|
|
|
37
87
|
// The writer space-pads each separator and appends a trailing one, so every piece arrives with
|
|
38
88
|
// surrounding whitespace and the last one is empty. `normalizeLocalityForKey` folds both away,
|
|
@@ -42,15 +92,26 @@ export function explodeAliasBags(
|
|
|
42
92
|
|
|
43
93
|
if (!k || seen.has(k)) continue
|
|
44
94
|
seen.add(k)
|
|
45
|
-
|
|
95
|
+
|
|
96
|
+
if (isRegion) {
|
|
97
|
+
const holder = official.get(`${a.cid}\0${k}`)
|
|
98
|
+
|
|
99
|
+
if (typeof holder === "number" && holder !== id) {
|
|
100
|
+
regionOfficialRefused++
|
|
101
|
+
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stageRow(k, a, id, 0)
|
|
46
107
|
|
|
47
108
|
nAlias++
|
|
48
109
|
}
|
|
49
110
|
|
|
50
|
-
keyCounts.set(
|
|
111
|
+
keyCounts.set(id, seen.size)
|
|
51
112
|
}
|
|
52
113
|
|
|
53
114
|
out.exec("COMMIT")
|
|
54
115
|
|
|
55
|
-
return { nAlias, keyCounts }
|
|
116
|
+
return { nAlias, keyCounts, regionOfficialRefused }
|
|
56
117
|
}
|
|
@@ -59,7 +59,7 @@ import { sql, type Kysely } from "kysely"
|
|
|
59
59
|
// Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's
|
|
60
60
|
// DB parameter is invariant, so the DDL functions must take the FULL database type their caller
|
|
61
61
|
// holds. Erased at runtime.
|
|
62
|
-
import type { CandidateDatabase } from "#candidate
|
|
62
|
+
import type { CandidateDatabase } from "#candidate/schema"
|
|
63
63
|
import type { NameKey } from "#street/normalize"
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
createCandidateAncestorTable,
|
|
16
16
|
createCandidateIntervalTable,
|
|
17
17
|
MAX_ANCESTOR_DEPTH,
|
|
18
|
-
} from "#candidate
|
|
19
|
-
import type { CandidateDatabase } from "#candidate-schema"
|
|
18
|
+
} from "#candidate/ancestors/schema"
|
|
20
19
|
import type { PlaceAttrs } from "#candidate/place-attrs"
|
|
20
|
+
import type { CandidateDatabase } from "#candidate/schema"
|
|
21
21
|
import type { WOFDatabase } from "#schema"
|
|
22
22
|
|
|
23
23
|
/**
|