@mailwoman/resolver-wof-sqlite 9.4.0 → 10.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.
- package/README.md +8 -8
- package/lib/address/{point-interpolation.ts → point/interpolation.ts} +40 -38
- package/lib/address/point/schema.ts +132 -0
- package/lib/address/point.ts +68 -81
- package/lib/{address/index.ts → address.ts} +2 -2
- package/lib/ancestry/backfill.ts +24 -88
- package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
- package/lib/build/candidate.ts +608 -0
- package/lib/build/population-corrections.ts +64 -0
- package/lib/build/slim.ts +338 -0
- package/lib/candidate/alias-bags.ts +74 -14
- package/lib/candidate/ancestors/schema.ts +211 -0
- package/lib/candidate/{ancestors-sidecar.ts → ancestors/sidecar.ts} +17 -24
- package/lib/candidate/country-display-names.ts +6 -20
- package/lib/candidate/extract-fold.ts +12 -57
- package/lib/candidate/fts.ts +28 -0
- package/lib/candidate/importance.ts +221 -0
- package/lib/candidate/lookup.ts +669 -0
- package/lib/candidate/name-roles.ts +16 -34
- package/lib/candidate/own-name.ts +34 -31
- package/lib/candidate/place-attrs.ts +20 -13
- package/lib/candidate/schema.ts +211 -0
- package/lib/candidate/scoring.ts +222 -0
- package/lib/capital-schema.ts +21 -17
- package/lib/capitals.ts +26 -24
- package/lib/coincident-roles.ts +74 -55
- package/lib/convention/schema.ts +13 -7
- package/lib/convention.ts +144 -0
- package/lib/coverage-manifest-schema.ts +34 -39
- package/lib/currency-backfill.ts +24 -69
- package/lib/env.ts +1 -1
- package/lib/exact-match.ts +31 -20
- package/lib/extracts.ts +96 -73
- package/lib/fst/autocomplete.ts +52 -35
- package/lib/fst/builder.ts +30 -36
- package/lib/fst/deserialize-web.ts +8 -9
- package/lib/fst/format.ts +41 -23
- package/lib/fst/freshness.ts +17 -95
- package/lib/fst/matcher.ts +2 -2
- package/lib/fst/serialize.ts +45 -60
- package/lib/fst/types.ts +71 -59
- package/lib/fts/query.ts +5 -36
- package/lib/fts.ts +280 -0
- package/lib/geonames/aliases.ts +72 -149
- package/lib/geonames/postal.ts +162 -77
- package/lib/index.ts +20 -27
- package/lib/interpolation.ts +43 -96
- package/lib/lookup.ts +137 -257
- package/lib/name-score.ts +9 -7
- package/lib/nsul/lookup.ts +36 -53
- package/lib/nsul/schema.ts +16 -43
- package/lib/paths.ts +159 -0
- package/lib/place-importance-schema.ts +57 -164
- package/lib/poi/lookup.ts +100 -109
- package/lib/poi/schema.ts +42 -26
- package/lib/polygon-schema.ts +10 -6
- package/lib/postal/city/alias/lookup.ts +79 -0
- package/lib/postal/{city-alias-schema.ts → city/alias/schema.ts} +14 -10
- package/lib/postal/{city-candidate-schema.ts → city/candidate-schema.ts} +26 -20
- package/lib/postal.ts +9 -0
- package/lib/postcode-point-lookup.ts +9 -19
- package/lib/primary-preference.ts +45 -125
- package/lib/proximity-rerank.ts +43 -32
- package/lib/ranking-weights.ts +98 -76
- package/lib/region-keys.ts +68 -40
- package/lib/reverse.ts +108 -72
- package/lib/schema.ts +74 -42
- package/lib/search-fetch.ts +90 -68
- package/lib/sqlite/convention-source.ts +57 -0
- package/lib/sqlite/utils.ts +79 -0
- package/lib/street/centroid/schema.ts +149 -0
- package/lib/street/centroid.ts +16 -25
- package/lib/street/{morphology-fst-builder.ts → morphology/fst/builder.ts} +56 -41
- package/lib/street/{morphology-fst-loader.ts → morphology/fst/loader.ts} +36 -29
- package/lib/street/name-lookup.ts +43 -35
- package/lib/street/normalize.ts +85 -216
- package/lib/street/segment-schema.ts +116 -43
- package/lib/{street/index.ts → street.ts} +3 -3
- package/lib/types.ts +94 -140
- package/lib/unified-schema.ts +29 -29
- package/lib/uprn/existence.ts +30 -19
- package/lib/uprn/lookup.ts +28 -67
- package/lib/uprn/schema.ts +18 -40
- package/lib/weights-overlay-linker.ts +204 -232
- package/out/address/point/interpolation.d.ts +45 -0
- package/out/address/point/interpolation.d.ts.map +1 -0
- package/out/address/{point-interpolation.js → point/interpolation.js} +38 -38
- package/out/address/point/interpolation.js.map +1 -0
- package/out/address/point/schema.d.ts +74 -0
- package/out/address/point/schema.d.ts.map +1 -0
- package/out/address/{point-schema.js → point/schema.js} +4 -20
- package/out/address/point/schema.js.map +1 -0
- package/out/address/point.d.ts +24 -21
- package/out/address/point.d.ts.map +1 -1
- package/out/address/point.js +60 -77
- package/out/address/point.js.map +1 -1
- package/out/{address/index.js → address.d.ts} +3 -3
- package/out/address.d.ts.map +1 -0
- package/out/{address/index.d.ts → address.js} +3 -3
- package/out/address.js.map +1 -0
- package/out/ancestry/backfill.d.ts +11 -64
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +18 -76
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry.d.ts +49 -0
- package/out/ancestry.d.ts.map +1 -0
- package/out/ancestry.js +59 -0
- package/out/ancestry.js.map +1 -0
- package/out/build/candidate.d.ts +170 -0
- package/out/build/candidate.d.ts.map +1 -0
- package/out/{build-candidate.js → build/candidate.js} +33 -102
- package/out/build/candidate.js.map +1 -0
- package/out/build/population-corrections.d.ts +52 -0
- package/out/build/population-corrections.d.ts.map +1 -0
- package/out/build/population-corrections.js +40 -0
- package/out/build/population-corrections.js.map +1 -0
- package/out/build/slim.d.ts +44 -0
- package/out/build/slim.d.ts.map +1 -0
- package/out/{build-slim.js → build/slim.js} +33 -94
- package/out/build/slim.js.map +1 -0
- package/out/candidate/alias-bags.d.ts +13 -5
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +55 -12
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +166 -0
- package/out/candidate/ancestors/schema.d.ts.map +1 -0
- package/out/candidate/ancestors/schema.js +131 -0
- package/out/candidate/ancestors/schema.js.map +1 -0
- package/out/candidate/ancestors/sidecar.d.ts +27 -0
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -0
- package/out/candidate/{ancestors-sidecar.js → ancestors/sidecar.js} +17 -24
- package/out/candidate/ancestors/sidecar.js.map +1 -0
- package/out/candidate/country-display-names.d.ts +5 -15
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +6 -20
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +8 -16
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +6 -48
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +19 -0
- package/out/candidate/fts.d.ts.map +1 -0
- package/out/candidate/fts.js +22 -0
- package/out/candidate/fts.js.map +1 -0
- package/out/candidate/importance.d.ts +127 -0
- package/out/candidate/importance.d.ts.map +1 -0
- package/out/candidate/importance.js +162 -0
- package/out/candidate/importance.js.map +1 -0
- package/out/candidate/lookup.d.ts +36 -0
- package/out/candidate/lookup.d.ts.map +1 -0
- package/out/candidate/lookup.js +456 -0
- package/out/candidate/lookup.js.map +1 -0
- package/out/candidate/name-roles.d.ts +10 -22
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +15 -33
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +23 -25
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +34 -31
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +20 -13
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +4 -5
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate/schema.d.ts +120 -0
- package/out/candidate/schema.d.ts.map +1 -0
- package/out/{candidate-schema.js → candidate/schema.js} +11 -23
- package/out/candidate/schema.js.map +1 -0
- package/out/{candidate-scoring.d.ts → candidate/scoring.d.ts} +8 -9
- package/out/candidate/scoring.d.ts.map +1 -0
- package/out/candidate/scoring.js +157 -0
- package/out/candidate/scoring.js.map +1 -0
- package/out/capital-schema.d.ts +21 -17
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +17 -15
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +26 -24
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +23 -21
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.d.ts +60 -43
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +61 -50
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/schema.d.ts +12 -6
- package/out/convention/schema.d.ts.map +1 -1
- package/out/convention/schema.js +6 -4
- package/out/convention/schema.js.map +1 -1
- package/out/convention.d.ts +97 -0
- package/out/convention.d.ts.map +1 -0
- package/out/convention.js +81 -0
- package/out/convention.js.map +1 -0
- package/out/coverage-manifest-schema.d.ts +33 -38
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +20 -29
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +13 -35
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +12 -39
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +2 -2
- package/out/env.js +1 -1
- package/out/exact-match.d.ts +19 -10
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +31 -20
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +65 -54
- package/out/extracts.d.ts.map +1 -1
- package/out/extracts.js +63 -55
- package/out/extracts.js.map +1 -1
- package/out/fst/autocomplete.d.ts +28 -20
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +38 -27
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +30 -36
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +3 -3
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +8 -9
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/format.d.ts +41 -23
- package/out/fst/format.d.ts.map +1 -1
- package/out/fst/format.js +41 -23
- package/out/fst/format.js.map +1 -1
- package/out/fst/freshness.d.ts +11 -73
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +12 -76
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +1 -1
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/matcher.js +2 -2
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.d.ts +30 -26
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +44 -58
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +71 -59
- package/out/fst/types.d.ts.map +1 -1
- package/out/fst/types.js +1 -4
- package/out/fst/types.js.map +1 -1
- package/out/{fst/index.js → fst.d.ts} +1 -1
- package/out/fst.d.ts.map +1 -0
- package/out/{fst/index.d.ts → fst.js} +1 -1
- package/out/fst.js.map +1 -0
- package/out/fts/query.d.ts +2 -27
- package/out/fts/query.d.ts.map +1 -1
- package/out/fts/query.js +5 -36
- package/out/fts/query.js.map +1 -1
- package/out/fts.d.ts +126 -0
- package/out/fts.d.ts.map +1 -0
- package/out/fts.js +188 -0
- package/out/fts.js.map +1 -0
- package/out/geonames/aliases.d.ts +26 -67
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +44 -121
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +84 -50
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +115 -78
- package/out/geonames/postal.js.map +1 -1
- package/out/{geonames/index.js → geonames.d.ts} +1 -1
- package/out/geonames.d.ts.map +1 -0
- package/out/{geonames/index.d.ts → geonames.js} +1 -1
- package/out/geonames.js.map +1 -0
- package/out/index.d.ts +20 -21
- package/out/index.d.ts.map +1 -1
- package/out/index.js +14 -15
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +15 -52
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +25 -68
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +24 -53
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +117 -223
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +2 -2
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +9 -7
- package/out/name-score.js.map +1 -1
- package/out/nsul/lookup.d.ts +33 -34
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +26 -40
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +16 -43
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +9 -35
- package/out/nsul/schema.js.map +1 -1
- package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
- package/out/nsul.d.ts.map +1 -0
- package/out/{nsul/index.js → nsul.js} +1 -1
- package/out/nsul.js.map +1 -0
- package/out/paths.d.ts +124 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +104 -0
- package/out/paths.js.map +1 -0
- package/out/place-importance-schema.d.ts +49 -141
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +25 -140
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +38 -42
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +67 -90
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts +41 -25
- package/out/poi/schema.d.ts.map +1 -1
- package/out/poi/schema.js +25 -17
- package/out/poi/schema.js.map +1 -1
- package/out/{poi/index.js → poi.d.ts} +1 -1
- package/out/poi.d.ts.map +1 -0
- package/out/{poi/index.d.ts → poi.js} +1 -1
- package/out/poi.js.map +1 -0
- package/out/polygon-schema.d.ts +10 -6
- package/out/polygon-schema.d.ts.map +1 -1
- package/out/polygon-schema.js +2 -2
- package/out/polygon-schema.js.map +1 -1
- package/out/postal/{city-alias-lookup.d.ts → city/alias/lookup.d.ts} +22 -25
- package/out/postal/city/alias/lookup.d.ts.map +1 -0
- package/out/postal/city/alias/lookup.js +49 -0
- package/out/postal/city/alias/lookup.js.map +1 -0
- package/out/postal/{city-alias-schema.d.ts → city/alias/schema.d.ts} +15 -11
- package/out/postal/city/alias/schema.d.ts.map +1 -0
- package/out/postal/{city-alias-schema.js → city/alias/schema.js} +11 -9
- package/out/postal/city/alias/schema.js.map +1 -0
- package/out/postal/city/candidate-schema.d.ts +72 -0
- package/out/postal/city/candidate-schema.d.ts.map +1 -0
- package/out/postal/city/candidate-schema.js +60 -0
- package/out/postal/city/candidate-schema.js.map +1 -0
- package/out/postal.d.ts +9 -0
- package/out/postal.d.ts.map +1 -0
- package/out/postal.js +9 -0
- package/out/postal.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +9 -19
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +5 -17
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +39 -93
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +13 -97
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +40 -29
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +38 -29
- package/out/proximity-rerank.js.map +1 -1
- package/out/ranking-weights.d.ts +91 -62
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +26 -25
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +48 -29
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +68 -40
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +75 -50
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +61 -49
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +74 -42
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +4 -5
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +16 -12
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +83 -63
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.d.ts +25 -0
- package/out/sqlite/convention-source.d.ts.map +1 -0
- package/out/sqlite/convention-source.js +48 -0
- package/out/sqlite/convention-source.js.map +1 -0
- package/out/sqlite/utils.d.ts +45 -0
- package/out/sqlite/utils.d.ts.map +1 -0
- package/out/sqlite/utils.js +56 -0
- package/out/sqlite/utils.js.map +1 -0
- package/out/street/centroid/schema.d.ts +100 -0
- package/out/street/centroid/schema.d.ts.map +1 -0
- package/out/street/centroid/schema.js +66 -0
- package/out/street/centroid/schema.js.map +1 -0
- package/out/street/centroid.d.ts +9 -19
- package/out/street/centroid.d.ts.map +1 -1
- package/out/street/centroid.js +11 -22
- package/out/street/centroid.js.map +1 -1
- package/out/street/{morphology-fst-builder.d.ts → morphology/fst/builder.d.ts} +21 -11
- package/out/street/morphology/fst/builder.d.ts.map +1 -0
- package/out/street/{morphology-fst-builder.js → morphology/fst/builder.js} +40 -34
- package/out/street/morphology/fst/builder.js.map +1 -0
- package/out/street/morphology/fst/loader.d.ts +74 -0
- package/out/street/morphology/fst/loader.d.ts.map +1 -0
- package/out/street/{morphology-fst-loader.js → morphology/fst/loader.js} +21 -21
- package/out/street/morphology/fst/loader.js.map +1 -0
- package/out/street/name-lookup.d.ts +24 -18
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js +32 -31
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +35 -136
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +74 -191
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts +79 -39
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/street/segment-schema.js +71 -21
- package/out/street/segment-schema.js.map +1 -1
- package/out/{street/index.d.ts → street.d.ts} +4 -4
- package/out/street.d.ts.map +1 -0
- package/out/{street/index.js → street.js} +4 -4
- package/out/street.js.map +1 -0
- package/out/types.d.ts +69 -138
- package/out/types.d.ts.map +1 -1
- package/out/types.js +0 -6
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +8 -12
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +29 -29
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +28 -17
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js +26 -16
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts +14 -44
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +20 -56
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +18 -40
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +10 -32
- package/out/uprn/schema.js.map +1 -1
- package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
- package/out/uprn.d.ts.map +1 -0
- package/out/{uprn/index.js → uprn.js} +1 -1
- package/out/uprn.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +132 -141
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +86 -154
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +94 -599
- package/lib/address/point-schema.ts +0 -155
- package/lib/build-candidate.ts +0 -632
- package/lib/build-slim.ts +0 -408
- package/lib/candidate-ancestors-schema.ts +0 -195
- package/lib/candidate-fts.ts +0 -46
- package/lib/candidate-importance.ts +0 -224
- package/lib/candidate-lookup.ts +0 -977
- package/lib/candidate-schema.ts +0 -247
- package/lib/candidate-scoring.ts +0 -261
- package/lib/convention/index.ts +0 -154
- package/lib/fts/index.ts +0 -361
- package/lib/postal/city-alias-lookup.ts +0 -97
- package/lib/postal/index.ts +0 -9
- package/lib/sqlite-convention-source.ts +0 -65
- package/lib/sqlite-utils.ts +0 -82
- package/lib/street/centroid-schema.ts +0 -154
- package/out/address/index.d.ts.map +0 -1
- package/out/address/index.js.map +0 -1
- package/out/address/point-interpolation.d.ts +0 -49
- 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 +0 -97
- package/out/address/point-schema.d.ts.map +0 -1
- package/out/address/point-schema.js.map +0 -1
- package/out/ancestry/index.d.ts +0 -45
- package/out/ancestry/index.d.ts.map +0 -1
- package/out/ancestry/index.js +0 -55
- package/out/ancestry/index.js.map +0 -1
- package/out/build-candidate.d.ts +0 -184
- package/out/build-candidate.d.ts.map +0 -1
- package/out/build-candidate.js.map +0 -1
- package/out/build-slim.d.ts +0 -80
- package/out/build-slim.d.ts.map +0 -1
- package/out/build-slim.js.map +0 -1
- package/out/candidate/ancestors-sidecar.d.ts +0 -32
- 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 +0 -150
- package/out/candidate-ancestors-schema.d.ts.map +0 -1
- package/out/candidate-ancestors-schema.js +0 -123
- package/out/candidate-ancestors-schema.js.map +0 -1
- package/out/candidate-fts.d.ts +0 -37
- package/out/candidate-fts.d.ts.map +0 -1
- package/out/candidate-fts.js +0 -40
- package/out/candidate-fts.js.map +0 -1
- package/out/candidate-importance.d.ts +0 -132
- package/out/candidate-importance.d.ts.map +0 -1
- package/out/candidate-importance.js +0 -169
- package/out/candidate-importance.js.map +0 -1
- package/out/candidate-lookup.d.ts +0 -76
- package/out/candidate-lookup.d.ts.map +0 -1
- package/out/candidate-lookup.js +0 -768
- package/out/candidate-lookup.js.map +0 -1
- package/out/candidate-schema.d.ts +0 -155
- 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 +0 -196
- package/out/candidate-scoring.js.map +0 -1
- package/out/convention/index.d.ts +0 -106
- package/out/convention/index.d.ts.map +0 -1
- package/out/convention/index.js +0 -93
- package/out/convention/index.js.map +0 -1
- package/out/fst/index.d.ts.map +0 -1
- package/out/fst/index.js.map +0 -1
- package/out/fts/index.d.ts +0 -184
- package/out/fts/index.d.ts.map +0 -1
- package/out/fts/index.js +0 -277
- package/out/fts/index.js.map +0 -1
- package/out/geonames/index.d.ts.map +0 -1
- package/out/geonames/index.js.map +0 -1
- package/out/nsul/index.d.ts.map +0 -1
- package/out/nsul/index.js.map +0 -1
- package/out/poi/index.d.ts.map +0 -1
- package/out/poi/index.js.map +0 -1
- package/out/postal/city-alias-lookup.d.ts.map +0 -1
- package/out/postal/city-alias-lookup.js +0 -62
- 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 +0 -66
- package/out/postal/city-candidate-schema.d.ts.map +0 -1
- package/out/postal/city-candidate-schema.js +0 -56
- package/out/postal/city-candidate-schema.js.map +0 -1
- package/out/postal/index.d.ts +0 -9
- package/out/postal/index.d.ts.map +0 -1
- package/out/postal/index.js +0 -9
- package/out/postal/index.js.map +0 -1
- package/out/sqlite-convention-source.d.ts +0 -29
- package/out/sqlite-convention-source.d.ts.map +0 -1
- package/out/sqlite-convention-source.js +0 -57
- package/out/sqlite-convention-source.js.map +0 -1
- package/out/sqlite-utils.d.ts +0 -45
- package/out/sqlite-utils.d.ts.map +0 -1
- package/out/sqlite-utils.js +0 -57
- package/out/sqlite-utils.js.map +0 -1
- package/out/street/centroid-schema.d.ts +0 -105
- package/out/street/centroid-schema.d.ts.map +0 -1
- package/out/street/centroid-schema.js +0 -78
- package/out/street/centroid-schema.js.map +0 -1
- package/out/street/index.d.ts.map +0 -1
- package/out/street/index.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 +0 -67
- package/out/street/morphology-fst-loader.d.ts.map +0 -1
- package/out/street/morphology-fst-loader.js.map +0 -1
- package/out/uprn/index.d.ts.map +0 -1
- package/out/uprn/index.js.map +0 -1
- /package/lib/{fst/index.ts → fst.ts} +0 -0
- /package/lib/{geonames/index.ts → geonames.ts} +0 -0
- /package/lib/{nsul/index.ts → nsul.ts} +0 -0
- /package/lib/{poi/index.ts → poi.ts} +0 -0
- /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/out/lookup.js
CHANGED
|
@@ -3,37 +3,27 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* query layer where the queries are non-trivial, and raw SQL where they aren't (FTS5 MATCH, the
|
|
8
|
-
* FTS index build).
|
|
9
|
-
*
|
|
10
|
-
* See `docs/plan/phases/PHASE_4_2_wof_sqlite.md` for the design rationale.
|
|
6
|
+
* `WOFSQLitePlaceLookup` — the resolver implementation backed by `node:sqlite` plus a Kysely-typed query layer where the queries are non-trivial and raw SQL where they are not.
|
|
11
7
|
*/
|
|
12
|
-
import { expandPlacetypeFilter } from "@mailwoman/
|
|
8
|
+
import { expandPlacetypeFilter } from "@mailwoman/codex/placetype-map";
|
|
13
9
|
import { allRows } from "@mailwoman/core/utils";
|
|
14
10
|
import { haversineKm } from "@mailwoman/spatial";
|
|
15
|
-
import {
|
|
16
|
-
import { ancestorLineage } from "#ancestry
|
|
17
|
-
import { candidateFromSearchRow, rankCandidates } from "#candidate
|
|
11
|
+
import { SQLiteLookup } from "@mailwoman/sqlite/lookup";
|
|
12
|
+
import { ancestorLineage } from "#ancestry";
|
|
13
|
+
import { candidateFromSearchRow, rankCandidates } from "#candidate/scoring";
|
|
18
14
|
import { loadCoincidentLocalities } from "#coincident-roles";
|
|
19
|
-
import { ADDRESS_CONVENTION_TABLE, resolveConvention, SeedConventionSource, } from "#convention
|
|
15
|
+
import { ADDRESS_CONVENTION_TABLE, resolveConvention, SeedConventionSource, } from "#convention";
|
|
20
16
|
import { pickExtractForPlacetype, pickExtractsForPlacetype, resolveExtracts, } from "#extracts";
|
|
21
|
-
import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE, placeBboxExists, placePopulationExists, placeSearchFTSExists, } from "#fts
|
|
17
|
+
import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE, placeBboxExists, placePopulationExists, placeSearchFTSExists, } from "#fts";
|
|
22
18
|
import { normalizePlacetypes, sanitizeFTSQuery } from "#fts/query";
|
|
23
19
|
import { cfNormalize, softNameScore } from "#name-score";
|
|
24
20
|
import { encyclopedicClauses } from "#place-importance-schema";
|
|
25
21
|
import { DEFAULT_WEIGHTS, populationScaleTerm } from "#ranking-weights";
|
|
26
22
|
import { fetchSearchRows } from "#search-fetch";
|
|
27
|
-
import { SqliteConventionSource } from "#sqlite
|
|
28
|
-
/**
|
|
29
|
-
* The coordinate-first candidate table (scripts/build-postcode-locality.ts): postcode → containing
|
|
30
|
-
*
|
|
31
|
-
* - Nearby localities with WOF alt-name aliases.
|
|
32
|
-
*/
|
|
23
|
+
import { SqliteConventionSource } from "#sqlite/convention-source";
|
|
33
24
|
/**
|
|
34
|
-
* The placetypes `pickExtractsForPlacetype`'s substring rule can route by name
|
|
35
|
-
* a
|
|
36
|
-
* claiming to enumerate the gazetteer.
|
|
25
|
+
* The placetypes `pickExtractsForPlacetype`'s substring rule can route by name,
|
|
26
|
+
* a subset of the WOF placetypes.
|
|
37
27
|
*/
|
|
38
28
|
const KNOWN_ROUTED_PLACETYPES = [
|
|
39
29
|
"postalcode",
|
|
@@ -45,85 +35,70 @@ const KNOWN_ROUTED_PLACETYPES = [
|
|
|
45
35
|
];
|
|
46
36
|
const POSTCODE_LOCALITY_TABLE = "postcode_locality";
|
|
47
37
|
/**
|
|
48
|
-
* Tunables for the coordinate-first locality soft-score
|
|
49
|
-
*
|
|
50
|
-
* EU values), so a locale can retune them as data. PC_DECAY_KM sets how fast S_pc falls with distance.
|
|
38
|
+
* Tunables for the coordinate-first locality soft-score
|
|
39
|
+
* `Score = pc·S_pc + name·S_name + pop·S_pop` (each S in [0,1]).
|
|
51
40
|
*/
|
|
52
41
|
const CF_PC_DECAY_KM = 8;
|
|
53
42
|
/**
|
|
54
|
-
* The chosen locality must be within this distance of the postcode's containing
|
|
55
|
-
*
|
|
56
|
-
* Generous enough that a city-state Ortsteil (~15km from the city centroid) and an abutting town (~few km) are NOT
|
|
57
|
-
* flagged, tight enough to catch a wrong city (hundreds of km).
|
|
43
|
+
* The chosen locality must be within this distance of the postcode's containing
|
|
44
|
+
* locality or the `mismatch` flag fires.
|
|
58
45
|
*/
|
|
59
46
|
const CF_MISMATCH_KM = 50;
|
|
60
|
-
export class WOFSQLitePlaceLookup {
|
|
61
|
-
#db;
|
|
62
|
-
/**
|
|
63
|
-
* Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
|
|
64
|
-
* ownership is membership rather than a flag a later branch has to check.
|
|
65
|
-
*/
|
|
66
|
-
#resources = new DisposableStack();
|
|
47
|
+
export class WOFSQLitePlaceLookup extends SQLiteLookup {
|
|
67
48
|
#weights;
|
|
68
49
|
/**
|
|
69
|
-
* Cached at construction
|
|
70
|
-
* fall back to no-filter when this is false, preserving compatibility with DBs that were FTS-built before the R*Tree
|
|
71
|
-
* shipped.
|
|
50
|
+
* Cached at construction.
|
|
72
51
|
*
|
|
73
|
-
*
|
|
52
|
+
* An extract is considered to have the bbox index only if its own R*Tree table exists.
|
|
74
53
|
*/
|
|
75
54
|
#hasBboxIndex;
|
|
76
55
|
/**
|
|
77
|
-
* Per-extract probe for the `place_population` aux table.
|
|
78
|
-
*
|
|
56
|
+
* Per-extract probe for the `place_population` aux table.
|
|
57
|
+
*
|
|
58
|
+
* When false, the left join is omitted and the population boost is 0 for every row.
|
|
79
59
|
*/
|
|
80
60
|
#hasPopulationIndex;
|
|
81
61
|
/**
|
|
82
|
-
* Per-extract
|
|
83
|
-
* built once at construction.
|
|
84
|
-
* extract today. See {@link encyclopedicClauses} for why the probe is a column and not a table.
|
|
62
|
+
* Per-extract select term and left join for the two-score split's `encyclopedic` value,
|
|
63
|
+
* probed and built once at construction.
|
|
85
64
|
*/
|
|
86
65
|
#encyclopedicClauses;
|
|
87
66
|
/**
|
|
88
|
-
* Per-extract probe for the `postcode_locality` table
|
|
89
|
-
*
|
|
90
|
-
* silently no-ops on a deployment that didn't ship the table.
|
|
67
|
+
* Per-extract probe for the `postcode_locality` table, cached at construction
|
|
68
|
+
* and null when absent so the coord-first path no-ops.
|
|
91
69
|
*/
|
|
92
70
|
#postcodeLocalityExtract;
|
|
93
71
|
/**
|
|
94
|
-
* Resolved extract list
|
|
95
|
-
* (or override) schema names.
|
|
72
|
+
* Resolved extract list, always at least one entry with `main` first.
|
|
96
73
|
*/
|
|
97
74
|
#extracts;
|
|
98
75
|
/**
|
|
99
|
-
*
|
|
76
|
+
* Per-schema probed country sets for country-aware extract routing, non-main extracts only.
|
|
100
77
|
*/
|
|
101
78
|
#extractCountries;
|
|
102
79
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* country-code
|
|
107
|
-
* cached).
|
|
80
|
+
* `#conventionSource` supplies per-WOF-polygon profiles for the Geographic Rule Engine.
|
|
81
|
+
*
|
|
82
|
+
* `#strategies` maps primitive names to strategies.
|
|
83
|
+
* `#countryWOFIdCache` memoizes country-code to country-WOF-id lookups.
|
|
108
84
|
*/
|
|
109
85
|
#conventionSource;
|
|
110
86
|
#strategies;
|
|
111
87
|
#countryWOFIdCache = new Map();
|
|
112
88
|
/**
|
|
113
|
-
* Strategy names already warned about
|
|
89
|
+
* Strategy names already warned about, so an unknown name surfaces once.
|
|
114
90
|
*/
|
|
115
91
|
#warnedUnknownStrategies = new Set();
|
|
116
92
|
/**
|
|
117
|
-
* Lazily-built `admin_id → coincident localities` map
|
|
93
|
+
* Lazily-built `admin_id → coincident localities` map, null until first use.
|
|
118
94
|
*/
|
|
119
95
|
#coincidentRolesCache = null;
|
|
120
96
|
/**
|
|
121
|
-
* Per-id memoized ancestor lineages
|
|
97
|
+
* Per-id memoized ancestor lineages, queried once per chain.
|
|
122
98
|
*/
|
|
123
99
|
#ancestorsCache = new Map();
|
|
124
100
|
/**
|
|
125
|
-
* Opt-in postal-city alias reader
|
|
126
|
-
* is conditioned on this, so the default resolver is byte-identical.
|
|
101
|
+
* Opt-in postal-city alias reader, `null` unless supplied, so every alias path is skipped by default.
|
|
127
102
|
*/
|
|
128
103
|
#postalCityAliases;
|
|
129
104
|
constructor(opts, weights) {
|
|
@@ -133,28 +108,22 @@ export class WOFSQLitePlaceLookup {
|
|
|
133
108
|
if (!opts.database && !opts.databasePath) {
|
|
134
109
|
throw new Error("WOFSQLitePlaceLookup: one of `database` or `databasePath` is required");
|
|
135
110
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this
|
|
150
|
-
// ATTACH each non-main extract. Schema names were validated by resolveExtracts, so safe to
|
|
151
|
-
// interpolate directly (SQLite ATTACH doesn't accept parameters for the schema name).
|
|
152
|
-
for (const s of extracts.slice(1)) {
|
|
153
|
-
this.#db.exec(`ATTACH DATABASE '${s.path.replaceAll("'", "''")}' AS ${s.schemaName}`);
|
|
154
|
-
}
|
|
111
|
+
const extracts = opts.database
|
|
112
|
+
? [{ path: ":memory:", schemaName: "main", placetypes: [] }]
|
|
113
|
+
: resolveExtracts(opts.databasePath);
|
|
114
|
+
// Read-only by default: shipped extracts are sealed 0444 and Docker `:ro`
|
|
115
|
+
// mounts forbid a write-mode open.
|
|
116
|
+
// Only `buildFTS` opens writable.
|
|
117
|
+
super(opts.database ? { database: opts.database } : { databasePath: extracts[0].path }, {
|
|
118
|
+
readOnly: !opts.buildFTS,
|
|
119
|
+
});
|
|
120
|
+
this.#extracts = extracts;
|
|
121
|
+
// Schema names were validated by resolveExtracts, so interpolating them is safe.
|
|
122
|
+
// SQLite attach accepts no parameter for a schema name.
|
|
123
|
+
for (const s of extracts.slice(1)) {
|
|
124
|
+
this.database.exec(`ATTACH DATABASE '${s.path.replaceAll("'", "''")}' AS ${s.schemaName}`);
|
|
155
125
|
}
|
|
156
|
-
|
|
157
|
-
this.#db.exec("PRAGMA busy_timeout = 5000");
|
|
126
|
+
this.database.exec("PRAGMA busy_timeout = 5000");
|
|
158
127
|
if (opts.buildFTS) {
|
|
159
128
|
this.#ensureFTS();
|
|
160
129
|
}
|
|
@@ -162,32 +131,17 @@ export class WOFSQLitePlaceLookup {
|
|
|
162
131
|
this.#assertFTSExists();
|
|
163
132
|
}
|
|
164
133
|
this.#weights = { ...DEFAULT_WEIGHTS, ...weights };
|
|
165
|
-
// Probe each extract's aux-table presence — driven by per-extract table existence in
|
|
166
|
-
// sqlite_master. Cached at construction so findPlace doesn't query sqlite_master per call.
|
|
167
134
|
this.#hasBboxIndex = new Map();
|
|
168
135
|
this.#hasPopulationIndex = new Map();
|
|
169
136
|
this.#encyclopedicClauses = new Map();
|
|
170
137
|
for (const s of this.#extracts) {
|
|
171
138
|
this.#hasBboxIndex.set(s.schemaName, this.#extractHasTable(s.schemaName, PLACE_BBOX_TABLE));
|
|
172
139
|
this.#hasPopulationIndex.set(s.schemaName, this.#extractHasTable(s.schemaName, PLACE_POPULATION_TABLE));
|
|
173
|
-
this.#encyclopedicClauses.set(s.schemaName, encyclopedicClauses(this
|
|
140
|
+
this.#encyclopedicClauses.set(s.schemaName, encyclopedicClauses(this.database, s.schemaName));
|
|
174
141
|
}
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// unroutable half is the worse of the two — a extract reaches routing only through the name
|
|
179
|
-
// `deriveSchemaName` derives from its FILENAME, so a file spelled one letter off the placetype it
|
|
180
|
-
// serves answers with nothing while holding every row that was asked for.
|
|
181
|
-
//
|
|
182
|
-
// Two independent things bring a extract under the guard, and it needs both. Carrying `spr` is a
|
|
183
|
-
// CLAIM to be a place extract. Carrying a name that routes is an INVITATION to be queried as one, and
|
|
184
|
-
// it is made by the filename alone — so a database with no tables at all still gets picked, still
|
|
185
|
-
// answers no query, and still dies inside a SELECT. Testing only the claim lets an empty or
|
|
186
|
-
// truncated file past construction; testing only the name would exempt a correctly-named build
|
|
187
|
-
// input. A extract needs to fail neither test to be exempt.
|
|
188
|
-
//
|
|
189
|
-
// Exempt by design: `postcode-locality-<cc>.db` carries a relation table and nothing else, matches
|
|
190
|
-
// no routed placetype, and is part of the documented default extract list.
|
|
142
|
+
// An extract is guarded when its path identifies a routed placetype
|
|
143
|
+
// or it contains `spr`, and must then contain `place_search`; testing only one of
|
|
144
|
+
// those would let an empty file through or exempt a build input.
|
|
191
145
|
for (const s of this.#extracts) {
|
|
192
146
|
if (s.schemaName === "main")
|
|
193
147
|
continue;
|
|
@@ -208,45 +162,34 @@ export class WOFSQLitePlaceLookup {
|
|
|
208
162
|
: ` Its schema name "${s.schemaName}" also matches no routed placetype (${KNOWN_ROUTED_PLACETYPES.join(", ")}), ` +
|
|
209
163
|
`so it would never have been queried even with the table — check the filename's spelling.`));
|
|
210
164
|
}
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// multi-GB admin DB and is the fallback anyway, so it is deliberately NOT scanned). Feeds
|
|
214
|
-
// pickExtractForPlacetype so two postcode extracts (postalcode-us + postalcode-geonames-tail)
|
|
215
|
-
// route by the query's country instead of first-match starving the second extract.
|
|
165
|
+
// Probe each non-main extract's country set once at construction so two postcode
|
|
166
|
+
// extracts route by the query's country instead of first-match starving the second.
|
|
216
167
|
this.#extractCountries = new Map();
|
|
217
168
|
for (const sh of this.#extracts) {
|
|
218
169
|
if (sh.schemaName === "main")
|
|
219
170
|
continue;
|
|
220
171
|
try {
|
|
221
|
-
const rows = this
|
|
172
|
+
const rows = this.database
|
|
222
173
|
.prepare(`SELECT DISTINCT country FROM ${sh.schemaName}.spr WHERE country != ''`)
|
|
223
174
|
.all();
|
|
224
175
|
this.#extractCountries.set(sh.schemaName, new Set(rows.map((r) => r.country)));
|
|
225
176
|
}
|
|
226
177
|
catch {
|
|
227
|
-
//
|
|
178
|
+
// An extract without spr (or an attach oddity) just doesn't participate in country routing.
|
|
228
179
|
}
|
|
229
180
|
}
|
|
230
|
-
// The postcode_locality table can live on any attached extract (typically its own
|
|
231
|
-
// `postcode-locality-<cc>.db`). Find the first extract that has it; null = coord-first disabled.
|
|
232
181
|
this.#postcodeLocalityExtract =
|
|
233
182
|
this.#extracts.find((s) => this.#extractHasTable(s.schemaName, POSTCODE_LOCALITY_TABLE))?.schemaName ?? null;
|
|
234
|
-
// Opt-in postal-city alias reader (#475). Construction-time present-or-not is the check: null
|
|
235
|
-
// keeps the coordinate-first scorer byte-identical to pre-#475.
|
|
236
183
|
this.#postalCityAliases = opts.postalCityAliases ?? null;
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
// attached (auto-detected, like the postcode_locality extract — adding conventions.db to
|
|
240
|
-
// databasePath enables it; queried on demand, not paged into memory); else empty, so EU rides
|
|
241
|
-
// WORLD_DEFAULT. The registry binds strategy NAMES to the SQL-bound primitives — adding a
|
|
242
|
-
// strategy is registering it here.
|
|
184
|
+
// Precedence: explicit `opts.conventions` wins, else an attached build-from-source
|
|
185
|
+
// convention asset, else empty so EU uses `WORLD_DEFAULT`.
|
|
243
186
|
const conventionExtract = this.#extracts.find((s) => this.#extractHasTable(s.schemaName, ADDRESS_CONVENTION_TABLE))?.schemaName ?? null;
|
|
244
187
|
this.#conventionSource = opts.conventions
|
|
245
188
|
? "get" in opts.conventions && typeof opts.conventions.get === "function"
|
|
246
189
|
? opts.conventions
|
|
247
190
|
: new SeedConventionSource(opts.conventions)
|
|
248
191
|
: conventionExtract
|
|
249
|
-
? new SqliteConventionSource(this
|
|
192
|
+
? new SqliteConventionSource(this.database, conventionExtract)
|
|
250
193
|
: new SeedConventionSource();
|
|
251
194
|
this.#strategies = new Map([
|
|
252
195
|
["postcode_area_resolution", (q, c) => this.#postcodeAreaResolution(q, c)],
|
|
@@ -254,26 +197,22 @@ export class WOFSQLitePlaceLookup {
|
|
|
254
197
|
]);
|
|
255
198
|
}
|
|
256
199
|
#extractHasTable(schemaName, tableName) {
|
|
257
|
-
//
|
|
258
|
-
// schema-qualified `sqlite_master
|
|
200
|
+
// Main uses the existing helpers.
|
|
201
|
+
// Attached extracts need the schema-qualified `sqlite_master`.
|
|
259
202
|
if (schemaName === "main") {
|
|
260
203
|
if (tableName === PLACE_BBOX_TABLE)
|
|
261
|
-
return placeBboxExists(this
|
|
204
|
+
return placeBboxExists(this.database);
|
|
262
205
|
if (tableName === PLACE_POPULATION_TABLE)
|
|
263
|
-
return placePopulationExists(this
|
|
206
|
+
return placePopulationExists(this.database);
|
|
264
207
|
}
|
|
265
|
-
const row = this
|
|
208
|
+
const row = this.database
|
|
266
209
|
.prepare(`SELECT name FROM ${schemaName}.sqlite_master WHERE type = 'table' AND name = ?`)
|
|
267
210
|
.get(tableName);
|
|
268
211
|
return Boolean(row);
|
|
269
212
|
}
|
|
270
213
|
async findPlace(query) {
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// in order; the first to return a non-null result wins. The default list,
|
|
274
|
-
// [postcode_area_resolution, fallback_fuzzy_name_match], reproduces the pre-engine coordinate-
|
|
275
|
-
// first → FTS fall-through exactly. Unknown strategy names are skipped, so a convention may name
|
|
276
|
-
// a primitive a future phase will register.
|
|
214
|
+
// Run the effective convention's candidate strategies in order.
|
|
215
|
+
// The first non-null result wins and unknown strategy names are skipped.
|
|
277
216
|
const convention = this.#conventionFor(query);
|
|
278
217
|
let outcome = [];
|
|
279
218
|
for (const name of convention.candidateStrategies) {
|
|
@@ -290,14 +229,8 @@ export class WOFSQLitePlaceLookup {
|
|
|
290
229
|
}
|
|
291
230
|
if (outcome.length)
|
|
292
231
|
return outcome;
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
// ('1012 LG') — two FTS tokens that can never match the one-token doc (the #920 name law,
|
|
296
|
-
// resurfacing in a WOF-built extract). On a postcode-typed NL-shape miss, retry ONCE with the
|
|
297
|
-
// whitespace-joined form (block-level precision when the full-code row exists), then the
|
|
298
|
-
// 4-digit stem (area-level). Country-restricted to NL — the same digits+letters shape elsewhere
|
|
299
|
-
// must not silently coarsen to a different system's code. Each retry only fires when its
|
|
300
|
-
// text differs from the current one, so the ladder terminates by construction.
|
|
232
|
+
// On a postcode-typed NL-shape miss, retry with the whitespace-joined form then the 4-digit stem,
|
|
233
|
+
// restricted to NL so the same shape elsewhere cannot coarsen to another system's code.
|
|
301
234
|
if (query.country?.toUpperCase() === "NL" &&
|
|
302
235
|
(normalizePlacetypes(query.placetype)?.includes("postalcode") ?? false) &&
|
|
303
236
|
/^\d{4}\s?[A-Za-z]{2}$/.test(query.text.trim())) {
|
|
@@ -315,28 +248,20 @@ export class WOFSQLitePlaceLookup {
|
|
|
315
248
|
return outcome;
|
|
316
249
|
}
|
|
317
250
|
/**
|
|
318
|
-
* Dual-role localities coincident with an admin id,
|
|
319
|
-
* {@link ResolveOpts.hierarchyCompletion} (#405): O(1) once the relation is loaded. Returns `[]` when the relation
|
|
320
|
-
* table is absent (older DB) or the admin isn't a dual-role place, so completion degrades gracefully. The relation +
|
|
321
|
-
* `spr` join is loaded once and memoized.
|
|
251
|
+
* Dual-role localities coincident with an admin id, or `[]` when the relation table is absent.
|
|
322
252
|
*/
|
|
323
253
|
coincidentLocalitiesFor(adminID) {
|
|
324
254
|
const id = typeof adminID === "number" ? adminID : Number(adminID);
|
|
325
255
|
if (!Number.isFinite(id))
|
|
326
256
|
return [];
|
|
327
257
|
if (!this.#coincidentRolesCache) {
|
|
328
|
-
this.#coincidentRolesCache = loadCoincidentLocalities(this
|
|
258
|
+
this.#coincidentRolesCache = loadCoincidentLocalities(this.database);
|
|
329
259
|
}
|
|
330
260
|
return this.#coincidentRolesCache.get(id) ?? [];
|
|
331
261
|
}
|
|
332
262
|
/**
|
|
333
|
-
* The ancestor lineage of a place
|
|
334
|
-
*
|
|
335
|
-
* is excluded; memoized per id. Returns `[]` when the place has no recorded ancestry.
|
|
336
|
-
*
|
|
337
|
-
* The walk itself lives in `ancestry.ts` (shared with the reverse geocoder, #484); the ordering is its
|
|
338
|
-
* `PLACETYPE_DEPTH` table — same ranking as the previous inline SQL CASE, extended below `localadmin` so
|
|
339
|
-
* locality/neighbourhood ancestors order correctly instead of sorting last.
|
|
263
|
+
* The ancestor lineage of a place, ordered nearest-first with self excluded,
|
|
264
|
+
* or `[]` when it has no recorded ancestry.
|
|
340
265
|
*/
|
|
341
266
|
ancestors(id) {
|
|
342
267
|
const pid = typeof id === "number" ? id : Number(id);
|
|
@@ -345,7 +270,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
345
270
|
const cached = this.#ancestorsCache.get(pid);
|
|
346
271
|
if (cached)
|
|
347
272
|
return cached;
|
|
348
|
-
const lineage = ancestorLineage(this
|
|
273
|
+
const lineage = ancestorLineage(this.database, pid).map((r) => ({
|
|
349
274
|
id: r.id,
|
|
350
275
|
placetype: r.placetype,
|
|
351
276
|
name: r.name,
|
|
@@ -354,10 +279,8 @@ export class WOFSQLitePlaceLookup {
|
|
|
354
279
|
return lineage;
|
|
355
280
|
}
|
|
356
281
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* throw so a convention asset built against a newer code revision (one that adds a strategy) degrades gracefully on
|
|
360
|
-
* an older build instead of taking down resolution.
|
|
282
|
+
* Warn once per unknown strategy name rather than throwing, so a convention built
|
|
283
|
+
* against a newer revision degrades instead of stopping resolution.
|
|
361
284
|
*/
|
|
362
285
|
#warnUnknownStrategy(name) {
|
|
363
286
|
if (this.#warnedUnknownStrategies.has(name))
|
|
@@ -368,10 +291,8 @@ export class WOFSQLitePlaceLookup {
|
|
|
368
291
|
`against a newer code revision, rebuild the asset for this one.`);
|
|
369
292
|
}
|
|
370
293
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
* strategy — when the condition is unmet or the postcode isn't in the table; otherwise the soft-scored postcode∪name
|
|
374
|
-
* candidate set.
|
|
294
|
+
* The coordinate-first locality strategy, returning `null` when its postcode,
|
|
295
|
+
* table, or locality conditions are unmet.
|
|
375
296
|
*/
|
|
376
297
|
#postcodeAreaResolution(query, convention) {
|
|
377
298
|
if (!(query.postcode && this.#postcodeLocalityExtract && this.#isLocalityQuery(query))) {
|
|
@@ -380,31 +301,27 @@ export class WOFSQLitePlaceLookup {
|
|
|
380
301
|
return this.#findLocalityCoordFirst(query, this.#postcodeLocalityExtract, convention);
|
|
381
302
|
}
|
|
382
303
|
/**
|
|
383
|
-
*
|
|
384
|
-
* returns an array (never null), so it terminates the dispatch chain.
|
|
304
|
+
* The BM25 FTS name-match fallback, always returning an array so it terminates the dispatch chain.
|
|
385
305
|
*/
|
|
386
306
|
async #fuzzyNameMatch(query, forceExtract) {
|
|
387
307
|
const limit = query.limit ?? 10;
|
|
388
|
-
// Expand the placetype filter through the shared equivalence table
|
|
389
|
-
//
|
|
390
|
-
// (pop 2.5M) is a borough, not a locality, and a strict filter made it unreachable so the
|
|
391
|
-
// fuzzy "Brooklyn Park, MN" won instead. Order-preserving: the FIRST entry stays the
|
|
392
|
-
// requested placetype, which is what extract routing keys off below.
|
|
308
|
+
// Expand the placetype filter through the shared equivalence table so a `locality`
|
|
309
|
+
// query also reaches `borough` and `localadmin` rows.
|
|
393
310
|
const placetypes = expandPlacetypeFilter(normalizePlacetypes(query.placetype));
|
|
394
|
-
// Postcode-typed queries keep the
|
|
395
|
-
// intra-token punctuation so hyphenated names
|
|
311
|
+
// Postcode-typed queries keep the fused name-law shape.
|
|
312
|
+
// Everything else splits on intra-token punctuation so hyphenated names
|
|
313
|
+
// reach the FTS as their real terms.
|
|
396
314
|
const ftsQuery = sanitizeFTSQuery(query.text, { fuseTokens: placetypes?.includes("postalcode") ?? false });
|
|
397
315
|
if (!ftsQuery)
|
|
398
316
|
return [];
|
|
399
|
-
//
|
|
400
|
-
// `placetype`
|
|
401
|
-
//
|
|
317
|
+
// Multi-extract routing uses placetype.
|
|
318
|
+
// Queries without `placetype` go to main.
|
|
319
|
+
// Mixed-placetype spread across extracts is unsupported.
|
|
402
320
|
const firstPlacetype = placetypes?.[0];
|
|
403
|
-
//
|
|
404
|
-
// cross-extract ambiguity
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
// keys the per-extract tier sort used. Bounded: hints + no country + >1 matching extract only.
|
|
321
|
+
// A country-less query with proximity hints queries every matching extract
|
|
322
|
+
// so cross-extract ambiguity is visible.
|
|
323
|
+
// The bound requires hints and an absent country.
|
|
324
|
+
// It also requires more than one matching extract.
|
|
408
325
|
const hasBiasHints = !!query.near || (query.bias?.length ?? 0) > 0;
|
|
409
326
|
if (!forceExtract && hasBiasHints && !query.country) {
|
|
410
327
|
const matching = pickExtractsForPlacetype(this.#extracts, firstPlacetype);
|
|
@@ -431,10 +348,10 @@ export class WOFSQLitePlaceLookup {
|
|
|
431
348
|
country: query.country,
|
|
432
349
|
countriesBySchema: this.#extractCountries,
|
|
433
350
|
});
|
|
434
|
-
//
|
|
351
|
+
// Validated at construction, so bare interpolation is safe.
|
|
435
352
|
const sch = extract.schemaName;
|
|
436
353
|
const rawRows = fetchSearchRows({
|
|
437
|
-
db: this
|
|
354
|
+
db: this.database,
|
|
438
355
|
schemaName: sch,
|
|
439
356
|
query,
|
|
440
357
|
placetypes,
|
|
@@ -453,7 +370,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
453
370
|
};
|
|
454
371
|
const candidates = rawRows.map((row) => candidateFromSearchRow(row, scoring));
|
|
455
372
|
rankCandidates(candidates, {
|
|
456
|
-
db: this
|
|
373
|
+
db: this.database,
|
|
457
374
|
schemaName: sch,
|
|
458
375
|
query,
|
|
459
376
|
weights: this.#weights,
|
|
@@ -465,11 +382,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
465
382
|
return !pts || pts.includes("locality");
|
|
466
383
|
}
|
|
467
384
|
/**
|
|
468
|
-
* Resolve the effective convention for a query
|
|
469
|
-
* by WOF polygon id; for #289 it carries just the country level — resolved from `query.country` via the cached
|
|
470
|
-
* code→WOF-id lookup — so the EU locales, which have no override rows, resolve to `WORLD_DEFAULT` and dispatch is
|
|
471
|
-
* byte-identical to the pre-engine path. E4 (JP) extends the chain with the resolved locality's `ancestors` row, so a
|
|
472
|
-
* region/locality-level convention (e.g. Sapporo's grid) deep-merges over the country one.
|
|
385
|
+
* Resolve the effective convention for a query, keyed by the country's WOF polygon id.
|
|
473
386
|
*/
|
|
474
387
|
#conventionFor(query) {
|
|
475
388
|
const chain = [];
|
|
@@ -482,8 +395,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
482
395
|
return resolveConvention(this.#conventionSource, chain);
|
|
483
396
|
}
|
|
484
397
|
/**
|
|
485
|
-
* Country ISO code
|
|
486
|
-
* country, then memoized (including a not-found `null`) so findPlace never pays for it twice.
|
|
398
|
+
* Country ISO code to its WOF polygon id, memoized including the not-found `null`.
|
|
487
399
|
*/
|
|
488
400
|
#countryWOFId(code) {
|
|
489
401
|
const cached = this.#countryWOFIdCache.get(code);
|
|
@@ -491,7 +403,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
491
403
|
return cached;
|
|
492
404
|
let id;
|
|
493
405
|
try {
|
|
494
|
-
const row = this
|
|
406
|
+
const row = this.database
|
|
495
407
|
.prepare(`SELECT id FROM main.spr WHERE placetype = 'country' AND country = ? AND is_current != 0 LIMIT 1`)
|
|
496
408
|
.get(code);
|
|
497
409
|
id = row?.id ?? null;
|
|
@@ -503,33 +415,28 @@ export class WOFSQLitePlaceLookup {
|
|
|
503
415
|
return id;
|
|
504
416
|
}
|
|
505
417
|
/**
|
|
506
|
-
* Coordinate-first locality resolution
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* so a small town the name-match never finds is recovered by the postcode, while an unambiguous name (Berlin) still
|
|
510
|
-
* wins on name + population. Returns null when the postcode isn't in the table (→ caller falls back to the FTS
|
|
511
|
-
* path).
|
|
418
|
+
* Coordinate-first locality resolution: union the postcode's containing
|
|
419
|
+
* and nearby localities with the FTS name candidates and soft-score them,
|
|
420
|
+
* returning null when the postcode is not in the table.
|
|
512
421
|
*/
|
|
513
422
|
async #findLocalityCoordFirst(query, sch, convention) {
|
|
514
423
|
const w = convention.scoringWeights;
|
|
515
424
|
const pc = query.postcode.trim();
|
|
516
425
|
const pcWhere = query.country ? "postcode = ? AND country = ?" : "postcode = ?";
|
|
517
426
|
const pcParams = query.country ? [pc, query.country] : [pc];
|
|
518
|
-
const pcRows = allRows(this
|
|
427
|
+
const pcRows = allRows(this.database.prepare(`SELECT locality_id AS id, aliases, distance_km AS dist, is_containing AS containing
|
|
519
428
|
FROM ${sch}.${POSTCODE_LOCALITY_TABLE} WHERE ${pcWhere}`), ...pcParams);
|
|
520
429
|
if (!pcRows.length)
|
|
521
430
|
return null;
|
|
522
431
|
const limit = query.limit ?? 10;
|
|
523
|
-
// Name-match candidates via the normal FTS path
|
|
432
|
+
// Name-match candidates via the normal FTS path with the postcode cleared, so this cannot recurse.
|
|
524
433
|
const ftsCands = await this.findPlace({ ...query, postcode: undefined, limit: Math.max(limit, 10) });
|
|
525
434
|
const pcInfo = new Map();
|
|
526
435
|
for (const r of pcRows) {
|
|
527
436
|
pcInfo.set(r.id, { dist: r.dist, containing: r.containing === 1, aliases: r.aliases ? r.aliases.split("|") : [] });
|
|
528
437
|
}
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
// alias for the geographic locality the postcode sits in ("Nashville"). Empty when the reader
|
|
532
|
-
// isn't supplied → the scoring loop below is byte-identical to pre-#475.
|
|
438
|
+
// Observed postal-city aliases for this postcode keyed by geographic locality name,
|
|
439
|
+
// empty when the reader is not supplied.
|
|
533
440
|
const postalAliasByGeo = new Map();
|
|
534
441
|
if (this.#postalCityAliases) {
|
|
535
442
|
for (const a of await this.#postalCityAliases.getDivergentAliases(pc)) {
|
|
@@ -557,9 +464,8 @@ export class WOFSQLitePlaceLookup {
|
|
|
557
464
|
for (const cand of merged.values()) {
|
|
558
465
|
const info = pcInfo.get(cand.id);
|
|
559
466
|
const sPc = info ? (info.containing ? 1 : Math.exp(-info.dist / CF_PC_DECAY_KM)) : 0;
|
|
560
|
-
// Fold
|
|
561
|
-
//
|
|
562
|
-
// reduces to the original `info?.aliases ?? []` and the score is unchanged.
|
|
467
|
+
// Fold postal-city aliases into the soft name match.
|
|
468
|
+
// The map is empty unless the opt-in reader was supplied, so scoring is unchanged when off.
|
|
563
469
|
const wofAliases = info?.aliases ?? [];
|
|
564
470
|
const aliases = postalAliasByGeo.size
|
|
565
471
|
? [...wofAliases, ...(postalAliasByGeo.get(cfNormalize(cand.name)) ?? [])]
|
|
@@ -568,24 +474,17 @@ export class WOFSQLitePlaceLookup {
|
|
|
568
474
|
const sPop = populationScaleTerm(cand.population, this.#weights);
|
|
569
475
|
scored.push({ ...cand, score: w.pc * sPc + w.name * sName + w.pop * sPop, exact: sName >= 1 });
|
|
570
476
|
}
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
// unambiguous city ("Berlin", exact + huge population) ahead of the fine-grained Ortsteil its
|
|
574
|
-
// postcode centroid lands in, while a small town the name-match never finds (no exact tier) is
|
|
575
|
-
// still recovered by its postcode's containing locality.
|
|
477
|
+
// An exact name or alias match tiers above coordinate-only candidates,
|
|
478
|
+
// with the soft score breaking ties within a tier.
|
|
576
479
|
scored.sort((a, b) => Number(b.exact) - Number(a.exact) || b.score - a.score);
|
|
577
|
-
//
|
|
578
|
-
// from it,
|
|
579
|
-
// We keep the name-chosen locality but flag it — the falsehood signal a BM25 geocoder can't give.
|
|
480
|
+
// When the chosen locality is not the postcode's containing locality and sits far
|
|
481
|
+
// from it, flag `mismatch` rather than overriding the name.
|
|
580
482
|
const top = scored[0];
|
|
581
483
|
if (top) {
|
|
582
|
-
//
|
|
583
|
-
// resolved (some — e.g. unnamed Ortsteile — are in the postcode table but not the admin DB),
|
|
584
|
-
// prefer the containing one, else the nearest. Postcodes whose centroid falls just outside
|
|
585
|
-
// every locality polygon still anchor to the closest town.
|
|
484
|
+
// Among the postcode's candidate localities that resolved, prefer the containing one, else the nearest.
|
|
586
485
|
const anchorRow = pcRows
|
|
587
486
|
.filter((r) => merged.has(r.id))
|
|
588
|
-
// oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array
|
|
487
|
+
// oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array. toSorted would double-allocate on a hot path
|
|
589
488
|
.sort((a, b) => b.containing - a.containing || a.dist - b.dist)[0];
|
|
590
489
|
const anchor = anchorRow ? merged.get(anchorRow.id) : undefined;
|
|
591
490
|
if (anchor &&
|
|
@@ -600,7 +499,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
600
499
|
});
|
|
601
500
|
}
|
|
602
501
|
/**
|
|
603
|
-
* Fetch locality spr rows
|
|
502
|
+
* Fetch locality spr rows from main for the postcode-injected candidate ids the FTS set missed.
|
|
604
503
|
*/
|
|
605
504
|
#fetchLocalitiesByID(ids) {
|
|
606
505
|
if (!ids.length)
|
|
@@ -609,7 +508,7 @@ export class WOFSQLitePlaceLookup {
|
|
|
609
508
|
const popSelect = hasPop ? `pp.population AS population` : `NULL AS population`;
|
|
610
509
|
const popJoin = hasPop ? `LEFT JOIN main.${PLACE_POPULATION_TABLE} pp ON pp.id = s.id` : "";
|
|
611
510
|
const ph = ids.map(() => "?").join(", ");
|
|
612
|
-
const rows = allRows(this
|
|
511
|
+
const rows = allRows(this.database.prepare(`SELECT s.id AS id, s.name AS name, s.country AS country, s.parent_id AS parent_id,
|
|
613
512
|
s.latitude AS lat, s.longitude AS lon, s.placetype AS placetype, ${popSelect}
|
|
614
513
|
FROM main.spr s ${popJoin}
|
|
615
514
|
WHERE s.id IN (${ph}) AND s.is_current != 0`), ...ids);
|
|
@@ -630,19 +529,14 @@ export class WOFSQLitePlaceLookup {
|
|
|
630
529
|
return c;
|
|
631
530
|
});
|
|
632
531
|
}
|
|
633
|
-
[Symbol.dispose]() {
|
|
634
|
-
// Only when we opened it. A caller who passed a pre-opened client keeps using it after this returns — the FTS
|
|
635
|
-
// build this lookup performed lives on their connection, and closing it would take that with us.
|
|
636
|
-
this.#resources[Symbol.dispose]();
|
|
637
|
-
}
|
|
638
532
|
/**
|
|
639
|
-
* Build the FTS5 virtual table from the `names`
|
|
533
|
+
* Build the FTS5 virtual table from the `names` and `places` tables.
|
|
640
534
|
*/
|
|
641
535
|
#ensureFTS() {
|
|
642
|
-
buildPlaceSearchFTS(this
|
|
536
|
+
buildPlaceSearchFTS(this.database);
|
|
643
537
|
}
|
|
644
538
|
#assertFTSExists() {
|
|
645
|
-
if (!placeSearchFTSExists(this
|
|
539
|
+
if (!placeSearchFTSExists(this.database)) {
|
|
646
540
|
throw new Error("WOFSQLitePlaceLookup: `place_search` FTS5 table is missing. Pass `buildFTS: true` to build it on open, or run `mailwoman gazetteer build fts <path-to-wof.db>` ahead of time (see resolver-wof-sqlite/README.md).");
|
|
647
541
|
}
|
|
648
542
|
}
|