@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/build/slim.ts
CHANGED
|
@@ -2,62 +2,33 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
-
*
|
|
6
|
-
* Build a "slim" Who's On First SQLite distribution that's small enough to ship as a static asset
|
|
7
|
-
* for the browser-side mailwoman demo (Path B of the demo plan). The full admin distribution is
|
|
8
|
-
* ~2 GB; the slim variant aims for the ~50–100 MB range by keeping only the places a public demo
|
|
9
|
-
* will actually query for.
|
|
10
|
-
*
|
|
11
|
-
* Selection policy (v1, US-focused):
|
|
12
|
-
*
|
|
13
|
-
* - All countries / regions / counties / boroughs in the configured `countries` set, so the ancestor
|
|
14
|
-
* chain a locality / postcode reports through `parent_id` stays intact.
|
|
15
|
-
* - Top-K localities by population (read from the source's pre-built `place_population` aux table) in
|
|
16
|
-
* those countries.
|
|
17
|
-
* - All postcodes in those countries — they're small and addressing-relevant.
|
|
18
|
-
* - All `names` + `place_population` rows for selected place IDs.
|
|
19
|
-
* - The `coincident_roles` dual-role relation (#402), filtered to surviving spr ids.
|
|
20
|
-
*
|
|
21
|
-
* No geojson, by design. The upstream WOF GeoJSON bodies live ONLY in the raw `whosonfirst-data-*`
|
|
22
|
-
* repos; `scripts/build-unified-wof.ts` extracts `wof:population` straight into
|
|
23
|
-
* `place_population` (and the bbox into `spr`) at ingest and never persists a `geojson` table. So
|
|
24
|
-
* the source admin DB carries population in `place_population`, and this builder consumes it
|
|
25
|
-
* directly — there is nothing to extract from, and nothing to drop.
|
|
26
|
-
*
|
|
27
|
-
* The output DB has the resolver-facing schema: `spr`, `names`, `place_population`, plus the
|
|
28
|
-
* `place_search` FTS5 / `place_bbox` R*Tree virtual tables rebuilt against the trimmed row set
|
|
29
|
-
* (both derive purely from `spr` + `names` — see `fts.ts`). That means `WOFSQLitePlaceLookup`
|
|
30
|
-
* opens the slim DB without any code change — it sees a smaller universe, nothing more.
|
|
31
|
-
*
|
|
32
|
-
* Multi-extract inputs (e.g. admin + postcode) are processed in sequence; selected rows accumulate
|
|
33
|
-
* into the single output DB. The postcode extract contributes only postcodes; admin contributes
|
|
34
|
-
* everything else. Empty / missing input paths are skipped (callers pass `""` when a extract, such
|
|
35
|
-
* as a custom postcode DB, isn't built yet).
|
|
36
5
|
*/
|
|
37
6
|
|
|
38
|
-
import { statPath, pathExists } from "@mailwoman/core/fs/readers"
|
|
7
|
+
import { statPath, pathExists } from "@mailwoman/core/fs/readers/stat"
|
|
39
8
|
import { temporaryDirectory } from "@mailwoman/core/fs/temporary"
|
|
40
9
|
import { copyFileTo, removePath } from "@mailwoman/core/fs/writers"
|
|
10
|
+
import { stringifyJSON } from "@mailwoman/core/json"
|
|
41
11
|
import { countRows } from "@mailwoman/sqlite"
|
|
42
12
|
import { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
43
|
-
import { sealDatabase } from "@mailwoman/sqlite/sealed
|
|
13
|
+
import { sealDatabase } from "@mailwoman/sqlite/sealed/db"
|
|
44
14
|
import { sql } from "kysely"
|
|
45
15
|
import type { Kysely } from "kysely"
|
|
16
|
+
import type { PathBuilderLike } from "path-ts"
|
|
46
17
|
|
|
47
|
-
import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE } from "#fts
|
|
18
|
+
import { buildPlaceSearchFTS, PLACE_BBOX_TABLE, PLACE_POPULATION_TABLE, PLACE_SEARCH_TABLE } from "#fts"
|
|
48
19
|
import type { NamesTable, SprTable, WOFDatabase } from "#schema"
|
|
49
20
|
|
|
50
21
|
export interface BuildSlimOptions {
|
|
51
22
|
/**
|
|
52
|
-
* Input WOF SQLite distributions
|
|
23
|
+
* Input WOF SQLite distributions, each already carrying spr / names / place_population tables.
|
|
53
24
|
*/
|
|
54
|
-
inputs:
|
|
25
|
+
inputs: readonly PathBuilderLike[]
|
|
55
26
|
/**
|
|
56
|
-
* Output path for the slim DB
|
|
27
|
+
* Output path for the slim DB, overwritten if it exists.
|
|
57
28
|
*/
|
|
58
|
-
output:
|
|
29
|
+
output: PathBuilderLike
|
|
59
30
|
/**
|
|
60
|
-
* Country codes to keep (ISO 2-letter)
|
|
31
|
+
* Country codes to keep (ISO 2-letter); defaults to `["US"]`.
|
|
61
32
|
*/
|
|
62
33
|
countries?: string[]
|
|
63
34
|
/**
|
|
@@ -65,16 +36,11 @@ export interface BuildSlimOptions {
|
|
|
65
36
|
*/
|
|
66
37
|
topLocalitiesPerCountry?: number
|
|
67
38
|
/**
|
|
68
|
-
* Drop the `names` table after the FTS index is built
|
|
69
|
-
*
|
|
70
|
-
* `place_search` + `spr` + `place_population` + `coincident_roles` and never reads `names` at runtime. Dropping it is
|
|
71
|
-
* the single biggest size win (~2/3 of the file for a multi-locale build; see #359). A future consumer that needs raw
|
|
72
|
-
* alt-names at runtime should ship a SEPARATE extract rather than re-bloat the hot DB.
|
|
39
|
+
* Drop the `names` table after the FTS index is built; `place_search` is self-contained
|
|
40
|
+
* and the resolver never reads `names` at runtime.
|
|
73
41
|
*/
|
|
74
42
|
dropNames?: boolean
|
|
75
|
-
|
|
76
|
-
* Optional progress callback for CLI / test introspection.
|
|
77
|
-
*/
|
|
43
|
+
|
|
78
44
|
onProgress?: (phase: SlimBuildPhase, detail: string) => void
|
|
79
45
|
}
|
|
80
46
|
|
|
@@ -112,7 +78,8 @@ export interface BuildSlimResult {
|
|
|
112
78
|
const ANCESTOR_PLACETYPES = ["country", "region", "county", "borough", "macroregion"] as const
|
|
113
79
|
|
|
114
80
|
/**
|
|
115
|
-
* Tables copied verbatim (schema + filtered rows) from each source DB.
|
|
81
|
+
* Tables copied verbatim (schema + filtered rows) from each source DB.
|
|
82
|
+
* Anything else is dropped.
|
|
116
83
|
*/
|
|
117
84
|
const COPIED_TABLES = ["spr", "names", PLACE_POPULATION_TABLE] as const
|
|
118
85
|
|
|
@@ -121,19 +88,14 @@ const COPIED_TABLES = ["spr", "names", PLACE_POPULATION_TABLE] as const
|
|
|
121
88
|
*/
|
|
122
89
|
const PLACE_POPULATION_DDL = `CREATE TABLE ${PLACE_POPULATION_TABLE} (id INTEGER PRIMARY KEY, population INTEGER NOT NULL DEFAULT 0)`
|
|
123
90
|
|
|
124
|
-
/**
|
|
125
|
-
* Minimal row shape for the population aux table — id + population, nothing else.
|
|
126
|
-
*/
|
|
127
91
|
interface PlacePopulationTable {
|
|
128
92
|
id: number
|
|
129
93
|
population: number
|
|
130
94
|
}
|
|
131
95
|
|
|
132
96
|
/**
|
|
133
|
-
* Kysely schema for the build phase
|
|
134
|
-
* row-copying queries can name the source schema
|
|
135
|
-
* still raw — Kysely doesn't model it — but everything downstream (the SELECT-INSERT step that does the actual
|
|
136
|
-
* filtering work) goes through the builder.
|
|
97
|
+
* Kysely schema for the build phase, including the ATTACHed `src.*` tables
|
|
98
|
+
* so row-copying queries can name the source schema.
|
|
137
99
|
*/
|
|
138
100
|
interface BuildSchema {
|
|
139
101
|
spr: SprTable
|
|
@@ -149,9 +111,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
149
111
|
const topLocalities = opts.topLocalitiesPerCountry ?? 1000
|
|
150
112
|
const progress = opts.onProgress ?? (() => {})
|
|
151
113
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
const inputs = opts.inputs.filter((p) => p.length > 0)
|
|
114
|
+
// An empty string marks an input that isn't built yet, so empties are skipped and every remaining path must exist.
|
|
115
|
+
const inputs = opts.inputs.map((input) => input.toString()).filter((p) => p.length)
|
|
155
116
|
|
|
156
117
|
if (!inputs.length) throw new Error("no input WOF dbs provided")
|
|
157
118
|
|
|
@@ -165,10 +126,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
165
126
|
await removePath(opts.output)
|
|
166
127
|
}
|
|
167
128
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
// ordering / types. `CREATE TABLE AS SELECT` flattens types to dynamic, which would break
|
|
171
|
-
// callers that rely on column-affinity behavior.
|
|
129
|
+
// The output schema is replayed from the first input's sqlite_master so column ordering
|
|
130
|
+
// and affinity survive; `create table AS select` would flatten types to dynamic.
|
|
172
131
|
const out = new DatabaseClient<BuildSchema>(opts.output)
|
|
173
132
|
let result: BuildSlimResult
|
|
174
133
|
|
|
@@ -183,20 +142,18 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
183
142
|
.get(table) as { sql?: string } | undefined
|
|
184
143
|
|
|
185
144
|
if (createSQL?.sql) {
|
|
186
|
-
// Raw DDL by design
|
|
187
|
-
// string read from sqlite_master, so a static Kysely builder can't express it. See AGENTS.md.
|
|
145
|
+
// Raw DDL by design: the source DB's own create-table string cannot be expressed by a static Kysely builder.
|
|
188
146
|
out.exec(createSQL.sql)
|
|
189
147
|
} else if (table === PLACE_POPULATION_TABLE) {
|
|
190
|
-
// Older source builds may predate the aux table
|
|
191
|
-
// copy + ranking have somewhere to land. Sparse-by-design; missing rows are fine.
|
|
148
|
+
// Older source builds may predate the aux table, so create it empty for the copy and ranking.
|
|
192
149
|
out.exec(PLACE_POPULATION_DDL)
|
|
193
150
|
} else {
|
|
194
151
|
throw new Error(`source DB ${inputs[0]} is missing required table '${table}'`)
|
|
195
152
|
}
|
|
196
153
|
}
|
|
197
154
|
|
|
198
|
-
//
|
|
199
|
-
// index on names.id helps the per-id
|
|
155
|
+
// The copied schemas define the primary keys.
|
|
156
|
+
// This index on `names.id` helps the per-id insert select.
|
|
200
157
|
out.exec(`CREATE INDEX IF NOT EXISTS names_id_idx ON names(id);`)
|
|
201
158
|
|
|
202
159
|
// Pull rows from each input.
|
|
@@ -204,10 +161,8 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
204
161
|
await copyFromSource(out, out, inputPath, countries, topLocalities, progress)
|
|
205
162
|
}
|
|
206
163
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// population aux table is NOT rebuilt here: it was copied verbatim above, and fts.ts only
|
|
210
|
-
// (re)builds it when a `geojson` table is present, which the slim DB intentionally has not.
|
|
164
|
+
// `place_search` and `place_bbox` derive purely from `spr` + `names`, and the
|
|
165
|
+
// population aux table is not rebuilt because it was copied verbatim.
|
|
211
166
|
progress("fts", "building place_search / place_bbox on slim DB")
|
|
212
167
|
|
|
213
168
|
buildPlaceSearchFTS(out, {
|
|
@@ -215,34 +170,24 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
215
170
|
onProgress: (phase, name) => progress("fts", `${phase} ${name}`),
|
|
216
171
|
})
|
|
217
172
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// querying `names` (`#exactMatchIDs`), but the slim DB drops `names` for size — so the
|
|
221
|
-
// browser resolver gets its own tiny lookup (~hundreds of rows) to do the same data-driven
|
|
222
|
-
// exact-abbrev tiering ("VT" → Vermont, not a token-matching foreign region) instead of the
|
|
223
|
-
// demo's hardcoded region-abbreviation map (since deleted). Sourced from the `language='abbr'` rows
|
|
224
|
-
// `add-region-abbrevs.ts` wrote, already filtered to surviving spr ids via the names copy. The
|
|
225
|
-
// table is always created (empty when the source predates the abbrev enrichment) so the
|
|
226
|
-
// resolver can query it unconditionally.
|
|
173
|
+
// `place_abbr` is materialized before `names` is dropped and always created,
|
|
174
|
+
// even empty, so the resolver can query it unconditionally.
|
|
227
175
|
progress("place_abbr", "materializing region abbreviations")
|
|
228
176
|
out.exec(`CREATE TABLE IF NOT EXISTS place_abbr (id INTEGER NOT NULL, abbr TEXT NOT NULL)`)
|
|
229
177
|
out.exec(`INSERT INTO place_abbr (id, abbr) SELECT id, name FROM names WHERE language = 'abbr'`)
|
|
230
178
|
out.exec(`CREATE INDEX IF NOT EXISTS place_abbr_by_abbr ON place_abbr (abbr COLLATE NOCASE)`)
|
|
231
179
|
out.exec(`CREATE INDEX IF NOT EXISTS place_abbr_by_id ON place_abbr (id)`)
|
|
232
180
|
|
|
233
|
-
// Capture the names count BEFORE any drop so the build report stays informative.
|
|
234
181
|
const namesRows = countRows(out, "names")
|
|
235
182
|
|
|
236
|
-
//
|
|
237
|
-
// it. The resolver never reads `names` at query time, so this is pure size reduction.
|
|
183
|
+
// The resolver never reads `names` at query time, so dropping it and its index is pure size reduction.
|
|
238
184
|
if (opts.dropNames) {
|
|
239
185
|
progress("vacuum", `dropping names table (${namesRows} rows; FTS5 is self-contained)`)
|
|
240
186
|
out.exec(`DROP INDEX IF EXISTS names_id_idx;`)
|
|
241
187
|
out.exec(`DROP TABLE IF EXISTS names;`)
|
|
242
188
|
}
|
|
243
189
|
|
|
244
|
-
//
|
|
245
|
-
// file size stays inflated from the in-flight INSERT churn.
|
|
190
|
+
// Vacuum so the on-disk file reflects the trimmed rows rather than the in-flight insert churn.
|
|
246
191
|
progress("vacuum", "VACUUM (final size reduction)")
|
|
247
192
|
out.exec("VACUUM;")
|
|
248
193
|
|
|
@@ -254,10 +199,10 @@ export async function buildSlimWOFDatabase(opts: BuildSlimOptions): Promise<Buil
|
|
|
254
199
|
placePopulation: countRows(out, PLACE_POPULATION_TABLE),
|
|
255
200
|
}
|
|
256
201
|
|
|
257
|
-
progress("done",
|
|
202
|
+
progress("done", stringifyJSON(rowCounts))
|
|
258
203
|
|
|
259
204
|
result = {
|
|
260
|
-
outputPath: opts.output,
|
|
205
|
+
outputPath: opts.output.toString(),
|
|
261
206
|
outputBytes: (await statPath(opts.output)).size,
|
|
262
207
|
rowCounts,
|
|
263
208
|
}
|
|
@@ -279,32 +224,24 @@ async function copyFromSource(
|
|
|
279
224
|
topLocalities: number,
|
|
280
225
|
progress: NonNullable<BuildSlimOptions["onProgress"]>
|
|
281
226
|
): Promise<void> {
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// ATTACH will still want a writable journal on the side; copying to /tmp dodges that without
|
|
285
|
-
// mutating the canonical files in /mnt/playpen/mailwoman-data/wof/. ATTACH / DETACH stay raw
|
|
286
|
-
// — Kysely doesn't model them.
|
|
227
|
+
// A fresh scratch copy is attached so read-only WOF distributions get the writable
|
|
228
|
+
// journal they need without mutating the canonical files.
|
|
287
229
|
await using tmpScratch = await temporaryDirectory("mailwoman-slim-src-")
|
|
288
|
-
const scratchPath = tmpScratch.
|
|
230
|
+
const scratchPath = tmpScratch.path("src.db")
|
|
289
231
|
|
|
290
232
|
await copyFileTo(inputPath, scratchPath)
|
|
291
233
|
|
|
292
|
-
out.exec(`ATTACH DATABASE '${scratchPath.replaceAll("'", "''")}' AS src;`)
|
|
234
|
+
out.exec(`ATTACH DATABASE '${scratchPath.toString().replaceAll("'", "''")}' AS src;`)
|
|
293
235
|
|
|
294
236
|
try {
|
|
295
|
-
// Does this extract carry the pre-built population aux table? The admin source does; a bare
|
|
296
|
-
// postcode extract might not. The locality ranking + population copy below adapt accordingly.
|
|
297
237
|
const srcHasPopulation = Boolean(
|
|
298
238
|
out.prepare(`SELECT 1 FROM src.sqlite_master WHERE type = 'table' AND name = '${PLACE_POPULATION_TABLE}'`).get()
|
|
299
239
|
)
|
|
300
240
|
|
|
301
|
-
// The
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// checking as the regular schema. SQLite parses the dotted identifier as a schema-name
|
|
305
|
-
// qualifier, so this works directly without any aliasing trick.
|
|
241
|
+
// The `src.spr` declaration and its peers in `BuildSchema` let Kysely
|
|
242
|
+
// column-check the cross-schema select.
|
|
243
|
+
// SQLite reads the dotted identifier as a schema qualifier.
|
|
306
244
|
|
|
307
|
-
// 1. Ancestor placetypes (country / region / county / etc.) — always-kept.
|
|
308
245
|
progress("country", `${inputPath}: ancestor placetypes in (${countries.join(",")})`)
|
|
309
246
|
|
|
310
247
|
await kysely
|
|
@@ -321,9 +258,8 @@ async function copyFromSource(
|
|
|
321
258
|
.onConflict((oc) => oc.doNothing())
|
|
322
259
|
.execute()
|
|
323
260
|
|
|
324
|
-
//
|
|
325
|
-
// aux table
|
|
326
|
-
// last). If the extract has no population table, fall back to a deterministic id ordering.
|
|
261
|
+
// Localities without a population row still qualify.
|
|
262
|
+
// Without the aux table, fall back to a deterministic id ordering.
|
|
327
263
|
progress("locality", `${inputPath}: top-${topLocalities} localities by population`)
|
|
328
264
|
|
|
329
265
|
await kysely
|
|
@@ -343,7 +279,6 @@ async function copyFromSource(
|
|
|
343
279
|
.onConflict((oc) => oc.doNothing())
|
|
344
280
|
.execute()
|
|
345
281
|
|
|
346
|
-
// 3. All postcodes in scope.
|
|
347
282
|
progress("postcode", `${inputPath}: all postcodes`)
|
|
348
283
|
|
|
349
284
|
await kysely
|
|
@@ -360,7 +295,6 @@ async function copyFromSource(
|
|
|
360
295
|
.onConflict((oc) => oc.doNothing())
|
|
361
296
|
.execute()
|
|
362
297
|
|
|
363
|
-
// 4. Pull names for the IDs we just selected.
|
|
364
298
|
progress("names", `${inputPath}: names rows for selected IDs`)
|
|
365
299
|
|
|
366
300
|
await kysely
|
|
@@ -369,7 +303,6 @@ async function copyFromSource(
|
|
|
369
303
|
.onConflict((oc) => oc.doNothing())
|
|
370
304
|
.execute()
|
|
371
305
|
|
|
372
|
-
// 5. Pull population rows for the selected IDs (sparse — only the places WOF has a count for).
|
|
373
306
|
if (srcHasPopulation) {
|
|
374
307
|
progress("place_population", `${inputPath}: population rows for selected IDs`)
|
|
375
308
|
|
|
@@ -382,11 +315,8 @@ async function copyFromSource(
|
|
|
382
315
|
.execute()
|
|
383
316
|
}
|
|
384
317
|
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// spr ids → no orphans. Tiny (~hundreds of rows). `ancestors` is intentionally NOT copied (huge
|
|
388
|
-
// + build-only), so we copy the derived table rather than rebuild it. Raw SQL — conditional +
|
|
389
|
-
// not in the Kysely build schema.
|
|
318
|
+
// `ancestors` is intentionally not copied, so the derived `coincident_roles`
|
|
319
|
+
// table is copied and filtered to surviving spr ids.
|
|
390
320
|
const relationSchema = out
|
|
391
321
|
.prepare(`SELECT sql FROM src.sqlite_master WHERE type = 'table' AND name = 'coincident_roles'`)
|
|
392
322
|
.get() as { sql?: string } | undefined
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
-
* @file Pass 2 of the candidate build
|
|
5
|
+
* @file Pass 2 of the candidate build: explode `place_search.alt_names` into distinct alias rows.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { DatabaseClient } from "@mailwoman/sqlite/client"
|
|
@@ -10,19 +10,18 @@ import { tableExists } from "@mailwoman/sqlite/introspection"
|
|
|
10
10
|
|
|
11
11
|
import type { PlaceAttrs, StageRow } from "#candidate/place-attrs"
|
|
12
12
|
import type { CandidateDatabase } from "#candidate/schema"
|
|
13
|
-
import { ALIAS_SEPARATOR } from "#fts
|
|
13
|
+
import { ALIAS_SEPARATOR } from "#fts"
|
|
14
14
|
import type { WOFDatabase } from "#schema"
|
|
15
15
|
import { normalizeLocalityForKey } from "#street/normalize"
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* The official name of every current region, keyed `${country id}\0${name key}` →
|
|
19
|
-
*
|
|
18
|
+
* The official name of every current region, keyed `${country id}\0${name key}` →
|
|
19
|
+
* region id, from the source `names` table's `official` bit.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* and every district scoped to the city contradicted it.
|
|
21
|
+
* Empty when the source has no `names` table (a fixture, or an extract shape).
|
|
22
|
+
*
|
|
23
|
+
* The positive evidence behind the alias refusal below: a name is refused from a region's
|
|
24
|
+
* bag only when another region of the same country holds it as its official name.
|
|
26
25
|
*/
|
|
27
26
|
function officialNameHoldersByRegion(
|
|
28
27
|
src: DatabaseClient<WOFDatabase>,
|
|
@@ -50,12 +49,12 @@ function officialNameHoldersByRegion(
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
/**
|
|
53
|
-
* Pass 2
|
|
54
|
-
* each place's distinct staged keys
|
|
52
|
+
* Pass 2: explode each place's `place_search.alt_names` bag into distinct-key alias rows
|
|
53
|
+
* (`is_primary = 0`), and count each place's distinct staged keys with the primary included.
|
|
55
54
|
*
|
|
56
|
-
* With `regionPlacetypeID` and `ccID` given, a
|
|
57
|
-
* refused and counted rather than staged (see {@link officialNameHoldersByRegion})
|
|
58
|
-
* alias.
|
|
55
|
+
* With `regionPlacetypeID` and `ccID` given, a region's alias that is another same-country region's
|
|
56
|
+
* official name is refused and counted rather than staged (see {@link officialNameHoldersByRegion}).
|
|
57
|
+
* Without them the pass stages every alias.
|
|
59
58
|
*/
|
|
60
59
|
export function explodeAliasBags(
|
|
61
60
|
src: DatabaseClient<WOFDatabase>,
|
|
@@ -84,9 +83,9 @@ export function explodeAliasBags(
|
|
|
84
83
|
const seen = new Set<string>([a.pkey])
|
|
85
84
|
const isRegion = a.ptid === opts.regionPlacetypeID
|
|
86
85
|
|
|
87
|
-
// The writer space-pads each separator and appends a trailing one, so every piece
|
|
88
|
-
// surrounding whitespace and the last one is empty.
|
|
89
|
-
//
|
|
86
|
+
// The writer space-pads each separator and appends a trailing one, so every piece
|
|
87
|
+
// includes surrounding whitespace and the last one is empty.
|
|
88
|
+
// `normalizeLocalityForKey` folds both away.
|
|
90
89
|
for (const piece of alt.split(ALIAS_SEPARATOR)) {
|
|
91
90
|
const k = normalizeLocalityForKey(piece)
|
|
92
91
|
|
|
@@ -3,80 +3,78 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* Typed schema for the candidate gazetteer's
|
|
6
|
+
* Typed schema for the candidate gazetteer's ancestors sidecar (`candidate_ancestor` +
|
|
7
7
|
* `candidate_interval`) — the containment lineage the candidate table itself cannot answer, built
|
|
8
8
|
* into the same `candidate.db` by `build-candidate.ts` and read by
|
|
9
9
|
* {@link WOFCandidateTableLookup.ancestors}.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* parent's placetype, display name and folded key
|
|
13
|
-
* candidate row. Decided by the two consumers:
|
|
11
|
+
* The sidecar stores denormalized closure rows. Each row represents one (place, ancestor) edge. The row includes the
|
|
12
|
+
* parent's placetype, display name and folded key. The two consumers determine this layout:
|
|
14
13
|
*
|
|
15
|
-
* 1. The admin-coherence check needs the
|
|
16
|
-
* A fixed-slot `[id
|
|
17
|
-
*
|
|
18
|
-
* 2. The account layer needs
|
|
14
|
+
* 1. The admin-coherence check needs the winner's chain as (placetype, name) pairs in one probe.
|
|
15
|
+
* A fixed-slot `[id.8]` chain returns ids. Each id would then need a name lookup, but the artifact has no
|
|
16
|
+
* per-id table. That design requires up to 8 indirections. The closure row requires none.
|
|
17
|
+
* 2. The account layer needs every candidate under a `name_key` enumerable with its chain from
|
|
19
18
|
* one artifact probe ("present-but-outranked, discriminated by containment"). That is the
|
|
20
19
|
* candidate probe (contiguous) followed by one `spr_id`-clustered closure probe per candidate
|
|
21
20
|
* — each a handful of adjacent pages.
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* 1-2 pages. The repeated parent strings are the price of the zero-join read, paid at build time.
|
|
22
|
+
* The artifact is read over HTTP byte ranges. A join to a dimension table scatters page fetches. A `without rowid`
|
|
23
|
+
* B-tree clustered on `(spr_id, depth)` keeps a whole chain in 1–2 pages. The builder repeats parent strings to
|
|
24
|
+
* avoid joins during reads.
|
|
27
25
|
*
|
|
28
|
-
* `depth` is 1 for the
|
|
29
|
-
* country
|
|
30
|
-
*
|
|
26
|
+
* `depth` is 1 for the nearest ancestor (deepest containment tier) and increases outward to the
|
|
27
|
+
* country. This matches the nearest-first order that `ancestry.ts` serves for the FTS backend. Both backends'
|
|
28
|
+
* `ancestors()` results therefore use the same order. The order within a place is deterministic:
|
|
31
29
|
* containment depth descending (`placetypeDepth`), then ancestor id ascending, capped at
|
|
32
|
-
* {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the
|
|
30
|
+
* {@link MAX_ANCESTOR_DEPTH}. `parent_name_key` is the shared {@link normalizeLocalityForKey}
|
|
33
31
|
* fold — the same fold `candidate.name_key` is built with, so a chain entry and a candidate key
|
|
34
32
|
* compare under one normalizer.
|
|
35
33
|
*
|
|
36
|
-
* `candidate_interval`
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* `
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* ABSENCE SEMANTICS (meaning-of-zero): a place with no `candidate_interval` row has no recorded
|
|
34
|
+
* `candidate_interval` stores pre/post-order labels over the canonical-parent forest. The builder assigns them
|
|
35
|
+
* during construction. The predicate `a.pre <= d.pre and d.post <= a.post` tests whether `a` contains `d` in O(1)
|
|
36
|
+
* time. It needs no chain scan or tier information. Descendants of `a` occupy the contiguous range
|
|
37
|
+
* `pre between a.pre and a.post`. Interval labels add relabeling work to updates. This database is sealed and
|
|
38
|
+
* read-only, so the builder can regenerate all labels together.
|
|
39
|
+
*
|
|
40
|
+
* WOF places can have multiple parents because boundaries can be ambiguous or participate in multiple
|
|
41
|
+
* hierarchies. `candidate_ancestor` stores every parent. Its closure rows hold the complete containment record.
|
|
42
|
+
* One interval pair encodes only a tree. The interval forest therefore links each place to one canonical parent:
|
|
43
|
+
* the depth-1 edge with the finest containment tier and lowest ancestor id. This follows the same MIN-based
|
|
44
|
+
* stability convention as the candidate table's `region_id` stamp. A containment query about another hierarchy
|
|
45
|
+
* must consult the closure rows. The interval result answers only whether a place is contained along the canonical
|
|
46
|
+
* hierarchy.
|
|
47
|
+
*
|
|
48
|
+
* absence semantics (meaning-of-zero): a place with no `candidate_interval` row has no recorded
|
|
53
49
|
* ancestry in the source (extract-fed postcodes and localities, isolated places, cycle-skipped
|
|
54
|
-
* rows). Absence is
|
|
50
|
+
* rows). Absence is unverifiable, never a containment verdict.
|
|
55
51
|
*/
|
|
56
52
|
|
|
57
53
|
import { sql, type Kysely } from "kysely"
|
|
58
54
|
|
|
59
|
-
// Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's
|
|
60
|
-
//
|
|
61
|
-
//
|
|
55
|
+
// Type-only and circular on purpose (candidate-schema extends CandidateAncestorsDatabase): Kysely's DB
|
|
56
|
+
// parameter is invariant, so the DDL functions must take the full database type their caller holds.
|
|
57
|
+
// Erased at runtime.
|
|
62
58
|
import type { CandidateDatabase } from "#candidate/schema"
|
|
63
59
|
import type { NameKey } from "#street/normalize"
|
|
64
60
|
|
|
65
61
|
/**
|
|
66
|
-
* The deepest chain the sidecar stores per place.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
62
|
+
* The deepest chain the sidecar stores per place.
|
|
63
|
+
*
|
|
64
|
+
* WOF containment within the resolvable placetypes (country … microhood) never legitimately exceeds this.
|
|
65
|
+
* The build drops and counts anything past it as source noise rather than storing it.
|
|
69
66
|
*/
|
|
70
67
|
export const MAX_ANCESTOR_DEPTH = 8
|
|
71
68
|
|
|
72
69
|
/**
|
|
73
|
-
* The closure-row table's name — probed with `hasTable` by the reader, so an artifact
|
|
74
|
-
* "no ancestors capability" rather than `no such table`.
|
|
70
|
+
* The closure-row table's name — probed with `hasTable` by the reader, so an artifact
|
|
71
|
+
* predating the sidecar degrades to "no ancestors capability" rather than `no such table`.
|
|
75
72
|
*/
|
|
76
73
|
export const CANDIDATE_ANCESTOR_TABLE = "candidate_ancestor"
|
|
77
74
|
|
|
78
75
|
/**
|
|
79
|
-
* The interval-label table's name — the closure table's seal-time sibling
|
|
76
|
+
* The interval-label table's name — the closure table's seal-time sibling.
|
|
77
|
+
* Existence-restricted the same way.
|
|
80
78
|
*/
|
|
81
79
|
export const CANDIDATE_INTERVAL_TABLE = "candidate_interval"
|
|
82
80
|
|
|
@@ -102,19 +100,24 @@ export interface CandidateAncestorTable {
|
|
|
102
100
|
*/
|
|
103
101
|
parent_placetype_id: number
|
|
104
102
|
/**
|
|
105
|
-
* The ancestor's canonical display name
|
|
103
|
+
* The ancestor's canonical display name.
|
|
104
|
+
*
|
|
105
|
+
* What {@link Ancestor.name} serves, matching the FTS backend's register.
|
|
106
106
|
*/
|
|
107
107
|
parent_name: string
|
|
108
108
|
/**
|
|
109
|
-
* The
|
|
110
|
-
* against a query-side fold under one normalizer, by construction.
|
|
109
|
+
* The shared {@link normalizeLocalityForKey} fold of `parent_name` — comparable against
|
|
110
|
+
* `candidate.name_key` and against a query-side fold under one normalizer, by construction.
|
|
111
111
|
*/
|
|
112
112
|
parent_name_key: NameKey
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* Pre/post-order labels over the canonical-parent forest — one row per place
|
|
117
|
-
* docstring for absence semantics).
|
|
116
|
+
* Pre/post-order labels over the canonical-parent forest — one row per place with
|
|
117
|
+
* recorded ancestry (see the module docstring for absence semantics).
|
|
118
|
+
*
|
|
119
|
+
* `pre < post` always.
|
|
120
|
+
* Labels are unique across the artifact.
|
|
118
121
|
*/
|
|
119
122
|
export interface CandidateIntervalTable {
|
|
120
123
|
spr_id: number
|
|
@@ -123,8 +126,10 @@ export interface CandidateIntervalTable {
|
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
/**
|
|
126
|
-
* The sidecar tables, for a `Kysely` view over the candidate DB.
|
|
127
|
-
*
|
|
129
|
+
* The sidecar tables, for a `Kysely` view over the candidate DB.
|
|
130
|
+
*
|
|
131
|
+
* `CandidateDatabase` (candidate-schema.ts) extends this, so the builder's
|
|
132
|
+
* one typed client sees both families.
|
|
128
133
|
*/
|
|
129
134
|
export interface CandidateAncestorsDatabase {
|
|
130
135
|
candidate_ancestor: CandidateAncestorTable
|
|
@@ -132,8 +137,12 @@ export interface CandidateAncestorsDatabase {
|
|
|
132
137
|
}
|
|
133
138
|
|
|
134
139
|
/**
|
|
135
|
-
* The `candidate_ancestor` columns in clustered-key order
|
|
136
|
-
*
|
|
140
|
+
* The `candidate_ancestor` columns in clustered-key order.
|
|
141
|
+
*
|
|
142
|
+
* The first two columns form the primary key.
|
|
143
|
+
* The builder's positional `insert` binds values in this order.
|
|
144
|
+
*
|
|
145
|
+
* Keep in sync with {@link CandidateAncestorTable}.
|
|
137
146
|
*/
|
|
138
147
|
export const CANDIDATE_ANCESTOR_COLUMNS = [
|
|
139
148
|
"spr_id",
|
|
@@ -145,8 +154,10 @@ export const CANDIDATE_ANCESTOR_COLUMNS = [
|
|
|
145
154
|
] as const
|
|
146
155
|
|
|
147
156
|
/**
|
|
148
|
-
* Create the clustered closure table.
|
|
149
|
-
*
|
|
157
|
+
* Create the clustered closure table.
|
|
158
|
+
*
|
|
159
|
+
* The builder inserts in `(spr_id, depth)` order so the B-tree leaves are contiguous
|
|
160
|
+
* per place — the byte-range read discipline.
|
|
150
161
|
*/
|
|
151
162
|
export async function createCandidateAncestorTable(db: Kysely<CandidateDatabase>): Promise<void> {
|
|
152
163
|
await db.schema
|
|
@@ -158,13 +169,15 @@ export async function createCandidateAncestorTable(db: Kysely<CandidateDatabase>
|
|
|
158
169
|
.addColumn("parent_name", "text", (c) => c.notNull())
|
|
159
170
|
.addColumn("parent_name_key", "text", (c) => c.notNull())
|
|
160
171
|
.addPrimaryKeyConstraint("candidate_ancestor_pk", ["spr_id", "depth"])
|
|
161
|
-
// `
|
|
172
|
+
// `without rowid` has no first-class builder. The raw modifier is the idiomatic fallback.
|
|
162
173
|
.modifyEnd(sql`without rowid`)
|
|
163
174
|
.execute()
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
/**
|
|
167
|
-
* Create the interval-label table.
|
|
178
|
+
* Create the interval-label table.
|
|
179
|
+
*
|
|
180
|
+
* Small rows probed by primary key — the `without rowid` win case.
|
|
168
181
|
*/
|
|
169
182
|
export async function createCandidateIntervalTable(db: Kysely<CandidateDatabase>): Promise<void> {
|
|
170
183
|
await db.schema
|
|
@@ -177,7 +190,8 @@ export async function createCandidateIntervalTable(db: Kysely<CandidateDatabase>
|
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
/**
|
|
180
|
-
* One place's interval label
|
|
193
|
+
* One place's interval label.
|
|
194
|
+
* The shape both sides of a containment comparison read.
|
|
181
195
|
*/
|
|
182
196
|
export interface IntervalLabel {
|
|
183
197
|
pre: number
|
|
@@ -185,10 +199,12 @@ export interface IntervalLabel {
|
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
/**
|
|
188
|
-
* Does `outer` contain `inner` along the canonical hierarchy?
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
202
|
+
* Does `outer` contain `inner` along the canonical hierarchy?
|
|
203
|
+
*
|
|
204
|
+
* Reflexive: a place contains itself (containment degenerates to identity,
|
|
205
|
+
* the same reading the admin-coherence check gives a self-match).
|
|
206
|
+
* The shared function for every consumer of the labels — a re-derived comparison risks
|
|
207
|
+
* disagreeing at exactly the strict/inclusive boundary this line settles.
|
|
192
208
|
*/
|
|
193
209
|
export function intervalContains(outer: IntervalLabel, inner: IntervalLabel): boolean {
|
|
194
210
|
return outer.pre <= inner.pre && inner.post <= outer.post
|