@mailwoman/resolver-wof-sqlite 9.4.0 → 10.1.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 +8 -8
- package/lib/address/{point-interpolation.ts → point/interpolation.ts} +40 -38
- package/lib/address/point/schema.ts +132 -0
- package/lib/address/point.ts +68 -81
- package/lib/{address/index.ts → address.ts} +2 -2
- package/lib/ancestry/backfill.ts +24 -88
- package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
- package/lib/build/candidate.ts +608 -0
- package/lib/build/population-corrections.ts +64 -0
- package/lib/build/slim.ts +338 -0
- package/lib/candidate/alias-bags.ts +74 -14
- package/lib/candidate/ancestors/schema.ts +211 -0
- package/lib/candidate/{ancestors-sidecar.ts → ancestors/sidecar.ts} +17 -24
- package/lib/candidate/country-display-names.ts +6 -20
- package/lib/candidate/extract-fold.ts +12 -57
- package/lib/candidate/fts.ts +28 -0
- package/lib/candidate/importance.ts +221 -0
- package/lib/candidate/lookup.ts +669 -0
- package/lib/candidate/name-roles.ts +16 -34
- package/lib/candidate/own-name.ts +34 -31
- package/lib/candidate/place-attrs.ts +20 -13
- package/lib/candidate/schema.ts +211 -0
- package/lib/candidate/scoring.ts +222 -0
- package/lib/capital-schema.ts +21 -17
- package/lib/capitals.ts +26 -24
- package/lib/coincident-roles.ts +74 -55
- package/lib/convention/schema.ts +13 -7
- package/lib/convention.ts +144 -0
- package/lib/coverage-manifest-schema.ts +34 -39
- package/lib/currency-backfill.ts +24 -69
- package/lib/env.ts +1 -1
- package/lib/exact-match.ts +31 -20
- package/lib/extracts.ts +96 -73
- package/lib/fst/autocomplete.ts +52 -35
- package/lib/fst/builder.ts +30 -36
- package/lib/fst/deserialize-web.ts +8 -9
- package/lib/fst/format.ts +41 -23
- package/lib/fst/freshness.ts +17 -95
- package/lib/fst/matcher.ts +2 -2
- package/lib/fst/serialize.ts +45 -60
- package/lib/fst/types.ts +71 -59
- package/lib/fts/query.ts +5 -36
- package/lib/fts.ts +280 -0
- package/lib/geonames/aliases.ts +72 -149
- package/lib/geonames/postal.ts +162 -77
- package/lib/index.ts +20 -27
- package/lib/interpolation.ts +43 -96
- package/lib/lookup.ts +137 -257
- package/lib/name-score.ts +9 -7
- package/lib/nsul/lookup.ts +36 -53
- package/lib/nsul/schema.ts +16 -43
- package/lib/paths.ts +159 -0
- package/lib/place-importance-schema.ts +57 -164
- package/lib/poi/lookup.ts +100 -109
- package/lib/poi/schema.ts +42 -26
- package/lib/polygon-schema.ts +10 -6
- package/lib/postal/city/alias/lookup.ts +79 -0
- package/lib/postal/{city-alias-schema.ts → city/alias/schema.ts} +14 -10
- package/lib/postal/{city-candidate-schema.ts → city/candidate-schema.ts} +26 -20
- package/lib/postal.ts +9 -0
- package/lib/postcode-point-lookup.ts +9 -19
- package/lib/primary-preference.ts +45 -125
- package/lib/proximity-rerank.ts +43 -32
- package/lib/ranking-weights.ts +98 -76
- package/lib/region-keys.ts +68 -40
- package/lib/reverse.ts +108 -72
- package/lib/schema.ts +74 -42
- package/lib/search-fetch.ts +90 -68
- package/lib/sqlite/convention-source.ts +57 -0
- package/lib/sqlite/utils.ts +79 -0
- package/lib/street/centroid/schema.ts +149 -0
- package/lib/street/centroid.ts +16 -25
- package/lib/street/{morphology-fst-builder.ts → morphology/fst/builder.ts} +56 -41
- package/lib/street/{morphology-fst-loader.ts → morphology/fst/loader.ts} +36 -29
- package/lib/street/name-lookup.ts +43 -35
- package/lib/street/normalize.ts +85 -216
- package/lib/street/segment-schema.ts +116 -43
- package/lib/{street/index.ts → street.ts} +3 -3
- package/lib/types.ts +94 -140
- package/lib/unified-schema.ts +29 -29
- package/lib/uprn/existence.ts +30 -19
- package/lib/uprn/lookup.ts +28 -67
- package/lib/uprn/schema.ts +18 -40
- package/lib/weights-overlay-linker.ts +204 -232
- package/out/address/point/interpolation.d.ts +45 -0
- package/out/address/point/interpolation.d.ts.map +1 -0
- package/out/address/{point-interpolation.js → point/interpolation.js} +38 -38
- package/out/address/point/interpolation.js.map +1 -0
- package/out/address/point/schema.d.ts +74 -0
- package/out/address/point/schema.d.ts.map +1 -0
- package/out/address/{point-schema.js → point/schema.js} +4 -20
- package/out/address/point/schema.js.map +1 -0
- package/out/address/point.d.ts +24 -21
- package/out/address/point.d.ts.map +1 -1
- package/out/address/point.js +60 -77
- package/out/address/point.js.map +1 -1
- package/out/{address/index.js → address.d.ts} +3 -3
- package/out/address.d.ts.map +1 -0
- package/out/{address/index.d.ts → address.js} +3 -3
- package/out/address.js.map +1 -0
- package/out/ancestry/backfill.d.ts +11 -64
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +18 -76
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry.d.ts +49 -0
- package/out/ancestry.d.ts.map +1 -0
- package/out/ancestry.js +59 -0
- package/out/ancestry.js.map +1 -0
- package/out/build/candidate.d.ts +170 -0
- package/out/build/candidate.d.ts.map +1 -0
- package/out/{build-candidate.js → build/candidate.js} +33 -102
- package/out/build/candidate.js.map +1 -0
- package/out/build/population-corrections.d.ts +52 -0
- package/out/build/population-corrections.d.ts.map +1 -0
- package/out/build/population-corrections.js +40 -0
- package/out/build/population-corrections.js.map +1 -0
- package/out/build/slim.d.ts +44 -0
- package/out/build/slim.d.ts.map +1 -0
- package/out/{build-slim.js → build/slim.js} +33 -94
- package/out/build/slim.js.map +1 -0
- package/out/candidate/alias-bags.d.ts +13 -5
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +55 -12
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +166 -0
- package/out/candidate/ancestors/schema.d.ts.map +1 -0
- package/out/candidate/ancestors/schema.js +131 -0
- package/out/candidate/ancestors/schema.js.map +1 -0
- package/out/candidate/ancestors/sidecar.d.ts +27 -0
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -0
- package/out/candidate/{ancestors-sidecar.js → ancestors/sidecar.js} +17 -24
- package/out/candidate/ancestors/sidecar.js.map +1 -0
- package/out/candidate/country-display-names.d.ts +5 -15
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +6 -20
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +8 -16
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +6 -48
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +19 -0
- package/out/candidate/fts.d.ts.map +1 -0
- package/out/candidate/fts.js +22 -0
- package/out/candidate/fts.js.map +1 -0
- package/out/candidate/importance.d.ts +127 -0
- package/out/candidate/importance.d.ts.map +1 -0
- package/out/candidate/importance.js +162 -0
- package/out/candidate/importance.js.map +1 -0
- package/out/candidate/lookup.d.ts +36 -0
- package/out/candidate/lookup.d.ts.map +1 -0
- package/out/candidate/lookup.js +456 -0
- package/out/candidate/lookup.js.map +1 -0
- package/out/candidate/name-roles.d.ts +10 -22
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +15 -33
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +23 -25
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +34 -31
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +20 -13
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +4 -5
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate/schema.d.ts +120 -0
- package/out/candidate/schema.d.ts.map +1 -0
- package/out/{candidate-schema.js → candidate/schema.js} +11 -23
- package/out/candidate/schema.js.map +1 -0
- package/out/{candidate-scoring.d.ts → candidate/scoring.d.ts} +8 -9
- package/out/candidate/scoring.d.ts.map +1 -0
- package/out/candidate/scoring.js +157 -0
- package/out/candidate/scoring.js.map +1 -0
- package/out/capital-schema.d.ts +21 -17
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +17 -15
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +26 -24
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +23 -21
- package/out/capitals.js.map +1 -1
- package/out/coincident-roles.d.ts +60 -43
- package/out/coincident-roles.d.ts.map +1 -1
- package/out/coincident-roles.js +61 -50
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/schema.d.ts +12 -6
- package/out/convention/schema.d.ts.map +1 -1
- package/out/convention/schema.js +6 -4
- package/out/convention/schema.js.map +1 -1
- package/out/convention.d.ts +97 -0
- package/out/convention.d.ts.map +1 -0
- package/out/convention.js +81 -0
- package/out/convention.js.map +1 -0
- package/out/coverage-manifest-schema.d.ts +33 -38
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +20 -29
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +13 -35
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +12 -39
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +2 -2
- package/out/env.js +1 -1
- package/out/exact-match.d.ts +19 -10
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +31 -20
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +65 -54
- package/out/extracts.d.ts.map +1 -1
- package/out/extracts.js +63 -55
- package/out/extracts.js.map +1 -1
- package/out/fst/autocomplete.d.ts +28 -20
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +38 -27
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +30 -36
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +3 -3
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +8 -9
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/format.d.ts +41 -23
- package/out/fst/format.d.ts.map +1 -1
- package/out/fst/format.js +41 -23
- package/out/fst/format.js.map +1 -1
- package/out/fst/freshness.d.ts +11 -73
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +12 -76
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +1 -1
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/matcher.js +2 -2
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.d.ts +30 -26
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +44 -58
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +71 -59
- package/out/fst/types.d.ts.map +1 -1
- package/out/fst/types.js +1 -4
- package/out/fst/types.js.map +1 -1
- package/out/{fst/index.js → fst.d.ts} +1 -1
- package/out/fst.d.ts.map +1 -0
- package/out/{fst/index.d.ts → fst.js} +1 -1
- package/out/fst.js.map +1 -0
- package/out/fts/query.d.ts +2 -27
- package/out/fts/query.d.ts.map +1 -1
- package/out/fts/query.js +5 -36
- package/out/fts/query.js.map +1 -1
- package/out/fts.d.ts +126 -0
- package/out/fts.d.ts.map +1 -0
- package/out/fts.js +188 -0
- package/out/fts.js.map +1 -0
- package/out/geonames/aliases.d.ts +26 -67
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +44 -121
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +84 -50
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +115 -78
- package/out/geonames/postal.js.map +1 -1
- package/out/{geonames/index.js → geonames.d.ts} +1 -1
- package/out/geonames.d.ts.map +1 -0
- package/out/{geonames/index.d.ts → geonames.js} +1 -1
- package/out/geonames.js.map +1 -0
- package/out/index.d.ts +20 -21
- package/out/index.d.ts.map +1 -1
- package/out/index.js +14 -15
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +15 -52
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +25 -68
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +24 -53
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +117 -223
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +2 -2
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +9 -7
- package/out/name-score.js.map +1 -1
- package/out/nsul/lookup.d.ts +33 -34
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +26 -40
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +16 -43
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +9 -35
- package/out/nsul/schema.js.map +1 -1
- package/out/{nsul/index.d.ts → nsul.d.ts} +1 -1
- package/out/nsul.d.ts.map +1 -0
- package/out/{nsul/index.js → nsul.js} +1 -1
- package/out/nsul.js.map +1 -0
- package/out/paths.d.ts +124 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +104 -0
- package/out/paths.js.map +1 -0
- package/out/place-importance-schema.d.ts +49 -141
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +25 -140
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +38 -42
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +67 -90
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts +41 -25
- package/out/poi/schema.d.ts.map +1 -1
- package/out/poi/schema.js +25 -17
- package/out/poi/schema.js.map +1 -1
- package/out/{poi/index.js → poi.d.ts} +1 -1
- package/out/poi.d.ts.map +1 -0
- package/out/{poi/index.d.ts → poi.js} +1 -1
- package/out/poi.js.map +1 -0
- package/out/polygon-schema.d.ts +10 -6
- package/out/polygon-schema.d.ts.map +1 -1
- package/out/polygon-schema.js +2 -2
- package/out/polygon-schema.js.map +1 -1
- package/out/postal/{city-alias-lookup.d.ts → city/alias/lookup.d.ts} +22 -25
- package/out/postal/city/alias/lookup.d.ts.map +1 -0
- package/out/postal/city/alias/lookup.js +49 -0
- package/out/postal/city/alias/lookup.js.map +1 -0
- package/out/postal/{city-alias-schema.d.ts → city/alias/schema.d.ts} +15 -11
- package/out/postal/city/alias/schema.d.ts.map +1 -0
- package/out/postal/{city-alias-schema.js → city/alias/schema.js} +11 -9
- package/out/postal/city/alias/schema.js.map +1 -0
- package/out/postal/city/candidate-schema.d.ts +72 -0
- package/out/postal/city/candidate-schema.d.ts.map +1 -0
- package/out/postal/city/candidate-schema.js +60 -0
- package/out/postal/city/candidate-schema.js.map +1 -0
- package/out/postal.d.ts +9 -0
- package/out/postal.d.ts.map +1 -0
- package/out/postal.js +9 -0
- package/out/postal.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +9 -19
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +5 -17
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +39 -93
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +13 -97
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +40 -29
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +38 -29
- package/out/proximity-rerank.js.map +1 -1
- package/out/ranking-weights.d.ts +91 -62
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +26 -25
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +48 -29
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +68 -40
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +75 -50
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +61 -49
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +74 -42
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +4 -5
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +16 -12
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +83 -63
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.d.ts +25 -0
- package/out/sqlite/convention-source.d.ts.map +1 -0
- package/out/sqlite/convention-source.js +48 -0
- package/out/sqlite/convention-source.js.map +1 -0
- package/out/sqlite/utils.d.ts +45 -0
- package/out/sqlite/utils.d.ts.map +1 -0
- package/out/sqlite/utils.js +56 -0
- package/out/sqlite/utils.js.map +1 -0
- package/out/street/centroid/schema.d.ts +100 -0
- package/out/street/centroid/schema.d.ts.map +1 -0
- package/out/street/centroid/schema.js +66 -0
- package/out/street/centroid/schema.js.map +1 -0
- package/out/street/centroid.d.ts +9 -19
- package/out/street/centroid.d.ts.map +1 -1
- package/out/street/centroid.js +11 -22
- package/out/street/centroid.js.map +1 -1
- package/out/street/{morphology-fst-builder.d.ts → morphology/fst/builder.d.ts} +21 -11
- package/out/street/morphology/fst/builder.d.ts.map +1 -0
- package/out/street/{morphology-fst-builder.js → morphology/fst/builder.js} +40 -34
- package/out/street/morphology/fst/builder.js.map +1 -0
- package/out/street/morphology/fst/loader.d.ts +74 -0
- package/out/street/morphology/fst/loader.d.ts.map +1 -0
- package/out/street/{morphology-fst-loader.js → morphology/fst/loader.js} +21 -21
- package/out/street/morphology/fst/loader.js.map +1 -0
- package/out/street/name-lookup.d.ts +24 -18
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js +32 -31
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +35 -136
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +74 -191
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts +79 -39
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/street/segment-schema.js +71 -21
- package/out/street/segment-schema.js.map +1 -1
- package/out/{street/index.d.ts → street.d.ts} +4 -4
- package/out/street.d.ts.map +1 -0
- package/out/{street/index.js → street.js} +4 -4
- package/out/street.js.map +1 -0
- package/out/types.d.ts +69 -138
- package/out/types.d.ts.map +1 -1
- package/out/types.js +0 -6
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +8 -12
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +29 -29
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +28 -17
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js +26 -16
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts +14 -44
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +20 -56
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +18 -40
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +10 -32
- package/out/uprn/schema.js.map +1 -1
- package/out/{uprn/index.d.ts → uprn.d.ts} +1 -1
- package/out/uprn.d.ts.map +1 -0
- package/out/{uprn/index.js → uprn.js} +1 -1
- package/out/uprn.js.map +1 -0
- package/out/weights-overlay-linker.d.ts +132 -141
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +86 -154
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +94 -599
- package/lib/address/point-schema.ts +0 -155
- package/lib/build-candidate.ts +0 -632
- package/lib/build-slim.ts +0 -408
- package/lib/candidate-ancestors-schema.ts +0 -195
- package/lib/candidate-fts.ts +0 -46
- package/lib/candidate-importance.ts +0 -224
- package/lib/candidate-lookup.ts +0 -977
- package/lib/candidate-schema.ts +0 -247
- package/lib/candidate-scoring.ts +0 -261
- package/lib/convention/index.ts +0 -154
- package/lib/fts/index.ts +0 -361
- package/lib/postal/city-alias-lookup.ts +0 -97
- package/lib/postal/index.ts +0 -9
- package/lib/sqlite-convention-source.ts +0 -65
- package/lib/sqlite-utils.ts +0 -82
- package/lib/street/centroid-schema.ts +0 -154
- package/out/address/index.d.ts.map +0 -1
- package/out/address/index.js.map +0 -1
- package/out/address/point-interpolation.d.ts +0 -49
- 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 +0 -97
- package/out/address/point-schema.d.ts.map +0 -1
- package/out/address/point-schema.js.map +0 -1
- package/out/ancestry/index.d.ts +0 -45
- package/out/ancestry/index.d.ts.map +0 -1
- package/out/ancestry/index.js +0 -55
- package/out/ancestry/index.js.map +0 -1
- package/out/build-candidate.d.ts +0 -184
- package/out/build-candidate.d.ts.map +0 -1
- package/out/build-candidate.js.map +0 -1
- package/out/build-slim.d.ts +0 -80
- package/out/build-slim.d.ts.map +0 -1
- package/out/build-slim.js.map +0 -1
- package/out/candidate/ancestors-sidecar.d.ts +0 -32
- package/out/candidate/ancestors-sidecar.d.ts.map +0 -1
- package/out/candidate/ancestors-sidecar.js.map +0 -1
- package/out/candidate-ancestors-schema.d.ts +0 -150
- package/out/candidate-ancestors-schema.d.ts.map +0 -1
- package/out/candidate-ancestors-schema.js +0 -123
- package/out/candidate-ancestors-schema.js.map +0 -1
- package/out/candidate-fts.d.ts +0 -37
- package/out/candidate-fts.d.ts.map +0 -1
- package/out/candidate-fts.js +0 -40
- package/out/candidate-fts.js.map +0 -1
- package/out/candidate-importance.d.ts +0 -132
- package/out/candidate-importance.d.ts.map +0 -1
- package/out/candidate-importance.js +0 -169
- package/out/candidate-importance.js.map +0 -1
- package/out/candidate-lookup.d.ts +0 -76
- package/out/candidate-lookup.d.ts.map +0 -1
- package/out/candidate-lookup.js +0 -768
- package/out/candidate-lookup.js.map +0 -1
- package/out/candidate-schema.d.ts +0 -155
- package/out/candidate-schema.d.ts.map +0 -1
- package/out/candidate-schema.js.map +0 -1
- package/out/candidate-scoring.d.ts.map +0 -1
- package/out/candidate-scoring.js +0 -196
- package/out/candidate-scoring.js.map +0 -1
- package/out/convention/index.d.ts +0 -106
- package/out/convention/index.d.ts.map +0 -1
- package/out/convention/index.js +0 -93
- package/out/convention/index.js.map +0 -1
- package/out/fst/index.d.ts.map +0 -1
- package/out/fst/index.js.map +0 -1
- package/out/fts/index.d.ts +0 -184
- package/out/fts/index.d.ts.map +0 -1
- package/out/fts/index.js +0 -277
- package/out/fts/index.js.map +0 -1
- package/out/geonames/index.d.ts.map +0 -1
- package/out/geonames/index.js.map +0 -1
- package/out/nsul/index.d.ts.map +0 -1
- package/out/nsul/index.js.map +0 -1
- package/out/poi/index.d.ts.map +0 -1
- package/out/poi/index.js.map +0 -1
- package/out/postal/city-alias-lookup.d.ts.map +0 -1
- package/out/postal/city-alias-lookup.js +0 -62
- package/out/postal/city-alias-lookup.js.map +0 -1
- package/out/postal/city-alias-schema.d.ts.map +0 -1
- package/out/postal/city-alias-schema.js.map +0 -1
- package/out/postal/city-candidate-schema.d.ts +0 -66
- package/out/postal/city-candidate-schema.d.ts.map +0 -1
- package/out/postal/city-candidate-schema.js +0 -56
- package/out/postal/city-candidate-schema.js.map +0 -1
- package/out/postal/index.d.ts +0 -9
- package/out/postal/index.d.ts.map +0 -1
- package/out/postal/index.js +0 -9
- package/out/postal/index.js.map +0 -1
- package/out/sqlite-convention-source.d.ts +0 -29
- package/out/sqlite-convention-source.d.ts.map +0 -1
- package/out/sqlite-convention-source.js +0 -57
- package/out/sqlite-convention-source.js.map +0 -1
- package/out/sqlite-utils.d.ts +0 -45
- package/out/sqlite-utils.d.ts.map +0 -1
- package/out/sqlite-utils.js +0 -57
- package/out/sqlite-utils.js.map +0 -1
- package/out/street/centroid-schema.d.ts +0 -105
- package/out/street/centroid-schema.d.ts.map +0 -1
- package/out/street/centroid-schema.js +0 -78
- package/out/street/centroid-schema.js.map +0 -1
- package/out/street/index.d.ts.map +0 -1
- package/out/street/index.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 +0 -67
- package/out/street/morphology-fst-loader.d.ts.map +0 -1
- package/out/street/morphology-fst-loader.js.map +0 -1
- package/out/uprn/index.d.ts.map +0 -1
- package/out/uprn/index.js.map +0 -1
- /package/lib/{fst/index.ts → fst.ts} +0 -0
- /package/lib/{geonames/index.ts → geonames.ts} +0 -0
- /package/lib/{nsul/index.ts → nsul.ts} +0 -0
- /package/lib/{poi/index.ts → poi.ts} +0 -0
- /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Databases are `ATTACH`ed by a schema name **derived from the filename**, and que
|
|
|
40
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".
|
|
41
41
|
- The database carries `spr` but no `place_search`. It routes, then dies mid-`SELECT`.
|
|
42
42
|
|
|
43
|
-
The predicate is the **table
|
|
43
|
+
The predicate is the **table rather than 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.
|
|
44
44
|
|
|
45
45
|
```
|
|
46
46
|
WOFSQLitePlaceLookup: …/postcode-ca-overture.db carries "spr" but no "place_search" table, so it
|
|
@@ -81,7 +81,7 @@ Cross-database `UNION` queries are not supported in one `findPlace` call — BM2
|
|
|
81
81
|
comparable across separately-indexed corpora. Issue two `findPlace` calls and merge in your
|
|
82
82
|
caller if you need that.
|
|
83
83
|
|
|
84
|
-
##
|
|
84
|
+
## Get the WOF SQLite distribution
|
|
85
85
|
|
|
86
86
|
The Geocode Earth team mirrors WOF SQLite distributions at <https://data.geocode.earth/wof/dist/sqlite/>. The two relevant distributions for v1:
|
|
87
87
|
|
|
@@ -137,7 +137,7 @@ Builds a trimmed WOF SQLite distribution sized for browser-side deployments (Pat
|
|
|
137
137
|
# Defaults: --top 1000 localities, --countries US, drops geojson after building aux tables
|
|
138
138
|
# The slim wof-hot.db distribution is RETIRED (2026-06-20): the demo byte-range-resolves
|
|
139
139
|
# against the global candidate table. `buildSlimWOFDatabase` remains importable from
|
|
140
|
-
# `@mailwoman/resolver-wof-sqlite/build
|
|
140
|
+
# `@mailwoman/resolver-wof-sqlite/build/slim` for test fixtures. Historical invocation:
|
|
141
141
|
# mailwoman-wof-build-slim \
|
|
142
142
|
--in /path/to/whosonfirst-data-admin-us-latest.db \
|
|
143
143
|
--in /path/to/whosonfirst-data-postalcode-us-latest.db \
|
|
@@ -175,7 +175,7 @@ const { created, indexedRows, durationMs } = buildPlaceSearchFts(db, {
|
|
|
175
175
|
})
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
##
|
|
178
|
+
## Rank places
|
|
179
179
|
|
|
180
180
|
The resolver scores candidates by:
|
|
181
181
|
|
|
@@ -237,11 +237,11 @@ are all stripped safely before going to FTS5. Per-token rules:
|
|
|
237
237
|
|
|
238
238
|
- **Bare tokens** (`"Paris"`, `"62701"`) become FTS5 **phrase matches**: `"Paris"` matches places
|
|
239
239
|
named exactly "Paris", `"62701"` matches the postcode 62701 exactly.
|
|
240
|
-
- **
|
|
240
|
+
- **A trailing `*`** (`"627*"`, `"Pari*"`) becomes FTS5 **prefix syntax**: `627*` matches every
|
|
241
241
|
postcode starting with 627, `Pari*` matches Paris / Parishville / etc. The caller explicitly
|
|
242
242
|
signals "prefix"; bare tokens stay phrase-matched for safety.
|
|
243
|
-
- **Multiple tokens** join with implicit AND
|
|
244
|
-
both a `Pari*`-prefixed word
|
|
243
|
+
- **Multiple tokens** join with an implicit `AND`: `"Pari* TX"` matches places whose name contains
|
|
244
|
+
both a `Pari*`-prefixed word and the word `TX`.
|
|
245
245
|
|
|
246
246
|
Example: `findPlace({ text: "902*", placetype: "postalcode" })` returns 90201, 90210, 90211, …
|
|
247
247
|
matching the Los Angeles ZIP corridor.
|
|
@@ -256,7 +256,7 @@ This package itself is AGPL-3.0; the WOF data it indexes is CC-BY 4.0. The two l
|
|
|
256
256
|
|
|
257
257
|
## Integration tests
|
|
258
258
|
|
|
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
|
|
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 `$MAILWOMAN_DATA_ROOT/db/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.
|
|
260
260
|
|
|
261
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).
|
|
262
262
|
|
|
@@ -3,47 +3,45 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Address-point interpolation
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* miss; TIGER range interpolation (`StreetInterpolator`) demotes to the fallback for streets too
|
|
12
|
-
* sparse to bracket.
|
|
6
|
+
* Address-point interpolation, the second method of the resolution ladder. When the exact
|
|
7
|
+
* address-point tier misses a house number, bracket the number with real neighbor points on the
|
|
8
|
+
* same street and interpolate linearly in house-number space between them. Real occupancy
|
|
9
|
+
* replaces the tiger range tier's uniform-spacing assumption. Tiger range interpolation
|
|
10
|
+
* (`StreetInterpolator`) is the fallback for streets too sparse to bracket.
|
|
13
11
|
*
|
|
14
|
-
*
|
|
15
|
-
* (`canonicalizeRouteKey`), identical at build time (`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* The matching key is `street_key`, the shared normalizer plus the route fold
|
|
13
|
+
* (`canonicalizeRouteKey`), identical at build time (`mailwoman situs address-points`) and query
|
|
14
|
+
* time by construction. Scope is postcode-first like the segment tier. A query without a postcode
|
|
15
|
+
* goes straight to the fallback. The fallback has its own statewide-ambiguity abstention.
|
|
18
16
|
*
|
|
19
|
-
*
|
|
17
|
+
* Neighbor candidates never include the queried number itself, so production never overrides an
|
|
18
|
+
* on-file number and grading against the same extract is non-circular.
|
|
20
19
|
*
|
|
21
|
-
* - Neighbor candidates NEVER include the queried number itself (any unit/duplicate row of it) — in
|
|
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 extract non-circular by construction.
|
|
24
20
|
* - Both-sided bracket (`bracket: "both"`): linear interpolation between the nearest known number
|
|
25
|
-
* below and above
|
|
26
|
-
* - Single-sided (`bracket: "single"`): linear extrapolation along the two nearest known numbers
|
|
27
|
-
* that side, capped at one pair-span beyond the nearest point (`t ≤ 2
|
|
28
|
-
*
|
|
29
|
-
* the extrapolated overshoot,
|
|
30
|
-
* - No bracket
|
|
31
|
-
* the
|
|
21
|
+
* below and above. `uncertaintyM` is half the distance between them.
|
|
22
|
+
* - Single-sided (`bracket: "single"`): linear extrapolation along the two nearest known numbers
|
|
23
|
+
* on that side, capped at one pair-span beyond the nearest point (`t ≤ 2`, past which the line
|
|
24
|
+
* provides no evidence and the query falls through). `uncertaintyM` is the pair distance plus
|
|
25
|
+
* the extrapolated overshoot, larger than the both-sided radius.
|
|
26
|
+
* - No bracket, meaning no neighbors, a single known number, or past the extrapolation cap: fall
|
|
27
|
+
* through to the tiger fallback when configured, else null.
|
|
32
28
|
*
|
|
33
|
-
* Standalone like the segment tier
|
|
29
|
+
* Standalone like the segment tier, wired through the ordered `spatialTiers` list.
|
|
34
30
|
*/
|
|
35
31
|
|
|
36
32
|
import type { InterpolationLookup } from "@mailwoman/core/resolver"
|
|
37
33
|
import { haversineKm } from "@mailwoman/spatial"
|
|
38
34
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
39
35
|
|
|
40
|
-
import type { AddressPointDatabase } from "#address/point
|
|
36
|
+
import type { AddressPointDatabase } from "#address/point/schema"
|
|
41
37
|
import type { InterpolatedHit, InterpolationQuery, StreetInterpolator } from "#interpolation"
|
|
42
38
|
import { hasTable, prepareAll, type PreparedAll } from "#sqlite-utils"
|
|
43
39
|
import { canonicalizeRouteKey, type RouteKey, streetKeyVariants } from "#street/normalize"
|
|
44
40
|
/**
|
|
45
|
-
* Extrapolation cap for a single-sided bracket: at most one pair-span beyond
|
|
46
|
-
*
|
|
41
|
+
* Extrapolation cap for a single-sided bracket: at most one pair-span beyond
|
|
42
|
+
* the nearest known point (`t = 2`).
|
|
43
|
+
*
|
|
44
|
+
* Past it, the two-point line provides no evidence about the query number.
|
|
47
45
|
*/
|
|
48
46
|
const MAX_EXTRAPOLATION_T = 2
|
|
49
47
|
|
|
@@ -71,29 +69,31 @@ export class AddressPointInterpolator<
|
|
|
71
69
|
> implements InterpolationLookup {
|
|
72
70
|
readonly #db: DatabaseClient<DB>
|
|
73
71
|
/**
|
|
74
|
-
* Resources this instance opened.
|
|
75
|
-
*
|
|
72
|
+
* Resources this instance opened.
|
|
73
|
+
*
|
|
74
|
+
* A connection handed in by a caller is not in here, so disposal cannot reach it.
|
|
75
|
+
* Ownership is membership rather than a flag a later branch checks.
|
|
76
76
|
*/
|
|
77
|
-
readonly #resources
|
|
77
|
+
readonly #resources: DisposableStack
|
|
78
78
|
readonly #fallback: StreetInterpolator | undefined
|
|
79
79
|
readonly #byPostcode: PreparedAll<[postcode: string, street: RouteKey, number: number], PointRow> | undefined
|
|
80
80
|
|
|
81
81
|
constructor(opts: { dbPath?: string; database?: DatabaseClient<DB>; fallback?: StreetInterpolator }) {
|
|
82
|
+
using resources = new DisposableStack()
|
|
83
|
+
|
|
82
84
|
if (opts.database) {
|
|
83
85
|
this.#db = opts.database
|
|
84
86
|
} else if (opts.dbPath) {
|
|
85
|
-
this.#db =
|
|
87
|
+
this.#db = resources.use(new DatabaseClient<DB>(opts.dbPath, { readOnly: true }))
|
|
86
88
|
} else {
|
|
87
89
|
throw new Error("AddressPointInterpolator: one of dbPath or database is required")
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
this.#fallback = opts.fallback
|
|
91
93
|
|
|
92
|
-
// Degrade gracefully on an
|
|
93
|
-
// is skipped
|
|
94
|
+
// Degrade gracefully on an extract without an `address_point` table.
|
|
95
|
+
// The tier is skipped and defers to the segment fallback rather than crashing at construction.
|
|
94
96
|
if (hasTable(this.#db, "address_point")) {
|
|
95
|
-
// Strictly-numeric neighbor numbers on the route-folded street key within the ZIP. The
|
|
96
|
-
// queried number itself is excluded HERE (see module doc: non-circular by construction).
|
|
97
97
|
this.#byPostcode = prepareAll(
|
|
98
98
|
this.#db,
|
|
99
99
|
`SELECT CAST(number AS INTEGER) AS n, lat, lon, source, release
|
|
@@ -103,6 +103,8 @@ export class AddressPointInterpolator<
|
|
|
103
103
|
AND CAST(number AS INTEGER) != ?`
|
|
104
104
|
)
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
this.#resources = resources.move()
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
find(query: InterpolationQuery): InterpolatedHit | null {
|
|
@@ -111,10 +113,9 @@ export class AddressPointInterpolator<
|
|
|
111
113
|
if (!/^\d+$/.test(numberRaw)) return null
|
|
112
114
|
const n = Number(numberRaw)
|
|
113
115
|
|
|
114
|
-
// No own table (empty extract) or no postcode → defer to the segment fallback rather than query.
|
|
115
116
|
if (!this.#byPostcode || !query.postcode) return this.#fallback?.find(query) ?? null
|
|
116
117
|
|
|
117
|
-
// Key-variant ladder (see `streetKeyVariants`)
|
|
118
|
+
// Key-variant ladder (see `streetKeyVariants`), the same probe order as the exact-point reader.
|
|
118
119
|
let rows: PointRow[] = []
|
|
119
120
|
|
|
120
121
|
for (const variant of streetKeyVariants(query.street)) {
|
|
@@ -165,7 +166,8 @@ function anchorsByNumber(rows: readonly PointRow[]): NumberAnchor[] {
|
|
|
165
166
|
function interpolateFromNeighbors(rows: readonly PointRow[], n: number): InterpolatedHit | null {
|
|
166
167
|
const anchors = anchorsByNumber(rows)
|
|
167
168
|
|
|
168
|
-
// Nearest known number below and above the query
|
|
169
|
+
// Nearest known number below and above the query.
|
|
170
|
+
// The rows never contain n itself.
|
|
169
171
|
let below: NumberAnchor | undefined
|
|
170
172
|
let above: NumberAnchor | undefined
|
|
171
173
|
|
|
@@ -196,7 +198,7 @@ function interpolateFromNeighbors(rows: readonly PointRow[], n: number): Interpo
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
// Single-sided: extrapolate along the two nearest known numbers on the populated side.
|
|
199
|
-
// `
|
|
201
|
+
// `t > 1` by construction because n lies outside the span.
|
|
200
202
|
const side = below ? anchors.slice(-2) : anchors.slice(0, 2)
|
|
201
203
|
|
|
202
204
|
if (side.length < 2) return null
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Kysely } from "kysely"
|
|
8
|
+
|
|
9
|
+
import type { NameKey, RouteKey, StreetKey } from "#street/normalize"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One rooftop address point; `(street_norm, number)` within a `postcode`
|
|
13
|
+
* or `locality_norm` scope is the lookup.
|
|
14
|
+
*/
|
|
15
|
+
export interface AddressPointTable {
|
|
16
|
+
/**
|
|
17
|
+
* Shared {@link normalizeStreetForKey} of the street — the build/query-consistent probe key.
|
|
18
|
+
*/
|
|
19
|
+
street_norm: StreetKey
|
|
20
|
+
/**
|
|
21
|
+
* `canonicalizeRouteKey(street_norm)` — the route-fold key, branded
|
|
22
|
+
* so it cannot be interchanged with `street_norm`.
|
|
23
|
+
*/
|
|
24
|
+
street_key: RouteKey
|
|
25
|
+
/**
|
|
26
|
+
* House number, normalized lower-case (kept text — "123-A", "12 1/2" must survive).
|
|
27
|
+
*/
|
|
28
|
+
number: string
|
|
29
|
+
unit: string | null
|
|
30
|
+
postcode: string | null
|
|
31
|
+
/**
|
|
32
|
+
* Shared {@link normalizeLocalityForKey} of the locality — the fallback scope.
|
|
33
|
+
*/
|
|
34
|
+
locality_norm: NameKey | null
|
|
35
|
+
|
|
36
|
+
street_raw: string
|
|
37
|
+
lat: number
|
|
38
|
+
lon: number
|
|
39
|
+
/**
|
|
40
|
+
* Provenance: the dataset this point came from (e.g. `overture:us`, `openaddresses`).
|
|
41
|
+
*/
|
|
42
|
+
source: string
|
|
43
|
+
|
|
44
|
+
release: string
|
|
45
|
+
/**
|
|
46
|
+
* The source register's stable administrative key for the point's commune or municipality —
|
|
47
|
+
* BAN's `code_insee`; the coverage basis is computed per this key.
|
|
48
|
+
*/
|
|
49
|
+
admin_code: string | null
|
|
50
|
+
/**
|
|
51
|
+
* The register's own certification flag for the point
|
|
52
|
+
* (BAN `certification_commune`: 1 certified, 0 not), never inferred from a share.
|
|
53
|
+
*/
|
|
54
|
+
certified: number | null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The address-point database schema for `new DatabaseClient<AddressPointDatabase>(...)`.
|
|
59
|
+
*/
|
|
60
|
+
export interface AddressPointDatabase {
|
|
61
|
+
address_point: AddressPointTable
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The subset of a Kysely handle the DDL touches, narrowed to `schema`
|
|
66
|
+
* because Kysely is invariant in its schema parameter.
|
|
67
|
+
*/
|
|
68
|
+
export type AddressPointSchemaHandle = Pick<Kysely<AddressPointDatabase>, "schema">
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The `address_point` columns in insert order, from which the builder derives its positional placeholders.
|
|
72
|
+
*/
|
|
73
|
+
export const ADDRESS_POINT_COLUMNS = [
|
|
74
|
+
"street_norm",
|
|
75
|
+
"street_key",
|
|
76
|
+
"number",
|
|
77
|
+
"unit",
|
|
78
|
+
"postcode",
|
|
79
|
+
"locality_norm",
|
|
80
|
+
"street_raw",
|
|
81
|
+
"lat",
|
|
82
|
+
"lon",
|
|
83
|
+
"source",
|
|
84
|
+
"release",
|
|
85
|
+
"admin_code",
|
|
86
|
+
"certified",
|
|
87
|
+
] as const
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create the `address_point` table — called before the streaming bulk load.
|
|
91
|
+
*/
|
|
92
|
+
export async function createAddressPointTable(db: AddressPointSchemaHandle): Promise<void> {
|
|
93
|
+
await db.schema
|
|
94
|
+
.createTable("address_point")
|
|
95
|
+
.addColumn("street_norm", "text", (c) => c.notNull())
|
|
96
|
+
|
|
97
|
+
.addColumn("street_key", "text", (c) => c.notNull())
|
|
98
|
+
.addColumn("number", "text", (c) => c.notNull())
|
|
99
|
+
.addColumn("unit", "text")
|
|
100
|
+
.addColumn("postcode", "text")
|
|
101
|
+
.addColumn("locality_norm", "text")
|
|
102
|
+
.addColumn("street_raw", "text", (c) => c.notNull())
|
|
103
|
+
.addColumn("lat", "real", (c) => c.notNull())
|
|
104
|
+
.addColumn("lon", "real", (c) => c.notNull())
|
|
105
|
+
.addColumn("source", "text", (c) => c.notNull())
|
|
106
|
+
.addColumn("release", "text", (c) => c.notNull())
|
|
107
|
+
.addColumn("admin_code", "text")
|
|
108
|
+
.addColumn("certified", "integer")
|
|
109
|
+
.execute()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create the three probe indexes the reader relies on (postcode-scope, locality-scope, route-key).
|
|
114
|
+
*/
|
|
115
|
+
export async function createAddressPointIndexes(db: AddressPointSchemaHandle): Promise<void> {
|
|
116
|
+
await db.schema
|
|
117
|
+
.createIndex("idx_ap_postcode")
|
|
118
|
+
.on("address_point")
|
|
119
|
+
.columns(["postcode", "street_norm", "number"])
|
|
120
|
+
.execute()
|
|
121
|
+
|
|
122
|
+
await db.schema
|
|
123
|
+
.createIndex("idx_ap_locality")
|
|
124
|
+
.on("address_point")
|
|
125
|
+
.columns(["locality_norm", "street_norm", "number"])
|
|
126
|
+
.execute()
|
|
127
|
+
|
|
128
|
+
await db.schema.createIndex("idx_ap_streetkey").on("address_point").columns(["postcode", "street_key"]).execute()
|
|
129
|
+
// Street-first index for the bbox scope: OSM points often have no postcode or locality,
|
|
130
|
+
// so the reader scopes a `(street_norm, number)` probe by the resolved locality's bbox.
|
|
131
|
+
await db.schema.createIndex("idx_ap_street").on("address_point").columns(["street_norm", "number"]).execute()
|
|
132
|
+
}
|
package/lib/address/point.ts
CHANGED
|
@@ -2,23 +2,14 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
-
*
|
|
6
|
-
* SQLite implementation of core's `AddressPointLookup` (#476): exact `(street, number)` within a
|
|
7
|
-
* postcode (preferred), locality, or — for extracts whose points carry no scope tag (OSM, #247) —
|
|
8
|
-
* the resolved locality's BBOX. Query-side normalization is THE shared normalizer
|
|
9
|
-
* (`street-normalize.ts`), selected per the extract's `streetLocale` so build-side and probe-side
|
|
10
|
-
* stay identical by construction (US delegates to the USPS pipeline; FR/DE/NL use the locale rules).
|
|
11
|
-
*
|
|
12
|
-
* Matching is exact-after-normalization only — no fuzzy street matching in this tier (measure how
|
|
13
|
-
* far exact gets first; fuzz is a later, separate decision). Scope order is most-selective first:
|
|
14
|
-
* postcode, then locality, then the bbox fall-through (only when a bbox is supplied AND the prior
|
|
15
|
-
* scopes missed). Multiple hits return the first by rowid — unit siblings share the building coord.
|
|
5
|
+
* @file SQLite implementation of core's `AddressPointLookup`.
|
|
16
6
|
*/
|
|
17
7
|
|
|
18
8
|
import type { AddressPointHit, AddressPointLookup } from "@mailwoman/core/resolver"
|
|
19
9
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
10
|
+
import type { PathBuilderLike } from "path-ts"
|
|
20
11
|
|
|
21
|
-
import type { AddressPointDatabase, AddressPointTable } from "#address/point
|
|
12
|
+
import type { AddressPointDatabase, AddressPointTable } from "#address/point/schema"
|
|
22
13
|
import { hasTable, prepareGet, type PreparedGet } from "#sqlite-utils"
|
|
23
14
|
import {
|
|
24
15
|
normalizeHouseNumberForKey,
|
|
@@ -33,17 +24,28 @@ import {
|
|
|
33
24
|
} from "#street/normalize"
|
|
34
25
|
|
|
35
26
|
/**
|
|
36
|
-
* The columns this lookup
|
|
37
|
-
*
|
|
27
|
+
* The columns this lookup reads, typed from the shared {@link AddressPointTable}
|
|
28
|
+
* so a writer-side rename fails to compile here.
|
|
38
29
|
*/
|
|
39
30
|
type AddressPointRow = Pick<AddressPointTable, "lat" | "lon" | "source" | "release" | "locality_norm" | "postcode">
|
|
40
31
|
|
|
41
32
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
33
|
+
* The column list every prepared SELECT uses.
|
|
34
|
+
* It must match the fields of `AddressPointRow`.
|
|
44
35
|
*/
|
|
45
36
|
const SELECT_COLS = "lat, lon, source, release, locality_norm, postcode"
|
|
46
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Finds an address point by exact normalized `(street, number)` within a postcode,
|
|
40
|
+
* a locality, or a bounding box.
|
|
41
|
+
*
|
|
42
|
+
* Query normalization uses the extract's `streetLocale`, which must match the
|
|
43
|
+
* locale the extract was built with.
|
|
44
|
+
* Street matching is exact after normalization.
|
|
45
|
+
*
|
|
46
|
+
* Scopes are tried from most to least selective: postcode, locality, locality suffix, then bounding box.
|
|
47
|
+
* When several rows match, any one of them can answer, because unit siblings share the building coordinate.
|
|
48
|
+
*/
|
|
47
49
|
export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressPointDatabase>
|
|
48
50
|
implements AddressPointLookup, Disposable
|
|
49
51
|
{
|
|
@@ -56,8 +58,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
56
58
|
| undefined
|
|
57
59
|
readonly #byLocality: PreparedGet<[locality: NameKey, street: StreetKey, number: string], AddressPointRow> | undefined
|
|
58
60
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
+
* This query matches the locality key by its suffix, for a `zh` query that gives the 鄉鎮市區 without its 縣市.
|
|
62
|
+
*
|
|
63
|
+
* The (street, number) index narrows the rows first, so the `LIKE` scans only rows that share the pair.
|
|
61
64
|
*/
|
|
62
65
|
readonly #byLocalityTail:
|
|
63
66
|
| PreparedGet<[street: StreetKey, number: string, tailPattern: string], AddressPointRow>
|
|
@@ -71,22 +74,25 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
71
74
|
|
|
72
75
|
/**
|
|
73
76
|
* @param dbPath Extract path.
|
|
74
|
-
* @param opts.streetLocale The street-normalization locale
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
77
|
+
* @param opts.streetLocale The street-normalization locale the extract was built with.
|
|
78
|
+
* A mismatch makes every key miss.
|
|
79
|
+
* It defaults to `"us"`.
|
|
80
|
+
* @param opts.localityKeys Whether the extract's `locality_norm` holds full place names.
|
|
81
|
+
* With `"abbreviated"`, a row's locality can select a row but never rejects one.
|
|
82
|
+
* The US situs extract uses NAD city fields.
|
|
83
|
+
* Some counties abbreviate them (`addi` for Addison).
|
|
84
|
+
* Other counties replace them with the parent town.
|
|
85
|
+
* It defaults to `"abbreviated"` for `"us"` and `"full"` for every other locale.
|
|
82
86
|
*/
|
|
83
|
-
constructor(
|
|
87
|
+
constructor(
|
|
88
|
+
dbPath: PathBuilderLike,
|
|
89
|
+
opts: { streetLocale?: StreetLocale; localityKeys?: "full" | "abbreviated" } = {}
|
|
90
|
+
) {
|
|
84
91
|
this.#db = new DatabaseClient<DB>(dbPath, { readOnly: true })
|
|
85
92
|
this.#locale = opts.streetLocale ?? "us"
|
|
86
93
|
this.#localityKeys = opts.localityKeys ?? (this.#locale === "us" ? "abbreviated" : "full")
|
|
87
94
|
|
|
88
|
-
//
|
|
89
|
-
// `address_point` table this lookup is a no-op miss, not a crash that loses the whole state (#568).
|
|
95
|
+
// An extract without an `address_point` table, such as an interrupted build, makes every lookup miss.
|
|
90
96
|
if (hasTable(this.#db, "address_point")) {
|
|
91
97
|
this.#byPostcode = prepareGet(
|
|
92
98
|
this.#db,
|
|
@@ -134,10 +140,10 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
134
140
|
|
|
135
141
|
if (!number) return null
|
|
136
142
|
|
|
137
|
-
// A `zh` extract
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
143
|
+
// A `zh` extract keys locality as 縣市 + 鄉鎮市區.
|
|
144
|
+
// The parser tags 縣市 as `region` and 鄉鎮市區 as `subregion`.
|
|
145
|
+
// The Taiwanese register has no postcode, so this pair becomes the locality key.
|
|
146
|
+
// A query with only the 鄉鎮市區 matches the stored key by its suffix.
|
|
141
147
|
const scoped =
|
|
142
148
|
this.#locale === "zh" && !query.locality && query.subregion
|
|
143
149
|
? query.region
|
|
@@ -145,9 +151,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
145
151
|
: { ...query, localityTail: normalizeLocalityForKeyLocale(query.subregion, "zh") }
|
|
146
152
|
: query
|
|
147
153
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
154
|
+
// `streetKeyVariants` yields the literal key first.
|
|
155
|
+
// Each variant runs the full number fallback sequence.
|
|
156
|
+
// The first variant with a match wins.
|
|
151
157
|
let row: AddressPointRow | undefined
|
|
152
158
|
|
|
153
159
|
for (const streetNorm of streetKeyVariants(query.street, streetLocaleForSurface(query.street, this.#locale))) {
|
|
@@ -169,8 +175,8 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
178
|
+
* Tries one normalized street key with the exact number, then fallback number forms.
|
|
179
|
+
* Each fallback runs only after the previous forms missed.
|
|
174
180
|
*/
|
|
175
181
|
#findForKey(
|
|
176
182
|
streetNorm: StreetKey,
|
|
@@ -186,10 +192,7 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
186
192
|
) {
|
|
187
193
|
let row = this.#probe(streetNorm, number, query)
|
|
188
194
|
|
|
189
|
-
//
|
|
190
|
-
// (G-NAF `NUMBER_FIRST`, BAN, OA, OSM `addr:housenumber`), but the attested surface is often
|
|
191
|
-
// a range — "385-387 Esplanade" keys `385`. Null-only: an exact range key that matched above
|
|
192
|
-
// (some OSM points DO carry "385-387" verbatim) is never second-guessed.
|
|
195
|
+
// The registers store one number per point, so a range such as "385-387" falls back to its low end.
|
|
193
196
|
if (!row) {
|
|
194
197
|
const low = /^(\d+[a-z]?)-\d+[a-z]?$/.exec(number)?.[1]
|
|
195
198
|
|
|
@@ -198,12 +201,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
// register attests no 3A but does attest 3 — the adjacent-parcel approximation, priced the
|
|
205
|
-
// same as the range fallback's low end). Null-only throughout, and only for the
|
|
206
|
-
// digits+single-letter shape (never touches "12 1/2" or unit-bearing forms).
|
|
204
|
+
// BAN stores a letter suffix as "3 a", while G-NAF and most OA sources store "3a".
|
|
205
|
+
// For a number of digits plus one letter, the lookup retries the other spacing
|
|
206
|
+
// and then the bare number.
|
|
207
207
|
if (!row) {
|
|
208
208
|
const joined = /^(\d+)\s+([a-z])$/.exec(number)
|
|
209
209
|
const spaced = /^(\d+)([a-z])$/.exec(number)
|
|
@@ -215,11 +215,10 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
// building.
|
|
218
|
+
// The Taiwanese register stores `14之12` and `30附40` as numbers `14` and `30`.
|
|
219
|
+
// It stores the rest in `unit`.
|
|
220
|
+
// A query number that still includes the sub-number falls back to the base number.
|
|
221
|
+
// That result is approximate.
|
|
223
222
|
if (!row && this.#locale === "zh") {
|
|
224
223
|
const base = /^(\d+)(?:[之附]\d+)+$/u.exec(number)?.[1]
|
|
225
224
|
|
|
@@ -232,8 +231,8 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
/**
|
|
235
|
-
*
|
|
236
|
-
* when the
|
|
234
|
+
* Probes one (street, number) key by postcode, then locality, then locality suffix, then bounding box.
|
|
235
|
+
* Each scope runs only when the previous ones missed.
|
|
237
236
|
*/
|
|
238
237
|
#probe(
|
|
239
238
|
streetNorm: StreetKey,
|
|
@@ -251,11 +250,9 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
251
250
|
const postcode = query.postcode.trim()
|
|
252
251
|
const localityKey = query.locality ? this.#localityKey(query.locality) : undefined
|
|
253
252
|
|
|
254
|
-
//
|
|
255
|
-
// so
|
|
256
|
-
//
|
|
257
|
-
// (see `#scopeContradicts` for what "different" tolerates): a query naming a third village under the postcode
|
|
258
|
-
// falls through to the locality rung rather than answering the wrong rooftop.
|
|
253
|
+
// One postcode can cover several places with the same street and number,
|
|
254
|
+
// so a row whose locality agrees with the query is tried first.
|
|
255
|
+
// The postcode-only row answers only if `#scopeContradicts` accepts it.
|
|
259
256
|
const agreeing = localityKey ? this.#byPostcodeLocality!(postcode, localityKey, streetNorm, number) : undefined
|
|
260
257
|
const candidate = agreeing ?? this.#byPostcode!(postcode, streetNorm, number)
|
|
261
258
|
|
|
@@ -263,27 +260,20 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
263
260
|
}
|
|
264
261
|
|
|
265
262
|
if (!row && query.locality) {
|
|
266
|
-
// FR extracts key arrondissement communes at the base city (both-sides fold, see the BAN
|
|
267
|
-
// builder + stripArrondissement) — fold the probe too so "Paris 13e Arrondissement" and
|
|
268
|
-
// "Paris" both hit. No-op for "us" extracts and every non-arrondissement commune.
|
|
269
263
|
row = this.#byLocality!(this.#localityKey(query.locality), streetNorm, number)
|
|
270
264
|
}
|
|
271
265
|
|
|
272
266
|
if (!row && query.localityTail && this.#byLocalityTail) {
|
|
273
|
-
// A Han key
|
|
267
|
+
// A Han key contains no `%` or `_`, so the pattern matches a literal suffix.
|
|
274
268
|
row = this.#byLocalityTail(streetNorm, number, `%${query.localityTail}`)
|
|
275
269
|
}
|
|
276
270
|
|
|
277
|
-
//
|
|
278
|
-
// inside the resolved locality's box. Only reached when the scoped probes missed AND a bbox was supplied.
|
|
271
|
+
// The bounding box serves extracts whose points have no postcode or locality, such as OSM.
|
|
279
272
|
if (!row && query.bbox) {
|
|
280
273
|
const b = query.bbox
|
|
281
274
|
const candidate = this.#byBbox!(streetNorm, number, b.minLat, b.maxLat, b.minLon, b.maxLon)
|
|
282
275
|
|
|
283
|
-
// A
|
|
284
|
-
// happens to share the street and number inside the box: `10 rue de la République, 75008 Paris` reached
|
|
285
|
-
// Servon's `10 rue de la République` (postcode 77170) 26 km away this way, at rooftop tier and 1 m uncertainty.
|
|
286
|
-
// The rung exists for points with no scope of their own; a point whose scope disagrees with the query is a miss.
|
|
276
|
+
// A row with its own scope that the scoped probes missed is a different address in the same box.
|
|
287
277
|
row = candidate && !this.#scopeContradicts(candidate, query) ? candidate : undefined
|
|
288
278
|
}
|
|
289
279
|
|
|
@@ -291,9 +281,10 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
291
281
|
}
|
|
292
282
|
|
|
293
283
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
284
|
+
* Folds the query's locality the way the extract's builder folded `locality_norm`.
|
|
285
|
+
*
|
|
286
|
+
* FR extracts key arrondissements at the base city, so "Paris 13e Arrondissement"
|
|
287
|
+
* and "Paris" produce the same key.
|
|
297
288
|
*/
|
|
298
289
|
#localityKey(locality: string): NameKey {
|
|
299
290
|
if (this.#locale === "fr") return stripArrondissement(normalizeLocalityForKey(locality))
|
|
@@ -302,15 +293,11 @@ export class AddressPointSqliteLookup<DB extends AddressPointDatabase = AddressP
|
|
|
302
293
|
}
|
|
303
294
|
|
|
304
295
|
/**
|
|
305
|
-
*
|
|
306
|
-
* a contradiction — it is the case the bbox rung was built for — and a rung that matched ON a field cannot contradict
|
|
307
|
-
* it, so at the postcode rung only the locality can disagree and at the bbox rung either can.
|
|
296
|
+
* Returns whether the row's own postcode or locality differs from the query's.
|
|
308
297
|
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* rows fell to interpolation 144–198 m away — invisible to a 1 km grade (#2155). `servon` against `paris` and
|
|
313
|
-
* `werlitzsch` against `krensitz`, on the BAN and OSM extracts, are different places and still refuse.
|
|
298
|
+
* A row field that is empty never contradicts.
|
|
299
|
+
* The locality is compared only when the extract's `localityKeys` is `"full"`,
|
|
300
|
+
* because abbreviated keys would reject correct rows.
|
|
314
301
|
*/
|
|
315
302
|
#scopeContradicts(row: AddressPointRow, query: { postcode?: string; locality?: string }): boolean {
|
|
316
303
|
if (query.postcode && row.postcode && row.postcode.trim() !== query.postcode.trim()) return true
|