@mailwoman/resolver-wof-sqlite 9.1.0 → 9.3.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 +36 -20
- package/lib/address/index.ts +9 -0
- package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +40 -28
- package/{address-point-schema.ts → lib/address/point-schema.ts} +34 -8
- package/lib/address/point.ts +280 -0
- package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
- package/{ancestry.ts → lib/ancestry/index.ts} +13 -8
- package/{build-candidate.ts → lib/build-candidate.ts} +243 -199
- package/{build-slim.ts → lib/build-slim.ts} +153 -168
- package/lib/candidate/alias-bags.ts +56 -0
- package/lib/candidate/ancestors-sidecar.ts +204 -0
- package/lib/candidate/country-display-names.ts +79 -0
- package/lib/candidate/extract-fold.ts +138 -0
- package/lib/candidate/name-roles.ts +238 -0
- package/lib/candidate/own-name.ts +146 -0
- package/lib/candidate/place-attrs.ts +44 -0
- package/lib/candidate-ancestors-schema.ts +195 -0
- package/{candidate-fts.ts → lib/candidate-fts.ts} +7 -5
- package/{candidate-importance.ts → lib/candidate-importance.ts} +54 -57
- package/lib/candidate-lookup.ts +976 -0
- package/{candidate-schema.ts → lib/candidate-schema.ts} +34 -6
- package/lib/candidate-scoring.ts +261 -0
- package/lib/capital-schema.ts +89 -0
- package/lib/capitals.ts +148 -0
- package/{coincident-roles.ts → lib/coincident-roles.ts} +75 -19
- package/{convention.ts → lib/convention/index.ts} +5 -5
- package/lib/convention/schema.ts +72 -0
- package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +15 -16
- package/lib/currency-backfill.ts +334 -0
- package/lib/env.ts +50 -0
- package/lib/exact-match.ts +113 -0
- package/{sharding.ts → lib/extracts.ts} +63 -60
- package/lib/fst/autocomplete.ts +176 -0
- package/{fst-builder.ts → lib/fst/builder.ts} +30 -27
- package/{fst-deserialize-web.ts → lib/fst/deserialize-web.ts} +27 -87
- package/lib/fst/format.ts +106 -0
- package/{fst-freshness.ts → lib/fst/freshness.ts} +42 -75
- package/lib/fst/index.ts +14 -0
- package/{fst-matcher.ts → lib/fst/matcher.ts} +1 -1
- package/{fst-serialize.ts → lib/fst/serialize.ts} +37 -115
- package/{fst-types.ts → lib/fst/types.ts} +4 -2
- package/{fts.ts → lib/fts/index.ts} +28 -23
- package/{fts-query.ts → lib/fts/query.ts} +2 -2
- package/{geonames-aliases.ts → lib/geonames/aliases.ts} +13 -13
- package/lib/geonames/index.ts +8 -0
- package/{geonames-postal.ts → lib/geonames/postal.ts} +16 -15
- package/{index.ts → lib/index.ts} +52 -48
- package/{interpolation.ts → lib/interpolation.ts} +137 -41
- package/lib/lookup.ts +861 -0
- package/{name-score.ts → lib/name-score.ts} +6 -4
- package/lib/nsul/index.ts +8 -0
- package/lib/nsul/lookup.ts +144 -0
- package/lib/nsul/schema.ts +130 -0
- package/{place-importance-schema.ts → lib/place-importance-schema.ts} +70 -22
- package/lib/poi/index.ts +8 -0
- package/{poi-lookup.ts → lib/poi/lookup.ts} +32 -39
- package/{poi-schema.ts → lib/poi/schema.ts} +10 -6
- package/lib/polygon-schema.ts +47 -0
- package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +15 -21
- package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +4 -2
- package/lib/postal/index.ts +9 -0
- package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
- package/lib/primary-preference.ts +226 -0
- package/lib/proximity-rerank.ts +120 -0
- package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
- package/lib/region-keys.ts +144 -0
- package/{reverse.ts → lib/reverse.ts} +45 -56
- package/{schema.ts → lib/schema.ts} +11 -1
- package/lib/search-fetch.ts +257 -0
- package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
- package/lib/sqlite-utils.ts +83 -0
- package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +12 -6
- package/{street-centroid.ts → lib/street/centroid.ts} +26 -28
- package/lib/street/index.ts +13 -0
- package/{street-morphology-fst-builder.ts → lib/street/morphology-fst-builder.ts} +24 -14
- package/{street-morphology-fst-loader.ts → lib/street/morphology-fst-loader.ts} +14 -12
- package/{street-name-lookup.ts → lib/street/name-lookup.ts} +14 -30
- package/lib/street/normalize.ts +572 -0
- package/{street-segment-schema.ts → lib/street/segment-schema.ts} +13 -8
- package/{types.ts → lib/types.ts} +49 -9
- package/{unified-schema.ts → lib/unified-schema.ts} +21 -25
- package/lib/uprn/existence.ts +84 -0
- package/lib/uprn/index.ts +9 -0
- package/lib/uprn/lookup.ts +206 -0
- package/lib/uprn/schema.ts +124 -0
- package/lib/weights-overlay-linker.ts +878 -0
- package/out/address/index.d.ts +9 -0
- package/out/address/index.d.ts.map +1 -0
- package/out/address/index.js +9 -0
- package/out/address/index.js.map +1 -0
- package/out/{address-point-interpolation.d.ts → address/point-interpolation.d.ts} +11 -10
- package/out/address/point-interpolation.d.ts.map +1 -0
- package/out/{address-point-interpolation.js → address/point-interpolation.js} +29 -23
- package/out/address/point-interpolation.js.map +1 -0
- package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +30 -10
- package/out/address/point-schema.d.ts.map +1 -0
- package/out/{address-point-schema.js → address/point-schema.js} +7 -3
- package/out/address/point-schema.js.map +1 -0
- package/out/{address-point.d.ts → address/point.d.ts} +16 -8
- package/out/address/point.d.ts.map +1 -0
- package/out/address/point.js +191 -0
- package/out/address/point.js.map +1 -0
- package/out/{ancestry-backfill.d.ts → ancestry/backfill.d.ts} +7 -5
- package/out/ancestry/backfill.d.ts.map +1 -0
- package/out/{ancestry-backfill.js → ancestry/backfill.js} +12 -16
- package/out/ancestry/backfill.js.map +1 -0
- package/out/{ancestry.d.ts → ancestry/index.d.ts} +6 -5
- package/out/ancestry/index.d.ts.map +1 -0
- package/out/{ancestry.js → ancestry/index.js} +7 -7
- package/out/ancestry/index.js.map +1 -0
- package/out/build-candidate.d.ts +88 -7
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +145 -149
- package/out/build-candidate.js.map +1 -1
- package/out/build-slim.d.ts +5 -5
- package/out/build-slim.d.ts.map +1 -1
- package/out/build-slim.js +127 -142
- package/out/build-slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +19 -0
- package/out/candidate/alias-bags.d.ts.map +1 -0
- package/out/candidate/alias-bags.js +39 -0
- package/out/candidate/alias-bags.js.map +1 -0
- package/out/candidate/ancestors-sidecar.d.ts +32 -0
- package/out/candidate/ancestors-sidecar.d.ts.map +1 -0
- package/out/candidate/ancestors-sidecar.js +140 -0
- package/out/candidate/ancestors-sidecar.js.map +1 -0
- package/out/candidate/country-display-names.d.ts +35 -0
- package/out/candidate/country-display-names.d.ts.map +1 -0
- package/out/candidate/country-display-names.js +59 -0
- package/out/candidate/country-display-names.js.map +1 -0
- package/out/candidate/extract-fold.d.ts +32 -0
- package/out/candidate/extract-fold.d.ts.map +1 -0
- package/out/candidate/extract-fold.js +104 -0
- package/out/candidate/extract-fold.js.map +1 -0
- package/out/candidate/name-roles.d.ts +57 -0
- package/out/candidate/name-roles.d.ts.map +1 -0
- package/out/candidate/name-roles.js +166 -0
- package/out/candidate/name-roles.js.map +1 -0
- package/out/candidate/own-name.d.ts +50 -0
- package/out/candidate/own-name.d.ts.map +1 -0
- package/out/candidate/own-name.js +132 -0
- package/out/candidate/own-name.js.map +1 -0
- package/out/candidate/place-attrs.d.ts +43 -0
- package/out/candidate/place-attrs.d.ts.map +1 -0
- package/out/candidate/place-attrs.js +15 -0
- package/out/candidate/place-attrs.js.map +1 -0
- package/out/candidate-ancestors-schema.d.ts +150 -0
- package/out/candidate-ancestors-schema.d.ts.map +1 -0
- package/out/candidate-ancestors-schema.js +123 -0
- package/out/candidate-ancestors-schema.js.map +1 -0
- package/out/candidate-fts.d.ts +7 -5
- package/out/candidate-fts.d.ts.map +1 -1
- package/out/candidate-fts.js +5 -3
- package/out/candidate-fts.js.map +1 -1
- package/out/candidate-importance.d.ts +16 -16
- package/out/candidate-importance.d.ts.map +1 -1
- package/out/candidate-importance.js +47 -52
- package/out/candidate-importance.js.map +1 -1
- package/out/candidate-lookup.d.ts +31 -52
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +380 -172
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +31 -7
- package/out/candidate-schema.d.ts.map +1 -1
- package/out/candidate-schema.js +3 -0
- package/out/candidate-schema.js.map +1 -1
- package/out/candidate-scoring.d.ts +34 -0
- package/out/candidate-scoring.d.ts.map +1 -0
- package/out/candidate-scoring.js +196 -0
- package/out/candidate-scoring.js.map +1 -0
- package/out/capital-schema.d.ts +51 -0
- package/out/capital-schema.d.ts.map +1 -0
- package/out/capital-schema.js +63 -0
- package/out/capital-schema.js.map +1 -0
- package/out/capitals.d.ts +69 -0
- package/out/capitals.d.ts.map +1 -0
- package/out/capitals.js +98 -0
- package/out/capitals.js.map +1 -0
- package/out/coincident-roles.d.ts +12 -4
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +43 -11
- package/out/coincident-roles.js.map +1 -1
- package/out/{convention.d.ts → convention/index.d.ts} +5 -5
- package/out/convention/index.d.ts.map +1 -0
- package/out/{convention.js → convention/index.js} +4 -4
- package/out/convention/index.js.map +1 -0
- package/out/convention/schema.d.ts +51 -0
- package/out/convention/schema.d.ts.map +1 -0
- package/out/convention/schema.js +34 -0
- package/out/convention/schema.js.map +1 -0
- package/out/coverage-manifest-schema.d.ts +8 -8
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +6 -10
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +90 -0
- package/out/currency-backfill.d.ts.map +1 -0
- package/out/currency-backfill.js +196 -0
- package/out/currency-backfill.js.map +1 -0
- package/out/env.d.ts +43 -0
- package/out/env.d.ts.map +1 -0
- package/out/env.js +48 -0
- package/out/env.js.map +1 -0
- package/out/exact-match.d.ts +25 -0
- package/out/exact-match.d.ts.map +1 -0
- package/out/exact-match.js +88 -0
- package/out/exact-match.js.map +1 -0
- package/out/extracts.d.ts +114 -0
- package/out/extracts.d.ts.map +1 -0
- package/out/{sharding.js → extracts.js} +38 -38
- package/out/extracts.js.map +1 -0
- package/out/{fst-autocomplete.d.ts → fst/autocomplete.d.ts} +13 -13
- package/out/fst/autocomplete.d.ts.map +1 -0
- package/out/fst/autocomplete.js +113 -0
- package/out/fst/autocomplete.js.map +1 -0
- package/out/{fst-builder.d.ts → fst/builder.d.ts} +5 -5
- package/out/fst/builder.d.ts.map +1 -0
- package/out/{fst-builder.js → fst/builder.js} +24 -24
- package/out/fst/builder.js.map +1 -0
- package/out/{fst-deserialize-web.d.ts → fst/deserialize-web.d.ts} +3 -3
- package/out/fst/deserialize-web.d.ts.map +1 -0
- package/out/{fst-deserialize-web.js → fst/deserialize-web.js} +10 -74
- package/out/fst/deserialize-web.js.map +1 -0
- package/out/fst/format.d.ts +80 -0
- package/out/fst/format.d.ts.map +1 -0
- package/out/fst/format.js +91 -0
- package/out/fst/format.js.map +1 -0
- package/out/{fst-freshness.d.ts → fst/freshness.d.ts} +11 -14
- package/out/fst/freshness.d.ts.map +1 -0
- package/out/{fst-freshness.js → fst/freshness.js} +42 -74
- package/out/fst/freshness.js.map +1 -0
- package/out/fst/index.d.ts +14 -0
- package/out/fst/index.d.ts.map +1 -0
- package/out/fst/index.js +14 -0
- package/out/fst/index.js.map +1 -0
- package/out/{fst-matcher.d.ts → fst/matcher.d.ts} +2 -2
- package/out/fst/matcher.d.ts.map +1 -0
- package/out/{fst-matcher.js → fst/matcher.js} +1 -1
- package/out/fst/matcher.js.map +1 -0
- package/out/{fst-serialize.d.ts → fst/serialize.d.ts} +5 -10
- package/out/fst/serialize.d.ts.map +1 -0
- package/out/{fst-serialize.js → fst/serialize.js} +18 -98
- package/out/fst/serialize.js.map +1 -0
- package/out/{fst-types.d.ts → fst/types.d.ts} +4 -3
- package/out/fst/types.d.ts.map +1 -0
- package/out/{fst-types.js → fst/types.js} +1 -1
- package/out/fst/types.js.map +1 -0
- package/out/{fts.d.ts → fts/index.d.ts} +20 -13
- package/out/fts/index.d.ts.map +1 -0
- package/out/{fts.js → fts/index.js} +20 -15
- package/out/fts/index.js.map +1 -0
- package/out/{fts-query.d.ts → fts/query.d.ts} +2 -2
- package/out/fts/query.d.ts.map +1 -0
- package/out/{fts-query.js → fts/query.js} +2 -2
- package/out/fts/query.js.map +1 -0
- package/out/{geonames-aliases.d.ts → geonames/aliases.d.ts} +8 -6
- package/out/geonames/aliases.d.ts.map +1 -0
- package/out/{geonames-aliases.js → geonames/aliases.js} +7 -7
- package/out/geonames/aliases.js.map +1 -0
- package/out/geonames/index.d.ts +8 -0
- package/out/geonames/index.d.ts.map +1 -0
- package/out/geonames/index.js +8 -0
- package/out/geonames/index.js.map +1 -0
- package/out/{geonames-postal.d.ts → geonames/postal.d.ts} +12 -10
- package/out/geonames/postal.d.ts.map +1 -0
- package/out/{geonames-postal.js → geonames/postal.js} +11 -11
- package/out/geonames/postal.js.map +1 -0
- package/out/index.d.ts +32 -31
- package/out/index.d.ts.map +1 -1
- package/out/index.js +24 -24
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +18 -9
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +108 -36
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +25 -25
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +174 -558
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +0 -10
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +6 -4
- package/out/name-score.js.map +1 -1
- package/out/nsul/index.d.ts +8 -0
- package/out/nsul/index.d.ts.map +1 -0
- package/out/nsul/index.js +8 -0
- package/out/nsul/index.js.map +1 -0
- package/out/nsul/lookup.d.ts +83 -0
- package/out/nsul/lookup.d.ts.map +1 -0
- package/out/nsul/lookup.js +80 -0
- package/out/nsul/lookup.js.map +1 -0
- package/out/nsul/schema.d.ts +100 -0
- package/out/nsul/schema.d.ts.map +1 -0
- package/out/nsul/schema.js +78 -0
- package/out/nsul/schema.js.map +1 -0
- package/out/place-importance-schema.d.ts +47 -10
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +56 -10
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/index.d.ts +8 -0
- package/out/poi/index.d.ts.map +1 -0
- package/out/poi/index.js +8 -0
- package/out/poi/index.js.map +1 -0
- package/out/{poi-lookup.d.ts → poi/lookup.d.ts} +8 -10
- package/out/poi/lookup.d.ts.map +1 -0
- package/out/{poi-lookup.js → poi/lookup.js} +24 -29
- package/out/poi/lookup.js.map +1 -0
- package/out/{poi-schema.d.ts → poi/schema.d.ts} +10 -6
- package/out/poi/schema.d.ts.map +1 -0
- package/out/{poi-schema.js → poi/schema.js} +1 -1
- package/out/poi/schema.js.map +1 -0
- package/out/polygon-schema.d.ts +37 -0
- package/out/polygon-schema.d.ts.map +1 -0
- package/out/polygon-schema.js +23 -0
- package/out/polygon-schema.js.map +1 -0
- package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +7 -6
- package/out/postal/city-alias-lookup.d.ts.map +1 -0
- package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +12 -16
- package/out/postal/city-alias-lookup.js.map +1 -0
- package/out/{postal-city-alias-schema.d.ts → postal/city-alias-schema.d.ts} +1 -1
- package/out/{postal-city-alias-schema.d.ts.map → postal/city-alias-schema.d.ts.map} +1 -1
- package/out/{postal-city-alias-schema.js → postal/city-alias-schema.js} +1 -1
- package/out/{postal-city-alias-schema.js.map → postal/city-alias-schema.js.map} +1 -1
- package/out/{postal-city-candidate-schema.d.ts → postal/city-candidate-schema.d.ts} +4 -3
- package/out/postal/city-candidate-schema.d.ts.map +1 -0
- package/out/{postal-city-candidate-schema.js → postal/city-candidate-schema.js} +2 -2
- package/out/{postal-city-candidate-schema.js.map → postal/city-candidate-schema.js.map} +1 -1
- package/out/postal/index.d.ts +9 -0
- package/out/postal/index.d.ts.map +1 -0
- package/out/postal/index.js +9 -0
- package/out/postal/index.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +10 -10
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +13 -13
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +133 -0
- package/out/primary-preference.d.ts.map +1 -0
- package/out/primary-preference.js +146 -0
- package/out/primary-preference.js.map +1 -0
- package/out/proximity-rerank.d.ts +77 -0
- package/out/proximity-rerank.d.ts.map +1 -0
- package/out/proximity-rerank.js +86 -0
- package/out/proximity-rerank.js.map +1 -0
- package/out/ranking-weights.d.ts +15 -3
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +21 -3
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +47 -0
- package/out/region-keys.d.ts.map +1 -0
- package/out/region-keys.js +121 -0
- package/out/region-keys.js.map +1 -0
- package/out/reverse.d.ts +6 -6
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +23 -35
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +10 -1
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +59 -0
- package/out/search-fetch.d.ts.map +1 -0
- package/out/search-fetch.js +184 -0
- package/out/search-fetch.js.map +1 -0
- package/out/sqlite-convention-source.d.ts +7 -7
- package/out/sqlite-convention-source.d.ts.map +1 -1
- package/out/sqlite-convention-source.js +5 -5
- package/out/sqlite-convention-source.js.map +1 -1
- package/out/sqlite-utils.d.ts +24 -6
- package/out/sqlite-utils.d.ts.map +1 -1
- package/out/sqlite-utils.js +24 -7
- package/out/sqlite-utils.js.map +1 -1
- package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +12 -7
- package/out/street/centroid-schema.d.ts.map +1 -0
- package/out/{street-centroid-schema.js → street/centroid-schema.js} +5 -5
- package/out/{street-centroid-schema.js.map → street/centroid-schema.js.map} +1 -1
- package/out/{street-centroid.d.ts → street/centroid.d.ts} +6 -6
- package/out/street/centroid.d.ts.map +1 -0
- package/out/{street-centroid.js → street/centroid.js} +18 -24
- package/out/street/centroid.js.map +1 -0
- package/out/street/index.d.ts +13 -0
- package/out/street/index.d.ts.map +1 -0
- package/out/street/index.js +13 -0
- package/out/street/index.js.map +1 -0
- package/out/{street-morphology-fst-builder.d.ts → street/morphology-fst-builder.d.ts} +4 -4
- package/out/street/morphology-fst-builder.d.ts.map +1 -0
- package/out/{street-morphology-fst-builder.js → street/morphology-fst-builder.js} +18 -13
- package/out/street/morphology-fst-builder.js.map +1 -0
- package/out/{street-morphology-fst-loader.d.ts → street/morphology-fst-loader.d.ts} +5 -5
- package/out/street/morphology-fst-loader.d.ts.map +1 -0
- package/out/{street-morphology-fst-loader.js → street/morphology-fst-loader.js} +11 -10
- package/out/street/morphology-fst-loader.js.map +1 -0
- package/out/{street-name-lookup.d.ts → street/name-lookup.d.ts} +4 -7
- package/out/street/name-lookup.d.ts.map +1 -0
- package/out/{street-name-lookup.js → street/name-lookup.js} +9 -23
- package/out/street/name-lookup.js.map +1 -0
- package/out/street/normalize.d.ts +179 -0
- package/out/street/normalize.d.ts.map +1 -0
- package/out/street/normalize.js +457 -0
- package/out/street/normalize.js.map +1 -0
- package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +13 -9
- package/out/street/segment-schema.d.ts.map +1 -0
- package/out/{street-segment-schema.js → street/segment-schema.js} +4 -4
- package/out/{street-segment-schema.js.map → street/segment-schema.js.map} +1 -1
- package/out/types.d.ts +49 -9
- package/out/types.d.ts.map +1 -1
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +6 -5
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +16 -19
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +53 -0
- package/out/uprn/existence.d.ts.map +1 -0
- package/out/uprn/existence.js +61 -0
- package/out/uprn/existence.js.map +1 -0
- package/out/uprn/index.d.ts +9 -0
- package/out/uprn/index.d.ts.map +1 -0
- package/out/uprn/index.js +9 -0
- package/out/uprn/index.js.map +1 -0
- package/out/uprn/lookup.d.ts +85 -0
- package/out/uprn/lookup.d.ts.map +1 -0
- package/out/uprn/lookup.js +149 -0
- package/out/uprn/lookup.js.map +1 -0
- package/out/uprn/schema.d.ts +93 -0
- package/out/uprn/schema.d.ts.map +1 -0
- package/out/uprn/schema.js +78 -0
- package/out/uprn/schema.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +299 -0
- package/out/weights-overlay-linker.d.ts.map +1 -0
- package/out/weights-overlay-linker.js +511 -0
- package/out/weights-overlay-linker.js.map +1 -0
- package/package.json +622 -154
- package/address-point.ts +0 -125
- package/candidate-lookup.ts +0 -720
- package/fst-autocomplete.ts +0 -204
- package/geo.ts +0 -121
- package/lookup.ts +0 -1354
- 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 +0 -84
- package/out/address-point.js.map +0 -1
- package/out/ancestry-backfill.d.ts.map +0 -1
- package/out/ancestry-backfill.js.map +0 -1
- package/out/ancestry.d.ts.map +0 -1
- package/out/ancestry.js.map +0 -1
- package/out/convention.d.ts.map +0 -1
- package/out/convention.js.map +0 -1
- package/out/fst-autocomplete.d.ts.map +0 -1
- package/out/fst-autocomplete.js +0 -130
- package/out/fst-autocomplete.js.map +0 -1
- package/out/fst-builder.d.ts.map +0 -1
- package/out/fst-builder.js.map +0 -1
- package/out/fst-deserialize-web.d.ts.map +0 -1
- package/out/fst-deserialize-web.js.map +0 -1
- package/out/fst-freshness.d.ts.map +0 -1
- package/out/fst-freshness.js.map +0 -1
- package/out/fst-matcher.d.ts.map +0 -1
- package/out/fst-matcher.js.map +0 -1
- package/out/fst-serialize.d.ts.map +0 -1
- package/out/fst-serialize.js.map +0 -1
- package/out/fst-types.d.ts.map +0 -1
- package/out/fst-types.js.map +0 -1
- package/out/fts-query.d.ts.map +0 -1
- package/out/fts-query.js.map +0 -1
- package/out/fts.d.ts.map +0 -1
- package/out/fts.js.map +0 -1
- package/out/geo.d.ts +0 -74
- package/out/geo.d.ts.map +0 -1
- package/out/geo.js +0 -71
- package/out/geo.js.map +0 -1
- package/out/geonames-aliases.d.ts.map +0 -1
- package/out/geonames-aliases.js.map +0 -1
- package/out/geonames-postal.d.ts.map +0 -1
- package/out/geonames-postal.js.map +0 -1
- package/out/poi-lookup.d.ts.map +0 -1
- package/out/poi-lookup.js.map +0 -1
- package/out/poi-schema.d.ts.map +0 -1
- package/out/poi-schema.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-candidate-schema.d.ts.map +0 -1
- package/out/sharding.d.ts +0 -114
- package/out/sharding.d.ts.map +0 -1
- package/out/sharding.js.map +0 -1
- package/out/street-centroid-schema.d.ts.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/out/street-name-lookup.d.ts.map +0 -1
- package/out/street-name-lookup.js.map +0 -1
- package/out/street-normalize.d.ts +0 -95
- package/out/street-normalize.d.ts.map +0 -1
- package/out/street-normalize.js +0 -272
- package/out/street-normalize.js.map +0 -1
- package/out/street-segment-schema.d.ts.map +0 -1
- package/sqlite-utils.ts +0 -46
- package/street-normalize.ts +0 -318
- /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The FTS5 candidate fetch behind the fuzzy name match: the schema-qualified `place_search`
|
|
7
|
+
* MATCH, its population-ordered companion fetch, and the raw row shape both of them return.
|
|
8
|
+
*
|
|
9
|
+
* Bbox and near-with-radius narrow at the SQL level through SQLite's built-in `rtree`, whose index name and schema
|
|
10
|
+
* live in `fts.ts` beside the FTS5 build. That is why this package pulls neither SpatiaLite nor turf: the R*Tree does
|
|
11
|
+
* the narrowing, and the passes downstream of it operate on ≤ a few hundred candidates per query rather than the
|
|
12
|
+
* whole corpus, so an exact haversine over the survivors is cheap.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { bboxAround } from "@mailwoman/spatial"
|
|
16
|
+
import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
|
|
17
|
+
|
|
18
|
+
import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "#fts/index"
|
|
19
|
+
import type { RankingWeights } from "#ranking-weights"
|
|
20
|
+
import { allRows } from "#sqlite-utils"
|
|
21
|
+
import type { FindPlaceQuery, WOFPlacetype } from "#types"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Query length at or below which the FTS window is widened. A two- or three-character query is almost always a region
|
|
25
|
+
* abbreviation, where the exact match can otherwise fall outside the window behind higher-bm25 partial hits — "NY"
|
|
26
|
+
* losing to "New York".
|
|
27
|
+
*/
|
|
28
|
+
const SHORT_QUERY_MAX_LENGTH = 3
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Over-fetch floor for SHORT (≤3-char) queries — region abbreviations like "NY"/"VT". An exact-abbrev holder's BM25 is
|
|
32
|
+
* poor (long multilingual alt-name document), so the normal `limit * 4` window can drop it before `exactMatchTiering`
|
|
33
|
+
* promotes it. 200 comfortably covers every same-abbrev region across the 12-country gazetteer (a 2-letter token
|
|
34
|
+
* matches a few dozen regions at most) while staying a cheap region-placetype fetch. See the `#fuzzyNameMatch`
|
|
35
|
+
* over-fetch comment.
|
|
36
|
+
*/
|
|
37
|
+
const SHORT_QUERY_OVERFETCH = 200
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How many rows the population-ordered companion fetch (#905) adds to the candidate pool. Small on purpose: its only
|
|
41
|
+
* job is to guarantee the FAMOUS holders of a name enter the pool at all — for "Paris"-class floods the bm25 window is
|
|
42
|
+
* saturated by thousands of tiny same-name rows and no boost inside the bm25-based ORDER BY can rescue a candidate
|
|
43
|
+
* whose bm25 is length-poisoned by ~15 points (see the fetch-site comment).
|
|
44
|
+
*/
|
|
45
|
+
const POPULATION_FETCH_LIMIT = 15
|
|
46
|
+
|
|
47
|
+
export interface RawSearchRow {
|
|
48
|
+
id: number
|
|
49
|
+
name: string
|
|
50
|
+
placetype: string
|
|
51
|
+
country: string | null
|
|
52
|
+
parent_id: number | null
|
|
53
|
+
rank: number // BM25 (lower = better in SQLite); we negate to get higher-is-better
|
|
54
|
+
lat: number | null
|
|
55
|
+
lon: number | null
|
|
56
|
+
min_latitude: number | null
|
|
57
|
+
max_latitude: number | null
|
|
58
|
+
min_longitude: number | null
|
|
59
|
+
max_longitude: number | null
|
|
60
|
+
population: number | null // from the place_population aux table; null when missing
|
|
61
|
+
/**
|
|
62
|
+
* From `place_importance.encyclopedic` when the extract's table carries the two-score split columns. NULL means the
|
|
63
|
+
* place has no Wikipedia article, or the extract predates the split — absence either way, and never 0 (ROAD_TO_V9
|
|
64
|
+
* §2).
|
|
65
|
+
*/
|
|
66
|
+
encyclopedic: number | null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fetch the raw candidate rows for a name match on one extract: the BM25-ordered window over `place_search` (widened
|
|
71
|
+
* for short queries), plus the population-ordered companion fetch that keeps the prominent holders of a name
|
|
72
|
+
* pool-complete. `schemaName` is the routed extract's bare schema name — validated at construction, so it is
|
|
73
|
+
* interpolated directly.
|
|
74
|
+
*/
|
|
75
|
+
export function fetchSearchRows<DB>(options: {
|
|
76
|
+
db: DatabaseClient<DB>
|
|
77
|
+
schemaName: string
|
|
78
|
+
query: FindPlaceQuery
|
|
79
|
+
placetypes: WOFPlacetype[] | null
|
|
80
|
+
ftsQuery: string
|
|
81
|
+
limit: number
|
|
82
|
+
hasBboxIndex: ReadonlyMap<string, boolean>
|
|
83
|
+
hasPopulationIndex: ReadonlyMap<string, boolean>
|
|
84
|
+
encyclopedicClauses: ReadonlyMap<string, { select: string; join: string }>
|
|
85
|
+
weights: RankingWeights
|
|
86
|
+
}): RawSearchRow[] {
|
|
87
|
+
const {
|
|
88
|
+
db,
|
|
89
|
+
schemaName: sch,
|
|
90
|
+
query,
|
|
91
|
+
placetypes,
|
|
92
|
+
ftsQuery,
|
|
93
|
+
limit,
|
|
94
|
+
hasBboxIndex,
|
|
95
|
+
hasPopulationIndex,
|
|
96
|
+
encyclopedicClauses,
|
|
97
|
+
weights,
|
|
98
|
+
} = options
|
|
99
|
+
|
|
100
|
+
// Over-fetch so post-scoring + exact-match tiering have room to re-rank. SHORT queries (a 2–3-char
|
|
101
|
+
// region abbreviation like "NY"/"VT") are the danger case the `exactMatchTiering` docstring flags:
|
|
102
|
+
// the exact-abbrev holder's BM25 is poor (its long multilingual alt-name document tanks the score),
|
|
103
|
+
// so under the normal `limit * 4` window it drops OUT of the candidate pool BEFORE tiering can
|
|
104
|
+
// promote it — "NY" then resolves to a token-matching foreign region (Highland, GB) instead of New
|
|
105
|
+
// York. Widen the window for short queries so the exact match is always present to be tiered.
|
|
106
|
+
// (Cross-country abbrev collisions — "VT" is BOTH Vermont and Viterbo — still need a country/
|
|
107
|
+
// postcode signal to disambiguate; this only rescues the window-drop class, not genuine ambiguity.
|
|
108
|
+
// With a `country` hint every abbrev resolves; bare + no-context lifts 7→10/15 US states.)
|
|
109
|
+
const ftsLimit =
|
|
110
|
+
query.text.trim().length <= SHORT_QUERY_MAX_LENGTH ? Math.max(limit * 4, SHORT_QUERY_OVERFETCH) : limit * 4
|
|
111
|
+
|
|
112
|
+
// Filter out historical / superseded / deprecated places by default — they live in the same
|
|
113
|
+
// spr table but should never win a contemporary lookup. `is_current = 0` is the only WOF
|
|
114
|
+
// value that means "not current"; both `-1` (modern) and `1` (legacy) mean current. See #91.
|
|
115
|
+
// Note: with schema-qualified FROM the bare `place_search` reference in MATCH resolves to
|
|
116
|
+
// the FROM table — required by FTS5 parser, see extracts.ts header comment.
|
|
117
|
+
const where: string[] = ["place_search MATCH ?", "spr.is_current != 0", "spr.is_deprecated = 0"]
|
|
118
|
+
const params: SQLInputValue[] = [ftsQuery]
|
|
119
|
+
|
|
120
|
+
if (placetypes && placetypes.length) {
|
|
121
|
+
where.push(`spr.placetype IN (${placetypes.map(() => "?").join(", ")})`)
|
|
122
|
+
params.push(...placetypes)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (query.country) {
|
|
126
|
+
where.push("spr.country = ?")
|
|
127
|
+
params.push(query.country)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (query.parentID !== undefined) {
|
|
131
|
+
where.push(`(spr.parent_id = ? OR spr.id IN (SELECT id FROM ${sch}.ancestors WHERE ancestor_id = ?))`)
|
|
132
|
+
params.push(query.parentID, query.parentID)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Bbox + near-with-radius are SQL-level filters via the R*Tree. We only emit the JOIN when
|
|
136
|
+
// the active extract has the R*Tree; missing-but-requested is silently treated as no-bbox-
|
|
137
|
+
// filter so legacy DBs / extracts-without-bbox don't crash.
|
|
138
|
+
const extractHasBbox = hasBboxIndex.get(sch) === true
|
|
139
|
+
const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) && extractHasBbox
|
|
140
|
+
let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
|
|
141
|
+
|
|
142
|
+
if (useBboxJoin) {
|
|
143
|
+
joinClause += ` JOIN ${sch}.${PLACE_BBOX_TABLE} bbox ON bbox.id = spr.id`
|
|
144
|
+
// AABB intersection — both bbox sides must overlap. R*Tree handles this in O(log n).
|
|
145
|
+
const filterBox = query.bbox || bboxAround(query.near!.lat, query.near!.lon, query.near!.maxDistanceKm!)
|
|
146
|
+
where.push("bbox.min_lat <= ? AND bbox.max_lat >= ?", "bbox.min_lon <= ? AND bbox.max_lon >= ?")
|
|
147
|
+
params.push(filterBox.maxLat, filterBox.minLat, filterBox.maxLon, filterBox.minLon)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// LEFT JOIN the population aux table when present. Missing-on-this-extract means the SELECT
|
|
151
|
+
// just doesn't include the population column; the post-scoring loop treats it as 0.
|
|
152
|
+
const extractHasPopulation = hasPopulationIndex.get(sch) === true
|
|
153
|
+
|
|
154
|
+
const populationSelect = extractHasPopulation
|
|
155
|
+
? `${PLACE_POPULATION_TABLE}.population AS population`
|
|
156
|
+
: `NULL AS population`
|
|
157
|
+
|
|
158
|
+
const populationJoin = extractHasPopulation
|
|
159
|
+
? `LEFT JOIN ${sch}.${PLACE_POPULATION_TABLE} ON ${PLACE_POPULATION_TABLE}.id = spr.id`
|
|
160
|
+
: ""
|
|
161
|
+
|
|
162
|
+
// The encyclopedic score is CARRIED, never ranked on (ROAD_TO_V9 §2, ratified 2026-08-06) — it
|
|
163
|
+
// appears in the SELECT and in no ORDER BY, here or in the companion fetch below. Conditioned on the
|
|
164
|
+
// split column, so a pre-split extract emits a literal NULL and builds no join at all.
|
|
165
|
+
const { select: encyclopedicSelect, join: encyclopedicJoin } = encyclopedicClauses.get(sch)!
|
|
166
|
+
|
|
167
|
+
// Push the population boost into the ORDER BY when the index is available, so famous places
|
|
168
|
+
// (whose long alt-name lists hurt BM25) actually make it into the over-fetch window. The TS
|
|
169
|
+
// post-scoring will still compute the same boost for the final score; this just ensures the
|
|
170
|
+
// candidate set is right.
|
|
171
|
+
//
|
|
172
|
+
// Formula: rank_adjusted = bm25 - populationBoost * min(1.0, log10(1 + pop) / scaleLog10)
|
|
173
|
+
// Lower rank_adjusted = better (matches SQLite's bm25 convention of "more negative = better").
|
|
174
|
+
//
|
|
175
|
+
// #905 — do NOT reach for bm25 column weights here. Measured falsification (2026-07-02): FTS5's
|
|
176
|
+
// bm25 length normalization is polluted by the row's TOTAL document size, so identical 1-token
|
|
177
|
+
// `name` docs read −16.0 (empty alt_names) vs −0.43 (2.7 KB alt_names) EVEN with the alt_names
|
|
178
|
+
// column weighted to zero — no weighting isolates name relevance in this schema. The famous-
|
|
179
|
+
// holder guarantee lives in the population-ordered companion fetch below instead, and the
|
|
180
|
+
// exact tier breaks ties by population in the post-scoring sort.
|
|
181
|
+
const orderByExpr = extractHasPopulation
|
|
182
|
+
? `(bm25(place_search) - ? * MIN(1.0, COALESCE(log10(1.0 + ${PLACE_POPULATION_TABLE}.population), 0) / ?))`
|
|
183
|
+
: "bm25(place_search)"
|
|
184
|
+
|
|
185
|
+
// Schema-qualified FROM with bare-name MATCH — required syntax for FTS5 on attached schemas.
|
|
186
|
+
// See extracts.ts header for the failure mode that drove this design.
|
|
187
|
+
const stmt = db.prepare(`
|
|
188
|
+
SELECT
|
|
189
|
+
spr.id AS id,
|
|
190
|
+
spr.name,
|
|
191
|
+
spr.placetype,
|
|
192
|
+
spr.country,
|
|
193
|
+
spr.parent_id,
|
|
194
|
+
bm25(place_search) AS rank,
|
|
195
|
+
spr.latitude AS lat,
|
|
196
|
+
spr.longitude AS lon,
|
|
197
|
+
spr.min_latitude, spr.max_latitude, spr.min_longitude, spr.max_longitude,
|
|
198
|
+
${populationSelect},
|
|
199
|
+
${encyclopedicSelect}
|
|
200
|
+
FROM ${sch}.place_search
|
|
201
|
+
${joinClause}
|
|
202
|
+
${populationJoin}
|
|
203
|
+
${encyclopedicJoin}
|
|
204
|
+
WHERE ${where.join(" AND ")}
|
|
205
|
+
ORDER BY ${orderByExpr} ASC
|
|
206
|
+
LIMIT ?
|
|
207
|
+
`)
|
|
208
|
+
|
|
209
|
+
if (extractHasPopulation) {
|
|
210
|
+
params.push(weights.populationBoost, weights.populationScaleLog10)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
params.push(ftsLimit)
|
|
214
|
+
|
|
215
|
+
const rawRows = allRows<RawSearchRow>(stmt, ...params)
|
|
216
|
+
|
|
217
|
+
// #905 companion fetch: the same MATCH, ordered by population alone. For name floods
|
|
218
|
+
// ("Paris" matches thousands of gap-fill villages) the bm25-based window above cannot admit
|
|
219
|
+
// the famous holder — its bm25 is length-poisoned by the row's alias bulk (measured ~15 pts,
|
|
220
|
+
// vs a +4.0 boost cap), so FR Paris never even reaches post-scoring. This fetch makes the
|
|
221
|
+
// prominent holders of a name pool-complete BY CONSTRUCTION; the exact-tier sort below
|
|
222
|
+
// decides whether they win. Skipped without a population index (nothing to order by).
|
|
223
|
+
if (extractHasPopulation) {
|
|
224
|
+
const popStmt = db.prepare(`
|
|
225
|
+
SELECT
|
|
226
|
+
spr.id AS id,
|
|
227
|
+
spr.name,
|
|
228
|
+
spr.placetype,
|
|
229
|
+
spr.country,
|
|
230
|
+
spr.parent_id,
|
|
231
|
+
bm25(place_search) AS rank,
|
|
232
|
+
spr.latitude AS lat,
|
|
233
|
+
spr.longitude AS lon,
|
|
234
|
+
spr.min_latitude, spr.max_latitude, spr.min_longitude, spr.max_longitude,
|
|
235
|
+
${populationSelect},
|
|
236
|
+
${encyclopedicSelect}
|
|
237
|
+
FROM ${sch}.place_search
|
|
238
|
+
${joinClause}
|
|
239
|
+
${populationJoin}
|
|
240
|
+
${encyclopedicJoin}
|
|
241
|
+
WHERE ${where.join(" AND ")}
|
|
242
|
+
ORDER BY COALESCE(${PLACE_POPULATION_TABLE}.population, 0) DESC
|
|
243
|
+
LIMIT ?
|
|
244
|
+
`)
|
|
245
|
+
|
|
246
|
+
const popParams = params.slice(0, -3) // drop the two boost params + ftsLimit
|
|
247
|
+
const seen = new Set(rawRows.map((r) => r.id))
|
|
248
|
+
|
|
249
|
+
for (const row of allRows<RawSearchRow>(popStmt, ...popParams, POPULATION_FETCH_LIMIT)) {
|
|
250
|
+
if (!seen.has(row.id)) {
|
|
251
|
+
rawRows.push(row)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return rawRows
|
|
257
|
+
}
|
|
@@ -8,21 +8,20 @@
|
|
|
8
8
|
* table keyed by WOF polygon id; this source queries them ON DEMAND by id (one indexed lookup,
|
|
9
9
|
* memoized) rather than paging the whole table into memory as a code constant — the deliberate
|
|
10
10
|
* counter to the Pelias "giant dictionary in RAM, no provenance" pattern (see the operator design
|
|
11
|
-
* value in memory `feedback-no-
|
|
11
|
+
* value in memory `feedback-no-irrelevant-trivia`).
|
|
12
12
|
*
|
|
13
13
|
* The asset is the queryable, distributable artifact; the strategy IMPLEMENTATIONS stay in code. An
|
|
14
14
|
* unknown strategy NAME is surfaced loudly at dispatch (see `lookup.ts`), not silently
|
|
15
15
|
* swallowed.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
19
|
+
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
19
20
|
|
|
20
|
-
import {
|
|
21
|
+
import { ADDRESS_CONVENTION_TABLE, type Convention, type ConventionSource } from "#convention/index"
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export class SqliteConventionSource implements ConventionSource {
|
|
25
|
-
readonly #db: DatabaseSync
|
|
23
|
+
export class SqliteConventionSource<DB> implements ConventionSource {
|
|
24
|
+
readonly #db: DatabaseClient<DB>
|
|
26
25
|
readonly #schema: string
|
|
27
26
|
/**
|
|
28
27
|
* Memoize per-id lookups (including misses, as `null`) so a hot ancestor chain is queried once.
|
|
@@ -31,10 +30,10 @@ export class SqliteConventionSource implements ConventionSource {
|
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
32
|
* @param db An open handle to a DB that has the convention asset attached (or is it).
|
|
34
|
-
* @param schema The schema name the `address_convention` table lives under (`main` or an ATTACHed
|
|
35
|
-
* `
|
|
33
|
+
* @param schema The schema name the `address_convention` table lives under (`main` or an ATTACHed extract name —
|
|
34
|
+
* `WOFSQLitePlaceLookup` auto-detects which extract carries the table).
|
|
36
35
|
*/
|
|
37
|
-
constructor(db:
|
|
36
|
+
constructor(db: DatabaseClient<DB>, schema: string) {
|
|
38
37
|
this.#db = db
|
|
39
38
|
this.#schema = schema
|
|
40
39
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Small shared helpers for the SQLite-backed lookups.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { allRows, getRow } from "@mailwoman/core/utils"
|
|
10
|
+
import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
|
|
11
|
+
import { hasColumn as columnExists, tableExists } from "@mailwoman/sqlite/introspection"
|
|
12
|
+
|
|
13
|
+
// The row-shape assertion itself lives in `core` so the readers that cannot depend on this package reach the same
|
|
14
|
+
// helper; re-exported here because this module is where this package's readers already look for it.
|
|
15
|
+
export { allRows, getRow } from "@mailwoman/core/utils"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A prepared single-row query whose parameter tuple remains visible to TypeScript. `StatementSync` accepts only the
|
|
19
|
+
* broad `SQLInputValue[]`, which otherwise erases tagged key types before they reach SQLite.
|
|
20
|
+
*/
|
|
21
|
+
export type PreparedGet<Parameters extends SQLInputValue[], Row> = (...parameters: Parameters) => Row | undefined
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Prepare a single-row query while preserving its exact parameter tuple at every call site.
|
|
25
|
+
*/
|
|
26
|
+
export function prepareGet<Parameters extends SQLInputValue[], Row, DB>(
|
|
27
|
+
db: DatabaseClient<DB>,
|
|
28
|
+
sql: string
|
|
29
|
+
): PreparedGet<Parameters, Row> {
|
|
30
|
+
const statement = db.prepare(sql)
|
|
31
|
+
|
|
32
|
+
return (...parameters) => getRow<Row>(statement, ...parameters)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Multi-row counterpart to {@link PreparedGet}.
|
|
37
|
+
*/
|
|
38
|
+
export type PreparedAll<Parameters extends SQLInputValue[], Row> = (...parameters: Parameters) => Row[]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prepare a multi-row query while preserving its exact parameter tuple at every call site.
|
|
42
|
+
*/
|
|
43
|
+
export function prepareAll<Parameters extends SQLInputValue[], Row, DB>(
|
|
44
|
+
db: DatabaseClient<DB>,
|
|
45
|
+
sql: string
|
|
46
|
+
): PreparedAll<Parameters, Row> {
|
|
47
|
+
const statement = db.prepare(sql)
|
|
48
|
+
|
|
49
|
+
return (...parameters) => allRows<Row>(statement, ...parameters)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when `name` is a table in the open database. The street-level lookups use this to degrade gracefully on an
|
|
54
|
+
* empty/tableless extract — an interrupted `build-*-extract.ts`, or a stray 0-byte file (e.g. `sqlite3 <missing>.db
|
|
55
|
+
* "…"` CREATES one) — rather than throwing `no such table` at construction and taking down a whole state's geocode
|
|
56
|
+
* (#568). A missing table makes the lookup a no-op miss.
|
|
57
|
+
*/
|
|
58
|
+
export function hasTable<DB>(db: DatabaseClient<DB>, name: string): boolean {
|
|
59
|
+
try {
|
|
60
|
+
return tableExists(db, name)
|
|
61
|
+
} catch {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* True when `table` exists in the open database AND carries `column`.
|
|
68
|
+
*
|
|
69
|
+
* The column-level sibling of {@link hasTable}, and it exists for the same reason one layer down: an artifact built
|
|
70
|
+
* before a column was added is still a VALID artifact, and a reader that unconditionally names the new column in its
|
|
71
|
+
* `SELECT` turns "this gazetteer is a build behind" into `no such column` at the first keystroke. Probe once at
|
|
72
|
+
* construction and shape the query — `table_info` is a PRAGMA, so it must not sit on a per-query path.
|
|
73
|
+
*
|
|
74
|
+
* Note the interpolation: PRAGMA does not take bound parameters, so `table` is spliced. Every caller passes a
|
|
75
|
+
* module-level constant; never pass user input.
|
|
76
|
+
*/
|
|
77
|
+
export function hasColumn<DB>(db: DatabaseClient<DB>, table: string, column: string): boolean {
|
|
78
|
+
try {
|
|
79
|
+
return columnExists(db, table, column)
|
|
80
|
+
} catch {
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Typed schema for the DERIVED STREET-CENTROID
|
|
7
|
-
* `ban/scripts/build-street-centroid-
|
|
8
|
-
* FR queries deserve a street-level answer"). The
|
|
9
|
-
* sealed rooftop address-point
|
|
6
|
+
* Typed schema for the DERIVED STREET-CENTROID extract (`street-centroids-<cc>.db`, built by
|
|
7
|
+
* `ban/scripts/build-street-centroid-extract.ts` — the #1042 street-level tier behind "street-only
|
|
8
|
+
* FR queries deserve a street-level answer"). The extract is a `GROUP BY street` roll-up of the
|
|
9
|
+
* sealed rooftop address-point extract: one row per (street, postcode, commune) carrying the street's
|
|
10
10
|
* CENTROID + bounding-box EXTENT + member-point count. No new data source — a derived artifact.
|
|
11
11
|
*
|
|
12
12
|
* Single source of truth for the columns shared by the BUILDER (a positional prepared INSERT for
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
import type { Kysely } from "kysely"
|
|
24
24
|
|
|
25
|
+
import type { NameKey, StreetKey } from "#street/normalize"
|
|
26
|
+
|
|
25
27
|
/**
|
|
26
28
|
* One street roll-up. `(street_norm, postcode, locality_base)` is unique. `lat`/`lon` are the UNWEIGHTED mean of the
|
|
27
29
|
* group's member address points (each source row = one point), so a cross-group weighted mean (`SUM(lat*point_count) /
|
|
@@ -32,7 +34,7 @@ export interface StreetCentroidTable {
|
|
|
32
34
|
/**
|
|
33
35
|
* Shared `normalizeStreetForKeyLocale` of the street — the build/query-consistent probe key.
|
|
34
36
|
*/
|
|
35
|
-
street_norm:
|
|
37
|
+
street_norm: StreetKey
|
|
36
38
|
/**
|
|
37
39
|
* The 5-digit postcode of this group, or null when the source row carried none.
|
|
38
40
|
*/
|
|
@@ -40,7 +42,7 @@ export interface StreetCentroidTable {
|
|
|
40
42
|
/**
|
|
41
43
|
* Arrondissement-stripped commune (`stripArrondissement(normalizeLocalityForKey(commune))`) — the fallback scope.
|
|
42
44
|
*/
|
|
43
|
-
locality_base:
|
|
45
|
+
locality_base: NameKey
|
|
44
46
|
/**
|
|
45
47
|
* Weighted-mean centroid latitude of the street's member points.
|
|
46
48
|
*/
|
|
@@ -75,6 +77,10 @@ export interface StreetCentroidTable {
|
|
|
75
77
|
* name-evidence rerank folds the model's street surface with the SAME `foldStreetSurface` used to build this column
|
|
76
78
|
* (the fold-parity contract), so it must not drift from `street_norm`'s richer normalizer. Indexed (`idx_sc_name`)
|
|
77
79
|
* for a direct seek.
|
|
80
|
+
*
|
|
81
|
+
* Deliberately UNBRANDED despite the `name_key` column name: `foldStreetSurface` is a different fold from the
|
|
82
|
+
* {@link NameKey} one every other `name_key` column carries, and giving it that brand would invite exactly the
|
|
83
|
+
* cross-fold probe the brands exist to stop.
|
|
78
84
|
*/
|
|
79
85
|
name_key: string
|
|
80
86
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* number) plus a postcode/commune scope, it returns the street's CENTROID + an honest extent-derived
|
|
9
9
|
* uncertainty from the derived `street-centroids-<cc>.db` roll-up.
|
|
10
10
|
*
|
|
11
|
-
* Query-side normalization is THE shared normalizer (`street-normalize.ts`), selected per the
|
|
11
|
+
* Query-side normalization is THE shared normalizer (`street-normalize.ts`), selected per the extract's
|
|
12
12
|
* `streetLocale`, so build-side and probe-side keys agree by construction. The commune scope folds
|
|
13
13
|
* through `normalizeLocalityForKey` + `stripArrondissement` (BAN names Paris/Lyon/Marseille per
|
|
14
14
|
* arrondissement; a query names the base commune).
|
|
@@ -19,17 +19,21 @@
|
|
|
19
19
|
* exact-after-normalization only (no fuzzy street matching in this tier).
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { DatabaseSync } from "node:sqlite"
|
|
23
|
-
|
|
24
22
|
import type { StreetCentroidHit, StreetCentroidLookup } from "@mailwoman/resolver"
|
|
23
|
+
import { haversineKm } from "@mailwoman/spatial"
|
|
24
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
25
25
|
|
|
26
|
-
import { hasTable } from "
|
|
26
|
+
import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
|
|
27
|
+
import type { StreetCentroidDatabase } from "#street/centroid-schema"
|
|
27
28
|
import {
|
|
28
29
|
normalizeLocalityForKey,
|
|
29
30
|
normalizeStreetForKeyLocale,
|
|
31
|
+
type NameKey,
|
|
32
|
+
type StreetKey,
|
|
33
|
+
streetLocaleForSurface,
|
|
30
34
|
type StreetLocale,
|
|
31
35
|
stripArrondissement,
|
|
32
|
-
} from "
|
|
36
|
+
} from "#street/normalize"
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* The weighted-centroid + extent + provenance an aggregate probe projects. `lat` is null when nothing matched.
|
|
@@ -58,40 +62,34 @@ const AGG_SELECT =
|
|
|
58
62
|
* Half the bbox diagonal, in METERS — an honest coarse radius for a street centroid.
|
|
59
63
|
*/
|
|
60
64
|
function extentRadiusM(minLat: number, maxLat: number, minLon: number, maxLon: number): number {
|
|
61
|
-
|
|
62
|
-
const toRad = (d: number): number => (d * Math.PI) / 180
|
|
63
|
-
const dLat = toRad(maxLat - minLat)
|
|
64
|
-
const dLon = toRad(maxLon - minLon)
|
|
65
|
-
const midLat = toRad((minLat + maxLat) / 2)
|
|
66
|
-
const a = Math.sin(dLat / 2) ** 2 + Math.cos(midLat) ** 2 * Math.sin(dLon / 2) ** 2
|
|
67
|
-
const diag = 2 * R * Math.asin(Math.min(1, Math.sqrt(a)))
|
|
68
|
-
|
|
69
|
-
return Math.round(diag / 2)
|
|
65
|
+
return Math.round(haversineKm(minLat, minLon, maxLat, maxLon) * 500)
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
export class StreetCentroidSqliteLookup implements StreetCentroidLookup {
|
|
73
|
-
readonly #db:
|
|
69
|
+
readonly #db: DatabaseClient<StreetCentroidDatabase>
|
|
74
70
|
readonly #locale: StreetLocale
|
|
75
|
-
readonly #byPostcode:
|
|
76
|
-
readonly #byLocality:
|
|
71
|
+
readonly #byPostcode: PreparedGet<[postcode: string, street: StreetKey], AggRow> | undefined
|
|
72
|
+
readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey], AggRow> | undefined
|
|
77
73
|
|
|
78
74
|
/**
|
|
79
|
-
* @param dbPath
|
|
80
|
-
* @param opts.streetLocale The street-normalization locale this
|
|
75
|
+
* @param dbPath Extract path.
|
|
76
|
+
* @param opts.streetLocale The street-normalization locale this extract was BUILT with — must match, or every key
|
|
81
77
|
* misses. Defaults to `"fr"` (BAN is the French national register; the tier is FR-only today).
|
|
82
78
|
*/
|
|
83
79
|
constructor(dbPath: string, opts: { streetLocale?: StreetLocale } = {}) {
|
|
84
|
-
this.#db = new
|
|
80
|
+
this.#db = new DatabaseClient<StreetCentroidDatabase>(dbPath, { readOnly: true })
|
|
85
81
|
this.#locale = opts.streetLocale ?? "fr"
|
|
86
82
|
|
|
87
|
-
// Degrade gracefully on an empty/tableless
|
|
83
|
+
// Degrade gracefully on an empty/tableless extract (interrupted build, stray 0-byte file): with no
|
|
88
84
|
// `street_centroid` table this lookup is a no-op miss, not a crash (mirrors the address-point reader).
|
|
89
85
|
if (hasTable(this.#db, "street_centroid")) {
|
|
90
|
-
this.#byPostcode =
|
|
86
|
+
this.#byPostcode = prepareGet(
|
|
87
|
+
this.#db,
|
|
91
88
|
`SELECT ${AGG_SELECT} FROM street_centroid WHERE postcode = ? AND street_norm = ?`
|
|
92
89
|
)
|
|
93
90
|
|
|
94
|
-
this.#byLocality =
|
|
91
|
+
this.#byLocality = prepareGet(
|
|
92
|
+
this.#db,
|
|
95
93
|
`SELECT ${AGG_SELECT} FROM street_centroid WHERE locality_base = ? AND street_norm = ?`
|
|
96
94
|
)
|
|
97
95
|
}
|
|
@@ -99,19 +97,19 @@ export class StreetCentroidSqliteLookup implements StreetCentroidLookup {
|
|
|
99
97
|
|
|
100
98
|
find(query: { street: string; postcode?: string; locality?: string }): StreetCentroidHit | null {
|
|
101
99
|
if (!this.#byPostcode || !this.#byLocality) return null
|
|
102
|
-
const streetNorm = normalizeStreetForKeyLocale(query.street, this.#locale)
|
|
100
|
+
const streetNorm = normalizeStreetForKeyLocale(query.street, streetLocaleForSurface(query.street, this.#locale))
|
|
103
101
|
|
|
104
102
|
if (!streetNorm) return null
|
|
105
103
|
|
|
106
104
|
let row: AggRow | undefined
|
|
107
105
|
|
|
108
106
|
if (query.postcode?.trim()) {
|
|
109
|
-
row = this.#byPostcode
|
|
107
|
+
row = this.#byPostcode(query.postcode.trim(), streetNorm)
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
if ((!row || row.lat == null) && query.locality?.trim()) {
|
|
113
111
|
const base = stripArrondissement(normalizeLocalityForKey(query.locality))
|
|
114
|
-
row = this.#byLocality
|
|
112
|
+
row = this.#byLocality(base, streetNorm)
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
if (!row || row.lat == null || row.lon == null) return null
|
|
@@ -125,7 +123,7 @@ export class StreetCentroidSqliteLookup implements StreetCentroidLookup {
|
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
this.#db.
|
|
126
|
+
[Symbol.dispose](): void {
|
|
127
|
+
this.#db[Symbol.dispose]()
|
|
130
128
|
}
|
|
131
129
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "#street/centroid"
|
|
8
|
+
export * from "#street/centroid-schema"
|
|
9
|
+
export * from "#street/morphology-fst-builder"
|
|
10
|
+
export * from "#street/morphology-fst-loader"
|
|
11
|
+
export * from "#street/name-lookup"
|
|
12
|
+
export * from "#street/normalize"
|
|
13
|
+
export * from "#street/segment-schema"
|
|
@@ -23,14 +23,13 @@
|
|
|
23
23
|
* `fst-street-morphology.bin`.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
import { join } from "
|
|
28
|
-
|
|
26
|
+
import { isDirectory, readDirectory, readLocalTextFile, statPath } from "@mailwoman/core/fs/readers"
|
|
27
|
+
import { join } from "path-ts"
|
|
29
28
|
import { TextSpliterator } from "spliterator"
|
|
30
29
|
|
|
31
|
-
import type { FSTNode } from "
|
|
32
|
-
import { FSTMatcher, normalizeTokens } from "
|
|
33
|
-
import type { FSTProvenance, PlaceEntry } from "
|
|
30
|
+
import type { FSTNode } from "#fst/matcher"
|
|
31
|
+
import { FSTMatcher, normalizeTokens } from "#fst/matcher"
|
|
32
|
+
import type { FSTProvenance, PlaceEntry } from "#fst/types"
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Reserved synthetic wofID base for street-morphology entries. 32-bit unsigned, well above any realistic WOF
|
|
@@ -96,7 +95,9 @@ function parseLine(line: string): { canonical: string; variants: string[] } | nu
|
|
|
96
95
|
return { canonical: parts[0]!, variants: parts }
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
export function buildStreetMorphologyFST(
|
|
98
|
+
export async function buildStreetMorphologyFST(
|
|
99
|
+
opts: BuildStreetMorphologyFSTOpts
|
|
100
|
+
): Promise<BuildStreetMorphologyFSTResult> {
|
|
100
101
|
const progress = opts.onProgress ?? (() => {})
|
|
101
102
|
const minVariantLength = opts.minVariantLength ?? 3
|
|
102
103
|
|
|
@@ -107,19 +108,28 @@ export function buildStreetMorphologyFST(opts: BuildStreetMorphologyFSTOpts): Bu
|
|
|
107
108
|
if (opts.locales && opts.locales.length) {
|
|
108
109
|
locales = opts.locales
|
|
109
110
|
} else {
|
|
110
|
-
|
|
111
|
+
const entries = await readDirectory(opts.dictionariesDir)
|
|
112
|
+
const localeProbes: Array<[string, boolean]> = []
|
|
113
|
+
|
|
114
|
+
for (const entry of entries) {
|
|
111
115
|
const localePath = join(opts.dictionariesDir, entry)
|
|
112
116
|
|
|
113
|
-
if (!
|
|
117
|
+
if (!(await isDirectory(localePath))) {
|
|
118
|
+
localeProbes.push([entry, false])
|
|
119
|
+
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
114
122
|
|
|
115
123
|
try {
|
|
116
|
-
|
|
124
|
+
await statPath(join(localePath, STREET_TYPES_FILENAME))
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
localeProbes.push([entry, true])
|
|
119
127
|
} catch {
|
|
120
|
-
|
|
128
|
+
localeProbes.push([entry, false])
|
|
121
129
|
}
|
|
122
|
-
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
locales = localeProbes.filter(([, hasFile]) => hasFile).map(([entry]) => entry)
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
progress("discover", `Found ${locales.length} locales with ${STREET_TYPES_FILENAME}`)
|
|
@@ -130,7 +140,7 @@ export function buildStreetMorphologyFST(opts: BuildStreetMorphologyFSTOpts): Bu
|
|
|
130
140
|
|
|
131
141
|
for (const locale of locales) {
|
|
132
142
|
const filePath = join(opts.dictionariesDir, locale, STREET_TYPES_FILENAME)
|
|
133
|
-
const content =
|
|
143
|
+
const content = await readLocalTextFile(filePath)
|
|
134
144
|
|
|
135
145
|
for (const line of TextSpliterator.from(content)) {
|
|
136
146
|
const parsed = parseLine(line)
|