@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
package/lib/env.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Environment variables naming the WOF SQLite artifacts this resolver opens, and the development weights the overlay
|
|
7
|
+
* linker materializes. CLI flags (`--resolve-db`, …) override these at the call site.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { $public as corePublic, liveEnv } from "@mailwoman/core/env"
|
|
11
|
+
import { z } from "zod"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gazetteer and resolver database paths, plus the development weights overlay.
|
|
15
|
+
*/
|
|
16
|
+
export const PublicWOFSQLiteEnvSchema = z.object({
|
|
17
|
+
MAILWOMAN_WOF_DB: z.string().optional().meta({
|
|
18
|
+
title: "Who's On First database",
|
|
19
|
+
description: "Path to a full Who's On First SQLite distribution used by the search resolver backend.",
|
|
20
|
+
}),
|
|
21
|
+
MAILWOMAN_WOF_ADMIN_DB: z.string().optional().meta({
|
|
22
|
+
title: "Who's On First admin database",
|
|
23
|
+
description: "Path to the full Who's On First distribution used for reverse geocoding.",
|
|
24
|
+
}),
|
|
25
|
+
MAILWOMAN_WOF_POLYGONS_DB: z.string().optional().meta({
|
|
26
|
+
title: "Who's On First polygon database",
|
|
27
|
+
description: "Path to the polygon sidecar used for exact reverse-geocode containment.",
|
|
28
|
+
}),
|
|
29
|
+
MAILWOMAN_CANDIDATE_DB: z.string().optional().meta({
|
|
30
|
+
title: "Candidate database",
|
|
31
|
+
description: "Path to candidate.db; set to `none` to pin the full-distribution search backend.",
|
|
32
|
+
}),
|
|
33
|
+
MAILWOMAN_POSTAL_CITY_ALIAS_DB: z.string().optional().meta({
|
|
34
|
+
title: "Postal city alias database",
|
|
35
|
+
description: "Path to postal-city alias data used for alias-aware resolution of U.S. mailing addresses.",
|
|
36
|
+
}),
|
|
37
|
+
MAILWOMAN_DEV_MODEL: z.string().optional().meta({
|
|
38
|
+
title: "Development model",
|
|
39
|
+
description: "Path to the model artifact linked for local neural-weights development.",
|
|
40
|
+
}),
|
|
41
|
+
MAILWOMAN_DEV_TOKENIZER: z.string().optional().meta({
|
|
42
|
+
title: "Development tokenizer",
|
|
43
|
+
description: "Path to the tokenizer artifact linked for local neural-weights development.",
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Live resolver settings over core's, sharing core's getters and cached values.
|
|
49
|
+
*/
|
|
50
|
+
export const $public = liveEnv(PublicWOFSQLiteEnvSchema, corePublic)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The exact-match tier's name probes — the two case-folded equality lookups that decide whether a
|
|
7
|
+
* candidate holds the query text as its own name, an alias, or an official name.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
11
|
+
|
|
12
|
+
import { aliasBagExactMatch, foldQueryText } from "#fts/index"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Among `ids`, return the subset whose name OR any alias equals `text` case-insensitively — the exact-match tier for
|
|
16
|
+
* ranking. One indexed query over `<schema>.names`. When the extract has no `names` table (a slim DB built with
|
|
17
|
+
* `dropNames`, or a postcode-only extract), fall back to the self-contained `place_search` FTS content: its `alt_names`
|
|
18
|
+
* column is the same alias set joined on the boundary-preserving `ALIAS_SEPARATOR` (#523), so `aliasBagExactMatch`
|
|
19
|
+
* recovers the exact alias tier ("New York City" → New York) that the dropped `names` table used to provide.
|
|
20
|
+
*/
|
|
21
|
+
export function exactMatchIDs<DB>(
|
|
22
|
+
db: DatabaseClient<DB>,
|
|
23
|
+
schemaName: string,
|
|
24
|
+
ids: number[],
|
|
25
|
+
text: string
|
|
26
|
+
): Set<number> {
|
|
27
|
+
const out = new Set<number>()
|
|
28
|
+
const trimmed = text.trim()
|
|
29
|
+
|
|
30
|
+
if (!ids.length || !trimmed) return out
|
|
31
|
+
const placeholders = ids.map(() => "?").join(", ")
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const rows = db
|
|
35
|
+
.prepare(`SELECT DISTINCT id FROM ${schemaName}.names WHERE id IN (${placeholders}) AND name = ? COLLATE NOCASE`)
|
|
36
|
+
.all(...ids, trimmed) as Array<{ id: number }>
|
|
37
|
+
|
|
38
|
+
for (const r of rows) {
|
|
39
|
+
out.add(r.id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return out
|
|
43
|
+
} catch {
|
|
44
|
+
// No `names` table on this extract — fall through to the place_search alias bag.
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const rows = db
|
|
49
|
+
.prepare(`SELECT wof_id AS id, name, alt_names FROM ${schemaName}.place_search WHERE wof_id IN (${placeholders})`)
|
|
50
|
+
.all(...ids) as Array<{ id: number; name: string | null; alt_names: string | null }>
|
|
51
|
+
|
|
52
|
+
const needle = foldQueryText(trimmed)
|
|
53
|
+
|
|
54
|
+
for (const r of rows) {
|
|
55
|
+
if (r.name !== null && foldQueryText(r.name) === needle) {
|
|
56
|
+
out.add(r.id)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Alias pass via the shared bag parser (#523). Separated bags (built since #523) get a true
|
|
61
|
+
// per-alias equality check, unrestricted — matching the `names`-table branch above, where an
|
|
62
|
+
// alias match counts as exact regardless of other candidates. Legacy bags (no separator)
|
|
63
|
+
// fall back to padded containment, conditioned on "no canonical exact in the pool" because their
|
|
64
|
+
// lost boundaries would otherwise false-promote interior fragments ("York" inside the alias
|
|
65
|
+
// "New York City") or cross-alias fragments ("York New" across "…York" + "New City…").
|
|
66
|
+
const anyCanonicalExact = out.size > 0
|
|
67
|
+
|
|
68
|
+
for (const r of rows) {
|
|
69
|
+
if (aliasBagExactMatch(r.alt_names, needle, anyCanonicalExact)) {
|
|
70
|
+
out.add(r.id)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Extract without place_search either → no exact-match tier. Falls back to weighted-sum order.
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Among `ids` (already known exact matches), the subset holding `text` as an OFFICIAL name (`names.official = 1`, the
|
|
82
|
+
* #940 ingest bit). Same COLLATE NOCASE semantics as {@link WOFSQLitePlaceLookup.#exactMatchIDs} so the two probes
|
|
83
|
+
* agree on what "equals the query" means. Fails soft on gazetteers built before #940 (no `official` column) — the
|
|
84
|
+
* sub-tier then behaves exactly as if `officialNameExact` were off.
|
|
85
|
+
*/
|
|
86
|
+
export function officialNameIDs<DB>(
|
|
87
|
+
db: DatabaseClient<DB>,
|
|
88
|
+
schemaName: string,
|
|
89
|
+
ids: number[],
|
|
90
|
+
text: string
|
|
91
|
+
): Set<number> {
|
|
92
|
+
const out = new Set<number>()
|
|
93
|
+
const trimmed = text.trim()
|
|
94
|
+
|
|
95
|
+
if (!ids.length || !trimmed) return out
|
|
96
|
+
const placeholders = ids.map(() => "?").join(", ")
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const rows = db
|
|
100
|
+
.prepare(
|
|
101
|
+
`SELECT DISTINCT id FROM ${schemaName}.names WHERE id IN (${placeholders}) AND official = 1 AND name = ? COLLATE NOCASE`
|
|
102
|
+
)
|
|
103
|
+
.all(...ids, trimmed) as Array<{ id: number }>
|
|
104
|
+
|
|
105
|
+
for (const r of rows) {
|
|
106
|
+
out.add(r.id)
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// Pre-#940 gazetteer (no `official` column) or a names-less slim extract — feature inert.
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { basename } from "path-ts"
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @copyright Sister Software
|
|
3
5
|
* @license AGPL-3.0
|
|
4
6
|
* @author Teffen Ellis, et al.
|
|
5
7
|
*
|
|
6
|
-
* Multi-
|
|
7
|
-
* connection via `ATTACH DATABASE`, and routes queries to the right
|
|
8
|
+
* Multi-extract support for `WOFSQLitePlaceLookup` — opens multiple WOF SQLite distributions on one
|
|
9
|
+
* connection via `ATTACH DATABASE`, and routes queries to the right extract based on placetype.
|
|
8
10
|
*
|
|
9
11
|
* ## The FTS5 syntax rule that drove this design
|
|
10
12
|
*
|
|
11
13
|
* The naive `SELECT … FROM pc.place_search WHERE pc.place_search MATCH ?` fails — SQLite parses the
|
|
12
14
|
* schema-qualified table on the left of MATCH as "column place_search of table pc". Discovered in
|
|
13
|
-
* the spike at PR review time; documented as `
|
|
15
|
+
* the spike at PR review time; documented as `_EXTRACT_RULE.md` should it ever bite again.
|
|
14
16
|
*
|
|
15
17
|
* The working form: schema-qualified in FROM, bare table name in MATCH:
|
|
16
18
|
*
|
|
@@ -18,15 +20,13 @@
|
|
|
18
20
|
* SELECT … FROM pc.place_search WHERE place_search MATCH ?
|
|
19
21
|
* ```
|
|
20
22
|
*
|
|
21
|
-
* Identical table names across attached
|
|
23
|
+
* Identical table names across attached extracts (which is what we have — every extract ships its own
|
|
22
24
|
* `place_search` + `place_bbox`) are fine because the bare-name MATCH resolves against FROM
|
|
23
25
|
* scope.
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
|
-
import { basename } from "node:path"
|
|
27
|
-
|
|
28
28
|
/**
|
|
29
|
-
* Derive a SQL-safe schema name from a WOF distribution filename. Used by `ATTACH DATABASE … AS <name>` so each
|
|
29
|
+
* Derive a SQL-safe schema name from a WOF distribution filename. Used by `ATTACH DATABASE … AS <name>` so each extract
|
|
30
30
|
* gets a stable, predictable handle.
|
|
31
31
|
*
|
|
32
32
|
* Convention strips the `whosonfirst-data-` prefix and the `-latest.db` (or just `.db`) suffix, then replaces `-` with
|
|
@@ -39,7 +39,7 @@ import { basename } from "node:path"
|
|
|
39
39
|
* - `whosonfirst-data-admin-latest.db` → `admin`
|
|
40
40
|
* - `my-custom.db` → `my_custom`
|
|
41
41
|
*
|
|
42
|
-
* Callers can override the derived name explicitly via `
|
|
42
|
+
* Callers can override the derived name explicitly via `ExtractConfig.schemaName` when the filename doesn't follow WOF
|
|
43
43
|
* convention.
|
|
44
44
|
*/
|
|
45
45
|
export function deriveSchemaName(path: string): string {
|
|
@@ -57,11 +57,11 @@ export function deriveSchemaName(path: string): string {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Per-
|
|
61
|
-
* lets callers override the derived schema name (useful when a filename doesn't follow WOF convention) or attach
|
|
62
|
-
* extra hint about which placetypes route here.
|
|
60
|
+
* Per-extract configuration. The simple form is just a path string — the schema name is derived from it. The object
|
|
61
|
+
* form lets callers override the derived schema name (useful when a filename doesn't follow WOF convention) or attach
|
|
62
|
+
* an extra hint about which placetypes route here.
|
|
63
63
|
*/
|
|
64
|
-
export interface
|
|
64
|
+
export interface ExtractConfig {
|
|
65
65
|
path: string
|
|
66
66
|
/**
|
|
67
67
|
* Override the auto-derived schema name. Useful when the filename doesn't match WOF convention or when you want a
|
|
@@ -69,18 +69,18 @@ export interface ShardConfig {
|
|
|
69
69
|
*/
|
|
70
70
|
schemaName?: string
|
|
71
71
|
/**
|
|
72
|
-
* Optional explicit list of placetypes this
|
|
73
|
-
* to this
|
|
74
|
-
* placetype as a substring (e.g. `postalcode_us` for `postalcode` queries) is preferred for that placetype.
|
|
72
|
+
* Optional explicit list of placetypes this extract serves. When set, queries against any listed placetype are routed
|
|
73
|
+
* to this extract. When omitted, routing falls back to a name-match heuristic: a extract whose `schemaName` contains
|
|
74
|
+
* the placetype as a substring (e.g. `postalcode_us` for `postalcode` queries) is preferred for that placetype.
|
|
75
75
|
*/
|
|
76
76
|
placetypes?: readonly string[]
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Resolved post-derivation: paired path + chosen schema name + (possibly empty) placetypes hint. Used internally by
|
|
81
|
-
* `
|
|
81
|
+
* `WOFSQLitePlaceLookup` so the routing logic operates on uniform structures.
|
|
82
82
|
*/
|
|
83
|
-
export interface
|
|
83
|
+
export interface ResolvedExtract {
|
|
84
84
|
path: string
|
|
85
85
|
schemaName: string
|
|
86
86
|
placetypes: readonly string[]
|
|
@@ -93,40 +93,40 @@ const SQLITE_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/u
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Normalize the user-provided `databasePath` opt (which may be a single string, an array of strings, or an array of
|
|
96
|
-
* `
|
|
96
|
+
* `ExtractConfig` objects) into a uniform `ResolvedExtract[]`.
|
|
97
97
|
*
|
|
98
|
-
* The first
|
|
99
|
-
*
|
|
98
|
+
* The first extract becomes `main` regardless of its derived schema name — that's the SQLite convention. Subsequent
|
|
99
|
+
* extracts keep their derived (or override) schema name.
|
|
100
100
|
*/
|
|
101
|
-
export function
|
|
101
|
+
export function resolveExtracts(input: string | ReadonlyArray<string | ExtractConfig>): ResolvedExtract[] {
|
|
102
102
|
const list = typeof input === "string" ? [input] : input
|
|
103
103
|
|
|
104
|
-
if (!list.length) throw new Error("
|
|
104
|
+
if (!list.length) throw new Error("resolveExtracts: at least one extract is required")
|
|
105
105
|
|
|
106
106
|
const seen = new Set<string>()
|
|
107
|
-
const out:
|
|
107
|
+
const out: ResolvedExtract[] = []
|
|
108
108
|
|
|
109
109
|
for (let i = 0; i < list.length; i++) {
|
|
110
110
|
const entry = list[i]!
|
|
111
|
-
const cfg:
|
|
111
|
+
const cfg: ExtractConfig = typeof entry === "string" ? { path: entry } : entry
|
|
112
112
|
const derived = cfg.schemaName ?? deriveSchemaName(cfg.path)
|
|
113
113
|
|
|
114
114
|
if (!SQLITE_IDENT_RE.test(derived)) {
|
|
115
115
|
throw new Error(
|
|
116
|
-
`
|
|
116
|
+
`resolveExtracts: schema name ${JSON.stringify(derived)} is not a valid SQLite identifier ` +
|
|
117
117
|
`(derived from path ${JSON.stringify(cfg.path)}). Pass an explicit ` +
|
|
118
118
|
`{ path, schemaName } to override.`
|
|
119
119
|
)
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
// The first
|
|
123
|
-
// only. Subsequent
|
|
122
|
+
// The first extract is always main per SQLite semantics — its derived name is informational
|
|
123
|
+
// only. Subsequent extracts must have unique non-main names.
|
|
124
124
|
const schemaName = i === 0 ? "main" : derived
|
|
125
125
|
|
|
126
126
|
if (i > 0 && (schemaName === "main" || seen.has(schemaName))) {
|
|
127
127
|
throw new Error(
|
|
128
|
-
`
|
|
129
|
-
`(either with "main" or another
|
|
128
|
+
`resolveExtracts: schema name ${JSON.stringify(schemaName)} collides ` +
|
|
129
|
+
`(either with "main" or another extract). Pass an explicit { path, schemaName }.`
|
|
130
130
|
)
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -143,36 +143,39 @@ export function resolveShards(input: string | ReadonlyArray<string | ShardConfig
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
|
-
* Pick the
|
|
146
|
+
* Pick the extract to route a query to given the requested placetype(s).
|
|
147
147
|
*
|
|
148
148
|
* Routing rules, in order:
|
|
149
149
|
*
|
|
150
|
-
* 1. If any
|
|
151
|
-
* 2. Otherwise, if a non-main
|
|
152
|
-
* it.
|
|
150
|
+
* 1. If any extract has explicit `placetypes` that includes the requested placetype, use it.
|
|
151
|
+
* 2. Otherwise, if a non-main extract's `schemaName` matches the placetype (e.g. `postalcode_us` matches `postalcode`),
|
|
152
|
+
* use it.
|
|
153
153
|
* 3. Otherwise, fall back to `main`.
|
|
154
154
|
*
|
|
155
|
-
* This deliberately doesn't UNION across
|
|
156
|
-
* the typical mailwoman query has a single placetype anyway. If a caller needs cross-
|
|
157
|
-
* `findPlace` calls.
|
|
155
|
+
* This deliberately doesn't UNION across extracts — BM25 scores aren't comparable across separately- indexed corpora,
|
|
156
|
+
* and the typical mailwoman query has a single placetype anyway. If a caller needs cross-extract results they can issue
|
|
157
|
+
* two `findPlace` calls.
|
|
158
158
|
*/
|
|
159
159
|
/**
|
|
160
|
-
* All placetype-matching
|
|
161
|
-
* a country-less postcode query with proximity hints fans out across every matching
|
|
162
|
-
* single-
|
|
160
|
+
* All placetype-matching extracts, in routing order (the country-aware pick chooses among these). Used by the bias
|
|
161
|
+
* path: a country-less postcode query with proximity hints fans out across every matching extract and merges, because
|
|
162
|
+
* single-extract routing would hide the cross-country ambiguity the hints exist to resolve ("48026" lives in
|
|
163
163
|
* postalcode-us AND postalcode-intl).
|
|
164
164
|
*/
|
|
165
|
-
export function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
export function pickExtractsForPlacetype(
|
|
166
|
+
extracts: ResolvedExtract[],
|
|
167
|
+
placetype: string | undefined
|
|
168
|
+
): ResolvedExtract[] {
|
|
169
|
+
if (!placetype) return [extracts[0]!]
|
|
170
|
+
const matches: ResolvedExtract[] = []
|
|
171
|
+
|
|
172
|
+
for (const s of extracts) {
|
|
170
173
|
if (s.placetypes.includes(placetype)) {
|
|
171
174
|
matches.push(s)
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
for (const s of
|
|
178
|
+
for (const s of extracts) {
|
|
176
179
|
if (s.schemaName === "main" || matches.includes(s)) continue
|
|
177
180
|
|
|
178
181
|
if (
|
|
@@ -184,38 +187,38 @@ export function pickShardsForPlacetype(shards: ResolvedShard[], placetype: strin
|
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
return matches.length ? matches : [
|
|
190
|
+
return matches.length ? matches : [extracts[0]!]
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
export function
|
|
191
|
-
|
|
193
|
+
export function pickExtractForPlacetype(
|
|
194
|
+
extracts: ResolvedExtract[],
|
|
192
195
|
placetype: string | undefined,
|
|
193
196
|
opts?: {
|
|
194
197
|
/**
|
|
195
|
-
* #920: the query's country constraint, when the caller has one. With MULTIPLE
|
|
196
|
-
* (postalcode-us + postalcode-geonames-tail), first-match routing sent every postcode query to the first
|
|
197
|
-
* starved the rest — a FI postcode could never reach the tail
|
|
198
|
-
* probed country set contains it, that
|
|
199
|
-
* order remains the tiebreak when no
|
|
198
|
+
* #920: the query's country constraint, when the caller has one. With MULTIPLE extracts matching a placetype
|
|
199
|
+
* (postalcode-us + postalcode-geonames-tail), first-match routing sent every postcode query to the first extract
|
|
200
|
+
* and starved the rest — a FI postcode could never reach the tail extract. When `country` is given and a matching
|
|
201
|
+
* extract's probed country set contains it, that extract wins; extracts without the country are skipped; the
|
|
202
|
+
* placetype-match order remains the tiebreak when no extract claims the country (or none was probed).
|
|
200
203
|
*/
|
|
201
204
|
country?: string
|
|
202
205
|
/**
|
|
203
|
-
* Per-schema probed country sets (see `
|
|
206
|
+
* Per-schema probed country sets (see `WOFSQLitePlaceLookup`'s construction probe).
|
|
204
207
|
*/
|
|
205
208
|
countriesBySchema?: ReadonlyMap<string, ReadonlySet<string>>
|
|
206
209
|
}
|
|
207
|
-
):
|
|
208
|
-
if (!placetype) return
|
|
210
|
+
): ResolvedExtract {
|
|
211
|
+
if (!placetype) return extracts[0]!
|
|
209
212
|
|
|
210
|
-
const matches:
|
|
213
|
+
const matches: ResolvedExtract[] = []
|
|
211
214
|
|
|
212
|
-
for (const s of
|
|
215
|
+
for (const s of extracts) {
|
|
213
216
|
if (s.placetypes.includes(placetype)) {
|
|
214
217
|
matches.push(s)
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
for (const s of
|
|
221
|
+
for (const s of extracts) {
|
|
219
222
|
if (s.schemaName === "main" || matches.includes(s)) continue
|
|
220
223
|
|
|
221
224
|
// Substring match: `postalcode_us` matches `postalcode`. Conservative — requires the
|
|
@@ -230,7 +233,7 @@ export function pickShardForPlacetype(
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
if (!matches.length) return
|
|
236
|
+
if (!matches.length) return extracts[0]!
|
|
234
237
|
|
|
235
238
|
if (opts?.country && opts.countriesBySchema) {
|
|
236
239
|
for (const s of matches) {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* FST-based autocomplete — mailwoman vocabulary over `@mailwoman/ancestrie`'s generic algorithm
|
|
7
|
+
* (#1728 phase 2). The #587 behavior — prefix walk + BFS expansion, partial-last-token completion,
|
|
8
|
+
* per-branch capping, dedupe — lives in ancestrie's `autocomplete`; this module contributes only
|
|
9
|
+
* the storage adapter ({@link FSTMatcher} → `AncestrieReaderLike`) and the mapping back to
|
|
10
|
+
* mailwoman's suggestion shape (name, placetype, referential/encyclopedic, WOF ids).
|
|
11
|
+
*
|
|
12
|
+
* THE BYTES DO NOT MIGRATE. The shipped artifacts are `FST\0` v1–v5 (`fst-serialize.ts`), not
|
|
13
|
+
* ancestrie's `ANCT`: ancestrie entries are id-keyed with one record per id, while an FST place row
|
|
14
|
+
* is per-(surface, place) — `crossCountryBranches` is a property of the SURFACE, so the same wofID
|
|
15
|
+
* legitimately carries different values under different aliases and cannot be represented id-keyed.
|
|
16
|
+
* The matcher, both deserializers, and the serializer therefore stay here; what migrated is the
|
|
17
|
+
* ALGORITHM, which is the half that drifts (the #861 share-the-function rule).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
AncestrieContinuation,
|
|
22
|
+
AncestrieMatch,
|
|
23
|
+
AncestrieReaderLike,
|
|
24
|
+
AncestrieRecord,
|
|
25
|
+
AncestrieSuggestion,
|
|
26
|
+
} from "@mailwoman/ancestrie"
|
|
27
|
+
import { autocomplete as ancestrieAutocomplete } from "@mailwoman/ancestrie"
|
|
28
|
+
|
|
29
|
+
import type { FSTMatcher } from "#fst/matcher"
|
|
30
|
+
import { normalizeTokens } from "#fst/matcher"
|
|
31
|
+
import type { PlaceEntry } from "#fst/types"
|
|
32
|
+
|
|
33
|
+
export interface AutocompleteResult {
|
|
34
|
+
query: string
|
|
35
|
+
normalizedTokens: string[]
|
|
36
|
+
depth: number
|
|
37
|
+
suggestions: AutocompleteSuggestion[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AutocompleteSuggestion {
|
|
41
|
+
name: string
|
|
42
|
+
placetype: string
|
|
43
|
+
/**
|
|
44
|
+
* The REFERENTIAL likelihood the suggestion is ranked by (ROAD_TO_V9 §2). Autocomplete answers "which place does the
|
|
45
|
+
* user mean", so it ranks referentially like everything else; encyclopedic importance rides along on
|
|
46
|
+
* {@link AutocompleteSuggestion.encyclopedic} for display and never enters the order.
|
|
47
|
+
*/
|
|
48
|
+
referential: number
|
|
49
|
+
/**
|
|
50
|
+
* Encyclopedic (Wikipedia) importance, when the FST artifact carries one for this place. `undefined` = no article, or
|
|
51
|
+
* a pre-v5 binary — never 0.
|
|
52
|
+
*/
|
|
53
|
+
encyclopedic?: number
|
|
54
|
+
wofID: number
|
|
55
|
+
parentChain: number[]
|
|
56
|
+
matchDepth: number
|
|
57
|
+
completionTokens: string[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface AutocompleteOpts {
|
|
61
|
+
maxSuggestions?: number
|
|
62
|
+
maxExpansionDepth?: number
|
|
63
|
+
/**
|
|
64
|
+
* Collapse same-name suggestions to the single highest-referential one. Off by default (the CLI surfaces distinct
|
|
65
|
+
* same-name places — New York the city vs the county); a typeahead wants it ON so the dropdown isn't four "New
|
|
66
|
+
* London"s. (#587)
|
|
67
|
+
*/
|
|
68
|
+
dedupeByName?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Max accepting entries collected per BFS branch — keeps one dense branch from starving the search.
|
|
73
|
+
*/
|
|
74
|
+
const PER_BRANCH = 4
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The top-`k` entries by REFERENTIAL likelihood (descending). Avoids sorting/allocating when `entries` is small — and
|
|
78
|
+
* that shortcut is part of the observable contract: at or under `k` the INSERTION order is served, which decides
|
|
79
|
+
* suggestion order among referential ties.
|
|
80
|
+
*/
|
|
81
|
+
function topByReferential(entries: readonly PlaceEntry[], k: number): PlaceEntry[] {
|
|
82
|
+
if (entries.length <= k) return [...entries]
|
|
83
|
+
|
|
84
|
+
return [...entries].toSorted((a, b) => b.referential - a.referential).slice(0, k)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* {@link FSTMatcher} presented through ancestrie's storage interface. Records carry the {@link PlaceEntry} itself as the
|
|
89
|
+
* payload, so the entry that WINS the algorithm's shallowest-depth rule is the entry whose fields the suggestion
|
|
90
|
+
* reports — a side lookup keyed on id could pick a different surface's row (`crossCountryBranches` differs per
|
|
91
|
+
* surface).
|
|
92
|
+
*/
|
|
93
|
+
class FSTReader implements AncestrieReaderLike<PlaceEntry> {
|
|
94
|
+
readonly #fst: FSTMatcher
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parent chains of the entries this reader has served, id-keyed. A place's chain is identical across its surfaces (it
|
|
98
|
+
* is place-row data), so last-write-wins is safe. The algorithm asks {@link FSTReader.ancestorsOf} only for ids it
|
|
99
|
+
* just received from {@link FSTReader.entriesAt}, so serving from this memo answers every real call without an
|
|
100
|
+
* artifact-wide id index.
|
|
101
|
+
*/
|
|
102
|
+
readonly #chains = new Map<number, number[]>()
|
|
103
|
+
|
|
104
|
+
constructor(fst: FSTMatcher) {
|
|
105
|
+
this.#fst = fst
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
walk(tokens: readonly string[]): AncestrieMatch | null {
|
|
109
|
+
return this.#fst.walk([...tokens])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
continuations(stateID: number): AncestrieContinuation[] {
|
|
113
|
+
// Insertion order, verbatim — BFS visit order under the suggestion budget depends on it.
|
|
114
|
+
return this.#fst.continuations(stateID).map((c) => ({
|
|
115
|
+
token: c.token,
|
|
116
|
+
targetState: c.targetState,
|
|
117
|
+
entryCount: c.acceptingCount,
|
|
118
|
+
}))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
entriesAt(stateID: number, limit?: number): AncestrieRecord<PlaceEntry>[] {
|
|
122
|
+
const places = this.#fst.accepting(stateID)
|
|
123
|
+
const selected = limit === undefined ? places : topByReferential(places, limit)
|
|
124
|
+
|
|
125
|
+
return selected.map((entry) => {
|
|
126
|
+
this.#chains.set(entry.wofID, entry.parentChain)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
id: entry.wofID,
|
|
130
|
+
rank: entry.referential,
|
|
131
|
+
parentIDs: entry.parentChain,
|
|
132
|
+
payload: entry,
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ancestorsOf(id: number): number[] {
|
|
138
|
+
return this.#chains.get(id) ?? []
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Autocomplete from the current prefix. Returns suggestions ranked referential-descending.
|
|
144
|
+
*/
|
|
145
|
+
export function autocomplete(fst: FSTMatcher, query: string, opts: AutocompleteOpts = {}): AutocompleteResult {
|
|
146
|
+
const normalizedTokens = normalizeTokens(query)
|
|
147
|
+
|
|
148
|
+
const result = ancestrieAutocomplete<PlaceEntry>(new FSTReader(fst), normalizedTokens, {
|
|
149
|
+
...(opts.maxSuggestions === undefined ? {} : { maxSuggestions: opts.maxSuggestions }),
|
|
150
|
+
...(opts.maxExpansionDepth === undefined ? {} : { maxExpansionDepth: opts.maxExpansionDepth }),
|
|
151
|
+
perBranchLimit: PER_BRANCH,
|
|
152
|
+
// The dedupe key is the DISPLAY name, not the token path: two surfaces of one name must still collapse. (#587)
|
|
153
|
+
...(opts.dedupeByName ? { dedupe: (s: AncestrieSuggestion<PlaceEntry>) => s.payload!.name.toLowerCase() } : {}),
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
query,
|
|
158
|
+
normalizedTokens,
|
|
159
|
+
depth: result.depth,
|
|
160
|
+
suggestions: result.suggestions.map((s) => {
|
|
161
|
+
// Every record this adapter serves carries its entry; the assertion documents the invariant.
|
|
162
|
+
const entry = s.payload!
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
name: entry.name,
|
|
166
|
+
placetype: entry.placetype,
|
|
167
|
+
referential: entry.referential,
|
|
168
|
+
...(entry.encyclopedic === undefined ? {} : { encyclopedic: entry.encyclopedic }),
|
|
169
|
+
wofID: s.id,
|
|
170
|
+
parentChain: s.parentIDs,
|
|
171
|
+
matchDepth: s.matchDepth,
|
|
172
|
+
completionTokens: s.completionTokens,
|
|
173
|
+
}
|
|
174
|
+
}),
|
|
175
|
+
}
|
|
176
|
+
}
|