@mailwoman/resolver-wof-sqlite 9.2.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 +16 -19
- package/lib/address/index.ts +9 -0
- package/{address-point-interpolation.ts → lib/address/point-interpolation.ts} +24 -22
- package/{address-point-schema.ts → lib/address/point-schema.ts} +18 -4
- package/{address-point.ts → lib/address/point.ts} +89 -27
- package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
- package/{ancestry.ts → lib/ancestry/index.ts} +5 -3
- package/{build-candidate.ts → lib/build-candidate.ts} +84 -77
- package/{build-slim.ts → lib/build-slim.ts} +150 -165
- package/{candidate → lib/candidate}/alias-bags.ts +8 -6
- package/{candidate → lib/candidate}/ancestors-sidecar.ts +13 -15
- package/{candidate → lib/candidate}/country-display-names.ts +2 -2
- package/{candidate/shard-fold.ts → lib/candidate/extract-fold.ts} +26 -25
- package/{candidate → lib/candidate}/name-roles.ts +17 -16
- package/{candidate → lib/candidate}/own-name.ts +1 -1
- package/{candidate → lib/candidate}/place-attrs.ts +3 -3
- package/{candidate-ancestors-schema.ts → lib/candidate-ancestors-schema.ts} +4 -4
- package/{candidate-fts.ts → lib/candidate-fts.ts} +3 -3
- package/{candidate-importance.ts → lib/candidate-importance.ts} +53 -57
- package/{candidate-lookup.ts → lib/candidate-lookup.ts} +76 -73
- package/{candidate-schema.ts → lib/candidate-schema.ts} +5 -5
- package/{candidate-scoring.ts → lib/candidate-scoring.ts} +15 -22
- package/{capital-schema.ts → lib/capital-schema.ts} +5 -6
- package/{capitals.ts → lib/capitals.ts} +2 -2
- package/{coincident-roles.ts → lib/coincident-roles.ts} +8 -11
- package/{convention.ts → lib/convention/index.ts} +3 -3
- package/{convention-schema.ts → lib/convention/schema.ts} +2 -2
- package/{coverage-manifest-schema.ts → lib/coverage-manifest-schema.ts} +9 -10
- package/{currency-backfill.ts → lib/currency-backfill.ts} +126 -41
- package/lib/env.ts +50 -0
- package/{exact-match.ts → lib/exact-match.ts} +23 -14
- package/{sharding.ts → lib/extracts.ts} +61 -58
- package/{fst-autocomplete.ts → lib/fst/autocomplete.ts} +4 -4
- package/{fst-builder.ts → lib/fst/builder.ts} +17 -16
- 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} +41 -74
- 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} +24 -19
- package/{fts-query.ts → lib/fts/query.ts} +1 -1
- 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} +14 -13
- package/{index.ts → lib/index.ts} +37 -37
- package/{interpolation.ts → lib/interpolation.ts} +27 -25
- package/{lookup.ts → lib/lookup.ts} +129 -141
- 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} +7 -8
- package/lib/poi/index.ts +8 -0
- package/{poi-lookup.ts → lib/poi/lookup.ts} +22 -28
- package/{poi-schema.ts → lib/poi/schema.ts} +3 -4
- package/{postal-city-alias-lookup.ts → lib/postal/city-alias-lookup.ts} +14 -20
- package/{postal-city-candidate-schema.ts → lib/postal/city-candidate-schema.ts} +2 -2
- package/lib/postal/index.ts +9 -0
- package/{postcode-point-lookup.ts → lib/postcode-point-lookup.ts} +17 -15
- package/{primary-preference.ts → lib/primary-preference.ts} +29 -10
- package/{proximity-rerank.ts → lib/proximity-rerank.ts} +2 -2
- package/{ranking-weights.ts → lib/ranking-weights.ts} +30 -4
- package/{region-keys.ts → lib/region-keys.ts} +9 -9
- package/{reverse.ts → lib/reverse.ts} +34 -46
- package/{schema.ts → lib/schema.ts} +10 -0
- package/{search-fetch.ts → lib/search-fetch.ts} +29 -28
- package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
- package/{sqlite-utils.ts → lib/sqlite-utils.ts} +14 -18
- package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +5 -5
- package/{street-centroid.ts → lib/street/centroid.ts} +14 -21
- 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/{street-normalize.ts → lib/street/normalize.ts} +44 -19
- package/{street-segment-schema.ts → lib/street/segment-schema.ts} +7 -7
- package/{types.ts → lib/types.ts} +18 -12
- package/{unified-schema.ts → lib/unified-schema.ts} +20 -24
- package/lib/uprn/existence.ts +84 -0
- package/lib/uprn/index.ts +9 -0
- package/{uprn-lookup.ts → lib/uprn/lookup.ts} +14 -18
- package/{uprn-schema.ts → lib/uprn/schema.ts} +3 -3
- 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} +18 -18
- package/out/address/point-interpolation.js.map +1 -0
- package/out/{address-point-schema.d.ts → address/point-schema.d.ts} +16 -6
- 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 → address/point.js} +71 -20
- 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} +4 -3
- package/out/ancestry/index.d.ts.map +1 -0
- package/out/{ancestry.js → ancestry/index.js} +3 -2
- package/out/ancestry/index.js.map +1 -0
- package/out/build-candidate.d.ts +30 -24
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +50 -52
- package/out/build-candidate.js.map +1 -1
- package/out/build-slim.d.ts +4 -4
- package/out/build-slim.d.ts.map +1 -1
- package/out/build-slim.js +124 -139
- package/out/build-slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +5 -3
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +2 -2
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors-sidecar.d.ts +9 -10
- package/out/candidate/ancestors-sidecar.d.ts.map +1 -1
- package/out/candidate/ancestors-sidecar.js +7 -7
- package/out/candidate/ancestors-sidecar.js.map +1 -1
- package/out/candidate/country-display-names.d.ts +1 -1
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +1 -1
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +32 -0
- package/out/candidate/extract-fold.d.ts.map +1 -0
- package/out/candidate/{shard-fold.js → extract-fold.js} +20 -20
- package/out/candidate/extract-fold.js.map +1 -0
- package/out/candidate/name-roles.d.ts +13 -11
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +11 -10
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +1 -1
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +1 -1
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +3 -3
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +1 -1
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate-ancestors-schema.d.ts +4 -4
- package/out/candidate-ancestors-schema.d.ts.map +1 -1
- package/out/candidate-ancestors-schema.js +2 -2
- package/out/candidate-ancestors-schema.js.map +1 -1
- package/out/candidate-fts.d.ts +3 -3
- package/out/candidate-fts.d.ts.map +1 -1
- package/out/candidate-fts.js +1 -1
- 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 +46 -51
- package/out/candidate-importance.js.map +1 -1
- package/out/candidate-lookup.d.ts +11 -9
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +61 -58
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +5 -5
- package/out/candidate-schema.d.ts.map +1 -1
- package/out/candidate-schema.js.map +1 -1
- package/out/candidate-scoring.d.ts +7 -7
- package/out/candidate-scoring.d.ts.map +1 -1
- package/out/candidate-scoring.js +10 -14
- package/out/candidate-scoring.js.map +1 -1
- package/out/capital-schema.d.ts +3 -3
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +2 -2
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +1 -1
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +2 -2
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.d.ts +6 -5
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +2 -4
- package/out/coincident-roles.js.map +1 -1
- package/out/{convention.d.ts → convention/index.d.ts} +4 -4
- package/out/convention/index.d.ts.map +1 -0
- package/out/{convention.js → convention/index.js} +2 -2
- package/out/convention/index.js.map +1 -0
- package/out/{convention-schema.d.ts → convention/schema.d.ts} +2 -2
- package/out/convention/schema.d.ts.map +1 -0
- package/out/{convention-schema.js → convention/schema.js} +3 -3
- 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 +4 -4
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +51 -7
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +65 -49
- package/out/currency-backfill.js.map +1 -1
- 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 +5 -5
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +10 -11
- package/out/exact-match.js.map +1 -1
- 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} +2 -2
- package/out/fst/autocomplete.d.ts.map +1 -0
- package/out/{fst-autocomplete.js → fst/autocomplete.js} +3 -3
- 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} +14 -13
- 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} +10 -13
- package/out/fst/freshness.d.ts.map +1 -0
- package/out/{fst-freshness.js → fst/freshness.js} +41 -73
- 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} +16 -9
- package/out/fts/index.d.ts.map +1 -0
- package/out/{fts.js → fts/index.js} +16 -11
- 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} +1 -1
- 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} +10 -8
- package/out/geonames/postal.d.ts.map +1 -0
- package/out/{geonames-postal.js → geonames/postal.js} +9 -9
- package/out/geonames/postal.js.map +1 -0
- package/out/index.d.ts +32 -32
- 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 +10 -9
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +20 -20
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +21 -20
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +102 -112
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts.map +1 -1
- 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 +5 -5
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +3 -3
- 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} +14 -18
- package/out/poi/lookup.js.map +1 -0
- package/out/{poi-schema.d.ts → poi/schema.d.ts} +4 -4
- 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.map +1 -1
- package/out/polygon-schema.js.map +1 -1
- package/out/{postal-city-alias-lookup.d.ts → postal/city-alias-lookup.d.ts} +6 -5
- package/out/postal/city-alias-lookup.d.ts.map +1 -0
- package/out/{postal-city-alias-lookup.js → postal/city-alias-lookup.js} +11 -15
- 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} +3 -3
- package/out/{postal-city-candidate-schema.d.ts.map → postal/city-candidate-schema.d.ts.map} +1 -1
- 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 +13 -5
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +16 -8
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +2 -2
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +2 -2
- package/out/proximity-rerank.js.map +1 -1
- 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 +8 -8
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +9 -9
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +6 -6
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +20 -29
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +9 -0
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +12 -10
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +21 -20
- package/out/search-fetch.js.map +1 -1
- 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 +8 -8
- package/out/sqlite-utils.d.ts.map +1 -1
- package/out/sqlite-utils.js +7 -8
- package/out/sqlite-utils.js.map +1 -1
- package/out/{street-centroid-schema.d.ts → street/centroid-schema.d.ts} +6 -6
- package/out/{street-centroid-schema.d.ts.map → street/centroid-schema.d.ts.map} +1 -1
- 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} +13 -19
- 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 → street/normalize.d.ts} +25 -14
- package/out/street/normalize.d.ts.map +1 -0
- package/out/{street-normalize.js → street/normalize.js} +36 -17
- package/out/street/normalize.js.map +1 -0
- package/out/{street-segment-schema.d.ts → street/segment-schema.d.ts} +8 -8
- package/out/{street-segment-schema.d.ts.map → street/segment-schema.d.ts.map} +1 -1
- 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 +18 -12
- package/out/types.d.ts.map +1 -1
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +5 -4
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +15 -18
- 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 → uprn/lookup.d.ts} +4 -4
- package/out/uprn/lookup.d.ts.map +1 -0
- package/out/{uprn-lookup.js → uprn/lookup.js} +11 -14
- package/out/uprn/lookup.js.map +1 -0
- package/out/{uprn-schema.d.ts → uprn/schema.d.ts} +4 -4
- package/out/uprn/schema.d.ts.map +1 -0
- package/out/{uprn-schema.js → uprn/schema.js} +4 -4
- package/out/uprn/schema.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +184 -26
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +338 -86
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +465 -269
- package/out/address-point-interpolation.d.ts.map +0 -1
- package/out/address-point-interpolation.js.map +0 -1
- package/out/address-point-schema.d.ts.map +0 -1
- package/out/address-point-schema.js.map +0 -1
- package/out/address-point.d.ts.map +0 -1
- package/out/address-point.js.map +0 -1
- package/out/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/candidate/shard-fold.d.ts +0 -31
- package/out/candidate/shard-fold.d.ts.map +0 -1
- package/out/candidate/shard-fold.js.map +0 -1
- package/out/convention-schema.d.ts.map +0 -1
- package/out/convention-schema.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.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/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/sharding.d.ts +0 -114
- package/out/sharding.d.ts.map +0 -1
- package/out/sharding.js.map +0 -1
- package/out/street-centroid.d.ts.map +0 -1
- package/out/street-centroid.js.map +0 -1
- package/out/street-morphology-fst-builder.d.ts.map +0 -1
- package/out/street-morphology-fst-builder.js.map +0 -1
- package/out/street-morphology-fst-loader.d.ts.map +0 -1
- package/out/street-morphology-fst-loader.js.map +0 -1
- package/out/street-name-lookup.d.ts.map +0 -1
- package/out/street-name-lookup.js.map +0 -1
- package/out/street-normalize.d.ts.map +0 -1
- package/out/street-normalize.js.map +0 -1
- package/out/uprn-lookup.d.ts.map +0 -1
- package/out/uprn-lookup.js.map +0 -1
- package/out/uprn-schema.d.ts.map +0 -1
- package/out/uprn-schema.js.map +0 -1
- package/weights-overlay-linker.ts +0 -377
- /package/{name-score.ts → lib/name-score.ts} +0 -0
- /package/{polygon-schema.ts → lib/polygon-schema.ts} +0 -0
- /package/{postal-city-alias-schema.ts → lib/postal/city-alias-schema.ts} +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Only type imports and arithmetic live here: anything with a `node:` import stays out.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { CandidateTable } from "
|
|
14
|
+
import type { CandidateTable } from "#candidate-schema"
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The row shape the re-rank needs. `is_primary` is optional: a reader over an artifact vintage that predates the column
|
|
@@ -54,8 +54,8 @@ const SEAT_PLACETYPE = "locality"
|
|
|
54
54
|
/**
|
|
55
55
|
* Over-fetch cap for {@link rankByPrimaryPreference}: the candidate rows for one `name_key` (all same-name places
|
|
56
56
|
* worldwide) are re-ranked in-process, so the probe fetches this many (population-ordered) before the re-rank rather
|
|
57
|
-
* than the caller's small `limit`, ensuring the intended primary isn't
|
|
58
|
-
* foreign aliases. Bounded and small — a single contiguous B-tree scan.
|
|
57
|
+
* than the caller's small `limit`, ensuring the intended primary isn't pushed below the fold by a cluster of
|
|
58
|
+
* more-populous foreign aliases. Bounded and small — a single contiguous B-tree scan.
|
|
59
59
|
*/
|
|
60
60
|
export const RERANK_FETCH = 64
|
|
61
61
|
|
|
@@ -66,7 +66,7 @@ export type RankedRow<R> = R & {
|
|
|
66
66
|
/**
|
|
67
67
|
* `neg_rank` plus the bounded cross-country alias penalty — the value the row is ORDERED by, and the base the emitted
|
|
68
68
|
* `prominence` is derived from (so the resolver walk's `prominence ?? score` sort, `resolve.ts`, agrees with this
|
|
69
|
-
* order; the raw `score`/`neg_rank` is left intact for the walk's `minWinningScore`
|
|
69
|
+
* order; the raw `score`/`neg_rank` is left intact for the walk's `minWinningScore` floor).
|
|
70
70
|
*/
|
|
71
71
|
effectiveNegRank: number
|
|
72
72
|
/**
|
|
@@ -93,6 +93,14 @@ export type RankedRow<R> = R & {
|
|
|
93
93
|
* was never asked, never "not contained".
|
|
94
94
|
*/
|
|
95
95
|
containedByQualifier?: boolean
|
|
96
|
+
/**
|
|
97
|
+
* The #1882 exemption's firing mark (#1893): this row would have taken the cross-country alias penalty and
|
|
98
|
+
* `exemptVariantAliases` prevented it. PRESENT only when the exemption changed this row's treatment; absent on every
|
|
99
|
+
* row it merely inspected — an in-country variant, a primary, a set with no foreign top primary. The winner-level
|
|
100
|
+
* receipt downstream (`mechanism_fired_on.variant_alias_exemption`) counts this mark only when it survives onto the
|
|
101
|
+
* selected candidate.
|
|
102
|
+
*/
|
|
103
|
+
variantExempted?: true
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
/**
|
|
@@ -126,7 +134,7 @@ export type RankedRow<R> = R & {
|
|
|
126
134
|
* the fame prior overrides by design; a probe with NO placetype filter (the browser cascade's last resort, the dev
|
|
127
135
|
* lookup tools) presents the full tie and this term is all that breaks it.
|
|
128
136
|
*
|
|
129
|
-
* BOTH
|
|
137
|
+
* BOTH CONDITIONS ARE required, and a plain "finer placetype wins" measured wrong before this shape was settled: it
|
|
130
138
|
* moved the top slot on 11,377 keys in `candidate.db`, of which only 722 were the seat/district duplicate. The rest
|
|
131
139
|
* were contests between genuinely distinct places that merely tie — 2,885 `locality → neighbourhood` (a bare city name
|
|
132
140
|
* losing to a same-named hood), 2,973 `region → county`, 2,662 `postalcode → locality` — and 7,179 of the 11,377 sat at
|
|
@@ -162,17 +170,28 @@ export function rankByPrimaryPreference<R extends PrimaryPreferenceRow>(
|
|
|
162
170
|
// query is naming THAT place, not colliding with it; the penalty exists for the coincidental-collision
|
|
163
171
|
// class ("Çançun"/`cancun`), which the detector's measured threshold keeps un-stamped. An artifact
|
|
164
172
|
// predating the role column carries no 'variant' rows, so the flag no-ops there by construction.
|
|
173
|
+
const wouldPenalize = (r: R): boolean =>
|
|
174
|
+
typeof topCountry === "number" && r.is_primary !== 1 && r.country_id !== topCountry
|
|
175
|
+
|
|
165
176
|
const isCrossCountryAlias = (r: R): boolean =>
|
|
166
|
-
|
|
167
|
-
r.is_primary !== 1 &&
|
|
168
|
-
r.country_id !== topCountry &&
|
|
169
|
-
!(exemptVariantAliases && r.name_role === "variant")
|
|
177
|
+
wouldPenalize(r) && !(exemptVariantAliases && r.name_role === "variant")
|
|
170
178
|
|
|
171
179
|
const annotate = (r: R): RankedRow<R> => {
|
|
172
180
|
const penalized = isCrossCountryAlias(r)
|
|
173
181
|
const effectiveNegRank = r.neg_rank + (penalized ? delta : 0)
|
|
174
182
|
|
|
175
|
-
|
|
183
|
+
// The firing mark (#1893): this row WOULD have taken the cross-country penalty and the exemption
|
|
184
|
+
// prevented it — conditions the winner-level receipt needs, computed where the decision is made. A
|
|
185
|
+
// variant row that was in-country, primary, or keyed under a set with no foreign primary never
|
|
186
|
+
// fired, and stays unmarked.
|
|
187
|
+
const exempted = !penalized && exemptVariantAliases && r.name_role === "variant" && wouldPenalize(r)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...r,
|
|
191
|
+
effectiveNegRank,
|
|
192
|
+
demoted: penalized && effectiveNegRank > topPrimary!.neg_rank,
|
|
193
|
+
...(exempted ? { variantExempted: true as const } : {}),
|
|
194
|
+
}
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
// 1 for a populated-place row that can BE a district's seat, 0 for everything else — no code map, no
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* filter. Byte-identical to plain population order when no bias is passed.
|
|
9
9
|
*
|
|
10
10
|
* This lives in its own platform-free module because it has to run identically in two places: the Node candidate
|
|
11
|
-
* reader and the browser byte-range twin. That is the #861 server↔demo parity contract
|
|
11
|
+
* reader and the browser byte-range twin. That is the #861 server↔demo parity contract and the second thing
|
|
12
12
|
* here held by construction rather than by comment (`primary-preference.ts` was the first). Constants alone were not
|
|
13
13
|
* enough — the two copies agreed on every literal and still diverged on which field the population term reads and on
|
|
14
14
|
* whether the combined value is written back, which is the half that actually decides the answer.
|
|
15
15
|
*
|
|
16
|
-
* Two properties are
|
|
16
|
+
* Two properties are required and easy to lose when transcribing:
|
|
17
17
|
*
|
|
18
18
|
* 1. The population base is `prominence ?? score`, NOT `score`. `prominence` carries the bounded cross-country
|
|
19
19
|
* primary preference, so reading raw score lets a coincidental foreign alias ride population back over a primary
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The locality-ranking weights and their shipped defaults. Its own module because every value here is
|
|
7
7
|
* a measured tuning decision with its rationale attached — the block reads as a reference table, not
|
|
8
|
-
* as part of the lookup's control flow, and the tests import it directly to pin one
|
|
8
|
+
* as part of the lookup's control flow, and the tests import it directly to pin one change at a time.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -96,7 +96,7 @@ export interface RankingWeights {
|
|
|
96
96
|
* Fixes unscoped "Åbo" → Turku (its official Swedish name) over a hamlet literally named Åbo; population still orders
|
|
97
97
|
* within the sub-tier, so Paris → Paris FR is untouched.
|
|
98
98
|
*
|
|
99
|
-
* Default true (operator-promoted 2026-07-03 after the pre-registered
|
|
99
|
+
* Default true (operator-promoted 2026-07-03 after the pre-registered eval battery: four intended exonym flips —
|
|
100
100
|
* Berne→Bern, Bruges→Brugge, Roma→Rome, Åbo→Turku — with the namesake/abbreviation rows and the US/FI panels
|
|
101
101
|
* byte-identical). Requires a gazetteer carrying the #940 ingest bit — on older DBs without the `official` column the
|
|
102
102
|
* probe fails soft and behavior is identical to the flag being off.
|
|
@@ -114,7 +114,7 @@ export interface RankingWeights {
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* The shipped weights. Every value is a measured decision — change one and re-run the resolver eval; the per-field docs
|
|
117
|
-
* on {@link RankingWeights} say what each
|
|
117
|
+
* on {@link RankingWeights} say what each change moves and what motivated its current value.
|
|
118
118
|
*/
|
|
119
119
|
export const DEFAULT_WEIGHTS: RankingWeights = {
|
|
120
120
|
placetypeMatchBoost: 0.5,
|
|
@@ -142,7 +142,33 @@ export const DEFAULT_WEIGHTS: RankingWeights = {
|
|
|
142
142
|
// consulted — keeps population as an intra-tier prominence tiebreaker, not a cross-tier promoter.
|
|
143
143
|
// Fixes the 2-letter-region-abbrev bug ("ME" → Maine, not the more-populous Missouri).
|
|
144
144
|
exactMatchTiering: true,
|
|
145
|
-
// #936 option 3 — promoted default-ON 2026-07-03 (
|
|
145
|
+
// #936 option 3 — promoted default-ON 2026-07-03 (eval battery PASS; see the RankingWeights docstring).
|
|
146
146
|
officialNameExact: true,
|
|
147
147
|
officialNameExactFloor: 100_000,
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The population contribution as a 0..1 fraction: `min(1, log10(1 + population) / populationScaleLog10)`. Zero for an
|
|
152
|
+
* absent or non-positive population — and zero for a non-positive scale, so a magnitude never carries its own absence.
|
|
153
|
+
* The coordinate-first locality path consumes this fraction directly; {@link populationBoostTerm} scales it.
|
|
154
|
+
*/
|
|
155
|
+
export function populationScaleTerm(
|
|
156
|
+
population: number | null | undefined,
|
|
157
|
+
weights: Pick<RankingWeights, "populationScaleLog10">
|
|
158
|
+
): number {
|
|
159
|
+
if (population == null || population <= 0 || weights.populationScaleLog10 <= 0) return 0
|
|
160
|
+
|
|
161
|
+
return Math.min(1, Math.log10(1 + population) / weights.populationScaleLog10)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The additive population boost: `populationBoost * populationScaleTerm(...)`, capped at `populationBoost` magnitude at
|
|
166
|
+
* `10^populationScaleLog10` people. Missing population contributes 0 — never a penalty. The one formula behind the Node
|
|
167
|
+
* weighted sum and the WASM re-rank, so the two backends cannot drift.
|
|
168
|
+
*/
|
|
169
|
+
export function populationBoostTerm(
|
|
170
|
+
population: number | null | undefined,
|
|
171
|
+
weights: Pick<RankingWeights, "populationBoost" | "populationScaleLog10">
|
|
172
|
+
): number {
|
|
173
|
+
return weights.populationBoost * populationScaleTerm(population, weights)
|
|
174
|
+
}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { matchSubdivision, matchSubdivisionIn } from "@mailwoman/codex/country"
|
|
20
20
|
|
|
21
|
-
import { normalizeLocalityForKey } from "
|
|
21
|
+
import { normalizeLocalityForKey } from "#street/normalize"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* The ancestry placetypes that answer for a parsed `region` qualifier — WOF's admin band between country and locality.
|
|
@@ -120,14 +120,14 @@ export function regionKeys(value: string, countryAlpha2?: string): Set<string> {
|
|
|
120
120
|
/**
|
|
121
121
|
* The PROBE-side expansion for a region qualifier — {@link regionKeys} plus the county-PREFIXED variant of every key.
|
|
122
122
|
*
|
|
123
|
-
* The verdict
|
|
124
|
-
* shared stripped key `donegal`. A table probe is one-sided: it matches the STORED fold verbatim, and WOF stores
|
|
125
|
-
* counties under `county donegal` with no bare `donegal` key (measured on the shipped candidate.db — the
|
|
126
|
-
* probe missed every Irish county until this variant landed). Adding `county <key>` restores the
|
|
127
|
-
* one stored-form family with an evidenced case; the union is monotone (a wider qualifier set can
|
|
128
|
-
* BEARERS, each of which must still genuinely contain a candidate before anything moves). The suffix
|
|
129
|
-
* province`) is deliberately absent — no stored-form case has been evidenced, and a
|
|
130
|
-
* built.
|
|
123
|
+
* The verdict implementation intersects two {@link regionKeys} SETS, so `Co. Donegal` meets stored `County Donegal` at
|
|
124
|
+
* the shared stripped key `donegal`. A table probe is one-sided: it matches the STORED fold verbatim, and WOF stores
|
|
125
|
+
* Irish counties under `county donegal` with no bare `donegal` key (measured on the shipped candidate.db — the
|
|
126
|
+
* qualifier probe missed every Irish county until this variant landed). Adding `county <key>` restores the
|
|
127
|
+
* two-sidedness for the one stored-form family with an evidenced case; the union is monotone (a wider qualifier set can
|
|
128
|
+
* only find more BEARERS, each of which must still genuinely contain a candidate before anything moves). The suffix
|
|
129
|
+
* sibling (`<key> province`) is deliberately absent — no stored-form case has been evidenced, and a change without a
|
|
130
|
+
* board does not get built.
|
|
131
131
|
*/
|
|
132
132
|
export function regionQualifierProbeKeys(value: string, countryAlpha2?: string): Set<string> {
|
|
133
133
|
const keys = regionKeys(value, countryAlpha2)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
6
|
* Reverse geocoding (#484): `(lat, lon)` → the containing admin hierarchy. Assembly over existing
|
|
7
|
-
*
|
|
7
|
+
* implementation, per the 2026-06-11 scoping notes:
|
|
8
8
|
*
|
|
9
9
|
* 1. **Candidate fetch** — the admin DB's `place_bbox` R*Tree (built by `fts.ts`) for places whose
|
|
10
10
|
* bbox contains the point, smallest-area-first (so the FIRST polygon confirmation is the
|
|
@@ -28,15 +28,16 @@
|
|
|
28
28
|
* says so per result rather than pretending.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
32
|
+
import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
|
|
33
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
34
|
+
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
32
35
|
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import { allRows } from "./sqlite-utils.ts"
|
|
39
|
-
import type { PlaceCandidate, WOFPlacetype } from "./types.ts"
|
|
36
|
+
import { ancestorLineage, placetypeDepth } from "#ancestry/index"
|
|
37
|
+
import { PLACE_BBOX_TABLE } from "#fts/index"
|
|
38
|
+
import type { WOFDatabase } from "#schema"
|
|
39
|
+
import { allRows } from "#sqlite-utils"
|
|
40
|
+
import type { PlaceCandidate, WOFPlacetype } from "#types"
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
43
|
* Largest absolute latitude in WGS-84 degrees.
|
|
@@ -80,7 +81,7 @@ export interface WOFReverseGeocoderOpts {
|
|
|
80
81
|
/**
|
|
81
82
|
* Pre-opened admin DB — primarily for tests against an inline fixture.
|
|
82
83
|
*/
|
|
83
|
-
adminDatabase?:
|
|
84
|
+
adminDatabase?: DatabaseClient<WOFDatabase>
|
|
84
85
|
/**
|
|
85
86
|
* Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL — without it every result
|
|
86
87
|
* is `containment: "approximate"` (centroid-only mode). Mutually exclusive with `polygonDatabase`.
|
|
@@ -89,7 +90,7 @@ export interface WOFReverseGeocoderOpts {
|
|
|
89
90
|
/**
|
|
90
91
|
* Pre-opened polygon DB — primarily for tests.
|
|
91
92
|
*/
|
|
92
|
-
polygonDatabase?:
|
|
93
|
+
polygonDatabase?: DatabaseClient<WOFDatabase>
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export interface ReverseGeocodeOpts {
|
|
@@ -160,9 +161,9 @@ function toPlaceCandidate(row: CandidateRow, distanceKm?: number): PlaceCandidat
|
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
export class WOFReverseGeocoder implements Disposable {
|
|
163
|
-
readonly #admin:
|
|
164
|
+
readonly #admin: DatabaseClient<WOFDatabase>
|
|
164
165
|
readonly #ownsAdmin: boolean
|
|
165
|
-
readonly #polygons:
|
|
166
|
+
readonly #polygons: DatabaseClient<WOFDatabase> | null
|
|
166
167
|
readonly #ownsPolygons: boolean
|
|
167
168
|
/**
|
|
168
169
|
* Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15 county polygons 1400
|
|
@@ -170,7 +171,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
170
171
|
* LRU-tracked; the polygons are DP-simplified and small, the cap exists only to keep a long-lived server process
|
|
171
172
|
* honest.
|
|
172
173
|
*/
|
|
173
|
-
readonly #geometryCache = new Map<number,
|
|
174
|
+
readonly #geometryCache = new Map<number, ParsedGeometry | null>()
|
|
174
175
|
static readonly #GEOMETRY_CACHE_CAP = 4096
|
|
175
176
|
|
|
176
177
|
constructor(opts: WOFReverseGeocoderOpts) {
|
|
@@ -186,38 +187,29 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
186
187
|
throw new Error("WOFReverseGeocoder: pass either `polygonDatabase` or `polygonDBPath`, not both")
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
this.#admin = opts.adminDatabase ?? new
|
|
190
|
+
this.#admin = opts.adminDatabase ?? new DatabaseClient<WOFDatabase>(opts.adminDBPath!, { readOnly: true })
|
|
190
191
|
this.#ownsAdmin = !opts.adminDatabase
|
|
191
192
|
|
|
192
193
|
this.#polygons =
|
|
193
|
-
opts.polygonDatabase ??
|
|
194
|
+
opts.polygonDatabase ??
|
|
195
|
+
(opts.polygonDBPath ? new DatabaseClient<WOFDatabase>(opts.polygonDBPath, { readOnly: true }) : null)
|
|
194
196
|
|
|
195
197
|
this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.polygonDBPath)
|
|
196
198
|
|
|
197
199
|
// Fail loudly up front — the R*Tree is a build artifact, not part of the upstream WOF
|
|
198
200
|
// distribution, and a missing index would otherwise surface as an opaque SQL error per query.
|
|
199
|
-
|
|
200
|
-
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
201
|
-
.get(PLACE_BBOX_TABLE)
|
|
202
|
-
|
|
203
|
-
if (!hasBbox) {
|
|
201
|
+
if (!tableExists(this.#admin, PLACE_BBOX_TABLE)) {
|
|
204
202
|
throw new Error(
|
|
205
203
|
`WOFReverseGeocoder: the admin DB has no \`${PLACE_BBOX_TABLE}\` R*Tree. Build it with ` +
|
|
206
204
|
"`mailwoman gazetteer build fts <path-to-wof.db>` (see resolver-wof-sqlite/README.md)."
|
|
207
205
|
)
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
if (this.#polygons) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (!hasPolygons) {
|
|
216
|
-
throw new Error(
|
|
217
|
-
"WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
|
|
218
|
-
"built by scripts/build-wof-polygons.mjs."
|
|
219
|
-
)
|
|
220
|
-
}
|
|
208
|
+
if (this.#polygons && !tableExists(this.#polygons, "polygons")) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
"WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
|
|
211
|
+
"built by scripts/build-wof-polygons.mjs."
|
|
212
|
+
)
|
|
221
213
|
}
|
|
222
214
|
}
|
|
223
215
|
|
|
@@ -415,7 +407,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
415
407
|
lon: number,
|
|
416
408
|
maxApproximateKm: number
|
|
417
409
|
): CandidateRow[] {
|
|
418
|
-
const
|
|
410
|
+
const window = bboxAround(lat, lon, maxApproximateKm * 4)
|
|
419
411
|
|
|
420
412
|
return allRows<CandidateRow>(
|
|
421
413
|
this.#admin.prepare(
|
|
@@ -427,17 +419,17 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
427
419
|
),
|
|
428
420
|
parentID,
|
|
429
421
|
placetype,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
422
|
+
window.minLat,
|
|
423
|
+
window.maxLat,
|
|
424
|
+
window.minLon,
|
|
425
|
+
window.maxLon
|
|
434
426
|
)
|
|
435
427
|
}
|
|
436
428
|
|
|
437
429
|
/**
|
|
438
430
|
* Parsed GeoJSON geometry for a WOF id, or null when absent / unparseable / no polygon DB.
|
|
439
431
|
*/
|
|
440
|
-
#geometry(id: number):
|
|
432
|
+
#geometry(id: number): ParsedGeometry | null {
|
|
441
433
|
if (!this.#polygons) return null
|
|
442
434
|
const cached = this.#geometryCache.get(id)
|
|
443
435
|
|
|
@@ -449,24 +441,20 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
449
441
|
|
|
450
442
|
const row = this.#polygons.prepare(`SELECT geom FROM polygons WHERE id = ?`).get(id) as { geom: string } | undefined
|
|
451
443
|
// Malformed row parses to null — treat as no-polygon rather than failing the query.
|
|
452
|
-
const geometry = row ? tryParsingJSON<
|
|
444
|
+
const geometry = row ? tryParsingJSON<ParsedGeometry>(row.geom) : null
|
|
453
445
|
|
|
454
446
|
this.#geometryCache.set(id, geometry)
|
|
455
447
|
|
|
456
448
|
return geometry
|
|
457
449
|
}
|
|
458
450
|
|
|
459
|
-
|
|
451
|
+
[Symbol.dispose](): void {
|
|
460
452
|
if (this.#ownsAdmin) {
|
|
461
|
-
this.#admin.
|
|
453
|
+
this.#admin.destroy()
|
|
462
454
|
}
|
|
463
455
|
|
|
464
456
|
if (this.#ownsPolygons) {
|
|
465
|
-
this.#polygons?.
|
|
457
|
+
this.#polygons?.destroy()
|
|
466
458
|
}
|
|
467
459
|
}
|
|
468
|
-
|
|
469
|
-
[Symbol.dispose](): void {
|
|
470
|
-
this.close()
|
|
471
|
-
}
|
|
472
460
|
}
|
|
@@ -163,7 +163,17 @@ export interface CoincidentRolesTable {
|
|
|
163
163
|
* build/augment WRITERS adopt it so a column rename is a compile error on both sides (the drift that bit the corpus
|
|
164
164
|
* TIGER adapter).
|
|
165
165
|
*/
|
|
166
|
+
/**
|
|
167
|
+
* The provenance row every built extract carries: source fingerprints travelling WITH the database rather than in a
|
|
168
|
+
* document that can drift from it. Written by the postcode builders in `mailwoman/gazetteer-pipeline`.
|
|
169
|
+
*/
|
|
170
|
+
export interface ExtractMetaTable {
|
|
171
|
+
key: string
|
|
172
|
+
value: string | null
|
|
173
|
+
}
|
|
174
|
+
|
|
166
175
|
export interface WOFDatabase {
|
|
176
|
+
meta: ExtractMetaTable
|
|
167
177
|
place_search: PlaceSearchTable
|
|
168
178
|
spr: SprTable
|
|
169
179
|
names: NamesTable
|
|
@@ -12,14 +12,13 @@
|
|
|
12
12
|
* whole corpus, so an exact haversine over the survivors is cheap.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import type { DatabaseSync, SQLInputValue } from "node:sqlite"
|
|
16
|
-
|
|
17
15
|
import { bboxAround } from "@mailwoman/spatial"
|
|
16
|
+
import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
|
|
18
17
|
|
|
19
|
-
import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "
|
|
20
|
-
import type { RankingWeights } from "
|
|
21
|
-
import { allRows } from "
|
|
22
|
-
import type { FindPlaceQuery, WOFPlacetype } from "
|
|
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"
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Query length at or below which the FTS window is widened. A two- or three-character query is almost always a region
|
|
@@ -60,19 +59,21 @@ export interface RawSearchRow {
|
|
|
60
59
|
max_longitude: number | null
|
|
61
60
|
population: number | null // from the place_population aux table; null when missing
|
|
62
61
|
/**
|
|
63
|
-
* From `place_importance.encyclopedic` when the
|
|
64
|
-
* place has no Wikipedia article, or the
|
|
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
65
|
*/
|
|
66
66
|
encyclopedic: number | null
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Fetch the raw candidate rows for a name match on one
|
|
71
|
-
* short queries), plus the population-ordered companion fetch that keeps the prominent holders of a name
|
|
72
|
-
* `schemaName` is the routed
|
|
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.
|
|
73
74
|
*/
|
|
74
|
-
export function fetchSearchRows(options: {
|
|
75
|
-
db:
|
|
75
|
+
export function fetchSearchRows<DB>(options: {
|
|
76
|
+
db: DatabaseClient<DB>
|
|
76
77
|
schemaName: string
|
|
77
78
|
query: FindPlaceQuery
|
|
78
79
|
placetypes: WOFPlacetype[] | null
|
|
@@ -112,7 +113,7 @@ export function fetchSearchRows(options: {
|
|
|
112
113
|
// spr table but should never win a contemporary lookup. `is_current = 0` is the only WOF
|
|
113
114
|
// value that means "not current"; both `-1` (modern) and `1` (legacy) mean current. See #91.
|
|
114
115
|
// Note: with schema-qualified FROM the bare `place_search` reference in MATCH resolves to
|
|
115
|
-
// the FROM table — required by FTS5 parser, see
|
|
116
|
+
// the FROM table — required by FTS5 parser, see extracts.ts header comment.
|
|
116
117
|
const where: string[] = ["place_search MATCH ?", "spr.is_current != 0", "spr.is_deprecated = 0"]
|
|
117
118
|
const params: SQLInputValue[] = [ftsQuery]
|
|
118
119
|
|
|
@@ -132,10 +133,10 @@ export function fetchSearchRows(options: {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
// Bbox + near-with-radius are SQL-level filters via the R*Tree. We only emit the JOIN when
|
|
135
|
-
// the active
|
|
136
|
-
// filter so legacy DBs /
|
|
137
|
-
const
|
|
138
|
-
const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) &&
|
|
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
|
|
139
140
|
let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
|
|
140
141
|
|
|
141
142
|
if (useBboxJoin) {
|
|
@@ -146,21 +147,21 @@ export function fetchSearchRows(options: {
|
|
|
146
147
|
params.push(filterBox.maxLat, filterBox.minLat, filterBox.maxLon, filterBox.minLon)
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
// LEFT JOIN the population aux table when present. Missing-on-this-
|
|
150
|
+
// LEFT JOIN the population aux table when present. Missing-on-this-extract means the SELECT
|
|
150
151
|
// just doesn't include the population column; the post-scoring loop treats it as 0.
|
|
151
|
-
const
|
|
152
|
+
const extractHasPopulation = hasPopulationIndex.get(sch) === true
|
|
152
153
|
|
|
153
|
-
const populationSelect =
|
|
154
|
+
const populationSelect = extractHasPopulation
|
|
154
155
|
? `${PLACE_POPULATION_TABLE}.population AS population`
|
|
155
156
|
: `NULL AS population`
|
|
156
157
|
|
|
157
|
-
const populationJoin =
|
|
158
|
+
const populationJoin = extractHasPopulation
|
|
158
159
|
? `LEFT JOIN ${sch}.${PLACE_POPULATION_TABLE} ON ${PLACE_POPULATION_TABLE}.id = spr.id`
|
|
159
160
|
: ""
|
|
160
161
|
|
|
161
162
|
// The encyclopedic score is CARRIED, never ranked on (ROAD_TO_V9 §2, ratified 2026-08-06) — it
|
|
162
|
-
// appears in the SELECT and in no ORDER BY, here or in the companion fetch below.
|
|
163
|
-
// split column, so a pre-split
|
|
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.
|
|
164
165
|
const { select: encyclopedicSelect, join: encyclopedicJoin } = encyclopedicClauses.get(sch)!
|
|
165
166
|
|
|
166
167
|
// Push the population boost into the ORDER BY when the index is available, so famous places
|
|
@@ -177,12 +178,12 @@ export function fetchSearchRows(options: {
|
|
|
177
178
|
// column weighted to zero — no weighting isolates name relevance in this schema. The famous-
|
|
178
179
|
// holder guarantee lives in the population-ordered companion fetch below instead, and the
|
|
179
180
|
// exact tier breaks ties by population in the post-scoring sort.
|
|
180
|
-
const orderByExpr =
|
|
181
|
+
const orderByExpr = extractHasPopulation
|
|
181
182
|
? `(bm25(place_search) - ? * MIN(1.0, COALESCE(log10(1.0 + ${PLACE_POPULATION_TABLE}.population), 0) / ?))`
|
|
182
183
|
: "bm25(place_search)"
|
|
183
184
|
|
|
184
185
|
// Schema-qualified FROM with bare-name MATCH — required syntax for FTS5 on attached schemas.
|
|
185
|
-
// See
|
|
186
|
+
// See extracts.ts header for the failure mode that drove this design.
|
|
186
187
|
const stmt = db.prepare(`
|
|
187
188
|
SELECT
|
|
188
189
|
spr.id AS id,
|
|
@@ -205,7 +206,7 @@ export function fetchSearchRows(options: {
|
|
|
205
206
|
LIMIT ?
|
|
206
207
|
`)
|
|
207
208
|
|
|
208
|
-
if (
|
|
209
|
+
if (extractHasPopulation) {
|
|
209
210
|
params.push(weights.populationBoost, weights.populationScaleLog10)
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -219,7 +220,7 @@ export function fetchSearchRows(options: {
|
|
|
219
220
|
// vs a +4.0 boost cap), so FR Paris never even reaches post-scoring. This fetch makes the
|
|
220
221
|
// prominent holders of a name pool-complete BY CONSTRUCTION; the exact-tier sort below
|
|
221
222
|
// decides whether they win. Skipped without a population index (nothing to order by).
|
|
222
|
-
if (
|
|
223
|
+
if (extractHasPopulation) {
|
|
223
224
|
const popStmt = db.prepare(`
|
|
224
225
|
SELECT
|
|
225
226
|
spr.id AS id,
|
|
@@ -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
|
-
* `WOFSQLitePlaceLookup` auto-detects which
|
|
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
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* Small shared helpers for the SQLite-backed lookups.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { DatabaseSync, SQLInputValue } from "node:sqlite"
|
|
10
|
-
|
|
11
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
12
|
|
|
13
13
|
// The row-shape assertion itself lives in `core` so the readers that cannot depend on this package reach the same
|
|
14
|
-
//
|
|
14
|
+
// helper; re-exported here because this module is where this package's readers already look for it.
|
|
15
15
|
export { allRows, getRow } from "@mailwoman/core/utils"
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -23,8 +23,8 @@ export type PreparedGet<Parameters extends SQLInputValue[], Row> = (...parameter
|
|
|
23
23
|
/**
|
|
24
24
|
* Prepare a single-row query while preserving its exact parameter tuple at every call site.
|
|
25
25
|
*/
|
|
26
|
-
export function prepareGet<Parameters extends SQLInputValue[], Row>(
|
|
27
|
-
db:
|
|
26
|
+
export function prepareGet<Parameters extends SQLInputValue[], Row, DB>(
|
|
27
|
+
db: DatabaseClient<DB>,
|
|
28
28
|
sql: string
|
|
29
29
|
): PreparedGet<Parameters, Row> {
|
|
30
30
|
const statement = db.prepare(sql)
|
|
@@ -40,8 +40,8 @@ export type PreparedAll<Parameters extends SQLInputValue[], Row> = (...parameter
|
|
|
40
40
|
/**
|
|
41
41
|
* Prepare a multi-row query while preserving its exact parameter tuple at every call site.
|
|
42
42
|
*/
|
|
43
|
-
export function prepareAll<Parameters extends SQLInputValue[], Row>(
|
|
44
|
-
db:
|
|
43
|
+
export function prepareAll<Parameters extends SQLInputValue[], Row, DB>(
|
|
44
|
+
db: DatabaseClient<DB>,
|
|
45
45
|
sql: string
|
|
46
46
|
): PreparedAll<Parameters, Row> {
|
|
47
47
|
const statement = db.prepare(sql)
|
|
@@ -51,15 +51,13 @@ export function prepareAll<Parameters extends SQLInputValue[], Row>(
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
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
|
|
55
|
-
* CREATES one) — rather than throwing `no such table` at construction and taking down a whole state's geocode
|
|
56
|
-
* missing table makes the lookup a no-op miss.
|
|
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
57
|
*/
|
|
58
|
-
export function hasTable(db:
|
|
58
|
+
export function hasTable<DB>(db: DatabaseClient<DB>, name: string): boolean {
|
|
59
59
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return row !== undefined
|
|
60
|
+
return tableExists(db, name)
|
|
63
61
|
} catch {
|
|
64
62
|
return false
|
|
65
63
|
}
|
|
@@ -76,11 +74,9 @@ export function hasTable(db: DatabaseSync, name: string): boolean {
|
|
|
76
74
|
* Note the interpolation: PRAGMA does not take bound parameters, so `table` is spliced. Every caller passes a
|
|
77
75
|
* module-level constant; never pass user input.
|
|
78
76
|
*/
|
|
79
|
-
export function hasColumn(db:
|
|
77
|
+
export function hasColumn<DB>(db: DatabaseClient<DB>, table: string, column: string): boolean {
|
|
80
78
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return rows.some((r) => String(r.name) === column)
|
|
79
|
+
return columnExists(db, table, column)
|
|
84
80
|
} catch {
|
|
85
81
|
return false
|
|
86
82
|
}
|