@mailwoman/resolver-wof-sqlite 11.0.0 → 11.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 +1 -1
- package/lib/address/duplicate.ts +133 -0
- package/lib/address/keys/attribution.ts +119 -0
- package/lib/address/keys/build.ts +768 -0
- package/lib/address/keys/layout.ts +166 -0
- package/lib/address/keys/local-runner.ts +18 -0
- package/lib/address/keys/lookup.ts +205 -0
- package/lib/address/keys/open.ts +33 -0
- package/lib/address/keys/point-lookup.ts +130 -0
- package/lib/address/keys/postcode.ts +27 -0
- package/lib/address/keys/schema.ts +264 -0
- package/lib/address/number-forms.ts +61 -0
- package/lib/address/point/interpolation.ts +28 -23
- package/lib/address/point/schema.ts +2 -5
- package/lib/address/point/spine-schema.ts +4 -4
- package/lib/address/point.ts +12 -79
- package/lib/address/record.ts +155 -0
- package/lib/address/stack.ts +20 -0
- package/lib/address/subdivision-bounds.ts +81 -0
- package/lib/address.ts +2 -5
- package/lib/ancestry/backfill.ts +5 -4
- package/lib/ancestry.ts +10 -10
- package/lib/build/candidate.ts +2 -46
- package/lib/build/population-corrections.ts +4 -4
- package/lib/build/slim.ts +3 -6
- package/lib/candidate/alias-bags.ts +4 -4
- package/lib/candidate/ancestors/schema.ts +63 -43
- package/lib/candidate/ancestors/sidecar.ts +3 -3
- package/lib/candidate/bare-admin-names.ts +3 -3
- package/lib/candidate/country-display-names.ts +2 -5
- package/lib/candidate/extract-fold.ts +2 -5
- package/lib/candidate/fts.ts +5 -5
- package/lib/candidate/importance.ts +29 -24
- package/lib/candidate/lookup.ts +2 -5
- package/lib/candidate/name-roles.ts +4 -4
- package/lib/candidate/own-name.ts +12 -10
- package/lib/candidate/place-attrs.ts +4 -4
- package/lib/candidate/schema.ts +2 -5
- package/lib/candidate/scoring.ts +3 -6
- package/lib/capital-schema.ts +11 -10
- package/lib/capitals.ts +15 -12
- package/lib/convention/schema.ts +7 -7
- package/lib/convention.ts +3 -3
- package/lib/coverage-manifest-schema.ts +4 -4
- package/lib/env.ts +7 -6
- package/lib/exact-match.ts +6 -7
- package/lib/extracts.ts +11 -9
- package/lib/fst/autocomplete.ts +19 -15
- package/lib/fst/binary-matcher.ts +10 -9
- package/lib/fst/builder.ts +32 -11
- package/lib/fst/deserialize-web.ts +9 -8
- package/lib/fst/format.ts +9 -7
- package/lib/fst/freshness.ts +3 -4
- package/lib/fst/matcher.ts +6 -6
- package/lib/fst/serialize.ts +5 -9
- package/lib/fst/types.ts +4 -4
- package/lib/fst.ts +2 -5
- package/lib/fts/query.ts +3 -4
- package/lib/fts.ts +2 -5
- package/lib/geonames/aliases.ts +193 -43
- package/lib/geonames/postal.ts +17 -15
- package/lib/geonames.ts +2 -5
- package/lib/index.ts +4 -5
- package/lib/interpolation.ts +7 -6
- package/lib/landmark/rows.ts +5 -5
- package/lib/landmark.ts +6 -6
- package/lib/lookup.ts +8 -8
- package/lib/name-score.ts +7 -6
- package/lib/nsul/lookup.ts +19 -15
- package/lib/nsul/schema.ts +4 -5
- package/lib/nsul.ts +2 -5
- package/lib/paths.ts +7 -7
- package/lib/place-importance-schema.ts +5 -8
- package/lib/poi/lookup.ts +11 -10
- package/lib/poi/schema.ts +16 -10
- package/lib/poi.ts +2 -5
- package/lib/polygon-schema.ts +10 -9
- package/lib/postal/city/alias/lookup.ts +14 -13
- package/lib/postal/city/alias/schema.ts +12 -12
- package/lib/postal/city/candidate-schema.ts +15 -12
- package/lib/postal.ts +2 -5
- package/lib/postcode-point-lookup.ts +2 -5
- package/lib/primary-preference.ts +2 -5
- package/lib/proximity-rerank.ts +23 -19
- package/lib/ranking-weights.ts +8 -7
- package/lib/region-keys.ts +15 -14
- package/lib/reverse.ts +16 -17
- package/lib/schema.ts +20 -6
- package/lib/search-fetch.ts +15 -11
- package/lib/sqlite/convention-source.ts +2 -5
- package/lib/sqlite/utils.ts +4 -4
- package/lib/street/centroid/schema.ts +6 -6
- package/lib/street/centroid.ts +3 -3
- package/lib/street/locale.ts +64 -0
- package/lib/street/morphology/fst/builder.ts +21 -19
- package/lib/street/morphology/fst/loader.ts +15 -13
- package/lib/street/name-lookup.ts +15 -14
- package/lib/street/normalize.ts +20 -17
- package/lib/street/segment-schema.ts +16 -14
- package/lib/street/table.ts +276 -0
- package/lib/street.ts +3 -5
- package/lib/types.ts +3 -6
- package/lib/unified-schema.ts +31 -9
- package/lib/uprn/existence.ts +10 -10
- package/lib/uprn/lookup.ts +7 -8
- package/lib/uprn/schema.ts +5 -5
- package/lib/uprn.ts +2 -5
- package/lib/weights-overlay-linker.ts +5 -8
- package/out/address/duplicate.d.ts +74 -0
- package/out/address/duplicate.d.ts.map +1 -0
- package/out/address/duplicate.js +79 -0
- package/out/address/duplicate.js.map +1 -0
- package/out/address/keys/attribution.d.ts +56 -0
- package/out/address/keys/attribution.d.ts.map +1 -0
- package/out/address/keys/attribution.js +64 -0
- package/out/address/keys/attribution.js.map +1 -0
- package/out/address/keys/build.d.ts +154 -0
- package/out/address/keys/build.d.ts.map +1 -0
- package/out/address/keys/build.js +423 -0
- package/out/address/keys/build.js.map +1 -0
- package/out/address/keys/layout.d.ts +92 -0
- package/out/address/keys/layout.d.ts.map +1 -0
- package/out/address/keys/layout.js +87 -0
- package/out/address/keys/layout.js.map +1 -0
- package/out/address/keys/local-runner.d.ts +9 -0
- package/out/address/keys/local-runner.d.ts.map +1 -0
- package/out/address/keys/local-runner.js +13 -0
- package/out/address/keys/local-runner.js.map +1 -0
- package/out/address/keys/lookup.d.ts +80 -0
- package/out/address/keys/lookup.d.ts.map +1 -0
- package/out/address/keys/lookup.js +104 -0
- package/out/address/keys/lookup.js.map +1 -0
- package/out/address/keys/open.d.ts +10 -0
- package/out/address/keys/open.d.ts.map +1 -0
- package/out/address/keys/open.js +28 -0
- package/out/address/keys/open.js.map +1 -0
- package/out/address/keys/point-lookup.d.ts +25 -0
- package/out/address/keys/point-lookup.d.ts.map +1 -0
- package/out/address/keys/point-lookup.js +100 -0
- package/out/address/keys/point-lookup.js.map +1 -0
- package/out/address/keys/postcode.d.ts +9 -0
- package/out/address/keys/postcode.d.ts.map +1 -0
- package/out/address/keys/postcode.js +25 -0
- package/out/address/keys/postcode.js.map +1 -0
- package/out/address/keys/schema.d.ts +156 -0
- package/out/address/keys/schema.d.ts.map +1 -0
- package/out/address/keys/schema.js +119 -0
- package/out/address/keys/schema.js.map +1 -0
- package/out/address/number-forms.d.ts +19 -0
- package/out/address/number-forms.d.ts.map +1 -0
- package/out/address/number-forms.js +50 -0
- package/out/address/number-forms.js.map +1 -0
- package/out/address/point/interpolation.d.ts +24 -22
- package/out/address/point/interpolation.d.ts.map +1 -1
- package/out/address/point/interpolation.js +3 -31
- package/out/address/point/interpolation.js.map +1 -1
- package/out/address/point/schema.d.ts +0 -5
- package/out/address/point/schema.d.ts.map +1 -1
- package/out/address/point/schema.js +2 -5
- package/out/address/point/schema.js.map +1 -1
- package/out/address/point/spine-schema.d.ts +1 -4
- package/out/address/point/spine-schema.d.ts.map +1 -1
- package/out/address/point/spine-schema.js +3 -4
- package/out/address/point/spine-schema.js.map +1 -1
- package/out/address/point.d.ts +0 -3
- package/out/address/point.d.ts.map +1 -1
- package/out/address/point.js +12 -71
- package/out/address/point.js.map +1 -1
- package/out/address/record.d.ts +125 -0
- package/out/address/record.d.ts.map +1 -0
- package/out/address/record.js +49 -0
- package/out/address/record.js.map +1 -0
- package/out/address/stack.d.ts +14 -0
- package/out/address/stack.d.ts.map +1 -0
- package/out/address/stack.js +19 -0
- package/out/address/stack.js.map +1 -0
- package/out/address/subdivision-bounds.d.ts +32 -0
- package/out/address/subdivision-bounds.d.ts.map +1 -0
- package/out/address/subdivision-bounds.js +48 -0
- package/out/address/subdivision-bounds.js.map +1 -0
- package/out/address.d.ts +0 -5
- package/out/address.d.ts.map +1 -1
- package/out/address.js +2 -5
- package/out/address.js.map +1 -1
- package/out/ancestry/backfill.d.ts +0 -7
- package/out/ancestry/backfill.d.ts.map +1 -1
- package/out/ancestry/backfill.js +4 -4
- package/out/ancestry/backfill.js.map +1 -1
- package/out/ancestry.d.ts +0 -14
- package/out/ancestry.d.ts.map +1 -1
- package/out/ancestry.js +9 -10
- package/out/ancestry.js.map +1 -1
- package/out/build/candidate.d.ts +0 -22
- package/out/build/candidate.d.ts.map +1 -1
- package/out/build/candidate.js +2 -26
- package/out/build/candidate.js.map +1 -1
- package/out/build/population-corrections.d.ts +1 -4
- package/out/build/population-corrections.d.ts.map +1 -1
- package/out/build/population-corrections.js +2 -19
- package/out/build/population-corrections.js.map +1 -1
- package/out/build/slim.d.ts +0 -5
- package/out/build/slim.d.ts.map +1 -1
- package/out/build/slim.js +3 -6
- package/out/build/slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +1 -4
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +3 -7
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +60 -43
- package/out/candidate/ancestors/schema.d.ts.map +1 -1
- package/out/candidate/ancestors/schema.js +62 -43
- package/out/candidate/ancestors/schema.js.map +1 -1
- package/out/candidate/ancestors/sidecar.d.ts +0 -3
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
- package/out/candidate/ancestors/sidecar.js +2 -6
- package/out/candidate/ancestors/sidecar.js.map +1 -1
- package/out/candidate/bare-admin-names.d.ts +0 -6
- package/out/candidate/bare-admin-names.d.ts.map +1 -1
- package/out/candidate/bare-admin-names.js +2 -3
- package/out/candidate/bare-admin-names.js.map +1 -1
- package/out/candidate/country-display-names.d.ts +0 -5
- package/out/candidate/country-display-names.d.ts.map +1 -1
- package/out/candidate/country-display-names.js +2 -5
- package/out/candidate/country-display-names.js.map +1 -1
- package/out/candidate/extract-fold.d.ts +0 -5
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +2 -5
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +2 -5
- package/out/candidate/fts.d.ts.map +1 -1
- package/out/candidate/fts.js +2 -7
- package/out/candidate/fts.js.map +1 -1
- package/out/candidate/importance.d.ts +3 -33
- package/out/candidate/importance.d.ts.map +1 -1
- package/out/candidate/importance.js +28 -24
- package/out/candidate/importance.js.map +1 -1
- package/out/candidate/lookup.d.ts +0 -5
- package/out/candidate/lookup.d.ts.map +1 -1
- package/out/candidate/lookup.js +2 -5
- package/out/candidate/lookup.js.map +1 -1
- package/out/candidate/name-roles.d.ts +0 -6
- package/out/candidate/name-roles.d.ts.map +1 -1
- package/out/candidate/name-roles.js +3 -4
- package/out/candidate/name-roles.js.map +1 -1
- package/out/candidate/own-name.d.ts +0 -18
- package/out/candidate/own-name.d.ts.map +1 -1
- package/out/candidate/own-name.js +11 -10
- package/out/candidate/own-name.js.map +1 -1
- package/out/candidate/place-attrs.d.ts +1 -4
- package/out/candidate/place-attrs.d.ts.map +1 -1
- package/out/candidate/place-attrs.js +2 -12
- package/out/candidate/place-attrs.js.map +1 -1
- package/out/candidate/schema.d.ts +0 -5
- package/out/candidate/schema.d.ts.map +1 -1
- package/out/candidate/schema.js +2 -5
- package/out/candidate/schema.js.map +1 -1
- package/out/candidate/scoring.d.ts +0 -5
- package/out/candidate/scoring.d.ts.map +1 -1
- package/out/candidate/scoring.js +3 -6
- package/out/candidate/scoring.js.map +1 -1
- package/out/capital-schema.d.ts +0 -14
- package/out/capital-schema.d.ts.map +1 -1
- package/out/capital-schema.js +10 -10
- package/out/capital-schema.js.map +1 -1
- package/out/capitals.d.ts +0 -16
- package/out/capitals.d.ts.map +1 -1
- package/out/capitals.js +14 -12
- package/out/capitals.js.map +1 -1
- package/out/convention/schema.d.ts +4 -7
- package/out/convention/schema.d.ts.map +1 -1
- package/out/convention/schema.js +2 -10
- package/out/convention/schema.js.map +1 -1
- package/out/convention.d.ts +0 -3
- package/out/convention.d.ts.map +1 -1
- package/out/convention.js +2 -8
- package/out/convention.js.map +1 -1
- package/out/coverage-manifest-schema.d.ts +1 -4
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +2 -3
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/env.d.ts +0 -8
- package/out/env.d.ts.map +1 -1
- package/out/env.js +6 -6
- package/out/env.js.map +1 -1
- package/out/exact-match.d.ts +2 -6
- package/out/exact-match.d.ts.map +1 -1
- package/out/exact-match.js +3 -9
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +8 -9
- package/out/extracts.d.ts.map +1 -1
- package/out/extracts.js +9 -8
- package/out/extracts.js.map +1 -1
- package/out/fst/autocomplete.d.ts +1 -19
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +2 -18
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/binary-matcher.d.ts +6 -8
- package/out/fst/binary-matcher.d.ts.map +1 -1
- package/out/fst/binary-matcher.js +3 -13
- package/out/fst/binary-matcher.js.map +1 -1
- package/out/fst/builder.d.ts +0 -12
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +23 -11
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +1 -12
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +8 -8
- package/out/fst/deserialize-web.js.map +1 -1
- package/out/fst/format.d.ts +6 -7
- package/out/fst/format.d.ts.map +1 -1
- package/out/fst/format.js +3 -10
- package/out/fst/format.js.map +1 -1
- package/out/fst/freshness.d.ts +0 -7
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +2 -4
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +3 -6
- package/out/fst/matcher.d.ts.map +1 -1
- package/out/fst/matcher.js +2 -9
- package/out/fst/matcher.js.map +1 -1
- package/out/fst/serialize.d.ts +0 -23
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +4 -8
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +1 -4
- package/out/fst/types.d.ts.map +1 -1
- package/out/fst/types.js +2 -6
- package/out/fst/types.js.map +1 -1
- package/out/fst.d.ts +0 -5
- package/out/fst.d.ts.map +1 -1
- package/out/fst.js +2 -5
- package/out/fst.js.map +1 -1
- package/out/fts/query.d.ts +0 -4
- package/out/fts/query.d.ts.map +1 -1
- package/out/fts/query.js +2 -7
- package/out/fts/query.js.map +1 -1
- package/out/fts.d.ts +0 -5
- package/out/fts.d.ts.map +1 -1
- package/out/fts.js +2 -5
- package/out/fts.js.map +1 -1
- package/out/geonames/aliases.d.ts +43 -5
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +109 -41
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +1 -25
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +15 -14
- package/out/geonames/postal.js.map +1 -1
- package/out/geonames.d.ts +0 -5
- package/out/geonames.d.ts.map +1 -1
- package/out/geonames.js +2 -5
- package/out/geonames.js.map +1 -1
- package/out/index.d.ts +2 -5
- package/out/index.d.ts.map +1 -1
- package/out/index.js +4 -5
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +0 -7
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +6 -6
- package/out/interpolation.js.map +1 -1
- package/out/landmark/rows.d.ts +2 -5
- package/out/landmark/rows.d.ts.map +1 -1
- package/out/landmark/rows.js +2 -8
- package/out/landmark/rows.js.map +1 -1
- package/out/landmark.d.ts +3 -6
- package/out/landmark.d.ts.map +1 -1
- package/out/landmark.js +2 -8
- package/out/landmark.js.map +1 -1
- package/out/lookup.d.ts +0 -7
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +7 -8
- package/out/lookup.js.map +1 -1
- package/out/name-score.d.ts +4 -6
- package/out/name-score.d.ts.map +1 -1
- package/out/name-score.js +6 -6
- package/out/name-score.js.map +1 -1
- package/out/nsul/lookup.d.ts +16 -15
- package/out/nsul/lookup.d.ts.map +1 -1
- package/out/nsul/lookup.js +2 -25
- package/out/nsul/lookup.js.map +1 -1
- package/out/nsul/schema.d.ts +1 -5
- package/out/nsul/schema.d.ts.map +1 -1
- package/out/nsul/schema.js +3 -8
- package/out/nsul/schema.js.map +1 -1
- package/out/nsul.d.ts +0 -5
- package/out/nsul.d.ts.map +1 -1
- package/out/nsul.js +2 -5
- package/out/nsul.js.map +1 -1
- package/out/paths.d.ts +1 -13
- package/out/paths.d.ts.map +1 -1
- package/out/paths.js +6 -7
- package/out/paths.js.map +1 -1
- package/out/place-importance-schema.d.ts +3 -8
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +3 -6
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +0 -13
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +10 -10
- package/out/poi/lookup.js.map +1 -1
- package/out/poi/schema.d.ts +13 -10
- package/out/poi/schema.d.ts.map +1 -1
- package/out/poi/schema.js +2 -13
- package/out/poi/schema.js.map +1 -1
- package/out/poi.d.ts +0 -5
- package/out/poi.d.ts.map +1 -1
- package/out/poi.js +2 -5
- package/out/poi.js.map +1 -1
- package/out/polygon-schema.d.ts +7 -9
- package/out/polygon-schema.d.ts.map +1 -1
- package/out/polygon-schema.js +2 -12
- package/out/polygon-schema.js.map +1 -1
- package/out/postal/city/alias/lookup.d.ts +11 -13
- package/out/postal/city/alias/lookup.d.ts.map +1 -1
- package/out/postal/city/alias/lookup.js +13 -13
- package/out/postal/city/alias/lookup.js.map +1 -1
- package/out/postal/city/alias/schema.d.ts +9 -12
- package/out/postal/city/alias/schema.d.ts.map +1 -1
- package/out/postal/city/alias/schema.js +2 -17
- package/out/postal/city/alias/schema.js.map +1 -1
- package/out/postal/city/candidate-schema.d.ts +12 -12
- package/out/postal/city/candidate-schema.d.ts.map +1 -1
- package/out/postal/city/candidate-schema.js +14 -12
- package/out/postal/city/candidate-schema.js.map +1 -1
- package/out/postal.d.ts +0 -5
- package/out/postal.d.ts.map +1 -1
- package/out/postal.js +2 -5
- package/out/postal.js.map +1 -1
- package/out/postcode-point-lookup.d.ts +0 -5
- package/out/postcode-point-lookup.d.ts.map +1 -1
- package/out/postcode-point-lookup.js +2 -5
- package/out/postcode-point-lookup.js.map +1 -1
- package/out/primary-preference.d.ts +0 -5
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +2 -5
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +2 -26
- package/out/proximity-rerank.d.ts.map +1 -1
- package/out/proximity-rerank.js +22 -19
- package/out/proximity-rerank.js.map +1 -1
- package/out/ranking-weights.d.ts +5 -7
- package/out/ranking-weights.d.ts.map +1 -1
- package/out/ranking-weights.js +2 -9
- package/out/ranking-weights.js.map +1 -1
- package/out/region-keys.d.ts +0 -15
- package/out/region-keys.d.ts.map +1 -1
- package/out/region-keys.js +14 -14
- package/out/region-keys.js.map +1 -1
- package/out/reverse.d.ts +1 -24
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +14 -16
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +17 -6
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +2 -15
- package/out/schema.js.map +1 -1
- package/out/search-fetch.d.ts +0 -13
- package/out/search-fetch.d.ts.map +1 -1
- package/out/search-fetch.js +14 -11
- package/out/search-fetch.js.map +1 -1
- package/out/sqlite/convention-source.d.ts +0 -5
- package/out/sqlite/convention-source.d.ts.map +1 -1
- package/out/sqlite/convention-source.js +2 -5
- package/out/sqlite/convention-source.js.map +1 -1
- package/out/sqlite/utils.d.ts +1 -7
- package/out/sqlite/utils.d.ts.map +1 -1
- package/out/sqlite/utils.js +3 -4
- package/out/sqlite/utils.js.map +1 -1
- package/out/street/centroid/schema.d.ts +3 -6
- package/out/street/centroid/schema.d.ts.map +1 -1
- package/out/street/centroid/schema.js +2 -7
- package/out/street/centroid/schema.js.map +1 -1
- package/out/street/centroid.d.ts +0 -3
- package/out/street/centroid.d.ts.map +1 -1
- package/out/street/centroid.js +2 -6
- package/out/street/centroid.js.map +1 -1
- package/out/street/locale.d.ts +12 -0
- package/out/street/locale.d.ts.map +1 -0
- package/out/street/locale.js +54 -0
- package/out/street/locale.js.map +1 -0
- package/out/street/morphology/fst/builder.d.ts +1 -26
- package/out/street/morphology/fst/builder.d.ts.map +1 -1
- package/out/street/morphology/fst/builder.js +19 -18
- package/out/street/morphology/fst/builder.js.map +1 -1
- package/out/street/morphology/fst/loader.d.ts +0 -20
- package/out/street/morphology/fst/loader.d.ts.map +1 -1
- package/out/street/morphology/fst/loader.js +14 -13
- package/out/street/morphology/fst/loader.js.map +1 -1
- package/out/street/name-lookup.d.ts +11 -13
- package/out/street/name-lookup.d.ts.map +1 -1
- package/out/street/name-lookup.js +14 -14
- package/out/street/name-lookup.js.map +1 -1
- package/out/street/normalize.d.ts +10 -12
- package/out/street/normalize.d.ts.map +1 -1
- package/out/street/normalize.js +15 -13
- package/out/street/normalize.js.map +1 -1
- package/out/street/segment-schema.d.ts +11 -12
- package/out/street/segment-schema.d.ts.map +1 -1
- package/out/street/segment-schema.js +13 -12
- package/out/street/segment-schema.js.map +1 -1
- package/out/street/table.d.ts +154 -0
- package/out/street/table.d.ts.map +1 -0
- package/out/street/table.js +122 -0
- package/out/street/table.js.map +1 -0
- package/out/street.d.ts +1 -5
- package/out/street.d.ts.map +1 -1
- package/out/street.js +3 -5
- package/out/street.js.map +1 -1
- package/out/types.d.ts +1 -6
- package/out/types.d.ts.map +1 -1
- package/out/types.js +2 -5
- package/out/types.js.map +1 -1
- package/out/unified-schema.d.ts +18 -7
- package/out/unified-schema.d.ts.map +1 -1
- package/out/unified-schema.js +14 -10
- package/out/unified-schema.js.map +1 -1
- package/out/uprn/existence.d.ts +7 -10
- package/out/uprn/existence.d.ts.map +1 -1
- package/out/uprn/existence.js +9 -10
- package/out/uprn/existence.js.map +1 -1
- package/out/uprn/lookup.d.ts +1 -8
- package/out/uprn/lookup.d.ts.map +1 -1
- package/out/uprn/lookup.js +6 -8
- package/out/uprn/lookup.js.map +1 -1
- package/out/uprn/schema.d.ts +2 -5
- package/out/uprn/schema.d.ts.map +1 -1
- package/out/uprn/schema.js +2 -7
- package/out/uprn/schema.js.map +1 -1
- package/out/uprn.d.ts +0 -5
- package/out/uprn.d.ts.map +1 -1
- package/out/uprn.js +2 -5
- package/out/uprn.js.map +1 -1
- package/out/weights-overlay-linker.d.ts +3 -8
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +5 -8
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +12 -12
- package/lib/currency-backfill.ts +0 -289
- package/out/currency-backfill.d.ts +0 -68
- package/out/currency-backfill.d.ts.map +0 -1
- package/out/currency-backfill.js +0 -169
- package/out/currency-backfill.js.map +0 -1
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Teffen Ellis and Mailwoman contributors
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds one region's address layer, `lookup.db` and `detail.db`, from a stream of address records.
|
|
6
|
+
*
|
|
7
|
+
* Records are staged once into a scratch database with one row per key they answer.
|
|
8
|
+
* Each key table is then written in key order with `INSERT … SELECT … ORDER BY`,
|
|
9
|
+
* so its B-tree pages hold contiguous keys.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ByteFormatter } from "@mailwoman/core/fs/formatters"
|
|
13
|
+
import { readFilesystemSpace, statPath } from "@mailwoman/core/fs/readers/stat"
|
|
14
|
+
import { makeDirectories, removePathIfPresent } from "@mailwoman/core/fs/writers"
|
|
15
|
+
import { parseJSONStrict, stringifyJSON } from "@mailwoman/core/json"
|
|
16
|
+
import { sourcePresentCoverageCells } from "@mailwoman/core/layers"
|
|
17
|
+
import { type LayerManifest, writeLayerCoverage, writeLayerManifest } from "@mailwoman/core/layers/manifest"
|
|
18
|
+
import { createLayerCoverageTable, createLayerManifestTable } from "@mailwoman/core/layers/schema"
|
|
19
|
+
import { shortCellToInt } from "@mailwoman/spatial"
|
|
20
|
+
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
21
|
+
import { buildSealedArtifact } from "@mailwoman/sqlite/sealed/build"
|
|
22
|
+
import { latLngToCell } from "@sister.software/h3"
|
|
23
|
+
import { type Kysely, sql } from "kysely"
|
|
24
|
+
import { basename, dirname, resolvePath, type PathBuilderLike } from "path-ts"
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
ADDRESS_DUPLICATE_STACK_LIMIT,
|
|
28
|
+
type AddressDuplicateRule,
|
|
29
|
+
type DuplicateCandidate,
|
|
30
|
+
isDuplicateAddress,
|
|
31
|
+
isNearbyDuplicateAddress,
|
|
32
|
+
} from "#address/duplicate"
|
|
33
|
+
import { addressPostcodeKey } from "#address/keys/postcode"
|
|
34
|
+
import {
|
|
35
|
+
ADDRESS_SCOPED_KEY_TABLES,
|
|
36
|
+
ADDRESS_STREET_TABLES,
|
|
37
|
+
type AddressDetailDatabase,
|
|
38
|
+
type AddressLookupDatabase,
|
|
39
|
+
createAddressDetailTable,
|
|
40
|
+
createAddressPlaceKeyTable,
|
|
41
|
+
createAddressRegionKeyTable,
|
|
42
|
+
createAddressScopedKeyTable,
|
|
43
|
+
createAddressSourceTable,
|
|
44
|
+
createAddressStreetTable,
|
|
45
|
+
} from "#address/keys/schema"
|
|
46
|
+
import type { AddressRecord } from "#address/record"
|
|
47
|
+
import { addressPointStacksSQL } from "#address/stack"
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The SQLite page size of both files.
|
|
51
|
+
*
|
|
52
|
+
* At 8 KiB a key hit read 4 pages in 3 range chunks on the Wisconsin prototype.
|
|
53
|
+
*/
|
|
54
|
+
export const ADDRESS_LAYER_PAGE_SIZE = 8192
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The H3 resolution `layer_coverage` counts points at, about 36 km² per cell.
|
|
58
|
+
*/
|
|
59
|
+
export const ADDRESS_LAYER_COVERAGE_RESOLUTION = 6
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The provenance a caller states for the layer it builds.
|
|
63
|
+
*
|
|
64
|
+
* The build supplies the schema version, the spine keys and the per-source record counts.
|
|
65
|
+
*/
|
|
66
|
+
export type AddressLayerManifest = Omit<LayerManifest, "schemaVersion" | "spineKeys" | "sourceRecords">
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The level each staged key row answers.
|
|
70
|
+
* Each level is written to the key table of the same name.
|
|
71
|
+
*/
|
|
72
|
+
const StagedLevel = {
|
|
73
|
+
Postcode: "key_postcode",
|
|
74
|
+
Locality: "key_locality",
|
|
75
|
+
Subregion: "key_subregion",
|
|
76
|
+
Region: "key_region",
|
|
77
|
+
} as const
|
|
78
|
+
|
|
79
|
+
type StagedLevel = (typeof StagedLevel)[keyof typeof StagedLevel]
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The scratch schema: every key row an address answers, and every address's detail row.
|
|
83
|
+
*/
|
|
84
|
+
interface StagingDatabase extends AddressDetailDatabase {
|
|
85
|
+
stage: {
|
|
86
|
+
level: StagedLevel
|
|
87
|
+
scope: string
|
|
88
|
+
street: string
|
|
89
|
+
number: string
|
|
90
|
+
address_id: number
|
|
91
|
+
lat: number
|
|
92
|
+
lon: number
|
|
93
|
+
source_id: number
|
|
94
|
+
}
|
|
95
|
+
source_ids: {
|
|
96
|
+
name: string
|
|
97
|
+
source_id: number
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One keyed read a build must answer: a street and number under one level's scope.
|
|
103
|
+
*
|
|
104
|
+
* `scope` is ignored for `key_region`, which has none.
|
|
105
|
+
*/
|
|
106
|
+
export interface AddressLayerReadCheck {
|
|
107
|
+
table: StagedLevel
|
|
108
|
+
scope: string
|
|
109
|
+
street: string
|
|
110
|
+
number: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* What a source's rows are published under, from the register.
|
|
115
|
+
*/
|
|
116
|
+
export interface AddressSourceTerms {
|
|
117
|
+
license: string | null
|
|
118
|
+
authority: string | null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface BuildAddressLayerOptions {
|
|
122
|
+
records: AsyncIterable<AddressRecord>
|
|
123
|
+
/**
|
|
124
|
+
* The terms of each source id the records hold.
|
|
125
|
+
* A record whose source has no entry stops the build.
|
|
126
|
+
*/
|
|
127
|
+
sources: ReadonlyMap<string, AddressSourceTerms>
|
|
128
|
+
/**
|
|
129
|
+
* The `layer_manifest` row `lookup.db` holds.
|
|
130
|
+
*/
|
|
131
|
+
manifest: AddressLayerManifest
|
|
132
|
+
/**
|
|
133
|
+
* Keyed reads the finished `lookup.db` must answer before it is sealed,
|
|
134
|
+
* such as a known address at each level.
|
|
135
|
+
*/
|
|
136
|
+
checks?: readonly AddressLayerReadCheck[]
|
|
137
|
+
lookupPath: PathBuilderLike
|
|
138
|
+
detailPath: PathBuilderLike
|
|
139
|
+
/**
|
|
140
|
+
* Where the staging database is written, the largest file a build makes.
|
|
141
|
+
*
|
|
142
|
+
* It defaults to beside `lookupPath`.
|
|
143
|
+
* A directory on another disk keeps that file off the layer's disk.
|
|
144
|
+
*/
|
|
145
|
+
stagingDirectory?: PathBuilderLike
|
|
146
|
+
/**
|
|
147
|
+
* Helper threads SQLite may use for each large sort: the staged key groups
|
|
148
|
+
* and every key table's `INSERT … SELECT … ORDER BY`.
|
|
149
|
+
*
|
|
150
|
+
* SQLite starts each connection with none and caps the count at its compile-time
|
|
151
|
+
* `SQLITE_MAX_WORKER_THREADS`, 8 in Node's build.
|
|
152
|
+
* The sorted output is the same with any count.
|
|
153
|
+
*
|
|
154
|
+
* @defaultValue 0
|
|
155
|
+
*/
|
|
156
|
+
sortThreads?: number
|
|
157
|
+
/**
|
|
158
|
+
* Called after every millionth record with the count staged so far.
|
|
159
|
+
*/
|
|
160
|
+
onProgress?: (records: number) => void
|
|
161
|
+
/**
|
|
162
|
+
* Returns why a record is left out of the layer, such as `outside-subdivision`, or `null` to keep it.
|
|
163
|
+
*
|
|
164
|
+
* Each refusal is counted in the summary under its source and reason.
|
|
165
|
+
*/
|
|
166
|
+
refuseRecord?: (record: AddressRecord) => string | null
|
|
167
|
+
/**
|
|
168
|
+
* The most distinct street-and-number pairs one source may place at one point, rounded to 1e-5 degrees.
|
|
169
|
+
*
|
|
170
|
+
* The records at a point over this limit are left out under the reason `stacked-point`.
|
|
171
|
+
*
|
|
172
|
+
* @defaultValue {@linkcode ADDRESS_LAYER_STACK_LIMIT}
|
|
173
|
+
*/
|
|
174
|
+
stackLimit?: number
|
|
175
|
+
/**
|
|
176
|
+
* Drops each record that repeats a higher-ranked source's record of the same postcode key,
|
|
177
|
+
* by {@link isDuplicateAddress}, under the reason `duplicate`.
|
|
178
|
+
* Without it every source's record is kept.
|
|
179
|
+
*/
|
|
180
|
+
duplicates?: AddressDuplicateRule
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The most distinct addresses one source may place at one point before the layer refuses them all.
|
|
185
|
+
*
|
|
186
|
+
* Across the 2026-11-08 US layer, point counts fall bin by bin from 2 to 1,000 addresses per point,
|
|
187
|
+
* and four points lie above 1,000: two hold 1,004 and 1,653 addresses, and two hold 10,525 and 12,868.
|
|
188
|
+
*/
|
|
189
|
+
export const ADDRESS_LAYER_STACK_LIMIT = 1000
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The counts one build wrote.
|
|
193
|
+
* Its checks compare them against the tables.
|
|
194
|
+
*/
|
|
195
|
+
export interface AddressLayerBuildSummary {
|
|
196
|
+
records: number
|
|
197
|
+
/**
|
|
198
|
+
* Key rows written per key table.
|
|
199
|
+
*/
|
|
200
|
+
keys: Record<StagedLevel, number>
|
|
201
|
+
/**
|
|
202
|
+
* Records per source id.
|
|
203
|
+
*/
|
|
204
|
+
sources: Record<string, number>
|
|
205
|
+
/**
|
|
206
|
+
* Locality names per record, the duplication the per-form keys cost.
|
|
207
|
+
*/
|
|
208
|
+
localityFormsPerRecord: number
|
|
209
|
+
/**
|
|
210
|
+
* Points per H3 cell at {@link ADDRESS_LAYER_COVERAGE_RESOLUTION}, keyed by the cell's short integer form.
|
|
211
|
+
*/
|
|
212
|
+
coverage: ReadonlyMap<number, number>
|
|
213
|
+
/**
|
|
214
|
+
* Records left out of the layer, per source id and reason.
|
|
215
|
+
*/
|
|
216
|
+
refused: Record<string, Record<string, number>>
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function createStagingTables(db: Kysely<StagingDatabase>): Promise<void> {
|
|
220
|
+
await db.schema
|
|
221
|
+
.createTable("stage")
|
|
222
|
+
.addColumn("level", "text", (c) => c.notNull())
|
|
223
|
+
.addColumn("scope", "text", (c) => c.notNull())
|
|
224
|
+
.addColumn("street", "text", (c) => c.notNull())
|
|
225
|
+
.addColumn("number", "text", (c) => c.notNull())
|
|
226
|
+
.addColumn("address_id", "integer", (c) => c.notNull())
|
|
227
|
+
.addColumn("lat", "real", (c) => c.notNull())
|
|
228
|
+
.addColumn("lon", "real", (c) => c.notNull())
|
|
229
|
+
.addColumn("source_id", "integer", (c) => c.notNull())
|
|
230
|
+
.execute()
|
|
231
|
+
|
|
232
|
+
await db.schema
|
|
233
|
+
.createTable("source_ids")
|
|
234
|
+
.addColumn("name", "text", (c) => c.notNull())
|
|
235
|
+
.addColumn("source_id", "integer", (c) => c.notNull())
|
|
236
|
+
.execute()
|
|
237
|
+
|
|
238
|
+
await createAddressDetailTable(db)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Stages every record, then writes `lookup.db` and `detail.db` beside their targets
|
|
243
|
+
* and swaps each into place.
|
|
244
|
+
*
|
|
245
|
+
* @throws When a record's source has no entry in `sources`, or when a key table's
|
|
246
|
+
* row count differs from the rows staged for its level.
|
|
247
|
+
*/
|
|
248
|
+
export async function buildAddressLayer(options: BuildAddressLayerOptions): Promise<AddressLayerBuildSummary> {
|
|
249
|
+
const stagingName = `${basename(options.lookupPath.toString())}.stage-${process.pid}`
|
|
250
|
+
|
|
251
|
+
const stagingPath = options.stagingDirectory
|
|
252
|
+
? resolvePath(options.stagingDirectory, stagingName)
|
|
253
|
+
: `${options.lookupPath}.stage-${process.pid}`
|
|
254
|
+
|
|
255
|
+
if (options.stagingDirectory) {
|
|
256
|
+
await makeDirectories(options.stagingDirectory)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await Promise.all([makeDirectories(dirname(options.lookupPath)), makeDirectories(dirname(options.detailPath))])
|
|
260
|
+
await removePathIfPresent(stagingPath)
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const summary = await stageRecords(options, stagingPath)
|
|
264
|
+
const threads = sortThreadsPragma(options.sortThreads)
|
|
265
|
+
|
|
266
|
+
await assertOutputSpace(options.lookupPath, stagingPath)
|
|
267
|
+
|
|
268
|
+
await writeLookup(
|
|
269
|
+
options.lookupPath,
|
|
270
|
+
stagingPath,
|
|
271
|
+
summary,
|
|
272
|
+
options.sources,
|
|
273
|
+
options.manifest,
|
|
274
|
+
options.checks ?? [],
|
|
275
|
+
threads
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
await writeDetail(options.detailPath, stagingPath, summary, threads)
|
|
279
|
+
|
|
280
|
+
return summary
|
|
281
|
+
} finally {
|
|
282
|
+
await removePathIfPresent(stagingPath)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* The `PRAGMA threads` statement for `count` sorter helper threads, a whole number from 0.
|
|
288
|
+
*/
|
|
289
|
+
function sortThreadsPragma(count: number | undefined): string {
|
|
290
|
+
return `PRAGMA threads = ${Math.max(0, Math.floor(count ?? 0))}`
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Throws before `lookup.db` and `detail.db` are written when their filesystem has
|
|
295
|
+
* less free space than the staging database occupies.
|
|
296
|
+
*
|
|
297
|
+
* The staging database holds every row both files are written from, so its size bounds what they need.
|
|
298
|
+
* A shortfall found here stops the build with the space it needs.
|
|
299
|
+
*
|
|
300
|
+
* Without the check, SQLite reports `database or disk is full` partway through writing.
|
|
301
|
+
*/
|
|
302
|
+
async function assertOutputSpace(lookupPath: PathBuilderLike, stagingPath: string): Promise<void> {
|
|
303
|
+
const needed = (await statPath(stagingPath)).size
|
|
304
|
+
const space = await readFilesystemSpace(dirname(lookupPath))
|
|
305
|
+
|
|
306
|
+
if (space.availableBytes < needed) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`${space.measuredAt} has ${ByteFormatter.formatIEC(space.availableBytes)} free, and writing ` +
|
|
309
|
+
`${basename(lookupPath.toString())} and detail.db from the staging database ${stagingPath} needs up to ` +
|
|
310
|
+
`${ByteFormatter.formatIEC(needed)}. Free space on that filesystem and build again.`
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function stageRecords(options: BuildAddressLayerOptions, stagingPath: string): Promise<AddressLayerBuildSummary> {
|
|
316
|
+
using db = new DatabaseClient<StagingDatabase>(stagingPath)
|
|
317
|
+
|
|
318
|
+
db.exec(
|
|
319
|
+
`PRAGMA page_size = ${ADDRESS_LAYER_PAGE_SIZE}; PRAGMA journal_mode = OFF; PRAGMA synchronous = OFF; PRAGMA cache_size = -400000;`
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
db.exec(sortThreadsPragma(options.sortThreads))
|
|
323
|
+
|
|
324
|
+
await createStagingTables(db)
|
|
325
|
+
|
|
326
|
+
const stage = db.prepare("INSERT INTO stage VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
|
327
|
+
const detail = db.prepare("INSERT INTO address VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
328
|
+
const sourceIDs = new Map<string, number>()
|
|
329
|
+
const keys: Record<StagedLevel, number> = { key_postcode: 0, key_locality: 0, key_subregion: 0, key_region: 0 }
|
|
330
|
+
const sources: Record<string, number> = {}
|
|
331
|
+
const coverage = new Map<number, number>()
|
|
332
|
+
const refused: Record<string, Record<string, number>> = {}
|
|
333
|
+
let records = 0
|
|
334
|
+
let localityNames = 0
|
|
335
|
+
|
|
336
|
+
const refuse = (source: string, reason: string, count = 1) => {
|
|
337
|
+
const bySource = (refused[source] ??= {})
|
|
338
|
+
|
|
339
|
+
bySource[reason] = (bySource[reason] ?? 0) + count
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
db.exec("BEGIN")
|
|
343
|
+
|
|
344
|
+
for await (const record of options.records) {
|
|
345
|
+
if (!options.sources.has(record.source)) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`address record ${record.sourceRecordID} has source ${record.source}, which the build lists no terms for`
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const reason = options.refuseRecord?.(record)
|
|
352
|
+
|
|
353
|
+
if (reason) {
|
|
354
|
+
refuse(record.source, reason)
|
|
355
|
+
|
|
356
|
+
continue
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let sourceID = sourceIDs.get(record.source)
|
|
360
|
+
|
|
361
|
+
if (sourceID == null) {
|
|
362
|
+
sourceID = sourceIDs.size + 1
|
|
363
|
+
sourceIDs.set(record.source, sourceID)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const addressID = ++records
|
|
367
|
+
|
|
368
|
+
sources[record.source] = (sources[record.source] ?? 0) + 1
|
|
369
|
+
|
|
370
|
+
const put = (level: StagedLevel, scope: string) => {
|
|
371
|
+
stage.run(level, scope, record.streetKey, record.number, addressID, record.lat, record.lon, sourceID)
|
|
372
|
+
|
|
373
|
+
keys[level]++
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const postcode = addressPostcodeKey(record.country, record.postcode)
|
|
377
|
+
|
|
378
|
+
if (postcode) {
|
|
379
|
+
put(StagedLevel.Postcode, postcode)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
for (const name of record.names) {
|
|
383
|
+
if (name.level === "locality" || name.level === "dependent_locality") {
|
|
384
|
+
put(StagedLevel.Locality, name.key)
|
|
385
|
+
|
|
386
|
+
localityNames++
|
|
387
|
+
} else if (name.level === "subregion") {
|
|
388
|
+
put(StagedLevel.Subregion, name.key)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
put(StagedLevel.Region, "")
|
|
393
|
+
|
|
394
|
+
const cell = shortCellToInt(latLngToCell(record.lat, record.lon, ADDRESS_LAYER_COVERAGE_RESOLUTION))
|
|
395
|
+
|
|
396
|
+
coverage.set(cell, (coverage.get(cell) ?? 0) + 1)
|
|
397
|
+
|
|
398
|
+
detail.run(
|
|
399
|
+
addressID,
|
|
400
|
+
sourceID,
|
|
401
|
+
record.sourceRecordID,
|
|
402
|
+
record.country,
|
|
403
|
+
record.number,
|
|
404
|
+
record.unit,
|
|
405
|
+
record.streetRaw,
|
|
406
|
+
record.postcode,
|
|
407
|
+
stringifyJSON(record.names.map(({ level, form, value }) => ({ level, form, value }))),
|
|
408
|
+
record.adminCode,
|
|
409
|
+
record.lat,
|
|
410
|
+
record.lon,
|
|
411
|
+
record.placement,
|
|
412
|
+
record.authority
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
if (records % 200_000 === 0) {
|
|
416
|
+
db.exec("COMMIT; BEGIN")
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (records % 1_000_000 === 0) {
|
|
420
|
+
options.onProgress?.(records)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
db.exec("COMMIT")
|
|
425
|
+
|
|
426
|
+
const sourceRow = db.prepare("INSERT INTO source_ids VALUES (?, ?)")
|
|
427
|
+
|
|
428
|
+
for (const [name, id] of sourceIDs) {
|
|
429
|
+
sourceRow.run(name, id)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const sourceNames = new Map([...sourceIDs].map(([name, id]) => [id, name]))
|
|
433
|
+
|
|
434
|
+
// Removes the records a temporary table lists from the staged keys and details,
|
|
435
|
+
// counts them under `reason`, and lowers the counts the build's checks compare to match.
|
|
436
|
+
// The rows are streamed: one state can drop tens of millions of repeats,
|
|
437
|
+
// more than fit in the heap as objects.
|
|
438
|
+
const drop = (table: string, reason: string) => {
|
|
439
|
+
let dropped = 0
|
|
440
|
+
|
|
441
|
+
for (const row of db
|
|
442
|
+
.prepare(`SELECT a.source_id, a.lat, a.lon FROM ${table} t JOIN address a USING (address_id)`)
|
|
443
|
+
.iterate() as Iterable<{
|
|
444
|
+
source_id: number
|
|
445
|
+
lat: number
|
|
446
|
+
lon: number
|
|
447
|
+
}>) {
|
|
448
|
+
dropped++
|
|
449
|
+
|
|
450
|
+
const source = sourceNames.get(row.source_id)!
|
|
451
|
+
const cell = shortCellToInt(latLngToCell(row.lat, row.lon, ADDRESS_LAYER_COVERAGE_RESOLUTION))
|
|
452
|
+
const remaining = (coverage.get(cell) ?? 0) - 1
|
|
453
|
+
|
|
454
|
+
if (remaining > 0) {
|
|
455
|
+
coverage.set(cell, remaining)
|
|
456
|
+
} else {
|
|
457
|
+
coverage.delete(cell)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
sources[source] = (sources[source] ?? 0) - 1
|
|
461
|
+
refuse(source, reason)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
for (const { level, n } of db
|
|
465
|
+
.prepare(
|
|
466
|
+
`SELECT level, count(*) AS n FROM stage WHERE address_id IN (SELECT address_id FROM ${table}) GROUP BY level`
|
|
467
|
+
)
|
|
468
|
+
.all() as Array<{ level: StagedLevel; n: number }>) {
|
|
469
|
+
keys[level] -= n
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
db.exec(`DELETE FROM stage WHERE address_id IN (SELECT address_id FROM ${table})`)
|
|
473
|
+
db.exec(`DELETE FROM address WHERE address_id IN (SELECT address_id FROM ${table})`)
|
|
474
|
+
db.exec(`DROP TABLE ${table}`)
|
|
475
|
+
records -= dropped
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// A point one source shares among more than `stackLimit` addresses locates none of them.
|
|
479
|
+
db.exec(`CREATE TEMP TABLE stacked AS
|
|
480
|
+
SELECT address_id FROM address
|
|
481
|
+
WHERE (source_id, round(lat, 5), round(lon, 5)) IN (
|
|
482
|
+
SELECT source_id, la, lo FROM (${addressPointStacksSQL(options.stackLimit ?? ADDRESS_LAYER_STACK_LIMIT)}))`)
|
|
483
|
+
|
|
484
|
+
drop("stacked", "stacked-point")
|
|
485
|
+
|
|
486
|
+
if (options.duplicates) {
|
|
487
|
+
markDuplicates(db, options.duplicates, sourceNames)
|
|
488
|
+
drop("duplicate", "duplicate")
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
records,
|
|
493
|
+
keys,
|
|
494
|
+
sources,
|
|
495
|
+
localityFormsPerRecord: records ? localityNames / records : 0,
|
|
496
|
+
coverage,
|
|
497
|
+
refused,
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Lists in a temporary `duplicate` table every staged record that repeats a higher-ranked
|
|
503
|
+
* record of its postcode key group, by {@link isDuplicateAddress}.
|
|
504
|
+
* Only groups holding two or more sources are read.
|
|
505
|
+
*/
|
|
506
|
+
function markDuplicates(
|
|
507
|
+
db: DatabaseClient<StagingDatabase>,
|
|
508
|
+
rule: AddressDuplicateRule,
|
|
509
|
+
sourceNames: ReadonlyMap<number, string>
|
|
510
|
+
): void {
|
|
511
|
+
// SQLite rounds the points for both the stack count and the join, so a record meets its own point's count.
|
|
512
|
+
db.exec(`CREATE TEMP TABLE stack AS ${addressPointStacksSQL(ADDRESS_DUPLICATE_STACK_LIMIT)}`)
|
|
513
|
+
|
|
514
|
+
db.exec("CREATE INDEX temp.stack_point ON stack (source_id, la, lo)")
|
|
515
|
+
db.exec("CREATE TEMP TABLE duplicate (address_id INTEGER PRIMARY KEY)")
|
|
516
|
+
|
|
517
|
+
const mark = db.prepare("INSERT OR IGNORE INTO duplicate VALUES (?)")
|
|
518
|
+
|
|
519
|
+
type Member = DuplicateCandidate & { addressID: number; rank: number; postcode: boolean }
|
|
520
|
+
|
|
521
|
+
// Reads each street-and-number group the query yields, in key order, and marks every
|
|
522
|
+
// lower-ranked member that `repeats` judges a repeat of a higher-ranked one.
|
|
523
|
+
const settleGroups = (query: string, repeats: (higher: Member, lower: Member) => boolean) => {
|
|
524
|
+
let group: Member[] = []
|
|
525
|
+
let key = ""
|
|
526
|
+
|
|
527
|
+
const settle = () => {
|
|
528
|
+
for (const lower of group) {
|
|
529
|
+
if (group.some((higher) => higher.rank < lower.rank && repeats(higher, lower))) {
|
|
530
|
+
mark.run(lower.addressID)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for (const row of db.prepare(query).iterate() as Iterable<{
|
|
536
|
+
scope: string
|
|
537
|
+
street: string
|
|
538
|
+
key_number: string
|
|
539
|
+
address_id: number
|
|
540
|
+
source_id: number
|
|
541
|
+
unit: string | null
|
|
542
|
+
number: string
|
|
543
|
+
street_raw: string
|
|
544
|
+
names: string
|
|
545
|
+
lat: number
|
|
546
|
+
lon: number
|
|
547
|
+
stack: number
|
|
548
|
+
postcode: number
|
|
549
|
+
}>) {
|
|
550
|
+
const rowKey = `${row.scope}|${row.street}|${row.key_number}`
|
|
551
|
+
|
|
552
|
+
if (rowKey !== key) {
|
|
553
|
+
settle()
|
|
554
|
+
group = []
|
|
555
|
+
key = rowKey
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const source = sourceNames.get(row.source_id)!
|
|
559
|
+
const names = parseJSONStrict<Array<{ level: string; value: string }>>(row.names)
|
|
560
|
+
|
|
561
|
+
group.push({
|
|
562
|
+
addressID: row.address_id,
|
|
563
|
+
rank: rule.rank(source),
|
|
564
|
+
source,
|
|
565
|
+
unit: row.unit,
|
|
566
|
+
number: row.number,
|
|
567
|
+
streetRaw: row.street_raw,
|
|
568
|
+
localities: names.filter((name) => name.level === "locality").map((name) => name.value),
|
|
569
|
+
lat: row.lat,
|
|
570
|
+
lon: row.lon,
|
|
571
|
+
stack: row.stack,
|
|
572
|
+
postcode: row.postcode === 1,
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
settle()
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const memberColumns = `s.scope, s.street, s.number AS key_number, a.address_id, a.source_id, a.unit, a.number,
|
|
580
|
+
a.street_raw, a.names, a.lat, a.lon, coalesce(k.n, 1) AS stack`
|
|
581
|
+
|
|
582
|
+
const stackJoin =
|
|
583
|
+
"LEFT JOIN stack k ON k.source_id = a.source_id AND k.la = round(a.lat, 5) AND k.lo = round(a.lon, 5)"
|
|
584
|
+
|
|
585
|
+
db.exec("BEGIN")
|
|
586
|
+
|
|
587
|
+
settleGroups(
|
|
588
|
+
`SELECT ${memberColumns}, 1 AS postcode
|
|
589
|
+
FROM stage s JOIN address a ON a.address_id = s.address_id ${stackJoin}
|
|
590
|
+
WHERE s.level = '${StagedLevel.Postcode}' AND (s.scope, s.street, s.number) IN (
|
|
591
|
+
SELECT scope, street, number FROM stage WHERE level = '${StagedLevel.Postcode}'
|
|
592
|
+
GROUP BY 1, 2, 3 HAVING count(DISTINCT source_id) > 1)
|
|
593
|
+
ORDER BY s.scope, s.street, s.number`,
|
|
594
|
+
(higher, lower) => isDuplicateAddress(rule, higher, lower)
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
// A record with no postcode key shares no postcode group, so it is compared at the region level
|
|
598
|
+
// with every record of its street and number, and repeats one only when their points are near.
|
|
599
|
+
db.exec(
|
|
600
|
+
`CREATE TEMP TABLE keyed (address_id INTEGER PRIMARY KEY);
|
|
601
|
+
INSERT OR IGNORE INTO keyed SELECT address_id FROM stage WHERE level = '${StagedLevel.Postcode}'`
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
settleGroups(
|
|
605
|
+
`SELECT ${memberColumns}, p.address_id IS NOT NULL AS postcode
|
|
606
|
+
FROM stage s JOIN address a ON a.address_id = s.address_id ${stackJoin}
|
|
607
|
+
LEFT JOIN keyed p ON p.address_id = s.address_id
|
|
608
|
+
WHERE s.level = '${StagedLevel.Region}' AND (s.street, s.number) IN (
|
|
609
|
+
SELECT g.street, g.number FROM stage g LEFT JOIN keyed q ON q.address_id = g.address_id
|
|
610
|
+
WHERE g.level = '${StagedLevel.Region}'
|
|
611
|
+
GROUP BY 1, 2 HAVING count(DISTINCT g.source_id) > 1 AND count(q.address_id) < count(*))
|
|
612
|
+
ORDER BY s.street, s.number`,
|
|
613
|
+
(higher, lower) => !(higher.postcode && lower.postcode) && isNearbyDuplicateAddress(rule, higher, lower)
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
db.exec("DROP TABLE keyed")
|
|
617
|
+
db.exec("COMMIT")
|
|
618
|
+
db.exec("DROP TABLE stack")
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function writeLookup(
|
|
622
|
+
lookupPath: PathBuilderLike,
|
|
623
|
+
stagingPath: string,
|
|
624
|
+
summary: AddressLayerBuildSummary,
|
|
625
|
+
terms: ReadonlyMap<string, AddressSourceTerms>,
|
|
626
|
+
manifest: AddressLayerManifest,
|
|
627
|
+
checks: readonly AddressLayerReadCheck[],
|
|
628
|
+
threads: string
|
|
629
|
+
): Promise<void> {
|
|
630
|
+
await buildSealedArtifact<AddressLookupDatabase, true, void>({
|
|
631
|
+
out: lookupPath,
|
|
632
|
+
createTables: async (db) => {
|
|
633
|
+
db.exec(`PRAGMA page_size = ${ADDRESS_LAYER_PAGE_SIZE}`)
|
|
634
|
+
db.exec(threads)
|
|
635
|
+
await createLayerManifestTable(db)
|
|
636
|
+
await createLayerCoverageTable(db)
|
|
637
|
+
await createAddressSourceTable(db)
|
|
638
|
+
|
|
639
|
+
for (const table of ADDRESS_SCOPED_KEY_TABLES) {
|
|
640
|
+
await createAddressScopedKeyTable(db, table)
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
await createAddressPlaceKeyTable(db)
|
|
644
|
+
await createAddressRegionKeyTable(db)
|
|
645
|
+
|
|
646
|
+
for (const table of ADDRESS_STREET_TABLES) {
|
|
647
|
+
await createAddressStreetTable(db, table)
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
ingest: async (db) => {
|
|
651
|
+
db.exec(`ATTACH DATABASE '${stagingPath.replaceAll("'", "''")}' AS staged`)
|
|
652
|
+
|
|
653
|
+
const insertSource = db.prepare("INSERT INTO source VALUES (?, ?, ?, ?, ?)")
|
|
654
|
+
|
|
655
|
+
const ids = db.prepare("SELECT name, source_id FROM staged.source_ids ORDER BY source_id").all() as Array<{
|
|
656
|
+
name: string
|
|
657
|
+
source_id: number
|
|
658
|
+
}>
|
|
659
|
+
|
|
660
|
+
for (const { name, source_id } of ids) {
|
|
661
|
+
const term = terms.get(name)!
|
|
662
|
+
|
|
663
|
+
insertSource.run(source_id, name, term.license, term.authority, summary.sources[name] ?? 0)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// A record's locality and dependent-locality names can share a key,
|
|
667
|
+
// so a key row repeated for one address is written once.
|
|
668
|
+
for (const table of ADDRESS_SCOPED_KEY_TABLES) {
|
|
669
|
+
db.exec(
|
|
670
|
+
`INSERT OR IGNORE INTO ${table} SELECT scope, street, number, address_id, lat, lon, source_id
|
|
671
|
+
FROM staged.stage WHERE level = '${table}' ORDER BY scope, street, number, address_id`
|
|
672
|
+
)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
db.exec(
|
|
676
|
+
`INSERT OR IGNORE INTO key_region SELECT street, number, address_id, lat, lon, source_id
|
|
677
|
+
FROM staged.stage WHERE level = 'key_region' ORDER BY street, number, address_id`
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
for (const [table, level] of [
|
|
681
|
+
["street_postcode", "key_postcode"],
|
|
682
|
+
["street_locality", "key_locality"],
|
|
683
|
+
] as const) {
|
|
684
|
+
db.exec(
|
|
685
|
+
`INSERT INTO ${table} SELECT scope, street, avg(lat), avg(lon), min(lat), max(lat), min(lon), max(lon), count(*)
|
|
686
|
+
FROM staged.stage WHERE level = '${level}' GROUP BY scope, street ORDER BY scope, street`
|
|
687
|
+
)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
db.exec("DETACH DATABASE staged")
|
|
691
|
+
|
|
692
|
+
return true
|
|
693
|
+
},
|
|
694
|
+
finish: async (db) => {
|
|
695
|
+
for (const table of [...ADDRESS_SCOPED_KEY_TABLES, "key_region"] as const) {
|
|
696
|
+
const { count } = (await db
|
|
697
|
+
.selectFrom(table)
|
|
698
|
+
.select(sql<number>`count(*)`.as("count"))
|
|
699
|
+
.executeTakeFirstOrThrow()) as { count: number }
|
|
700
|
+
|
|
701
|
+
if (count > summary.keys[table]) {
|
|
702
|
+
throw new Error(`${table} holds ${count} rows, more than the ${summary.keys[table]} staged for it`)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const unanswered = checks.filter((check) => {
|
|
707
|
+
const row =
|
|
708
|
+
check.table === StagedLevel.Region
|
|
709
|
+
? db
|
|
710
|
+
.prepare("SELECT 1 FROM key_region WHERE street = ? AND number = ? LIMIT 1")
|
|
711
|
+
.get(check.street, check.number)
|
|
712
|
+
: db
|
|
713
|
+
.prepare(`SELECT 1 FROM ${check.table} WHERE scope = ? AND street = ? AND number = ? LIMIT 1`)
|
|
714
|
+
.get(check.scope, check.street, check.number)
|
|
715
|
+
|
|
716
|
+
return row == null
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
if (unanswered.length) {
|
|
720
|
+
const listed = unanswered.map((check) => `${check.table} ${check.scope} ${check.street} ${check.number}`)
|
|
721
|
+
|
|
722
|
+
throw new Error(`lookup.db misses ${unanswered.length} of ${checks.length} checked reads: ${listed.join("; ")}`)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
await writeLayerCoverage(db, sourcePresentCoverageCells(summary.coverage))
|
|
726
|
+
|
|
727
|
+
await writeLayerManifest(db, {
|
|
728
|
+
...manifest,
|
|
729
|
+
schemaVersion: 1,
|
|
730
|
+
spineKeys: { h3: { column: "layer_coverage.h3_cell", resolution: ADDRESS_LAYER_COVERAGE_RESOLUTION } },
|
|
731
|
+
sourceRecords: summary.sources,
|
|
732
|
+
})
|
|
733
|
+
},
|
|
734
|
+
})
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
async function writeDetail(
|
|
738
|
+
detailPath: PathBuilderLike,
|
|
739
|
+
stagingPath: string,
|
|
740
|
+
summary: AddressLayerBuildSummary,
|
|
741
|
+
threads: string
|
|
742
|
+
) {
|
|
743
|
+
await buildSealedArtifact<AddressDetailDatabase, true, void>({
|
|
744
|
+
out: detailPath,
|
|
745
|
+
createTables: async (db) => {
|
|
746
|
+
db.exec(`PRAGMA page_size = ${ADDRESS_LAYER_PAGE_SIZE}`)
|
|
747
|
+
db.exec(threads)
|
|
748
|
+
await createAddressDetailTable(db)
|
|
749
|
+
},
|
|
750
|
+
ingest: async (db) => {
|
|
751
|
+
db.exec(`ATTACH DATABASE '${stagingPath.replaceAll("'", "''")}' AS staged`)
|
|
752
|
+
db.exec("INSERT INTO address SELECT * FROM staged.address ORDER BY address_id")
|
|
753
|
+
db.exec("DETACH DATABASE staged")
|
|
754
|
+
|
|
755
|
+
return true
|
|
756
|
+
},
|
|
757
|
+
finish: async (db) => {
|
|
758
|
+
const { count } = (await db
|
|
759
|
+
.selectFrom("address")
|
|
760
|
+
.select(sql<number>`count(*)`.as("count"))
|
|
761
|
+
.executeTakeFirstOrThrow()) as { count: number }
|
|
762
|
+
|
|
763
|
+
if (count !== summary.records) {
|
|
764
|
+
throw new Error(`address holds ${count} rows, against ${summary.records} records staged`)
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
})
|
|
768
|
+
}
|