@mailwoman/resolver-wof-sqlite 10.0.0 → 10.2.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 +9 -8
- package/lib/address/point/interpolation.ts +45 -39
- package/lib/address/point/schema.ts +17 -40
- package/lib/address/point.ts +316 -0
- package/lib/{address/index.ts → address.ts} +1 -1
- package/lib/ancestry/backfill.ts +20 -86
- package/lib/{ancestry/index.ts → ancestry.ts} +19 -15
- package/lib/build/candidate.ts +122 -174
- package/lib/build/population-corrections.ts +64 -0
- package/lib/build/slim.ts +44 -114
- package/lib/candidate/alias-bags.ts +16 -17
- package/lib/candidate/ancestors/schema.ts +77 -61
- package/lib/candidate/ancestors/sidecar.ts +15 -22
- package/lib/candidate/country-display-names.ts +6 -20
- package/lib/candidate/extract-fold.ts +10 -55
- package/lib/candidate/fts.ts +5 -23
- package/lib/candidate/importance.ts +65 -68
- package/lib/candidate/lookup.ts +124 -415
- package/lib/candidate/name-roles.ts +15 -33
- package/lib/candidate/own-name.ts +35 -32
- package/lib/candidate/place-attrs.ts +20 -13
- package/lib/candidate/schema.ts +29 -65
- package/lib/candidate/scoring.ts +54 -93
- package/lib/capital-schema.ts +21 -17
- package/lib/capitals.ts +29 -27
- package/lib/coincident-roles.ts +85 -55
- package/lib/convention/schema.ts +15 -16
- package/lib/convention.ts +144 -0
- package/lib/coverage-manifest-schema.ts +40 -45
- package/lib/currency-backfill.ts +34 -79
- package/lib/env.ts +1 -1
- package/lib/exact-match.ts +32 -21
- package/lib/extracts.ts +97 -74
- package/lib/fst/autocomplete.ts +54 -37
- package/lib/fst/builder.ts +37 -41
- package/lib/fst/deserialize-web.ts +16 -17
- package/lib/fst/format.ts +41 -23
- package/lib/fst/freshness.ts +43 -120
- package/lib/fst/matcher.ts +7 -5
- package/lib/fst/serialize.ts +54 -74
- package/lib/fst/types.ts +78 -75
- package/lib/{fst/index.ts → fst.ts} +1 -0
- package/lib/fts/query.ts +5 -36
- package/lib/fts.ts +280 -0
- package/lib/geonames/aliases.ts +73 -144
- package/lib/geonames/postal.ts +162 -71
- package/lib/index.ts +9 -8
- package/lib/interpolation.ts +48 -100
- package/lib/lookup.ts +155 -270
- package/lib/name-score.ts +9 -7
- package/lib/nsul/lookup.ts +37 -54
- package/lib/nsul/schema.ts +16 -43
- package/lib/paths.ts +159 -0
- package/lib/place-importance-schema.ts +62 -169
- package/lib/poi/lookup.ts +104 -121
- package/lib/poi/schema.ts +42 -26
- package/lib/polygon-schema.ts +10 -6
- package/lib/postal/city/alias/lookup.ts +22 -40
- package/lib/postal/city/alias/schema.ts +14 -10
- package/lib/postal/city/candidate-schema.ts +26 -20
- package/lib/postcode-point-lookup.ts +8 -27
- package/lib/primary-preference.ts +46 -126
- package/lib/proximity-rerank.ts +44 -33
- package/lib/ranking-weights.ts +105 -78
- package/lib/region-keys.ts +70 -42
- package/lib/reverse.ts +149 -86
- package/lib/schema.ts +73 -50
- package/lib/search-fetch.ts +90 -68
- package/lib/sqlite/convention-source.ts +15 -23
- package/lib/sqlite/utils.ts +19 -22
- package/lib/street/centroid/schema.ts +37 -42
- package/lib/street/{centroid/index.ts → centroid.ts} +16 -25
- package/lib/street/morphology/fst/builder.ts +60 -42
- package/lib/street/morphology/fst/loader.ts +40 -32
- package/lib/street/name-lookup.ts +45 -41
- package/lib/street/normalize.ts +88 -219
- package/lib/street/segment-schema.ts +116 -43
- package/lib/{street/index.ts → street.ts} +1 -1
- package/lib/types.ts +109 -150
- package/lib/unified-schema.ts +29 -29
- package/lib/uprn/existence.ts +30 -19
- package/lib/uprn/lookup.ts +29 -68
- package/lib/uprn/schema.ts +18 -40
- package/lib/weights-overlay-linker.ts +236 -252
- package/out/address/point/interpolation.d.ts +20 -23
- package/out/address/point/interpolation.d.ts.map +1 -1
- package/out/address/point/interpolation.js +42 -39
- package/out/address/point/interpolation.js.map +1 -1
- package/out/address/point/schema.d.ts +12 -35
- package/out/address/point/schema.d.ts.map +1 -1
- package/out/address/point/schema.js +3 -19
- package/out/address/point/schema.js.map +1 -1
- package/out/address/point.d.ts +57 -0
- package/out/address/point.d.ts.map +1 -0
- package/out/address/point.js +206 -0
- package/out/address/point.js.map +1 -0
- package/out/{address/index.js → address.d.ts} +2 -2
- package/out/address.d.ts.map +1 -0
- package/out/{address/index.d.ts → address.js} +2 -2
- 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 +14 -74
- 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 +82 -112
- package/out/build/candidate.d.ts.map +1 -1
- package/out/build/candidate.js +17 -100
- package/out/build/candidate.js.map +1 -1
- 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 +8 -44
- package/out/build/slim.d.ts.map +1 -1
- package/out/build/slim.js +33 -94
- package/out/build/slim.js.map +1 -1
- package/out/candidate/alias-bags.d.ts +6 -6
- package/out/candidate/alias-bags.d.ts.map +1 -1
- package/out/candidate/alias-bags.js +16 -17
- package/out/candidate/alias-bags.js.map +1 -1
- package/out/candidate/ancestors/schema.d.ts +73 -57
- package/out/candidate/ancestors/schema.d.ts.map +1 -1
- package/out/candidate/ancestors/schema.js +56 -48
- package/out/candidate/ancestors/schema.js.map +1 -1
- package/out/candidate/ancestors/sidecar.d.ts +3 -8
- package/out/candidate/ancestors/sidecar.d.ts.map +1 -1
- package/out/candidate/ancestors/sidecar.js +15 -22
- package/out/candidate/ancestors/sidecar.js.map +1 -1
- 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 +7 -15
- package/out/candidate/extract-fold.d.ts.map +1 -1
- package/out/candidate/extract-fold.js +5 -47
- package/out/candidate/extract-fold.js.map +1 -1
- package/out/candidate/fts.d.ts +5 -23
- package/out/candidate/fts.d.ts.map +1 -1
- package/out/candidate/fts.js +5 -23
- package/out/candidate/fts.js.map +1 -1
- package/out/candidate/importance.d.ts +60 -65
- package/out/candidate/importance.d.ts.map +1 -1
- package/out/candidate/importance.js +56 -63
- package/out/candidate/importance.js.map +1 -1
- package/out/candidate/lookup.d.ts +18 -55
- package/out/candidate/lookup.d.ts.map +1 -1
- package/out/candidate/lookup.js +82 -385
- package/out/candidate/lookup.js.map +1 -1
- package/out/candidate/name-roles.d.ts +9 -21
- 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 +35 -32
- 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 +27 -62
- package/out/candidate/schema.d.ts.map +1 -1
- package/out/candidate/schema.js +10 -22
- package/out/candidate/schema.js.map +1 -1
- package/out/candidate/scoring.d.ts +7 -8
- package/out/candidate/scoring.d.ts.map +1 -1
- package/out/candidate/scoring.js +52 -89
- package/out/candidate/scoring.js.map +1 -1
- 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 +27 -25
- 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 +72 -50
- package/out/coincident-roles.js.map +1 -1
- package/out/convention/schema.d.ts +14 -14
- 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 +34 -39
- package/out/coverage-manifest-schema.d.ts.map +1 -1
- package/out/coverage-manifest-schema.js +25 -34
- package/out/coverage-manifest-schema.js.map +1 -1
- package/out/currency-backfill.d.ts +16 -38
- package/out/currency-backfill.d.ts.map +1 -1
- package/out/currency-backfill.js +19 -46
- package/out/currency-backfill.js.map +1 -1
- package/out/env.d.ts +1 -1
- 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 +32 -21
- package/out/exact-match.js.map +1 -1
- package/out/extracts.d.ts +66 -55
- 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 +29 -21
- package/out/fst/autocomplete.d.ts.map +1 -1
- package/out/fst/autocomplete.js +39 -28
- package/out/fst/autocomplete.js.map +1 -1
- package/out/fst/builder.d.ts.map +1 -1
- package/out/fst/builder.js +37 -41
- package/out/fst/builder.js.map +1 -1
- package/out/fst/deserialize-web.d.ts +4 -4
- package/out/fst/deserialize-web.d.ts.map +1 -1
- package/out/fst/deserialize-web.js +15 -16
- 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 +19 -81
- package/out/fst/freshness.d.ts.map +1 -1
- package/out/fst/freshness.js +31 -94
- package/out/fst/freshness.js.map +1 -1
- package/out/fst/matcher.d.ts +5 -4
- 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 +28 -28
- package/out/fst/serialize.d.ts.map +1 -1
- package/out/fst/serialize.js +53 -71
- package/out/fst/serialize.js.map +1 -1
- package/out/fst/types.d.ts +78 -74
- 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} +2 -1
- package/out/fst.d.ts.map +1 -0
- package/out/{fst/index.d.ts → fst.js} +2 -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 -62
- package/out/geonames/aliases.d.ts.map +1 -1
- package/out/geonames/aliases.js +45 -117
- package/out/geonames/aliases.js.map +1 -1
- package/out/geonames/postal.d.ts +84 -44
- package/out/geonames/postal.d.ts.map +1 -1
- package/out/geonames/postal.js +115 -73
- 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 +9 -8
- package/out/index.d.ts.map +1 -1
- package/out/index.js +6 -5
- package/out/index.js.map +1 -1
- package/out/interpolation.d.ts +18 -55
- package/out/interpolation.d.ts.map +1 -1
- package/out/interpolation.js +27 -69
- package/out/interpolation.js.map +1 -1
- package/out/lookup.d.ts +23 -51
- package/out/lookup.d.ts.map +1 -1
- package/out/lookup.js +136 -234
- 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 +51 -143
- package/out/place-importance-schema.d.ts.map +1 -1
- package/out/place-importance-schema.js +28 -143
- package/out/place-importance-schema.js.map +1 -1
- package/out/poi/lookup.d.ts +40 -47
- package/out/poi/lookup.d.ts.map +1 -1
- package/out/poi/lookup.js +69 -96
- 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 +20 -23
- package/out/postal/city/alias/lookup.d.ts.map +1 -1
- package/out/postal/city/alias/lookup.js +17 -30
- package/out/postal/city/alias/lookup.js.map +1 -1
- package/out/postal/city/alias/schema.d.ts +14 -10
- package/out/postal/city/alias/schema.d.ts.map +1 -1
- package/out/postal/city/alias/schema.js +10 -8
- package/out/postal/city/alias/schema.js.map +1 -1
- package/out/postal/city/candidate-schema.d.ts +25 -19
- package/out/postal/city/candidate-schema.d.ts.map +1 -1
- package/out/postal/city/candidate-schema.js +21 -17
- package/out/postal/city/candidate-schema.js.map +1 -1
- package/out/{postal/index.d.ts → postal.d.ts} +1 -1
- package/out/postal.d.ts.map +1 -0
- package/out/{postal/index.js → postal.js} +1 -1
- package/out/postal.js.map +1 -0
- package/out/postcode-point-lookup.d.ts +6 -24
- 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 +38 -92
- package/out/primary-preference.d.ts.map +1 -1
- package/out/primary-preference.js +15 -99
- package/out/primary-preference.js.map +1 -1
- package/out/proximity-rerank.d.ts +41 -30
- 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 +98 -64
- 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 +50 -31
- 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 +78 -50
- package/out/reverse.d.ts.map +1 -1
- package/out/reverse.js +98 -61
- package/out/reverse.js.map +1 -1
- package/out/schema.d.ts +72 -49
- 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 +12 -16
- package/out/sqlite/convention-source.d.ts.map +1 -1
- package/out/sqlite/convention-source.js +13 -22
- package/out/sqlite/convention-source.js.map +1 -1
- package/out/sqlite/utils.d.ts +19 -19
- package/out/sqlite/utils.d.ts.map +1 -1
- package/out/sqlite/utils.js +14 -15
- package/out/sqlite/utils.js.map +1 -1
- package/out/street/centroid/schema.d.ts +37 -42
- package/out/street/centroid/schema.d.ts.map +1 -1
- package/out/street/centroid/schema.js +9 -21
- package/out/street/centroid/schema.js.map +1 -1
- package/out/street/centroid.d.ts +30 -0
- package/out/street/centroid.d.ts.map +1 -0
- package/out/street/{centroid/index.js → centroid.js} +13 -24
- package/out/street/centroid.js.map +1 -0
- package/out/street/morphology/fst/builder.d.ts +20 -10
- package/out/street/morphology/fst/builder.d.ts.map +1 -1
- package/out/street/morphology/fst/builder.js +44 -34
- package/out/street/morphology/fst/builder.js.map +1 -1
- package/out/street/morphology/fst/loader.d.ts +33 -26
- package/out/street/morphology/fst/loader.d.ts.map +1 -1
- package/out/street/morphology/fst/loader.js +23 -22
- package/out/street/morphology/fst/loader.js.map +1 -1
- 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 +34 -35
- 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 +77 -194
- 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.js → street.d.ts} +2 -2
- package/out/street.d.ts.map +1 -0
- package/out/{street/index.d.ts → street.js} +2 -2
- package/out/street.js.map +1 -0
- package/out/types.d.ts +84 -149
- 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 +21 -57
- 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 +138 -144
- package/out/weights-overlay-linker.d.ts.map +1 -1
- package/out/weights-overlay-linker.js +105 -159
- package/out/weights-overlay-linker.js.map +1 -1
- package/package.json +91 -596
- package/lib/address/point/index.ts +0 -328
- package/lib/convention/index.ts +0 -154
- package/lib/fts/index.ts +0 -361
- package/out/address/index.d.ts.map +0 -1
- package/out/address/index.js.map +0 -1
- package/out/address/point/index.d.ts +0 -53
- package/out/address/point/index.d.ts.map +0 -1
- package/out/address/point/index.js +0 -222
- package/out/address/point/index.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/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/index.d.ts.map +0 -1
- package/out/postal/index.js.map +0 -1
- package/out/street/centroid/index.d.ts +0 -40
- package/out/street/centroid/index.d.ts.map +0 -1
- package/out/street/centroid/index.js.map +0 -1
- package/out/street/index.d.ts.map +0 -1
- package/out/street/index.js.map +0 -1
- package/out/uprn/index.d.ts.map +0 -1
- package/out/uprn/index.js.map +0 -1
- /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/{postal/index.ts → postal.ts} +0 -0
- /package/lib/{uprn/index.ts → uprn.ts} +0 -0
package/lib/currency-backfill.ts
CHANGED
|
@@ -3,17 +3,14 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* source independently attests. Extracted from `build-candidate.ts` as a cohesive unit — the checks, their measured
|
|
8
|
-
* constants, and the GeoNames dump reader live together here; the build calls {@link resurrectCurrencyHoles} once,
|
|
9
|
-
* between the primaries pass and the alias pass.
|
|
6
|
+
* Restores deprecated WOF localities only when an independent source attests them.
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
import { pathExists } from "@mailwoman/core/fs/readers"
|
|
9
|
+
import { pathExists } from "@mailwoman/core/fs/readers/stat"
|
|
13
10
|
import { isStrictlyFiner } from "@mailwoman/core/resources/whosonfirst"
|
|
14
11
|
import { haversineKm } from "@mailwoman/spatial"
|
|
15
12
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
16
|
-
import { resolvePath } from "path-ts"
|
|
13
|
+
import { type PathBuilderLike, resolvePath } from "path-ts"
|
|
17
14
|
import { TSVSpliterator } from "spliterator"
|
|
18
15
|
|
|
19
16
|
import type { loadImportanceIndex } from "#candidate/importance"
|
|
@@ -22,42 +19,15 @@ import type { CandidateDatabase } from "#candidate/schema"
|
|
|
22
19
|
import type { WOFDatabase } from "#schema"
|
|
23
20
|
import { normalizeLocalityForKey } from "#street/normalize"
|
|
24
21
|
|
|
25
|
-
/**
|
|
26
|
-
* Corroboration radius for the currency backfill (#1737), km — both for the live-near blocker and the GeoNames
|
|
27
|
-
* attestation. Measured basis (2026-08-19 prototype, GB locality slice): at 10 km, 20 of 108 dead names resurrect —
|
|
28
|
-
* Rochester (Kent), Aldershot, Staines, Telford, Ebbw Vale among them — while the `Birmingham/Wolverhampton/…`
|
|
29
|
-
* conurbation blobs stay dead (no attestation) and Swansea/Wrexham stay out because a live same-name row already serves
|
|
30
|
-
* them within the radius.
|
|
31
|
-
*/
|
|
32
22
|
const CURRENCY_BACKFILL_RADIUS_KM = 10
|
|
33
23
|
|
|
34
|
-
/**
|
|
35
|
-
* Minimum attestor population for a resurrection. The same prototype measured 44 of 108 dead GB names attested but
|
|
36
|
-
* under this floor — hamlet-scale ghosts whose absence from the index nobody has reported. A floor keeps the pass
|
|
37
|
-
* answering the measured defect (real settlements) rather than re-importing the tail WOF chose to prune.
|
|
38
|
-
*/
|
|
39
24
|
const CURRENCY_BACKFILL_POP_FLOOR = 1000
|
|
40
25
|
|
|
41
26
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* WOF deprecations WITH a successor need nothing — the successor indexes. A deprecation with `is_superseded = 0` on a
|
|
45
|
-
* populated place is the shape of an upstream mistake (Rochester Kent, Aldershot, Telford; 120 GB localities alone),
|
|
46
|
-
* and it is indistinguishable from a correct pruning at this layer without outside evidence. So every resurrection
|
|
47
|
-
* requires all three conditions, positive evidence only:
|
|
48
|
-
*
|
|
49
|
-
* 1. NO live same-name spr row of any placetype within {@link CURRENCY_BACKFILL_RADIUS_KM} of the dead record — a live row
|
|
50
|
-
* means the place is alive (possibly under another placetype) and there is no hole. A DISTANT same-name row is a
|
|
51
|
-
* namesake and does not block.
|
|
52
|
-
* 2. A GeoNames feature-class-P attestation of the same folded name within the radius.
|
|
53
|
-
* 3. The attestor at or above {@link CURRENCY_BACKFILL_POP_FLOOR}.
|
|
54
|
-
*
|
|
55
|
-
* The staged row keeps the WOF identity — id, name, centroid, bbox, region ancestry — because the dead record's own
|
|
56
|
-
* data is not what is wrong with it. GeoNames contributes exactly two things: the attestation, and the population that
|
|
57
|
-
* lets the row stand in prominence races (the dead record's own population is absent). Each name is judged once per
|
|
58
|
-
* country; the resurrected place joins `attrs`, so the alias pass explodes its alt names like any primary's.
|
|
27
|
+
* Restore a deprecated locality only when no nearby live namesake exists and GeoNames
|
|
28
|
+
* attests the same folded name above {@link CURRENCY_BACKFILL_POP_FLOOR}.
|
|
59
29
|
*/
|
|
60
|
-
export interface
|
|
30
|
+
export interface CurrencyBackfillResults {
|
|
61
31
|
judged: number
|
|
62
32
|
blocked: number
|
|
63
33
|
unattested: number
|
|
@@ -66,17 +36,16 @@ export interface CurrencyBackfillOutcomes {
|
|
|
66
36
|
}
|
|
67
37
|
|
|
68
38
|
/**
|
|
69
|
-
* One country's read of the check — every dead name judged, by
|
|
70
|
-
* can say what a wider
|
|
39
|
+
* One country's read of the check — every dead name judged, by result and by the dead
|
|
40
|
+
* row's placetype — so a census can say what a wider query admits.
|
|
71
41
|
*/
|
|
72
|
-
export interface CurrencyBackfillCountryReport extends
|
|
42
|
+
export interface CurrencyBackfillCountryReport extends CurrencyBackfillResults {
|
|
73
43
|
country: string
|
|
74
44
|
deadPlacetypes: readonly string[]
|
|
75
45
|
dumpPresent: boolean
|
|
76
|
-
byDeadPlacetype: Record<string,
|
|
46
|
+
byDeadPlacetype: Record<string, CurrencyBackfillResults>
|
|
77
47
|
/**
|
|
78
|
-
* The first resurrected names, `placetype:name`,
|
|
79
|
-
* report is a receipt and not the table.
|
|
48
|
+
* The first resurrected names, `placetype:name`, capped because the report is a receipt and not the table.
|
|
80
49
|
*/
|
|
81
50
|
sample: string[]
|
|
82
51
|
}
|
|
@@ -84,40 +53,37 @@ export interface CurrencyBackfillCountryReport extends CurrencyBackfillOutcomes
|
|
|
84
53
|
const REPORT_SAMPLE_SIZE = 25
|
|
85
54
|
|
|
86
55
|
/**
|
|
87
|
-
* The placetypes a dead row may
|
|
88
|
-
* second cause #1746 named (one GB row of seventeen) and is admitted through this option once its census is read.
|
|
56
|
+
* The placetypes a dead row may have to be judged at all; `locality` is the shipped default.
|
|
89
57
|
*/
|
|
90
58
|
export const DEFAULT_DEAD_PLACETYPES: readonly string[] = ["locality"]
|
|
91
59
|
|
|
92
|
-
function
|
|
60
|
+
function emptyResults(): CurrencyBackfillResults {
|
|
93
61
|
return { judged: 0, blocked: 0, unattested: 0, floored: 0, resurrected: 0 }
|
|
94
62
|
}
|
|
95
63
|
|
|
96
64
|
export async function resurrectCurrencyHoles(ctx: {
|
|
97
65
|
src: DatabaseClient<WOFDatabase>
|
|
98
66
|
/**
|
|
99
|
-
* The candidate build's transaction
|
|
67
|
+
* The candidate build's transaction, required unless `dryRun`.
|
|
100
68
|
*/
|
|
101
69
|
tx?: DatabaseClient<CandidateDatabase>
|
|
102
|
-
geonamesDir:
|
|
70
|
+
geonamesDir: PathBuilderLike
|
|
103
71
|
countries: readonly string[]
|
|
104
72
|
attrs: Map<number, PlaceAttrs>
|
|
105
73
|
ccID: (code: string | null) => number
|
|
106
74
|
ptID: (pt: string | null) => number
|
|
107
75
|
regionOf: Map<number, number>
|
|
108
|
-
importance: ReturnType<typeof loadImportanceIndex> |
|
|
76
|
+
importance: ReturnType<typeof loadImportanceIndex> | null
|
|
109
77
|
stageRow: StageRow
|
|
110
78
|
progress: (phase: string, message: string) => void
|
|
111
|
-
|
|
112
|
-
* Which dead placetypes are judged. Default {@link DEFAULT_DEAD_PLACETYPES}.
|
|
113
|
-
*/
|
|
79
|
+
|
|
114
80
|
deadPlacetypes?: readonly string[]
|
|
115
81
|
/**
|
|
116
|
-
* Judge and count
|
|
82
|
+
* Judge and count while staging no row and opening no transaction.
|
|
117
83
|
*/
|
|
118
84
|
dryRun?: boolean
|
|
119
85
|
/**
|
|
120
|
-
* Receives one report per country judged (a country with no dump or no dead rows reports zero
|
|
86
|
+
* Receives one report per country judged (a country with no dump or no dead rows reports zero results).
|
|
121
87
|
*/
|
|
122
88
|
onCountry?: (report: CurrencyBackfillCountryReport) => void
|
|
123
89
|
}): Promise<number> {
|
|
@@ -151,7 +117,7 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
151
117
|
country: cc,
|
|
152
118
|
deadPlacetypes,
|
|
153
119
|
dumpPresent: await pathExists(dumpPath),
|
|
154
|
-
...
|
|
120
|
+
...emptyResults(),
|
|
155
121
|
byDeadPlacetype: {},
|
|
156
122
|
sample: [],
|
|
157
123
|
}
|
|
@@ -163,9 +129,9 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
163
129
|
continue
|
|
164
130
|
}
|
|
165
131
|
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
132
|
+
// Read dead rows first.
|
|
133
|
+
// A country with no dead names then avoids loading its national dump.
|
|
134
|
+
// That avoids heap pressure on a build near its limit.
|
|
169
135
|
const dead = deadStmt.all(cc, ...deadPlacetypes)
|
|
170
136
|
|
|
171
137
|
if (!dead.length) {
|
|
@@ -175,8 +141,8 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
175
141
|
continue
|
|
176
142
|
}
|
|
177
143
|
|
|
178
|
-
// Only the dead names' own folded keys can ever be probed, so
|
|
179
|
-
//
|
|
144
|
+
// Only the dead names' own folded keys can ever be probed, so the rest of the
|
|
145
|
+
// national dump streams through without residency.
|
|
180
146
|
const deadKeys = new Set<string>()
|
|
181
147
|
|
|
182
148
|
for (const d of dead) {
|
|
@@ -187,8 +153,7 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
187
153
|
}
|
|
188
154
|
}
|
|
189
155
|
|
|
190
|
-
//
|
|
191
|
-
// 6 feature_class, 14 population.
|
|
156
|
+
// GeoNames columns by index: 1 name, 2 ascii, 4 lat, 5 lon, 6 feature_class, 14 population.
|
|
192
157
|
const attestors = new Map<string, { lat: number; lon: number; pop: number }[]>()
|
|
193
158
|
|
|
194
159
|
for await (const f of TSVSpliterator.fromAsync(dumpPath, { header: false })) {
|
|
@@ -215,11 +180,11 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
215
180
|
|
|
216
181
|
const seen = new Set<string>()
|
|
217
182
|
|
|
218
|
-
const count = (deadPlacetype: string,
|
|
219
|
-
report[
|
|
220
|
-
const bucket = (report.byDeadPlacetype[deadPlacetype] ??=
|
|
183
|
+
const count = (deadPlacetype: string, result: keyof CurrencyBackfillResults): void => {
|
|
184
|
+
report[result] += 1
|
|
185
|
+
const bucket = (report.byDeadPlacetype[deadPlacetype] ??= emptyResults())
|
|
221
186
|
|
|
222
|
-
bucket[
|
|
187
|
+
bucket[result] += 1
|
|
223
188
|
}
|
|
224
189
|
|
|
225
190
|
if (!dryRun) {
|
|
@@ -232,31 +197,21 @@ export async function resurrectCurrencyHoles(ctx: {
|
|
|
232
197
|
|
|
233
198
|
if (!pkey || seen.has(pkey)) continue
|
|
234
199
|
seen.add(pkey)
|
|
235
|
-
//
|
|
236
|
-
//
|
|
200
|
+
// The query admits whatever `deadPlacetypes` names, so the rank comparison
|
|
201
|
+
// judges each candidate against its own dead rung.
|
|
237
202
|
const deadPlacetype = String(d.placetype ?? "locality")
|
|
238
203
|
|
|
239
204
|
count(deadPlacetype, "judged")
|
|
240
205
|
const dLat = Number(d.latitude)
|
|
241
206
|
const dLon = Number(d.longitude)
|
|
242
207
|
|
|
243
|
-
// A live row blocks
|
|
244
|
-
//
|
|
245
|
-
// placetype" — true for a place recorded twice, false for a placetype DEMOTION, which is the shape that
|
|
246
|
-
// actually occurs: WOF retired `Gillingham` the locality (pop 101,187) and kept `Gillingham` the
|
|
247
|
-
// neighbourhood 3.2 km away, and the check read the surviving CHILD as covering its own dead parent.
|
|
248
|
-
// Sixteen of seventeen GB refusals had exactly that shape (#1746).
|
|
249
|
-
//
|
|
250
|
-
// An UNRANKED placetype blocks, which is the conservative direction: this check's failure mode is inventing
|
|
251
|
-
// a place, so a row we cannot rank is treated as covering rather than waved through.
|
|
208
|
+
// A live row blocks unless it is strictly finer than the dead one.
|
|
209
|
+
// An unranked placetype also blocks because the failure mode is inventing a place.
|
|
252
210
|
const liveNear = liveStmt.all(cc, name).some((row) => {
|
|
253
211
|
if (haversineKm(dLat, dLon, Number(row.latitude), Number(row.longitude)) > CURRENCY_BACKFILL_RADIUS_KM) {
|
|
254
212
|
return false
|
|
255
213
|
}
|
|
256
214
|
|
|
257
|
-
// Blocks UNLESS the live row is strictly finer. The equal rung must still block — a live `locality`
|
|
258
|
-
// covers a dead `locality` — and an unranked placetype blocks too, since this check's failure mode
|
|
259
|
-
// is inventing a place.
|
|
260
215
|
return isStrictlyFiner(String(row.placetype ?? ""), deadPlacetype) !== true
|
|
261
216
|
})
|
|
262
217
|
|
package/lib/env.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Environment variables
|
|
6
|
+
* Environment variables name the WOF SQLite artifacts this resolver opens and the development weights the overlay
|
|
7
7
|
* linker materializes. CLI flags (`--resolve-db`, …) override these at the call site.
|
|
8
8
|
*/
|
|
9
9
|
|
package/lib/exact-match.ts
CHANGED
|
@@ -3,20 +3,25 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* The exact-match tier's name probes
|
|
6
|
+
* The exact-match tier's name probes, the two case-folded equality lookups that decide whether a
|
|
7
7
|
* candidate holds the query text as its own name, an alias, or an official name.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
11
11
|
|
|
12
|
-
import { aliasBagExactMatch, foldQueryText } from "#fts
|
|
12
|
+
import { aliasBagExactMatch, foldQueryText } from "#fts"
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Among `ids`, return the subset whose name
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* Among `ids`, return the subset whose name or any alias equals `text` case-insensitively,
|
|
16
|
+
* the exact-match tier for ranking.
|
|
17
|
+
*
|
|
18
|
+
* One indexed query over `<schema>.names`.
|
|
19
|
+
* When the extract has no `names` table (a slim database built with `dropNames`, or a postcode-only extract),
|
|
20
|
+
* fall back to the self-contained `place_search` FTS content.
|
|
21
|
+
*
|
|
22
|
+
* Its `alt_names` column is the same alias set joined on the boundary-preserving
|
|
23
|
+
* `ALIAS_SEPARATOR`, so `aliasBagExactMatch` recovers the exact alias tier
|
|
24
|
+
* ("New York City" → New York) that the dropped `names` table used to provide.
|
|
20
25
|
*/
|
|
21
26
|
export function exactMatchIDs<DB>(
|
|
22
27
|
db: DatabaseClient<DB>,
|
|
@@ -41,7 +46,8 @@ export function exactMatchIDs<DB>(
|
|
|
41
46
|
|
|
42
47
|
return out
|
|
43
48
|
} catch {
|
|
44
|
-
// No `names` table on this extract
|
|
49
|
+
// No `names` table on this extract.
|
|
50
|
+
// Fall through to the place_search alias bag.
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
try {
|
|
@@ -52,17 +58,17 @@ export function exactMatchIDs<DB>(
|
|
|
52
58
|
const needle = foldQueryText(trimmed)
|
|
53
59
|
|
|
54
60
|
for (const r of rows) {
|
|
55
|
-
if (r.name
|
|
61
|
+
if (r.name && foldQueryText(r.name) === needle) {
|
|
56
62
|
out.add(r.id)
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
// Alias pass via the shared bag parser
|
|
61
|
-
// per-alias equality check, unrestricted
|
|
62
|
-
// alias match counts as exact regardless of other candidates.
|
|
63
|
-
// fall back to padded containment, conditioned on "no
|
|
64
|
-
// lost boundaries would otherwise false-promote
|
|
65
|
-
//
|
|
66
|
+
// Alias pass via the shared bag parser.
|
|
67
|
+
// Separated bags get a true per-alias equality check, unrestricted, matching the `names`-table
|
|
68
|
+
// branch above where an alias match counts as exact regardless of other candidates.
|
|
69
|
+
// Legacy bags without a separator fall back to padded containment, conditioned on "no
|
|
70
|
+
// canonical exact in the pool" because their lost boundaries would otherwise false-promote
|
|
71
|
+
// interior fragments ("York" inside the alias "New York City") or cross-alias fragments.
|
|
66
72
|
const anyCanonicalExact = out.size > 0
|
|
67
73
|
|
|
68
74
|
for (const r of rows) {
|
|
@@ -71,17 +77,22 @@ export function exactMatchIDs<DB>(
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
} catch {
|
|
74
|
-
// Extract without place_search either
|
|
80
|
+
// Extract without place_search either, so there is no exact-match tier.
|
|
81
|
+
// The caller falls back to weighted-sum order.
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
return out
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
/**
|
|
81
|
-
* Among `ids` (already known exact matches), the subset holding `text` as an
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
88
|
+
* Among `ids` (already known exact matches), the subset holding `text` as an
|
|
89
|
+
* official name (`names.official = 1`, the ingest bit).
|
|
90
|
+
*
|
|
91
|
+
* Same collate nocase semantics as {@link WOFSQLitePlaceLookup.#exactMatchIDs},
|
|
92
|
+
* so the two probes agree on what "equals the query" means.
|
|
93
|
+
* Fails soft on gazetteers built before the `official` column existed.
|
|
94
|
+
*
|
|
95
|
+
* The sub-tier then behaves exactly as if `officialNameExact` were off.
|
|
85
96
|
*/
|
|
86
97
|
export function officialNameIDs<DB>(
|
|
87
98
|
db: DatabaseClient<DB>,
|
|
@@ -106,7 +117,7 @@ export function officialNameIDs<DB>(
|
|
|
106
117
|
out.add(r.id)
|
|
107
118
|
}
|
|
108
119
|
} catch {
|
|
109
|
-
//
|
|
120
|
+
// A gazetteer without the `official` column, or a names-less slim extract, leaves the feature inert.
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
return out
|
package/lib/extracts.ts
CHANGED
|
@@ -1,36 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stringifyJSON } from "@mailwoman/core/json"
|
|
2
|
+
import { basename, PathBuilder, type PathBuilderLike } from "path-ts"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @copyright Sister Software
|
|
5
6
|
* @license AGPL-3.0
|
|
6
7
|
* @author Teffen Ellis, et al.
|
|
7
8
|
*
|
|
8
|
-
* Multi-extract support for `WOFSQLitePlaceLookup
|
|
9
|
-
* connection
|
|
9
|
+
* Multi-extract support for `WOFSQLitePlaceLookup`. It opens multiple WOF SQLite distributions
|
|
10
|
+
* on one connection with `attach database`. It routes queries by placetype.
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* The naive `SELECT … FROM pc.place_search WHERE pc.place_search MATCH ?` fails — SQLite parses the
|
|
14
|
-
* schema-qualified table on the left of MATCH as "column place_search of table pc". Discovered in
|
|
15
|
-
* the spike at PR review time; documented as `_EXTRACT_RULE.md` should it ever bite again.
|
|
16
|
-
*
|
|
17
|
-
* The working form: schema-qualified in FROM, bare table name in MATCH:
|
|
12
|
+
* SQLite parses a schema-qualified table on the left of `match` as "column place_search of table
|
|
13
|
+
* pc", so the working form is schema-qualified in `from` and a bare table name in `match`:
|
|
18
14
|
*
|
|
19
15
|
* ```sql
|
|
20
|
-
*
|
|
16
|
+
* select … from pc.place_search where place_search match ?
|
|
21
17
|
* ```
|
|
22
18
|
*
|
|
23
|
-
* Identical table names across attached extracts
|
|
24
|
-
*
|
|
25
|
-
* scope.
|
|
19
|
+
* Identical table names across attached extracts are fine, because the bare-name `match` resolves
|
|
20
|
+
* against `from` scope. Every extract ships its own `place_search` and `place_bbox`.
|
|
26
21
|
*/
|
|
27
22
|
|
|
28
23
|
/**
|
|
29
|
-
* Derive a SQL-safe schema name from a WOF distribution filename.
|
|
30
|
-
* gets a stable, predictable handle.
|
|
24
|
+
* Derive a SQL-safe schema name from a WOF distribution filename.
|
|
31
25
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* Used by `attach database … AS <name>` so each extract gets a stable, predictable handle.
|
|
27
|
+
*
|
|
28
|
+
* Convention strips the `whosonfirst-data-` prefix and the `-latest.db` (or just `.db`)
|
|
29
|
+
* suffix, then replaces `-` with `_` for SQL identifier safety.
|
|
34
30
|
*
|
|
35
31
|
* Examples:
|
|
36
32
|
*
|
|
@@ -39,46 +35,56 @@ import { basename } from "path-ts"
|
|
|
39
35
|
* - `whosonfirst-data-admin-latest.db` → `admin`
|
|
40
36
|
* - `my-custom.db` → `my_custom`
|
|
41
37
|
*
|
|
42
|
-
* Callers can override the derived name explicitly via `ExtractConfig.schemaName`
|
|
43
|
-
* convention.
|
|
38
|
+
* Callers can override the derived name explicitly via `ExtractConfig.schemaName`
|
|
39
|
+
* when the filename doesn't follow WOF convention.
|
|
44
40
|
*/
|
|
45
|
-
export function deriveSchemaName(path:
|
|
46
|
-
const stem = basename(path)
|
|
41
|
+
export function deriveSchemaName(path: PathBuilderLike): string {
|
|
42
|
+
const stem = basename(path.toString())
|
|
47
43
|
.replace(/^whosonfirst-data-/u, "")
|
|
48
44
|
.replace(/-latest\.db$/u, "")
|
|
49
45
|
.replace(/\.db$/u, "")
|
|
50
46
|
.replaceAll(/[^a-zA-Z0-9_]/g, "_")
|
|
51
47
|
|
|
52
48
|
if (!stem) {
|
|
53
|
-
throw new Error(`deriveSchemaName: could not derive a SQL schema name from path ${
|
|
49
|
+
throw new Error(`deriveSchemaName: could not derive a SQL schema name from path ${stringifyJSON(path)}`)
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
return stem
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
/**
|
|
60
|
-
* Per-extract configuration.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
56
|
+
* Per-extract configuration.
|
|
57
|
+
*
|
|
58
|
+
* The simple form is just a path string.
|
|
59
|
+
* The schema name is derived from it.
|
|
60
|
+
*
|
|
61
|
+
* The object form lets callers override the derived schema name (useful when a filename
|
|
62
|
+
* doesn't follow WOF convention) or attach an extra hint about which placetypes route here.
|
|
63
63
|
*/
|
|
64
64
|
export interface ExtractConfig {
|
|
65
|
-
path:
|
|
65
|
+
path: PathBuilderLike
|
|
66
66
|
/**
|
|
67
|
-
* Override the auto-derived schema name.
|
|
68
|
-
*
|
|
67
|
+
* Override the auto-derived schema name.
|
|
68
|
+
*
|
|
69
|
+
* Useful when the filename does not match WOF convention or when you want a memorable handle.
|
|
70
|
+
* Must be a valid SQLite identifier matching `[a-zA-Z_][a-zA-Z0-9_]*`.
|
|
69
71
|
*/
|
|
70
72
|
schemaName?: string
|
|
71
73
|
/**
|
|
72
|
-
* Optional explicit list of placetypes this extract serves.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
74
|
+
* Optional explicit list of placetypes this extract serves.
|
|
75
|
+
*
|
|
76
|
+
* When set, queries against any listed placetype are routed to this extract.
|
|
77
|
+
* When omitted, routing falls back to a name-match heuristic: an extract whose `schemaName`
|
|
78
|
+
* contains the placetype as a substring (e.g. `postalcode_us` for `postalcode` queries)
|
|
79
|
+
* is preferred for that placetype.
|
|
75
80
|
*/
|
|
76
81
|
placetypes?: readonly string[]
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
/**
|
|
80
|
-
* Resolved post-derivation: paired path + chosen schema name + (possibly empty) placetypes hint.
|
|
81
|
-
*
|
|
85
|
+
* Resolved post-derivation: paired path + chosen schema name + (possibly empty) placetypes hint.
|
|
86
|
+
*
|
|
87
|
+
* Used internally by `WOFSQLitePlaceLookup` so the routing logic operates on uniform structures.
|
|
82
88
|
*/
|
|
83
89
|
export interface ResolvedExtract {
|
|
84
90
|
path: string
|
|
@@ -87,19 +93,30 @@ export interface ResolvedExtract {
|
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
/**
|
|
90
|
-
* SQLite identifier regex
|
|
96
|
+
* SQLite identifier regex matching `[A-Za-z_][A-Za-z0-9_]*`.
|
|
91
97
|
*/
|
|
92
98
|
const SQLITE_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/u
|
|
93
99
|
|
|
94
100
|
/**
|
|
95
|
-
*
|
|
96
|
-
* `ExtractConfig` objects) into a uniform `ResolvedExtract[]`.
|
|
101
|
+
* Whether a `databasePath` entry is a path or an {@link ExtractConfig}.
|
|
97
102
|
*
|
|
98
|
-
*
|
|
99
|
-
* extracts keep their derived (or override) schema name.
|
|
103
|
+
* A builder is a `String` object, so `typeof` by itself reads it as an object.
|
|
100
104
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
function isPathBuilderLike(value: unknown): value is PathBuilderLike {
|
|
106
|
+
return typeof value === "string" || value instanceof PathBuilder
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Normalize the user-provided `databasePath` opt (which may be a single string, an array
|
|
111
|
+
* of strings, or an array of `ExtractConfig` objects) into a uniform `ResolvedExtract[]`.
|
|
112
|
+
*
|
|
113
|
+
* The first extract becomes `main` regardless of its derived schema name, following SQLite convention.
|
|
114
|
+
* Subsequent extracts keep their derived or overridden schema name.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveExtracts(
|
|
117
|
+
input: PathBuilderLike | ReadonlyArray<PathBuilderLike | ExtractConfig>
|
|
118
|
+
): ResolvedExtract[] {
|
|
119
|
+
const list = isPathBuilderLike(input) ? [input] : input
|
|
103
120
|
|
|
104
121
|
if (!list.length) throw new Error("resolveExtracts: at least one extract is required")
|
|
105
122
|
|
|
@@ -108,24 +125,25 @@ export function resolveExtracts(input: string | ReadonlyArray<string | ExtractCo
|
|
|
108
125
|
|
|
109
126
|
for (let i = 0; i < list.length; i++) {
|
|
110
127
|
const entry = list[i]!
|
|
111
|
-
const cfg: ExtractConfig =
|
|
128
|
+
const cfg: ExtractConfig = isPathBuilderLike(entry) ? { path: entry } : entry
|
|
112
129
|
const derived = cfg.schemaName ?? deriveSchemaName(cfg.path)
|
|
113
130
|
|
|
114
131
|
if (!SQLITE_IDENT_RE.test(derived)) {
|
|
115
132
|
throw new Error(
|
|
116
|
-
`resolveExtracts: schema name ${
|
|
117
|
-
`(derived from path ${
|
|
133
|
+
`resolveExtracts: schema name ${stringifyJSON(derived)} is not a valid SQLite identifier ` +
|
|
134
|
+
`(derived from path ${stringifyJSON(cfg.path)}). Pass an explicit ` +
|
|
118
135
|
`{ path, schemaName } to override.`
|
|
119
136
|
)
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
//
|
|
123
|
-
//
|
|
139
|
+
// SQLite always treats the first extract as main.
|
|
140
|
+
// Its derived name is informational only.
|
|
141
|
+
// Subsequent extracts must have unique non-main names.
|
|
124
142
|
const schemaName = i === 0 ? "main" : derived
|
|
125
143
|
|
|
126
144
|
if (i > 0 && (schemaName === "main" || seen.has(schemaName))) {
|
|
127
145
|
throw new Error(
|
|
128
|
-
`resolveExtracts: schema name ${
|
|
146
|
+
`resolveExtracts: schema name ${stringifyJSON(schemaName)} collides ` +
|
|
129
147
|
`(either with "main" or another extract). Pass an explicit { path, schemaName }.`
|
|
130
148
|
)
|
|
131
149
|
}
|
|
@@ -133,7 +151,7 @@ export function resolveExtracts(input: string | ReadonlyArray<string | ExtractCo
|
|
|
133
151
|
seen.add(schemaName)
|
|
134
152
|
|
|
135
153
|
out.push({
|
|
136
|
-
path: cfg.path,
|
|
154
|
+
path: cfg.path.toString(),
|
|
137
155
|
schemaName,
|
|
138
156
|
placetypes: cfg.placetypes ?? [],
|
|
139
157
|
})
|
|
@@ -143,24 +161,11 @@ export function resolveExtracts(input: string | ReadonlyArray<string | ExtractCo
|
|
|
143
161
|
}
|
|
144
162
|
|
|
145
163
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* Routing rules, in order:
|
|
164
|
+
* All placetype-matching extracts, in routing order (the country-aware pick chooses among these).
|
|
149
165
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* 3. Otherwise, fall back to `main`.
|
|
154
|
-
*
|
|
155
|
-
* This deliberately doesn't UNION across extracts — BM25 scores aren't comparable across separately- indexed corpora,
|
|
156
|
-
* and the typical mailwoman query has a single placetype anyway. If a caller needs cross-extract results they can issue
|
|
157
|
-
* two `findPlace` calls.
|
|
158
|
-
*/
|
|
159
|
-
/**
|
|
160
|
-
* All placetype-matching extracts, in routing order (the country-aware pick chooses among these). Used by the bias
|
|
161
|
-
* path: a country-less postcode query with proximity hints fans out across every matching extract and merges, because
|
|
162
|
-
* single-extract routing would hide the cross-country ambiguity the hints exist to resolve ("48026" lives in
|
|
163
|
-
* postalcode-us AND postalcode-intl).
|
|
166
|
+
* Used by the bias path: a country-less postcode query with proximity hints fans out across
|
|
167
|
+
* every matching extract and merges, because single-extract routing would hide the cross-country
|
|
168
|
+
* ambiguity the hints exist to resolve ("48026" lives in postalcode-us and postalcode-intl).
|
|
164
169
|
*/
|
|
165
170
|
export function pickExtractsForPlacetype(
|
|
166
171
|
extracts: ResolvedExtract[],
|
|
@@ -190,16 +195,34 @@ export function pickExtractsForPlacetype(
|
|
|
190
195
|
return matches.length ? matches : [extracts[0]!]
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Pick the extract to route a query to given the requested placetype(s).
|
|
200
|
+
*
|
|
201
|
+
* The resolver applies these routing rules in order:
|
|
202
|
+
*
|
|
203
|
+
* 1. If any extract has explicit `placetypes` that includes the requested placetype, use it.
|
|
204
|
+
* 2. Otherwise, if a non-main extract's `schemaName` matches the placetype
|
|
205
|
+
* (e.g. `postalcode_us` matches `postalcode`), use it.
|
|
206
|
+
* 3. Otherwise, fall back to `main`.
|
|
207
|
+
*
|
|
208
|
+
* This deliberately does not union across extracts.
|
|
209
|
+
* BM25 scores are not comparable across separately indexed corpora.
|
|
210
|
+
* A typical mailwoman query has one placetype.
|
|
211
|
+
*
|
|
212
|
+
* Callers that need cross-extract results can issue two `findPlace` calls.
|
|
213
|
+
*/
|
|
193
214
|
export function pickExtractForPlacetype(
|
|
194
215
|
extracts: ResolvedExtract[],
|
|
195
|
-
placetype: string |
|
|
216
|
+
placetype: string | null,
|
|
196
217
|
opts?: {
|
|
197
218
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
219
|
+
* The query's country constraint, when the caller has one.
|
|
220
|
+
*
|
|
221
|
+
* When `country` is given and a matching extract's probed country set contains it, that extract wins.
|
|
222
|
+
* Extracts without the country are skipped.
|
|
223
|
+
*
|
|
224
|
+
* The placetype-match order remains the tiebreak when no extract claims
|
|
225
|
+
* the country (or none was probed).
|
|
203
226
|
*/
|
|
204
227
|
country?: string
|
|
205
228
|
/**
|
|
@@ -221,9 +244,9 @@ export function pickExtractForPlacetype(
|
|
|
221
244
|
for (const s of extracts) {
|
|
222
245
|
if (s.schemaName === "main" || matches.includes(s)) continue
|
|
223
246
|
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
// `region` matching `arboregion`.
|
|
247
|
+
// A substring match lets `postalcode_us` match `postalcode`.
|
|
248
|
+
// Conservative, requiring the placetype at a word boundary in the schema name
|
|
249
|
+
// to avoid false hits like `region` matching `arboregion`.
|
|
227
250
|
if (
|
|
228
251
|
s.schemaName === placetype ||
|
|
229
252
|
s.schemaName.startsWith(`${placetype}_`) ||
|