@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
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Requires Node 22+ for built-in `node:sqlite`.
|
|
|
17
17
|
```ts
|
|
18
18
|
import { WOFSQLitePlaceLookup } from "@mailwoman/resolver-wof-sqlite"
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
using lookup = new WOFSQLitePlaceLookup({
|
|
21
21
|
databasePath: "/path/to/whosonfirst-data-admin-us-latest.db",
|
|
22
22
|
buildFTS: true, // build the FTS5 index on first open (one-time cost)
|
|
23
23
|
})
|
|
@@ -31,18 +31,16 @@ const candidates = await lookup.findPlace({
|
|
|
31
31
|
for (const c of candidates) {
|
|
32
32
|
console.log(c.id, c.name, c.country, c.lat, c.lon, "score:", c.score)
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
lookup.close()
|
|
36
34
|
```
|
|
37
35
|
|
|
38
|
-
## A
|
|
36
|
+
## A database that cannot answer says so on construction
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
Databases are `ATTACH`ed by a schema name **derived from the filename**, and queries route to them by matching that name against the requested placetype — `postalcode_us` serves `postalcode`. Two ways that used to fail without logging, and both now throw when you build the lookup:
|
|
41
39
|
|
|
42
40
|
- The name does not route. `postcode-ca-overture.db` derives `postcode_ca_overture`, and the router tests `startsWith("postalcode_")` — **"postcode" is not "postalcode"**. It held 843,739 Canadian codes and answered every query with zero hits, which is indistinguishable from "this country has no places".
|
|
43
|
-
- The
|
|
41
|
+
- The database carries `spr` but no `place_search`. It routes, then dies mid-`SELECT`.
|
|
44
42
|
|
|
45
|
-
The predicate is the **table, not the filename**: a
|
|
43
|
+
The predicate is the **table, not the filename**: a database carrying `spr` is claiming to be a place database, and every lookup path here reaches the FTS index. A relation-table database like `postcode-locality-<cc>.db` carries no `spr`, never makes that claim, and is exempt — which is what keeps the documented default database list working.
|
|
46
44
|
|
|
47
45
|
```
|
|
48
46
|
WOFSQLitePlaceLookup: …/postcode-ca-overture.db carries "spr" but no "place_search" table, so it
|
|
@@ -54,21 +52,21 @@ locality, region, county, country, venue), so it would never have been queried e
|
|
|
54
52
|
|
|
55
53
|
The second sentence appears only when the name routes nowhere. It is the half that turns zero hits into a diagnosis.
|
|
56
54
|
|
|
57
|
-
##
|
|
55
|
+
## Multiple databases (admin + postcode in one connection)
|
|
58
56
|
|
|
59
|
-
Pass an array of paths to open multiple WOF
|
|
57
|
+
Pass an array of paths to open multiple WOF databases on a single connection — each is opened as a
|
|
60
58
|
separate SQLite schema via `ATTACH DATABASE`. Schema names auto-derive from filenames
|
|
61
59
|
(`whosonfirst-data-admin-us-latest.db` → `admin_us`, `whosonfirst-data-postalcode-us-latest.db` →
|
|
62
60
|
`postalcode_us`). Queries route by `placetype` — a `postalcode` query goes to the
|
|
63
|
-
`postalcode_us`
|
|
61
|
+
`postalcode_us` database automatically, everything else hits main.
|
|
64
62
|
|
|
65
63
|
```ts
|
|
66
64
|
const lookup = new WOFSQLitePlaceLookup({
|
|
67
65
|
databasePath: ["/data/wof/whosonfirst-data-admin-us-latest.db", "/data/wof/whosonfirst-data-postalcode-us-latest.db"],
|
|
68
66
|
})
|
|
69
67
|
|
|
70
|
-
await lookup.findPlace({ text: "Springfield", placetype: "locality" }) // → admin
|
|
71
|
-
await lookup.findPlace({ text: "62701", placetype: "postalcode" }) // → postcode
|
|
68
|
+
await lookup.findPlace({ text: "Springfield", placetype: "locality" }) // → admin extract
|
|
69
|
+
await lookup.findPlace({ text: "62701", placetype: "postalcode" }) // → postcode extract
|
|
72
70
|
```
|
|
73
71
|
|
|
74
72
|
Override schema names or routing explicitly when needed:
|
|
@@ -79,13 +77,13 @@ new WOFSQLitePlaceLookup({
|
|
|
79
77
|
})
|
|
80
78
|
```
|
|
81
79
|
|
|
82
|
-
Cross-
|
|
80
|
+
Cross-database `UNION` queries are not supported in one `findPlace` call — BM25 scores aren't
|
|
83
81
|
comparable across separately-indexed corpora. Issue two `findPlace` calls and merge in your
|
|
84
82
|
caller if you need that.
|
|
85
83
|
|
|
86
84
|
## Getting the WOF SQLite distribution
|
|
87
85
|
|
|
88
|
-
The Geocode Earth team mirrors WOF SQLite distributions at <https://data.geocode.earth/wof/dist/sqlite/>. The two relevant
|
|
86
|
+
The Geocode Earth team mirrors WOF SQLite distributions at <https://data.geocode.earth/wof/dist/sqlite/>. The two relevant distributions for v1:
|
|
89
87
|
|
|
90
88
|
| Distribution | Size (bz2) | Use |
|
|
91
89
|
| ---------------------------------------------- | ---------- | ----------------------------------------------------------------- |
|
|
@@ -102,7 +100,7 @@ bunzip2 whosonfirst-data-admin-us-latest.db.bz2
|
|
|
102
100
|
|
|
103
101
|
Upstream WOF SQLite distributions ship a `places` table but **not** an FTS5 index. The resolver needs FTS5 to do fast prefix + token-bag matching. Two options:
|
|
104
102
|
|
|
105
|
-
1. **`buildFTS: true` on construction** — builds the index lazily on first open. Cost is one-time but expensive (~minutes on the full US admin
|
|
103
|
+
1. **`buildFTS: true` on construction** — builds the index lazily on first open. Cost is one-time but expensive (~minutes on the full US admin database). Use for prototyping.
|
|
106
104
|
2. **Pre-build the index with `mailwoman gazetteer build fts`** — ship the DB with the index included so first-open is fast. Recommended for production.
|
|
107
105
|
|
|
108
106
|
### `mailwoman gazetteer build fts`
|
|
@@ -170,12 +168,11 @@ You can also build the index programmatically via the package's `./fts` subpath:
|
|
|
170
168
|
import { DatabaseSync } from "node:sqlite"
|
|
171
169
|
import { buildPlaceSearchFts } from "@mailwoman/resolver-wof-sqlite/fts"
|
|
172
170
|
|
|
173
|
-
|
|
171
|
+
using db = new DatabaseSync("/path/to/wof.db")
|
|
174
172
|
const { created, indexedRows, durationMs } = buildPlaceSearchFts(db, {
|
|
175
173
|
drop: false,
|
|
176
174
|
onProgress: (phase, detail) => console.log(phase, detail),
|
|
177
175
|
})
|
|
178
|
-
db.close()
|
|
179
176
|
```
|
|
180
177
|
|
|
181
178
|
## Ranking
|
|
@@ -261,11 +258,11 @@ This package itself is AGPL-3.0; the WOF data it indexes is CC-BY 4.0. The two l
|
|
|
261
258
|
|
|
262
259
|
`resolver-wof-sqlite/integration.test.ts` exercises the resolver against a real WOF SQLite distribution. The suite is **skipped** when no DB is present — set `MAILWOMAN_WOF_DB` to override the lookup path, otherwise it defaults to `/mnt/playpen/mailwoman-data/wof/whosonfirst-data-admin-us-latest.db` (the canonical lab location). CI runs against the fixture-only suites; operators with real WOF data locally get an extra layer of validation.
|
|
263
260
|
|
|
264
|
-
Coverage includes: placetype filtering, country filtering, the empty-result case, FTS5 special-character sanitization, Japanese alt-name resolution, parent-constrained lookup, and a performance budget (`findPlace` < 250 ms against the 142 k-row US admin
|
|
261
|
+
Coverage includes: placetype filtering, country filtering, the empty-result case, FTS5 special-character sanitization, Japanese alt-name resolution, parent-constrained lookup, and a performance budget (`findPlace` < 250 ms against the 142 k-row US admin database).
|
|
265
262
|
|
|
266
263
|
## Concurrency model
|
|
267
264
|
|
|
268
|
-
This package opens a single `node:sqlite` connection per `WOFSQLitePlaceLookup` instance. SQLite is single-writer /
|
|
265
|
+
This package opens a single `node:sqlite` connection per `WOFSQLitePlaceLookup` instance. SQLite is single-writer / multiple-reader; the Kysely wrapper around the connection serializes all queries through a mutex. For high-concurrency HTTP servers, instantiate one resolver per request handler or per pool slot — sharing a single instance across concurrent requests is safe (queries queue) but won't parallelize across cores.
|
|
269
266
|
|
|
270
267
|
## License
|
|
271
268
|
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* Address-point interpolation — "Method 2" of the resolution ladder (#483, Phase 1 of
|
|
7
7
|
* `docs/articles/plan/2026-06-11-resolution-ladder.md`): when the exact address-point tier (#476)
|
|
8
8
|
* misses a house number, bracket the number with REAL neighbor points on the same street from the
|
|
9
|
-
* same #476
|
|
10
|
-
* replaces TIGER's uniform-spacing assumption — the dominant error term of the TIGER pilot's
|
|
9
|
+
* same #476 extract and interpolate linearly in house-number space between them. Real occupancy
|
|
10
|
+
* replaces TIGER's uniform-spacing assumption — the dominant error term of the TIGER pilot's check
|
|
11
11
|
* miss; TIGER range interpolation (`StreetInterpolator`) demotes to the fallback for streets too
|
|
12
12
|
* sparse to bracket.
|
|
13
13
|
*
|
|
14
14
|
* Matching key is `street_key` — THE shared normalizer plus the route fold
|
|
15
|
-
* (`canonicalizeRouteKey`), identical at build time (`scripts/build-address-point-
|
|
15
|
+
* (`canonicalizeRouteKey`), identical at build time (`scripts/build-address-point-extract.ts`) and
|
|
16
16
|
* query time, by construction. Scope is postcode-first like the segment tier; a query without a
|
|
17
17
|
* postcode goes straight to the fallback (which carries its own statewide-ambiguity abstention).
|
|
18
18
|
*
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* - Neighbor candidates NEVER include the queried number itself (any unit/duplicate row of it) — in
|
|
22
22
|
* production the exact tier would already have answered an on-file number, and in the eval
|
|
23
|
-
* this is what makes grading against the same
|
|
23
|
+
* this is what makes grading against the same extract non-circular by construction.
|
|
24
24
|
* - Both-sided bracket (`bracket: "both"`): linear interpolation between the nearest known number
|
|
25
25
|
* below and above; `uncertaintyM` = half the distance between them.
|
|
26
26
|
* - Single-sided (`bracket: "single"`): linear extrapolation along the two nearest known numbers on
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
* Standalone like the segment tier — core wiring rides the Phase 2 ordered `spatialTiers` list.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import { DatabaseSync } from "node:sqlite"
|
|
37
|
-
|
|
38
36
|
import type { InterpolationLookup } from "@mailwoman/resolver"
|
|
39
37
|
import { haversineKm } from "@mailwoman/spatial"
|
|
38
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
40
39
|
|
|
41
|
-
import type {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
40
|
+
import type { AddressPointDatabase } from "#address/point-schema"
|
|
41
|
+
import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#interpolation"
|
|
42
|
+
import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
|
|
43
|
+
import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Extrapolation cap for a single-sided bracket: at most one pair-span beyond the nearest known point (`t = 2`). Past
|
|
@@ -67,26 +67,30 @@ interface NumberAnchor {
|
|
|
67
67
|
release: string
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export class AddressPointInterpolator
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
export class AddressPointInterpolator<
|
|
71
|
+
DB extends AddressPointDatabase = AddressPointDatabase,
|
|
72
|
+
> implements InterpolationLookup {
|
|
73
|
+
readonly #db: DatabaseClient<DB>
|
|
74
|
+
/**
|
|
75
|
+
* Resources this instance opened. A connection handed in by a caller is NOT in here, so disposal cannot reach it —
|
|
76
|
+
* ownership is membership rather than a flag a later branch has to check.
|
|
77
|
+
*/
|
|
78
|
+
readonly #resources = new DisposableStack()
|
|
73
79
|
readonly #fallback: StreetInterpolator | undefined
|
|
74
80
|
readonly #byPostcode: PreparedAll<[postcode: string, street: RouteKey, number: number], PointRow> | undefined
|
|
75
81
|
|
|
76
|
-
constructor(opts: { dbPath?: string; database?:
|
|
82
|
+
constructor(opts: { dbPath?: string; database?: DatabaseClient<DB>; fallback?: StreetInterpolator }) {
|
|
77
83
|
if (opts.database) {
|
|
78
84
|
this.#db = opts.database
|
|
79
|
-
this.#ownsDB = false
|
|
80
85
|
} else if (opts.dbPath) {
|
|
81
|
-
this.#db = new
|
|
82
|
-
this.#ownsDB = true
|
|
86
|
+
this.#db = this.#resources.use(new DatabaseClient<DB>(opts.dbPath, { readOnly: true }))
|
|
83
87
|
} else {
|
|
84
88
|
throw new Error("AddressPointInterpolator: one of dbPath or database is required")
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
this.#fallback = opts.fallback
|
|
88
92
|
|
|
89
|
-
// Degrade gracefully on an empty/tableless
|
|
93
|
+
// Degrade gracefully on an empty/tableless extract (#568): with no `address_point` table this tier
|
|
90
94
|
// is skipped, deferring to the segment fallback rather than crashing at construction.
|
|
91
95
|
if (hasTable(this.#db, "address_point")) {
|
|
92
96
|
// Strictly-numeric neighbor numbers on the route-folded street key within the ZIP. The
|
|
@@ -108,7 +112,7 @@ export class AddressPointInterpolator implements InterpolationLookup {
|
|
|
108
112
|
if (!/^\d+$/.test(numberRaw)) return null
|
|
109
113
|
const n = Number(numberRaw)
|
|
110
114
|
|
|
111
|
-
// No own table (empty
|
|
115
|
+
// No own table (empty extract) or no postcode → defer to the segment fallback rather than query.
|
|
112
116
|
if (!this.#byPostcode || !query.postcode) return this.#fallback?.find(query) ?? null
|
|
113
117
|
|
|
114
118
|
// Key-variant ladder (see `streetKeyVariants`) — same probe order as the exact-point reader.
|
|
@@ -127,10 +131,8 @@ export class AddressPointInterpolator implements InterpolationLookup {
|
|
|
127
131
|
return hit ?? this.#fallback?.find(query) ?? null
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.#db.close()
|
|
133
|
-
}
|
|
134
|
+
[Symbol.dispose](): void {
|
|
135
|
+
this.#resources[Symbol.dispose]()
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Typed schema for the SITUS / rooftop ADDRESS-POINT
|
|
7
|
-
* by `scripts/build-address-point-
|
|
6
|
+
* Typed schema for the SITUS / rooftop ADDRESS-POINT extracts (`address-points-<cc>-<slug>.db`, built
|
|
7
|
+
* by `scripts/build-address-point-extract.ts` — the #476/#567 national rooftop tier behind the
|
|
8
8
|
* demo's "type any US address, get the building"). Single source of truth for the columns shared
|
|
9
9
|
* by the BUILDER and the READER ({@link AddressPointSqliteLookup}), so a column rename in one is a
|
|
10
10
|
* compile error in the other.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import type { Kysely } from "kysely"
|
|
20
20
|
|
|
21
|
-
import type { NameKey, RouteKey, StreetKey } from "
|
|
21
|
+
import type { NameKey, RouteKey, StreetKey } from "#street/normalize"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* One rooftop address point. `(street_norm, number)` within a `postcode` (preferred) or `locality_norm` scope is the
|
|
@@ -59,6 +59,16 @@ export interface AddressPointTable {
|
|
|
59
59
|
* The pinned data release the point was ingested from.
|
|
60
60
|
*/
|
|
61
61
|
release: string
|
|
62
|
+
/**
|
|
63
|
+
* The source register's stable administrative key for the point's commune or municipality — BAN's `code_insee`. A
|
|
64
|
+
* display name (`locality_norm`) is not a key; the coverage basis is computed per THIS.
|
|
65
|
+
*/
|
|
66
|
+
admin_code: string | null
|
|
67
|
+
/**
|
|
68
|
+
* The register's own certification flag for the point (BAN `certification_commune`: 1 certified by the commune, 0
|
|
69
|
+
* not), or null for a source that states none. A basis is never inferred from a share of these.
|
|
70
|
+
*/
|
|
71
|
+
certified: number | null
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
/**
|
|
@@ -71,7 +81,7 @@ export interface AddressPointDatabase {
|
|
|
71
81
|
/**
|
|
72
82
|
* The slice of a Kysely handle the `address_point` DDL touches — the parameter type its builders take.
|
|
73
83
|
*
|
|
74
|
-
* Kysely is invariant in its schema parameter (the incompatibility is in `transaction()`), so a
|
|
84
|
+
* Kysely is invariant in its schema parameter (the incompatibility is in `transaction()`), so a extract that EXTENDS
|
|
75
85
|
* `AddressPointTable` — OSM adds `h3_cell` — cannot pass its own handle to a `Kysely<AddressPointDatabase>` parameter.
|
|
76
86
|
* Naming only `schema` lets it, and the DDL below needs nothing else.
|
|
77
87
|
*/
|
|
@@ -93,6 +103,8 @@ export const ADDRESS_POINT_COLUMNS = [
|
|
|
93
103
|
"lon",
|
|
94
104
|
"source",
|
|
95
105
|
"release",
|
|
106
|
+
"admin_code",
|
|
107
|
+
"certified",
|
|
96
108
|
] as const
|
|
97
109
|
|
|
98
110
|
/**
|
|
@@ -113,6 +125,8 @@ export async function createAddressPointTable(db: AddressPointSchemaHandle): Pro
|
|
|
113
125
|
.addColumn("lon", "real", (c) => c.notNull())
|
|
114
126
|
.addColumn("source", "text", (c) => c.notNull())
|
|
115
127
|
.addColumn("release", "text", (c) => c.notNull())
|
|
128
|
+
.addColumn("admin_code", "text")
|
|
129
|
+
.addColumn("certified", "integer")
|
|
116
130
|
.execute()
|
|
117
131
|
}
|
|
118
132
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
6
|
* SQLite implementation of core's `AddressPointLookup` (#476): exact `(street, number)` within a
|
|
7
|
-
* postcode (preferred), locality, or — for
|
|
7
|
+
* postcode (preferred), locality, or — for extracts whose points carry no scope tag (OSM, #247) —
|
|
8
8
|
* the resolved locality's BBOX. Query-side normalization is THE shared normalizer
|
|
9
|
-
* (`street-normalize.ts`), selected per the
|
|
9
|
+
* (`street-normalize.ts`), selected per the extract's `streetLocale` so build-side and probe-side
|
|
10
10
|
* stay identical by construction (US delegates to the USPS pipeline; FR/DE/NL use the locale rules).
|
|
11
11
|
*
|
|
12
12
|
* Matching is exact-after-normalization only — no fuzzy street matching in this tier (measure how
|
|
@@ -15,12 +15,11 @@
|
|
|
15
15
|
* scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { DatabaseSync } from "node:sqlite"
|
|
19
|
-
|
|
20
18
|
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/resolver"
|
|
19
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
21
20
|
|
|
22
|
-
import type { AddressPointTable } from "
|
|
23
|
-
import { hasTable, prepareGet, type PreparedGet } from "
|
|
21
|
+
import type { AddressPointDatabase, AddressPointTable } from "#address/point-schema"
|
|
22
|
+
import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
|
|
24
23
|
import {
|
|
25
24
|
normalizeLocalityForKey,
|
|
26
25
|
type NameKey,
|
|
@@ -29,11 +28,11 @@ import {
|
|
|
29
28
|
streetLocaleForSurface,
|
|
30
29
|
stripArrondissement,
|
|
31
30
|
type StreetLocale,
|
|
32
|
-
} from "
|
|
31
|
+
} from "#street/normalize"
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* The columns this lookup projects — a typed slice of the SHARED {@link AddressPointTable}, so a column rename in
|
|
36
|
-
* `build-address-point-
|
|
35
|
+
* `build-address-point-extract.ts` (the writer) is a compile error here (the reader).
|
|
37
36
|
*/
|
|
38
37
|
type AddressPointRow = Pick<AddressPointTable, "lat" | "lon" | "source" | "release" | "locality_norm" | "postcode">
|
|
39
38
|
|
|
@@ -43,10 +42,16 @@ type AddressPointRow = Pick<AddressPointTable, "lat" | "lon" | "source" | "relea
|
|
|
43
42
|
*/
|
|
44
43
|
const SELECT_COLS = "lat, lon, source, release, locality_norm, postcode"
|
|
45
44
|
|
|
46
|
-
export class AddressPointSqliteLookup
|
|
47
|
-
|
|
45
|
+
export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressPointDatabase>
|
|
46
|
+
implements AddressPointLookup, Disposable
|
|
47
|
+
{
|
|
48
|
+
readonly #db: DatabaseClient<DB>
|
|
48
49
|
readonly #locale: StreetLocale
|
|
50
|
+
readonly #localityKeys: "full" | "abbreviated"
|
|
49
51
|
readonly #byPostcode: PreparedGet<[postcode: string, street: StreetKey, number: string], AddressPointRow> | undefined
|
|
52
|
+
readonly #byPostcodeLocality:
|
|
53
|
+
| PreparedGet<[postcode: string, locality: NameKey, street: StreetKey, number: string], AddressPointRow>
|
|
54
|
+
| undefined
|
|
50
55
|
readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
|
|
51
56
|
readonly #byBbox:
|
|
52
57
|
| PreparedGet<
|
|
@@ -56,15 +61,22 @@ export class AddressPointSqliteLookup implements AddressPointLookup {
|
|
|
56
61
|
| undefined
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
* @param dbPath
|
|
60
|
-
* @param opts.streetLocale The street-normalization locale this
|
|
64
|
+
* @param dbPath Extract path.
|
|
65
|
+
* @param opts.streetLocale The street-normalization locale this extract was BUILT with — must match, or every key
|
|
61
66
|
* misses. Defaults to `"us"` (the situs tier), so existing callers are unchanged.
|
|
67
|
+
* @param opts.localityKeys Whether the extract's `locality_norm` is a FULL place name a query can be held to. The BAN
|
|
68
|
+
* and OSM extracts write the commune or `addr:city` in full; the US situs extract writes the NAD city field, which
|
|
69
|
+
* several counties abbreviate (`addi` for Addison on 5,174 Texas rows, 327,264 Texas rows at four characters or
|
|
70
|
+
* fewer) or give as the parent town (`easton` for North Easton). A key like that can steer WHICH row answers but
|
|
71
|
+
* cannot refuse one, so it never contradicts. Defaults from the street locale: `"us"` is abbreviated, the rest
|
|
72
|
+
* full.
|
|
62
73
|
*/
|
|
63
|
-
constructor(dbPath: string, opts: { streetLocale?: StreetLocale } = {}) {
|
|
64
|
-
this.#db = new
|
|
74
|
+
constructor(dbPath: string, opts: { streetLocale?: StreetLocale; localityKeys?: "full" | "abbreviated" } = {}) {
|
|
75
|
+
this.#db = new DatabaseClient<DB>(dbPath, { readOnly: true })
|
|
65
76
|
this.#locale = opts.streetLocale ?? "us"
|
|
77
|
+
this.#localityKeys = opts.localityKeys ?? (this.#locale === "us" ? "abbreviated" : "full")
|
|
66
78
|
|
|
67
|
-
// Degrade gracefully on an empty/tableless
|
|
79
|
+
// Degrade gracefully on an empty/tableless extract (interrupted build, stray 0-byte file): with no
|
|
68
80
|
// `address_point` table this lookup is a no-op miss, not a crash that loses the whole state (#568).
|
|
69
81
|
if (hasTable(this.#db, "address_point")) {
|
|
70
82
|
this.#byPostcode = prepareGet(
|
|
@@ -73,6 +85,12 @@ export class AddressPointSqliteLookup implements AddressPointLookup {
|
|
|
73
85
|
WHERE postcode = ? AND street_norm = ? AND number = ? LIMIT 1`
|
|
74
86
|
)
|
|
75
87
|
|
|
88
|
+
this.#byPostcodeLocality = prepareGet(
|
|
89
|
+
this.#db,
|
|
90
|
+
`SELECT ${SELECT_COLS} FROM address_point
|
|
91
|
+
WHERE postcode = ? AND locality_norm = ? AND street_norm = ? AND number = ? LIMIT 1`
|
|
92
|
+
)
|
|
93
|
+
|
|
76
94
|
this.#byLocality = prepareGet(
|
|
77
95
|
this.#db,
|
|
78
96
|
`SELECT ${SELECT_COLS} FROM address_point
|
|
@@ -187,32 +205,76 @@ export class AddressPointSqliteLookup implements AddressPointLookup {
|
|
|
187
205
|
let row: AddressPointRow | undefined
|
|
188
206
|
|
|
189
207
|
if (query.postcode) {
|
|
190
|
-
|
|
208
|
+
const postcode = query.postcode.trim()
|
|
209
|
+
const localityKey = query.locality ? this.#localityKey(query.locality) : undefined
|
|
210
|
+
|
|
211
|
+
// A postcode can span several places — DE 04509 covers Schönwölkau and Werlitzsch, both with a Teichstraße 3 —
|
|
212
|
+
// so when the query names a locality the row whose own locality agrees is asked for FIRST. Only when no such
|
|
213
|
+
// row exists does the postcode-only row answer, and then only if its locality does not name a different place
|
|
214
|
+
// (see `#scopeContradicts` for what "different" tolerates): a query naming a third village under the postcode
|
|
215
|
+
// falls through to the locality rung rather than answering the wrong rooftop.
|
|
216
|
+
const agreeing = localityKey ? this.#byPostcodeLocality!(postcode, localityKey, streetNorm, number) : undefined
|
|
217
|
+
const candidate = agreeing ?? this.#byPostcode!(postcode, streetNorm, number)
|
|
218
|
+
|
|
219
|
+
row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
|
|
191
220
|
}
|
|
192
221
|
|
|
193
222
|
if (!row && query.locality) {
|
|
194
|
-
// FR
|
|
223
|
+
// FR extracts key arrondissement communes at the base city (both-sides fold, see the BAN
|
|
195
224
|
// builder + stripArrondissement) — fold the probe too so "Paris 13e Arrondissement" and
|
|
196
|
-
// "Paris" both hit. No-op for "us"
|
|
197
|
-
|
|
198
|
-
this.#locale === "fr"
|
|
199
|
-
? stripArrondissement(normalizeLocalityForKey(query.locality))
|
|
200
|
-
: normalizeLocalityForKey(query.locality)
|
|
201
|
-
|
|
202
|
-
row = this.#byLocality!(localityKey, streetNorm, number)
|
|
225
|
+
// "Paris" both hit. No-op for "us" extracts and every non-arrondissement commune.
|
|
226
|
+
row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
|
|
203
227
|
}
|
|
204
228
|
|
|
205
229
|
// Bbox fall-through (#247): the point carries no postcode/locality of its own, but its coordinate falls
|
|
206
230
|
// inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
|
|
207
231
|
if (!row && query.bbox) {
|
|
208
232
|
const b = query.bbox
|
|
209
|
-
|
|
233
|
+
const candidate = this.#byBbox!(streetNorm, number, b.minLat, b.maxLat, b.minLon, b.maxLon)
|
|
234
|
+
|
|
235
|
+
// A register row that carries its own scope and was NOT found by the scoped rungs is a different address that
|
|
236
|
+
// happens to share the street and number inside the box: `10 rue de la République, 75008 Paris` reached
|
|
237
|
+
// Servon's `10 rue de la République` (postcode 77170) 26 km away this way, at rooftop tier and 1 m uncertainty.
|
|
238
|
+
// The rung exists for points with no scope of their own; a point whose scope disagrees with the query is a miss.
|
|
239
|
+
row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
|
|
210
240
|
}
|
|
211
241
|
|
|
212
242
|
return row
|
|
213
243
|
}
|
|
214
244
|
|
|
215
|
-
|
|
216
|
-
|
|
245
|
+
/**
|
|
246
|
+
* The query's locality folded the way the extract's builder folded its `locality_norm`. FR extracts key
|
|
247
|
+
* arrondissement communes at the base city (both-sides fold, see the BAN builder + stripArrondissement), so "Paris
|
|
248
|
+
* 13e Arrondissement" and "Paris" both hit; a no-op for every other locale.
|
|
249
|
+
*/
|
|
250
|
+
#localityKey(locality: string): NameKey {
|
|
251
|
+
return this.#locale === "fr"
|
|
252
|
+
? stripArrondissement(normalizeLocalityForKey(locality))
|
|
253
|
+
: normalizeLocalityForKey(locality)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Whether a row's OWN postcode or locality names a different place than the query did. Absent scope on the row is not
|
|
258
|
+
* a contradiction — it is the case the bbox rung was built for — and a rung that matched ON a field cannot contradict
|
|
259
|
+
* it, so at the postcode rung only the locality can disagree and at the bbox rung either can.
|
|
260
|
+
*
|
|
261
|
+
* The locality is consulted only on an extract whose keys are full names (the constructor's `localityKeys`). Under
|
|
262
|
+
* exact comparison against the US extract's abbreviated keys, the postcode rung refused `4900 Airport Pkwy, Addison
|
|
263
|
+
* TX 75001`'s own rooftop row (`addi`) and `678 Depot St, North Easton, MA 02356`'s (`easton`), and both `pass` board
|
|
264
|
+
* rows fell to interpolation 144–198 m away — invisible to a 1 km grade (#2155). `servon` against `paris` and
|
|
265
|
+
* `werlitzsch` against `krensitz`, on the BAN and OSM extracts, are different places and still refuse.
|
|
266
|
+
*/
|
|
267
|
+
#scopeContradicts(row: AddressPointRow, query: { postcode?: string; locality?: string }): boolean {
|
|
268
|
+
if (query.postcode && row.postcode && row.postcode.trim() !== query.postcode.trim()) return true
|
|
269
|
+
|
|
270
|
+
if (this.#localityKeys === "full" && query.locality && row.locality_norm) {
|
|
271
|
+
return row.locality_norm !== this.#localityKey(query.locality)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[Symbol.dispose](): void {
|
|
278
|
+
this.#db[Symbol.dispose]()
|
|
217
279
|
}
|
|
218
280
|
}
|
|
@@ -45,14 +45,12 @@
|
|
|
45
45
|
* nothing.
|
|
46
46
|
*/
|
|
47
47
|
|
|
48
|
-
import {
|
|
49
|
-
import type { DatabaseSync } from "node:sqlite"
|
|
50
|
-
|
|
51
|
-
import { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
48
|
+
import { readDirectoryEntries } from "@mailwoman/core/fs/readers"
|
|
52
49
|
import { readWOFFeature } from "@mailwoman/core/resources/whosonfirst"
|
|
53
|
-
import {
|
|
50
|
+
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
51
|
+
import { join, resolvePath, type PathBuilderLike } from "path-ts"
|
|
54
52
|
|
|
55
|
-
import type { WOFDatabase } from "
|
|
53
|
+
import type { WOFDatabase } from "#schema"
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
56
|
* Genuinely top-level placetypes — they never have (or need) an ancestor, so skip them.
|
|
@@ -76,23 +74,21 @@ export interface AncestryBackfillResult {
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
/**
|
|
79
|
-
* Discover the `data` directories under a WOF repos root that hold
|
|
77
|
+
* Discover the `data` directories under a WOF repos root that hold attached geojson, e.g.
|
|
80
78
|
* `<root>/whosonfirst-data/whosonfirst-data-admin-us/data`. Resolves an id to its geojson via these roots. Accepts both
|
|
81
79
|
* the nested lab layout (a `whosonfirst-data` group dir holding the admin repos) and a flat layout (admin repos
|
|
82
80
|
* directly under the root); searches at most two directory levels deep.
|
|
83
81
|
*/
|
|
84
|
-
export function discoverAdminDataRoots(reposRoot:
|
|
82
|
+
export async function discoverAdminDataRoots(reposRoot: PathBuilderLike): Promise<string[]> {
|
|
85
83
|
const roots: string[] = []
|
|
86
84
|
|
|
87
|
-
const visit = (dir: string, depth: number): void => {
|
|
85
|
+
const visit = async (dir: string, depth: number): Promise<void> => {
|
|
88
86
|
if (depth > 2) return
|
|
89
87
|
|
|
90
88
|
let names: string[]
|
|
91
89
|
|
|
92
90
|
try {
|
|
93
|
-
names =
|
|
94
|
-
.filter((e) => e.isDirectory())
|
|
95
|
-
.map((e) => e.name)
|
|
91
|
+
names = (await readDirectoryEntries(dir)).filter((e) => e.isDirectory()).map((e) => e.name)
|
|
96
92
|
} catch {
|
|
97
93
|
return
|
|
98
94
|
}
|
|
@@ -103,12 +99,12 @@ export function discoverAdminDataRoots(reposRoot: string): string[] {
|
|
|
103
99
|
if (name === "data") {
|
|
104
100
|
roots.push(child)
|
|
105
101
|
} else if (name.startsWith("whosonfirst-data")) {
|
|
106
|
-
visit(child, depth + 1)
|
|
102
|
+
await visit(child, depth + 1)
|
|
107
103
|
}
|
|
108
104
|
}
|
|
109
105
|
}
|
|
110
106
|
|
|
111
|
-
visit(reposRoot, 0)
|
|
107
|
+
await visit(resolvePath(reposRoot), 0)
|
|
112
108
|
|
|
113
109
|
return roots
|
|
114
110
|
}
|
|
@@ -136,18 +132,17 @@ function placetypeFromKey(key: string): string | null {
|
|
|
136
132
|
* skipped rows would have `noGeojson`-skipped anyway. Omit `maxID` (default) for the legacy WOF-only DBs.
|
|
137
133
|
*/
|
|
138
134
|
export async function backfillAncestorsFromHierarchy(
|
|
139
|
-
db:
|
|
135
|
+
db: DatabaseClient<WOFDatabase>,
|
|
140
136
|
geojsonRoots: readonly string[],
|
|
141
137
|
opts: { maxID?: number } = {}
|
|
142
138
|
): Promise<AncestryBackfillResult> {
|
|
143
139
|
const maxID = opts.maxID ?? Number.MAX_SAFE_INTEGER
|
|
144
|
-
const kdb = new DatabaseClient<WOFDatabase>({ database: db })
|
|
145
140
|
|
|
146
141
|
// "No country-tier ancestor" is the dead-end signal at any depth — see the module docstring. The
|
|
147
142
|
// earlier "<= 1 ancestor row" test only caught the dead end's origin, never the children that
|
|
148
143
|
// inherit it (a child of a repaired -4 place has two rows: itself and that parent) (#1445).
|
|
149
144
|
// The id bound is stated first so SQLite prunes by the PK index before the NOT EXISTS runs at all.
|
|
150
|
-
const candidateBase =
|
|
145
|
+
const candidateBase = db
|
|
151
146
|
.selectFrom("spr")
|
|
152
147
|
.where("id", "<", maxID)
|
|
153
148
|
.where((eb) =>
|
|
@@ -172,7 +167,7 @@ export async function backfillAncestorsFromHierarchy(
|
|
|
172
167
|
// (measured 2026-08-04).
|
|
173
168
|
const alreadyPresent = new Map<number, Set<number>>()
|
|
174
169
|
|
|
175
|
-
for (const row of await
|
|
170
|
+
for (const row of await db
|
|
176
171
|
.selectFrom("ancestors")
|
|
177
172
|
.select(["id", "ancestor_id"])
|
|
178
173
|
.where("id", "in", candidateBase.select("spr.id"))
|
|
@@ -198,7 +193,7 @@ export async function backfillAncestorsFromHierarchy(
|
|
|
198
193
|
|
|
199
194
|
for (const { id, placetype } of candidates) {
|
|
200
195
|
if (placetype && TOP_PLACETYPES.has(placetype)) continue
|
|
201
|
-
const gj = readWOFFeature(id, geojsonRoots)
|
|
196
|
+
const gj = await readWOFFeature(id, geojsonRoots)
|
|
202
197
|
const hierarchy = gj?.properties?.["wof:hierarchy"]
|
|
203
198
|
|
|
204
199
|
if (!hierarchy || !hierarchy.length) {
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* geocoding always does).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type {
|
|
18
|
+
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
19
19
|
|
|
20
|
-
import { allRows } from "
|
|
20
|
+
import { allRows } from "#sqlite-utils"
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* WOF placetype → containment depth, coarsest = 1. Higher = finer. Placetypes we never resolve (continent, empire, …)
|
|
@@ -61,7 +61,7 @@ export interface AncestorPlaceRow {
|
|
|
61
61
|
* The ancestor lineage of `id` — self excluded, nearest-first. Returns `[]` when the place has no recorded ancestry.
|
|
62
62
|
* NOT memoized here; `WOFSQLitePlaceLookup` keeps its own per-id cache.
|
|
63
63
|
*/
|
|
64
|
-
export function ancestorLineage(db:
|
|
64
|
+
export function ancestorLineage<DB>(db: DatabaseClient<DB>, id: number, schemaName = "main"): AncestorPlaceRow[] {
|
|
65
65
|
const rows = allRows<AncestorPlaceRow>(
|
|
66
66
|
db.prepare(
|
|
67
67
|
`SELECT a.ancestor_id AS id, a.ancestor_placetype AS placetype, s.name AS name,
|
|
@@ -76,3 +76,5 @@ export function ancestorLineage(db: DatabaseSync, id: number, schemaName = "main
|
|
|
76
76
|
|
|
77
77
|
return rows
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
export * from "#ancestry/backfill"
|