@mailwoman/resolver-wof-sqlite 9.2.0 → 9.4.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} +25 -24
- package/{address-point-schema.ts → lib/address/point-schema.ts} +18 -4
- package/lib/address/point.ts +328 -0
- package/{ancestry-backfill.ts → lib/ancestry/backfill.ts} +14 -19
- package/{ancestry.ts → lib/ancestry/index.ts} +5 -4
- package/{build-candidate.ts → lib/build-candidate.ts} +99 -79
- 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/lib/candidate/extract-fold.ts +219 -0
- package/{candidate → lib/candidate}/name-roles.ts +17 -16
- package/{candidate → lib/candidate}/own-name.ts +2 -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} +84 -80
- package/{candidate-schema.ts → lib/candidate-schema.ts} +6 -6
- 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} +9 -13
- 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} +10 -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} +40 -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} +28 -26
- package/{lookup.ts → lib/lookup.ts} +130 -142
- 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 -16
- package/lib/poi/index.ts +8 -0
- package/{poi-lookup.ts → lib/poi/lookup.ts} +22 -29
- 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} +43 -14
- 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 -47
- package/{schema.ts → lib/schema.ts} +10 -0
- package/{search-fetch.ts → lib/search-fetch.ts} +29 -29
- package/{sqlite-convention-source.ts → lib/sqlite-convention-source.ts} +9 -10
- package/{sqlite-utils.ts → lib/sqlite-utils.ts} +14 -19
- package/{street-centroid-schema.ts → lib/street/centroid-schema.ts} +5 -5
- package/{street-centroid.ts → lib/street/centroid.ts} +15 -22
- 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} +86 -20
- 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 -19
- package/{uprn-schema.ts → lib/uprn/schema.ts} +3 -3
- package/lib/weights-overlay-linker.ts +879 -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} +12 -11
- 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} +19 -9
- package/out/address/point.d.ts.map +1 -0
- package/out/address/point.js +222 -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} +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 +32 -24
- package/out/build-candidate.d.ts.map +1 -1
- package/out/build-candidate.js +60 -54
- 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 +41 -0
- package/out/candidate/extract-fold.d.ts.map +1 -0
- package/out/candidate/extract-fold.js +153 -0
- 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 +2 -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 +16 -14
- package/out/candidate-lookup.d.ts.map +1 -1
- package/out/candidate-lookup.js +67 -63
- package/out/candidate-lookup.js.map +1 -1
- package/out/candidate-schema.d.ts +6 -6
- package/out/candidate-schema.d.ts.map +1 -1
- package/out/candidate-schema.js +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 +7 -6
- 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 +5 -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} +9 -13
- package/out/fst/freshness.d.ts.map +1 -0
- package/out/{fst-freshness.js → fst/freshness.js} +40 -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 +11 -10
- 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 +22 -21
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +103 -113
- 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 -6
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +3 -4
- 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 +15 -7
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +28 -12
- 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 -9
- package/out/sqlite-utils.d.ts.map +1 -1
- package/out/sqlite-utils.js +6 -10
- 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} +7 -7
- 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} +38 -15
- package/out/street/normalize.d.ts.map +1 -0
- package/out/{street-normalize.js → street/normalize.js} +73 -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 +339 -86
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +465 -269
- package/address-point.ts +0 -218
- package/candidate/shard-fold.ts +0 -137
- 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 -140
- 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 +0 -104
- 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
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Bounded cross-country PRIMARY-NAME preference over a population-ordered candidate row set — the
|
|
7
7
|
* `is_primary` ranking signal, PURE and platform-free so the Node candidate lookup
|
|
8
|
-
* (`candidate-lookup.ts`) and the browser twin (`
|
|
9
|
-
* SAME function rather than two copies that drift (the #861 server↔
|
|
8
|
+
* (`candidate-lookup.ts`) and the browser twin (`packages/resolver-wof-wasm/lib/httpvfs/resolver.ts`) rank with
|
|
9
|
+
* the SAME function rather than two copies that drift (the #861 server↔browser parity contract).
|
|
10
10
|
*
|
|
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
|
|
@@ -188,17 +207,27 @@ export function rankByPrimaryPreference<R extends PrimaryPreferenceRow>(
|
|
|
188
207
|
? 1
|
|
189
208
|
: 0
|
|
190
209
|
|
|
210
|
+
// 1 for a PRIMARY-name row whose prominence is unmeasured (population 0), 0 for everything else. Two unmeasured
|
|
211
|
+
// rows tie on `neg_rank`, and that tie used to fall through to scan order — the B-tree's `spr_id`, which is no
|
|
212
|
+
// evidence about either place. A row that IS named X ranks ahead of one merely also-known-as X when nothing else
|
|
213
|
+
// separates them: Taiwan's 溪州鄉 township row (its own name, unmeasured) over the village 溪洲 30 km away that
|
|
214
|
+
// carries `溪州鄉` as an alias (unmeasured), which the lower WOF id had been winning. Populated ties are untouched —
|
|
215
|
+
// a populated alias ("NYC") never reaches this term, and two populated rows keep the order they had.
|
|
216
|
+
const unmeasuredPrimary = (r: R): number =>
|
|
217
|
+
r.is_primary === 1 && typeof r.population === "number" && r.population === 0 ? 1 : 0
|
|
218
|
+
|
|
191
219
|
return (
|
|
192
220
|
rows
|
|
193
221
|
.map((r, i) => ({ row: annotate(r), i }))
|
|
194
|
-
// Effective rank ASC; ties keep population order, then the seat preference DESC, then
|
|
195
|
-
// index (stable).
|
|
222
|
+
// Effective rank ASC; ties keep population order, then the seat preference DESC, then the primary name
|
|
223
|
+
// among unmeasured rows, then original index (stable).
|
|
196
224
|
// oxlint-disable-next-line unicorn/no-array-sort -- sorts a freshly-built array; toSorted would double-allocate on a hot path
|
|
197
225
|
.sort(
|
|
198
226
|
(a, b) =>
|
|
199
227
|
a.row.effectiveNegRank - b.row.effectiveNegRank ||
|
|
200
228
|
a.row.neg_rank - b.row.neg_rank ||
|
|
201
229
|
seatPreference(b.row) - seatPreference(a.row) ||
|
|
230
|
+
unmeasuredPrimary(b.row) - unmeasuredPrimary(a.row) ||
|
|
202
231
|
a.i - b.i
|
|
203
232
|
)
|
|
204
233
|
.slice(0, limit)
|
|
@@ -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,16 +28,16 @@
|
|
|
28
28
|
* says so per result rather than pretending.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
|
|
36
|
-
import { ancestorLineage, placetypeDepth } from "./ancestry.ts"
|
|
37
|
-
import { PLACE_BBOX_TABLE } from "./fts.ts"
|
|
38
|
-
import { allRows } from "./sqlite-utils.ts"
|
|
39
|
-
import type { PlaceCandidate, WOFPlacetype } from "./types.ts"
|
|
31
|
+
import { tryParsingJSON } from "@mailwoman/core/json"
|
|
32
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
33
|
+
import { bboxAround, geometryContains, haversineKm, type ParsedGeometry } from "@mailwoman/spatial"
|
|
34
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
35
|
+
import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
40
36
|
|
|
37
|
+
import { ancestorLineage, placetypeDepth } from "#ancestry/index"
|
|
38
|
+
import { PLACE_BBOX_TABLE } from "#fts/index"
|
|
39
|
+
import type { WOFDatabase } from "#schema"
|
|
40
|
+
import type { PlaceCandidate, WOFPlacetype } from "#types"
|
|
41
41
|
/**
|
|
42
42
|
* Largest absolute latitude in WGS-84 degrees.
|
|
43
43
|
*/
|
|
@@ -80,7 +80,7 @@ export interface WOFReverseGeocoderOpts {
|
|
|
80
80
|
/**
|
|
81
81
|
* Pre-opened admin DB — primarily for tests against an inline fixture.
|
|
82
82
|
*/
|
|
83
|
-
adminDatabase?:
|
|
83
|
+
adminDatabase?: DatabaseClient<WOFDatabase>
|
|
84
84
|
/**
|
|
85
85
|
* Path to the polygon sidecar DB (`wof-polygons.db`, table `polygons(id, geom)`). OPTIONAL — without it every result
|
|
86
86
|
* is `containment: "approximate"` (centroid-only mode). Mutually exclusive with `polygonDatabase`.
|
|
@@ -89,7 +89,7 @@ export interface WOFReverseGeocoderOpts {
|
|
|
89
89
|
/**
|
|
90
90
|
* Pre-opened polygon DB — primarily for tests.
|
|
91
91
|
*/
|
|
92
|
-
polygonDatabase?:
|
|
92
|
+
polygonDatabase?: DatabaseClient<WOFDatabase>
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export interface ReverseGeocodeOpts {
|
|
@@ -160,9 +160,9 @@ function toPlaceCandidate(row: CandidateRow, distanceKm?: number): PlaceCandidat
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
export class WOFReverseGeocoder implements Disposable {
|
|
163
|
-
readonly #admin:
|
|
163
|
+
readonly #admin: DatabaseClient<WOFDatabase>
|
|
164
164
|
readonly #ownsAdmin: boolean
|
|
165
|
-
readonly #polygons:
|
|
165
|
+
readonly #polygons: DatabaseClient<WOFDatabase> | null
|
|
166
166
|
readonly #ownsPolygons: boolean
|
|
167
167
|
/**
|
|
168
168
|
* Parsed-geometry cache. Reverse queries cluster geographically (an eval run hits the same ~15 county polygons 1400
|
|
@@ -170,7 +170,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
170
170
|
* LRU-tracked; the polygons are DP-simplified and small, the cap exists only to keep a long-lived server process
|
|
171
171
|
* honest.
|
|
172
172
|
*/
|
|
173
|
-
readonly #geometryCache = new Map<number,
|
|
173
|
+
readonly #geometryCache = new Map<number, ParsedGeometry | null>()
|
|
174
174
|
static readonly #GEOMETRY_CACHE_CAP = 4096
|
|
175
175
|
|
|
176
176
|
constructor(opts: WOFReverseGeocoderOpts) {
|
|
@@ -186,38 +186,29 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
186
186
|
throw new Error("WOFReverseGeocoder: pass either `polygonDatabase` or `polygonDBPath`, not both")
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
this.#admin = opts.adminDatabase ?? new
|
|
189
|
+
this.#admin = opts.adminDatabase ?? new DatabaseClient<WOFDatabase>(opts.adminDBPath!, { readOnly: true })
|
|
190
190
|
this.#ownsAdmin = !opts.adminDatabase
|
|
191
191
|
|
|
192
192
|
this.#polygons =
|
|
193
|
-
opts.polygonDatabase ??
|
|
193
|
+
opts.polygonDatabase ??
|
|
194
|
+
(opts.polygonDBPath ? new DatabaseClient<WOFDatabase>(opts.polygonDBPath, { readOnly: true }) : null)
|
|
194
195
|
|
|
195
196
|
this.#ownsPolygons = !opts.polygonDatabase && Boolean(opts.polygonDBPath)
|
|
196
197
|
|
|
197
198
|
// Fail loudly up front — the R*Tree is a build artifact, not part of the upstream WOF
|
|
198
199
|
// 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) {
|
|
200
|
+
if (!tableExists(this.#admin, PLACE_BBOX_TABLE)) {
|
|
204
201
|
throw new Error(
|
|
205
202
|
`WOFReverseGeocoder: the admin DB has no \`${PLACE_BBOX_TABLE}\` R*Tree. Build it with ` +
|
|
206
203
|
"`mailwoman gazetteer build fts <path-to-wof.db>` (see resolver-wof-sqlite/README.md)."
|
|
207
204
|
)
|
|
208
205
|
}
|
|
209
206
|
|
|
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
|
-
}
|
|
207
|
+
if (this.#polygons && !tableExists(this.#polygons, "polygons")) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"WOFReverseGeocoder: the polygon DB has no `polygons` table. Expected a `wof-polygons.db` " +
|
|
210
|
+
"built by scripts/build-wof-polygons.mjs."
|
|
211
|
+
)
|
|
221
212
|
}
|
|
222
213
|
}
|
|
223
214
|
|
|
@@ -415,7 +406,7 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
415
406
|
lon: number,
|
|
416
407
|
maxApproximateKm: number
|
|
417
408
|
): CandidateRow[] {
|
|
418
|
-
const
|
|
409
|
+
const window = bboxAround(lat, lon, maxApproximateKm * 4)
|
|
419
410
|
|
|
420
411
|
return allRows<CandidateRow>(
|
|
421
412
|
this.#admin.prepare(
|
|
@@ -427,17 +418,17 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
427
418
|
),
|
|
428
419
|
parentID,
|
|
429
420
|
placetype,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
421
|
+
window.minLat,
|
|
422
|
+
window.maxLat,
|
|
423
|
+
window.minLon,
|
|
424
|
+
window.maxLon
|
|
434
425
|
)
|
|
435
426
|
}
|
|
436
427
|
|
|
437
428
|
/**
|
|
438
429
|
* Parsed GeoJSON geometry for a WOF id, or null when absent / unparseable / no polygon DB.
|
|
439
430
|
*/
|
|
440
|
-
#geometry(id: number):
|
|
431
|
+
#geometry(id: number): ParsedGeometry | null {
|
|
441
432
|
if (!this.#polygons) return null
|
|
442
433
|
const cached = this.#geometryCache.get(id)
|
|
443
434
|
|
|
@@ -449,24 +440,20 @@ export class WOFReverseGeocoder implements Disposable {
|
|
|
449
440
|
|
|
450
441
|
const row = this.#polygons.prepare(`SELECT geom FROM polygons WHERE id = ?`).get(id) as { geom: string } | undefined
|
|
451
442
|
// Malformed row parses to null — treat as no-polygon rather than failing the query.
|
|
452
|
-
const geometry = row ? tryParsingJSON<
|
|
443
|
+
const geometry = row ? tryParsingJSON<ParsedGeometry>(row.geom) : null
|
|
453
444
|
|
|
454
445
|
this.#geometryCache.set(id, geometry)
|
|
455
446
|
|
|
456
447
|
return geometry
|
|
457
448
|
}
|
|
458
449
|
|
|
459
|
-
|
|
450
|
+
[Symbol.dispose](): void {
|
|
460
451
|
if (this.#ownsAdmin) {
|
|
461
|
-
this.#admin.
|
|
452
|
+
this.#admin.destroy()
|
|
462
453
|
}
|
|
463
454
|
|
|
464
455
|
if (this.#ownsPolygons) {
|
|
465
|
-
this.#polygons?.
|
|
456
|
+
this.#polygons?.destroy()
|
|
466
457
|
}
|
|
467
458
|
}
|
|
468
|
-
|
|
469
|
-
[Symbol.dispose](): void {
|
|
470
|
-
this.close()
|
|
471
|
-
}
|
|
472
459
|
}
|
|
@@ -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,15 +12,13 @@
|
|
|
12
12
|
* whole corpus, so an exact haversine over the survivors is cheap.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
|
|
15
|
+
import { allRows } from "@mailwoman/core/utils"
|
|
17
16
|
import { bboxAround } from "@mailwoman/spatial"
|
|
17
|
+
import type { DatabaseClient, SQLInputValue } from "@mailwoman/sqlite/client"
|
|
18
18
|
|
|
19
|
-
import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "
|
|
20
|
-
import type { RankingWeights } from "
|
|
21
|
-
import {
|
|
22
|
-
import type { FindPlaceQuery, WOFPlacetype } from "./types.ts"
|
|
23
|
-
|
|
19
|
+
import { PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE } from "#fts/index"
|
|
20
|
+
import type { RankingWeights } from "#ranking-weights"
|
|
21
|
+
import type { FindPlaceQuery, WOFPlacetype } from "#types"
|
|
24
22
|
/**
|
|
25
23
|
* Query length at or below which the FTS window is widened. A two- or three-character query is almost always a region
|
|
26
24
|
* abbreviation, where the exact match can otherwise fall outside the window behind higher-bm25 partial hits — "NY"
|
|
@@ -60,19 +58,21 @@ export interface RawSearchRow {
|
|
|
60
58
|
max_longitude: number | null
|
|
61
59
|
population: number | null // from the place_population aux table; null when missing
|
|
62
60
|
/**
|
|
63
|
-
* From `place_importance.encyclopedic` when the
|
|
64
|
-
* place has no Wikipedia article, or the
|
|
61
|
+
* From `place_importance.encyclopedic` when the extract's table carries the two-score split columns. NULL means the
|
|
62
|
+
* place has no Wikipedia article, or the extract predates the split — absence either way, and never 0 (ROAD_TO_V9
|
|
63
|
+
* §2).
|
|
65
64
|
*/
|
|
66
65
|
encyclopedic: number | null
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
/**
|
|
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
|
|
69
|
+
* Fetch the raw candidate rows for a name match on one extract: the BM25-ordered window over `place_search` (widened
|
|
70
|
+
* for short queries), plus the population-ordered companion fetch that keeps the prominent holders of a name
|
|
71
|
+
* pool-complete. `schemaName` is the routed extract's bare schema name — validated at construction, so it is
|
|
72
|
+
* interpolated directly.
|
|
73
73
|
*/
|
|
74
|
-
export function fetchSearchRows(options: {
|
|
75
|
-
db:
|
|
74
|
+
export function fetchSearchRows<DB>(options: {
|
|
75
|
+
db: DatabaseClient<DB>
|
|
76
76
|
schemaName: string
|
|
77
77
|
query: FindPlaceQuery
|
|
78
78
|
placetypes: WOFPlacetype[] | null
|
|
@@ -112,7 +112,7 @@ export function fetchSearchRows(options: {
|
|
|
112
112
|
// spr table but should never win a contemporary lookup. `is_current = 0` is the only WOF
|
|
113
113
|
// value that means "not current"; both `-1` (modern) and `1` (legacy) mean current. See #91.
|
|
114
114
|
// Note: with schema-qualified FROM the bare `place_search` reference in MATCH resolves to
|
|
115
|
-
// the FROM table — required by FTS5 parser, see
|
|
115
|
+
// the FROM table — required by FTS5 parser, see extracts.ts header comment.
|
|
116
116
|
const where: string[] = ["place_search MATCH ?", "spr.is_current != 0", "spr.is_deprecated = 0"]
|
|
117
117
|
const params: SQLInputValue[] = [ftsQuery]
|
|
118
118
|
|
|
@@ -132,10 +132,10 @@ export function fetchSearchRows(options: {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// 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) &&
|
|
135
|
+
// the active extract has the R*Tree; missing-but-requested is silently treated as no-bbox-
|
|
136
|
+
// filter so legacy DBs / extracts-without-bbox don't crash.
|
|
137
|
+
const extractHasBbox = hasBboxIndex.get(sch) === true
|
|
138
|
+
const useBboxJoin = (query.bbox || query.near?.maxDistanceKm !== undefined) && extractHasBbox
|
|
139
139
|
let joinClause = `JOIN ${sch}.spr ON spr.id = place_search.wof_id`
|
|
140
140
|
|
|
141
141
|
if (useBboxJoin) {
|
|
@@ -146,21 +146,21 @@ export function fetchSearchRows(options: {
|
|
|
146
146
|
params.push(filterBox.maxLat, filterBox.minLat, filterBox.maxLon, filterBox.minLon)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// LEFT JOIN the population aux table when present. Missing-on-this-
|
|
149
|
+
// LEFT JOIN the population aux table when present. Missing-on-this-extract means the SELECT
|
|
150
150
|
// just doesn't include the population column; the post-scoring loop treats it as 0.
|
|
151
|
-
const
|
|
151
|
+
const extractHasPopulation = hasPopulationIndex.get(sch) === true
|
|
152
152
|
|
|
153
|
-
const populationSelect =
|
|
153
|
+
const populationSelect = extractHasPopulation
|
|
154
154
|
? `${PLACE_POPULATION_TABLE}.population AS population`
|
|
155
155
|
: `NULL AS population`
|
|
156
156
|
|
|
157
|
-
const populationJoin =
|
|
157
|
+
const populationJoin = extractHasPopulation
|
|
158
158
|
? `LEFT JOIN ${sch}.${PLACE_POPULATION_TABLE} ON ${PLACE_POPULATION_TABLE}.id = spr.id`
|
|
159
159
|
: ""
|
|
160
160
|
|
|
161
161
|
// 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
|
|
162
|
+
// appears in the SELECT and in no ORDER BY, here or in the companion fetch below. Conditioned on the
|
|
163
|
+
// split column, so a pre-split extract emits a literal NULL and builds no join at all.
|
|
164
164
|
const { select: encyclopedicSelect, join: encyclopedicJoin } = encyclopedicClauses.get(sch)!
|
|
165
165
|
|
|
166
166
|
// Push the population boost into the ORDER BY when the index is available, so famous places
|
|
@@ -177,12 +177,12 @@ export function fetchSearchRows(options: {
|
|
|
177
177
|
// column weighted to zero — no weighting isolates name relevance in this schema. The famous-
|
|
178
178
|
// holder guarantee lives in the population-ordered companion fetch below instead, and the
|
|
179
179
|
// exact tier breaks ties by population in the post-scoring sort.
|
|
180
|
-
const orderByExpr =
|
|
180
|
+
const orderByExpr = extractHasPopulation
|
|
181
181
|
? `(bm25(place_search) - ? * MIN(1.0, COALESCE(log10(1.0 + ${PLACE_POPULATION_TABLE}.population), 0) / ?))`
|
|
182
182
|
: "bm25(place_search)"
|
|
183
183
|
|
|
184
184
|
// Schema-qualified FROM with bare-name MATCH — required syntax for FTS5 on attached schemas.
|
|
185
|
-
// See
|
|
185
|
+
// See extracts.ts header for the failure mode that drove this design.
|
|
186
186
|
const stmt = db.prepare(`
|
|
187
187
|
SELECT
|
|
188
188
|
spr.id AS id,
|
|
@@ -205,7 +205,7 @@ export function fetchSearchRows(options: {
|
|
|
205
205
|
LIMIT ?
|
|
206
206
|
`)
|
|
207
207
|
|
|
208
|
-
if (
|
|
208
|
+
if (extractHasPopulation) {
|
|
209
209
|
params.push(weights.populationBoost, weights.populationScaleLog10)
|
|
210
210
|
}
|
|
211
211
|
|
|
@@ -219,7 +219,7 @@ export function fetchSearchRows(options: {
|
|
|
219
219
|
// vs a +4.0 boost cap), so FR Paris never even reaches post-scoring. This fetch makes the
|
|
220
220
|
// prominent holders of a name pool-complete BY CONSTRUCTION; the exact-tier sort below
|
|
221
221
|
// decides whether they win. Skipped without a population index (nothing to order by).
|
|
222
|
-
if (
|
|
222
|
+
if (extractHasPopulation) {
|
|
223
223
|
const popStmt = db.prepare(`
|
|
224
224
|
SELECT
|
|
225
225
|
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
|
}
|