@mailwoman/corpus 8.6.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/out/src/adapter.d.ts +41 -0
- package/out/src/adapter.d.ts.map +1 -1
- package/out/src/adapter.js +46 -1
- package/out/src/adapter.js.map +1 -1
- package/out/src/adapters/ban/adapter.d.ts +4 -3
- package/out/src/adapters/ban/adapter.d.ts.map +1 -1
- package/out/src/adapters/ban/adapter.js +60 -67
- package/out/src/adapters/ban/adapter.js.map +1 -1
- package/out/src/adapters/ban/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/ban/street-decompose.js +18 -25
- package/out/src/adapters/ban/street-decompose.js.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.d.ts +0 -6
- package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.js +2 -24
- package/out/src/adapters/fcc-bdc/adapter.js.map +1 -1
- package/out/src/adapters/geonames/adapter.d.ts.map +1 -1
- package/out/src/adapters/geonames/adapter.js +70 -76
- package/out/src/adapters/geonames/adapter.js.map +1 -1
- package/out/src/adapters/geonames-postal/adapter.d.ts.map +1 -1
- package/out/src/adapters/geonames-postal/adapter.js +44 -49
- package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
- package/out/src/adapters/gnaf/adapter.js +4 -7
- package/out/src/adapters/gnaf/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/assemble.d.ts.map +1 -1
- package/out/src/adapters/gnaf/assemble.js +6 -11
- package/out/src/adapters/gnaf/assemble.js.map +1 -1
- package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
- package/out/src/adapters/openaddresses/adapter.js +2 -7
- package/out/src/adapters/openaddresses/adapter.js.map +1 -1
- package/out/src/adapters/overture/adapter.d.ts.map +1 -1
- package/out/src/adapters/overture/adapter.js +3 -7
- package/out/src/adapters/overture/adapter.js.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.js +55 -73
- package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.js +58 -76
- package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.js +67 -85
- package/out/src/adapters/state-ny-notaries/adapter.js.map +1 -1
- package/out/src/adapters/state-tx-notaries/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-tx-notaries/adapter.js +69 -87
- package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.js +7 -15
- package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
- package/out/src/adapters/tiger/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/tiger/street-decompose.js +18 -27
- package/out/src/adapters/tiger/street-decompose.js.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts +3 -3
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +62 -80
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.js +60 -82
- package/out/src/adapters/usgov-imls-pls/adapter.js.map +1 -1
- package/out/src/adapters/usgov-irs-bmf/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-irs-bmf/adapter.js +58 -65
- package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.js +5 -8
- package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.js +58 -76
- package/out/src/adapters/usgov-nppes/adapter.js.map +1 -1
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js +51 -71
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.js +24 -6
- package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
- package/out/src/build.d.ts.map +1 -1
- package/out/src/build.js +2 -1
- package/out/src/build.js.map +1 -1
- package/out/src/golden.d.ts +5 -0
- package/out/src/golden.d.ts.map +1 -1
- package/out/src/golden.js +23 -9
- package/out/src/golden.js.map +1 -1
- package/out/src/parquet-wrapper/reader.d.ts +8 -0
- package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
- package/out/src/parquet-wrapper/reader.js +16 -0
- package/out/src/parquet-wrapper/reader.js.map +1 -1
- package/out/src/parquet-wrapper/schema.d.ts +4 -0
- package/out/src/parquet-wrapper/schema.d.ts.map +1 -1
- package/out/src/parquet-wrapper/schema.js.map +1 -1
- package/out/src/parquet.d.ts.map +1 -1
- package/out/src/parquet.js +2 -13
- package/out/src/parquet.js.map +1 -1
- package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -1
- package/out/src/shard-recipes/anchor-absorption.js +2 -1
- package/out/src/shard-recipes/anchor-absorption.js.map +1 -1
- package/out/src/shard-recipes/country-balanced.d.ts.map +1 -1
- package/out/src/shard-recipes/country-balanced.js +68 -60
- package/out/src/shard-recipes/country-balanced.js.map +1 -1
- package/out/src/shard-recipes/fr-fragment.d.ts.map +1 -1
- package/out/src/shard-recipes/fr-fragment.js +8 -5
- package/out/src/shard-recipes/fr-fragment.js.map +1 -1
- package/out/src/shard-recipes/fr-order.d.ts.map +1 -1
- package/out/src/shard-recipes/fr-order.js +14 -58
- package/out/src/shard-recipes/fr-order.js.map +1 -1
- package/out/src/shard-recipes/german.d.ts.map +1 -1
- package/out/src/shard-recipes/german.js +11 -58
- package/out/src/shard-recipes/german.js.map +1 -1
- package/out/src/shard-recipes/index.d.ts.map +1 -1
- package/out/src/shard-recipes/index.js +2 -0
- package/out/src/shard-recipes/index.js.map +1 -1
- package/out/src/shard-recipes/intersection.d.ts +2 -2
- package/out/src/shard-recipes/intersection.d.ts.map +1 -1
- package/out/src/shard-recipes/intersection.js +24 -62
- package/out/src/shard-recipes/intersection.js.map +1 -1
- package/out/src/shard-recipes/locale.d.ts.map +1 -1
- package/out/src/shard-recipes/locale.js +14 -16
- package/out/src/shard-recipes/locale.js.map +1 -1
- package/out/src/shard-recipes/no-fragment.d.ts.map +1 -1
- package/out/src/shard-recipes/no-fragment.js +8 -5
- package/out/src/shard-recipes/no-fragment.js.map +1 -1
- package/out/src/shard-recipes/no-street-led.d.ts.map +1 -1
- package/out/src/shard-recipes/no-street-led.js +8 -5
- package/out/src/shard-recipes/no-street-led.js.map +1 -1
- package/out/src/shard-recipes/po-box-cedex.d.ts +3 -3
- package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -1
- package/out/src/shard-recipes/po-box-cedex.js +51 -94
- package/out/src/shard-recipes/po-box-cedex.js.map +1 -1
- package/out/src/shard-recipes/scaffold.d.ts +59 -9
- package/out/src/shard-recipes/scaffold.d.ts.map +1 -1
- package/out/src/shard-recipes/scaffold.js +51 -30
- package/out/src/shard-recipes/scaffold.js.map +1 -1
- package/out/src/shard-recipes/street-affix.d.ts.map +1 -1
- package/out/src/shard-recipes/street-affix.js +56 -82
- package/out/src/shard-recipes/street-affix.js.map +1 -1
- package/out/src/shard-recipes/sub-venue-sources.d.ts +231 -0
- package/out/src/shard-recipes/sub-venue-sources.d.ts.map +1 -0
- package/out/src/shard-recipes/sub-venue-sources.js +669 -0
- package/out/src/shard-recipes/sub-venue-sources.js.map +1 -0
- package/out/src/shard-recipes/sub-venue.d.ts +269 -0
- package/out/src/shard-recipes/sub-venue.d.ts.map +1 -0
- package/out/src/shard-recipes/sub-venue.js +709 -0
- package/out/src/shard-recipes/sub-venue.js.map +1 -0
- package/out/src/shard-recipes/unit.d.ts +1 -1
- package/out/src/shard-recipes/unit.d.ts.map +1 -1
- package/out/src/shard-recipes/unit.js +22 -64
- package/out/src/shard-recipes/unit.js.map +1 -1
- package/out/src/split.d.ts +7 -0
- package/out/src/split.d.ts.map +1 -1
- package/out/src/split.js +7 -0
- package/out/src/split.js.map +1 -1
- package/out/src/synthesize.d.ts.map +1 -1
- package/out/src/synthesize.js +1 -12
- package/out/src/synthesize.js.map +1 -1
- package/out/src/tools/align-shard.d.ts.map +1 -1
- package/out/src/tools/align-shard.js +3 -7
- package/out/src/tools/align-shard.js.map +1 -1
- package/out/src/tools/audit.d.ts.map +1 -1
- package/out/src/tools/audit.js +4 -4
- package/out/src/tools/audit.js.map +1 -1
- package/out/src/tools/corpus-stats.d.ts +2 -2
- package/out/src/tools/corpus-stats.d.ts.map +1 -1
- package/out/src/tools/corpus-stats.js +18 -31
- package/out/src/tools/corpus-stats.js.map +1 -1
- package/out/src/tools/fetch/download.d.ts.map +1 -1
- package/out/src/tools/fetch/download.js +5 -6
- package/out/src/tools/fetch/download.js.map +1 -1
- package/out/src/tools/fetch/imls-pls.d.ts.map +1 -1
- package/out/src/tools/fetch/imls-pls.js +2 -2
- package/out/src/tools/fetch/imls-pls.js.map +1 -1
- package/out/src/tools/fetch/index.d.ts +14 -1
- package/out/src/tools/fetch/index.d.ts.map +1 -1
- package/out/src/tools/fetch/index.js +14 -1
- package/out/src/tools/fetch/index.js.map +1 -1
- package/out/src/tools/fetch/nad.d.ts +1 -1
- package/out/src/tools/fetch/nad.js +1 -1
- package/out/src/tools/fetch/nppes.d.ts.map +1 -1
- package/out/src/tools/fetch/nppes.js +2 -1
- package/out/src/tools/fetch/nppes.js.map +1 -1
- package/out/src/tools/fetch/openaddresses.d.ts +1 -1
- package/out/src/tools/fetch/openaddresses.js +1 -1
- package/out/src/tools/fetch/ourairports.d.ts +45 -0
- package/out/src/tools/fetch/ourairports.d.ts.map +1 -0
- package/out/src/tools/fetch/ourairports.js +123 -0
- package/out/src/tools/fetch/ourairports.js.map +1 -0
- package/out/src/tools/fetch/wikidata-subvenue.d.ts +171 -0
- package/out/src/tools/fetch/wikidata-subvenue.d.ts.map +1 -0
- package/out/src/tools/fetch/wikidata-subvenue.js +275 -0
- package/out/src/tools/fetch/wikidata-subvenue.js.map +1 -0
- package/out/src/tools/golden-expand.d.ts.map +1 -1
- package/out/src/tools/golden-expand.js +18 -16
- package/out/src/tools/golden-expand.js.map +1 -1
- package/out/src/tools/golden-promote.d.ts.map +1 -1
- package/out/src/tools/golden-promote.js +12 -6
- package/out/src/tools/golden-promote.js.map +1 -1
- package/out/src/tools/golden-relabel-street.d.ts +196 -0
- package/out/src/tools/golden-relabel-street.d.ts.map +1 -0
- package/out/src/tools/golden-relabel-street.js +513 -0
- package/out/src/tools/golden-relabel-street.js.map +1 -0
- package/out/src/tools/index.d.ts +4 -0
- package/out/src/tools/index.d.ts.map +1 -1
- package/out/src/tools/index.js +4 -0
- package/out/src/tools/index.js.map +1 -1
- package/out/src/tools/jsonl-to-parquet.d.ts.map +1 -1
- package/out/src/tools/jsonl-to-parquet.js +3 -2
- package/out/src/tools/jsonl-to-parquet.js.map +1 -1
- package/out/src/tools/lint-shard-vocab.d.ts.map +1 -1
- package/out/src/tools/lint-shard-vocab.js +3 -2
- package/out/src/tools/lint-shard-vocab.js.map +1 -1
- package/out/src/tools/lint-shard.d.ts +3 -3
- package/out/src/tools/lint-shard.d.ts.map +1 -1
- package/out/src/tools/lint-shard.js +23 -32
- package/out/src/tools/lint-shard.js.map +1 -1
- package/out/src/tools/overlay-manifest.d.ts.map +1 -1
- package/out/src/tools/overlay-manifest.js +2 -1
- package/out/src/tools/overlay-manifest.js.map +1 -1
- package/out/src/tools/overture-subvenue.d.ts +112 -0
- package/out/src/tools/overture-subvenue.d.ts.map +1 -0
- package/out/src/tools/overture-subvenue.js +144 -0
- package/out/src/tools/overture-subvenue.js.map +1 -0
- package/out/src/tools/shard-kryptonite.d.ts +1 -1
- package/out/src/tools/shard-kryptonite.d.ts.map +1 -1
- package/out/src/tools/shard-kryptonite.js +5 -4
- package/out/src/tools/shard-kryptonite.js.map +1 -1
- package/out/src/tools/shard-translit.d.ts +6 -3
- package/out/src/tools/shard-translit.d.ts.map +1 -1
- package/out/src/tools/shard-translit.js +8 -6
- package/out/src/tools/shard-translit.js.map +1 -1
- package/out/src/tools/sub-venue-lexicon.d.ts +507 -0
- package/out/src/tools/sub-venue-lexicon.d.ts.map +1 -0
- package/out/src/tools/sub-venue-lexicon.js +817 -0
- package/out/src/tools/sub-venue-lexicon.js.map +1 -0
- package/out/src/tools/sub-venue-promotions.d.ts +94 -0
- package/out/src/tools/sub-venue-promotions.d.ts.map +1 -0
- package/out/src/tools/sub-venue-promotions.js +266 -0
- package/out/src/tools/sub-venue-promotions.js.map +1 -0
- package/out/src/wof-json.d.ts +2 -2
- package/out/src/wof-json.d.ts.map +1 -1
- package/out/src/wof-json.js +5 -8
- package/out/src/wof-json.js.map +1 -1
- package/package.json +8 -8
- package/src/adapter.ts +58 -1
- package/src/adapters/ban/adapter.ts +55 -65
- package/src/adapters/ban/street-decompose.ts +17 -25
- package/src/adapters/fcc-bdc/adapter.ts +2 -33
- package/src/adapters/geonames/adapter.ts +64 -72
- package/src/adapters/geonames-postal/adapter.ts +42 -50
- package/src/adapters/gnaf/adapter.ts +4 -7
- package/src/adapters/gnaf/assemble.ts +6 -10
- package/src/adapters/openaddresses/adapter.ts +2 -7
- package/src/adapters/overture/adapter.ts +3 -6
- package/src/adapters/state-hi-schools/adapter.ts +50 -73
- package/src/adapters/state-ia-contractors/adapter.ts +52 -76
- package/src/adapters/state-ny-notaries/adapter.ts +61 -85
- package/src/adapters/state-tx-notaries/adapter.ts +60 -84
- package/src/adapters/synth-po-box/adapter.ts +7 -17
- package/src/adapters/tiger/street-decompose.ts +21 -31
- package/src/adapters/usgov-hrsa-fqhc/adapter.ts +61 -84
- package/src/adapters/usgov-imls-pls/adapter.ts +54 -82
- package/src/adapters/usgov-irs-bmf/adapter.ts +53 -63
- package/src/adapters/usgov-nad/adapter.ts +5 -8
- package/src/adapters/usgov-nppes/adapter.ts +51 -75
- package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +45 -71
- package/src/adapters/wof-admin-jp/adapter.ts +34 -8
- package/src/build.ts +2 -1
- package/src/golden.ts +24 -9
- package/src/parquet-wrapper/reader.ts +19 -0
- package/src/parquet-wrapper/schema.ts +4 -0
- package/src/parquet.ts +3 -16
- package/src/shard-recipes/anchor-absorption.ts +2 -1
- package/src/shard-recipes/country-balanced.ts +69 -70
- package/src/shard-recipes/fr-fragment.ts +10 -7
- package/src/shard-recipes/fr-order.ts +13 -63
- package/src/shard-recipes/german.ts +12 -65
- package/src/shard-recipes/index.ts +2 -0
- package/src/shard-recipes/intersection.ts +32 -60
- package/src/shard-recipes/locale.ts +14 -16
- package/src/shard-recipes/no-fragment.ts +10 -7
- package/src/shard-recipes/no-street-led.ts +10 -7
- package/src/shard-recipes/po-box-cedex.ts +68 -119
- package/src/shard-recipes/scaffold.ts +82 -28
- package/src/shard-recipes/street-affix.ts +58 -95
- package/src/shard-recipes/sub-venue-sources.ts +895 -0
- package/src/shard-recipes/sub-venue.ts +982 -0
- package/src/shard-recipes/unit.ts +22 -70
- package/src/split.ts +7 -0
- package/src/synthesize.ts +1 -15
- package/src/tools/align-shard.ts +3 -6
- package/src/tools/audit.ts +6 -5
- package/src/tools/corpus-stats.ts +25 -33
- package/src/tools/fetch/download.ts +7 -5
- package/src/tools/fetch/imls-pls.ts +2 -2
- package/src/tools/fetch/index.ts +14 -1
- package/src/tools/fetch/nad.ts +1 -1
- package/src/tools/fetch/nppes.ts +2 -1
- package/src/tools/fetch/openaddresses.ts +1 -1
- package/src/tools/fetch/ourairports.ts +166 -0
- package/src/tools/fetch/wikidata-subvenue.ts +386 -0
- package/src/tools/golden-expand.ts +18 -13
- package/src/tools/golden-promote.ts +15 -6
- package/src/tools/golden-relabel-street.ts +748 -0
- package/src/tools/index.ts +4 -0
- package/src/tools/jsonl-to-parquet.ts +3 -2
- package/src/tools/lint-shard-vocab.ts +3 -2
- package/src/tools/lint-shard.ts +33 -36
- package/src/tools/overlay-manifest.ts +2 -1
- package/src/tools/overture-subvenue.ts +215 -0
- package/src/tools/shard-kryptonite.ts +5 -4
- package/src/tools/shard-translit.ts +12 -7
- package/src/tools/sub-venue-lexicon.ts +1250 -0
- package/src/tools/sub-venue-promotions.ts +330 -0
- package/src/wof-json.ts +5 -8
|
@@ -25,14 +25,13 @@
|
|
|
25
25
|
* conservative `ODbL-1.0` label, which wrongly read as Tier-C-denied in the corpus license audit.
|
|
26
26
|
* The model card MUST carry the BAN attribution (Tier B obligation).
|
|
27
27
|
*
|
|
28
|
-
* The adapter is streaming-aware:
|
|
29
|
-
* sits in memory. Honors `opts.limit` for fixture / smoke runs,
|
|
30
|
-
* and `opts.country` for a self-consistency check (errors if
|
|
28
|
+
* The adapter is streaming-aware: `CSVSpliterator.fromAsync` reads the `;`-delimited dump row by
|
|
29
|
+
* row, so a 25M-row file never sits in memory. Honors `opts.limit` for fixture / smoke runs,
|
|
30
|
+
* `opts.signal` for cancellation, and `opts.country` for a self-consistency check (errors if
|
|
31
|
+
* country !== FR).
|
|
31
32
|
*/
|
|
32
33
|
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
import { parse as csvParse } from "csv-parse"
|
|
34
|
+
import { CSVSpliterator } from "spliterator"
|
|
36
35
|
|
|
37
36
|
import { stableSourceID } from "../../adapter.ts"
|
|
38
37
|
import { reconcileComponents } from "../../format.ts"
|
|
@@ -108,86 +107,77 @@ export function createBanAdapter(): CorpusAdapter {
|
|
|
108
107
|
throw new Error(`ban adapter: only FR supported, got country=${opts.country}`)
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
skip_empty_lines: true,
|
|
118
|
-
relax_quotes: true,
|
|
119
|
-
relax_column_count: true,
|
|
120
|
-
})
|
|
121
|
-
)
|
|
110
|
+
const rows = CSVSpliterator.fromAsync(opts.inputPath, {
|
|
111
|
+
mode: "object",
|
|
112
|
+
normalizeKeys: false,
|
|
113
|
+
columnDelimiter: ";",
|
|
114
|
+
enableQuoteHandling: true,
|
|
115
|
+
})
|
|
122
116
|
|
|
123
117
|
let emitted = 0
|
|
124
118
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (opts.signal?.aborted) break
|
|
128
|
-
|
|
129
|
-
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
119
|
+
for await (const record of rows as AsyncIterable<BanRow>) {
|
|
120
|
+
if (opts.signal?.aborted) break
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
const street = (record.nom_voie ?? "").trim()
|
|
133
|
-
const postcode = (record.code_postal ?? "").trim()
|
|
134
|
-
const locality = (record.nom_commune ?? "").trim()
|
|
122
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
135
123
|
|
|
136
|
-
|
|
124
|
+
const house = composeHouseNumber(record.numero ?? "", record.rep ?? "")
|
|
125
|
+
const street = (record.nom_voie ?? "").trim()
|
|
126
|
+
const postcode = (record.code_postal ?? "").trim()
|
|
127
|
+
const locality = (record.nom_commune ?? "").trim()
|
|
137
128
|
|
|
138
|
-
|
|
129
|
+
if (!street || !locality) continue
|
|
139
130
|
|
|
140
|
-
|
|
131
|
+
if (!house && !postcode) continue
|
|
141
132
|
|
|
142
|
-
|
|
133
|
+
const decomposed = decomposeFrStreet(street)
|
|
143
134
|
|
|
144
|
-
|
|
145
|
-
components.house_number = house
|
|
146
|
-
}
|
|
135
|
+
const components: CanonicalRow["components"] = {}
|
|
147
136
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
if (house) {
|
|
138
|
+
components.house_number = house
|
|
139
|
+
}
|
|
151
140
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
141
|
+
if (decomposed.prefix) {
|
|
142
|
+
components.street_prefix = decomposed.prefix
|
|
143
|
+
}
|
|
155
144
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
145
|
+
if (decomposed.street) {
|
|
146
|
+
components.street = decomposed.street
|
|
147
|
+
}
|
|
159
148
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
149
|
+
if (postcode) {
|
|
150
|
+
components.postcode = postcode
|
|
151
|
+
}
|
|
163
152
|
|
|
164
|
-
|
|
153
|
+
if (locality) {
|
|
154
|
+
components.locality = locality
|
|
155
|
+
}
|
|
165
156
|
|
|
166
|
-
|
|
157
|
+
const raw = composeRaw(house, street, postcode, locality)
|
|
167
158
|
|
|
168
|
-
|
|
159
|
+
if (!raw) continue
|
|
169
160
|
|
|
170
|
-
|
|
161
|
+
const aligned = reconcileComponents(components, raw)
|
|
171
162
|
|
|
172
|
-
|
|
173
|
-
? `${BAN_ADAPTER_ID}-${record.id.trim()}`
|
|
174
|
-
: stableSourceID(BAN_ADAPTER_ID, aligned)
|
|
163
|
+
if (!Object.keys(aligned).length) continue
|
|
175
164
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
country: "FR",
|
|
180
|
-
locale: "fr-FR",
|
|
181
|
-
source: BAN_ADAPTER_ID,
|
|
182
|
-
source_id: sourceID,
|
|
183
|
-
corpus_version: "",
|
|
184
|
-
license: "Licence Ouverte 2.0",
|
|
185
|
-
}
|
|
165
|
+
const sourceID = record.id?.trim()
|
|
166
|
+
? `${BAN_ADAPTER_ID}-${record.id.trim()}`
|
|
167
|
+
: stableSourceID(BAN_ADAPTER_ID, aligned)
|
|
186
168
|
|
|
187
|
-
|
|
169
|
+
yield {
|
|
170
|
+
raw,
|
|
171
|
+
components: aligned,
|
|
172
|
+
country: "FR",
|
|
173
|
+
locale: "fr-FR",
|
|
174
|
+
source: BAN_ADAPTER_ID,
|
|
175
|
+
source_id: sourceID,
|
|
176
|
+
corpus_version: "",
|
|
177
|
+
license: "Licence Ouverte 2.0",
|
|
188
178
|
}
|
|
189
|
-
|
|
190
|
-
|
|
179
|
+
|
|
180
|
+
emitted++
|
|
191
181
|
}
|
|
192
182
|
},
|
|
193
183
|
}
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { readFileSync } from "node:fs"
|
|
19
|
-
import { resolve } from "node:path"
|
|
20
19
|
|
|
21
20
|
import { resourceDictionaryPath } from "@mailwoman/core/utils"
|
|
21
|
+
import { TextSpliterator } from "spliterator"
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Tokens a BAN street needs before a type/article/name decomposition is attempted.
|
|
@@ -26,38 +26,30 @@ import { resourceDictionaryPath } from "@mailwoman/core/utils"
|
|
|
26
26
|
const MIN_TOKENS_FOR_DECOMPOSE = 3
|
|
27
27
|
|
|
28
28
|
function loadDictionary(filename: string): Set<string> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// `resourceDictionaryPath` already resolves both layouts — `core/data/...` from source and from the
|
|
30
|
+
// packaged `out/` tree. The candidate list this replaced named it TWICE and then guessed a third path
|
|
31
|
+
// off `process.cwd()`, and swallowed every error while probing, so a corrupt dictionary reported as a
|
|
32
|
+
// missing one.
|
|
33
|
+
const text = readFileSync(resourceDictionaryPath("libpostal", "fr", filename), "utf8")
|
|
34
|
+
const set = new Set<string>()
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const set = new Set<string>()
|
|
36
|
+
// The largest libpostal dictionary is 8.4 KB, and this runs once per process at module load.
|
|
37
|
+
for (const line of TextSpliterator.from(text)) {
|
|
38
|
+
const trimmed = line.trim()
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const trimmed = line.trim()
|
|
40
|
+
if (!trimmed || trimmed.startsWith("#")) continue
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
// libpostal format: canonical|abbr|abbr|... — index all forms
|
|
43
|
+
for (const form of trimmed.split("|")) {
|
|
44
|
+
const f = form.trim().toLowerCase()
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (f) {
|
|
49
|
-
set.add(f)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
46
|
+
if (f) {
|
|
47
|
+
set.add(f)
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
return set
|
|
55
|
-
} catch {
|
|
56
|
-
// try next
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
|
|
60
|
-
|
|
52
|
+
return set
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
const STREET_TYPES_FR = loadDictionary("street_types.txt")
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
import { DatabaseSync } from "node:sqlite"
|
|
38
38
|
|
|
39
|
+
import { splitStreetLine } from "../../adapter.ts"
|
|
39
40
|
import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
|
|
40
41
|
import { formatAddress, reconcileComponents } from "../../format.ts"
|
|
41
42
|
import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
|
|
@@ -63,38 +64,6 @@ interface BdcLocationRow {
|
|
|
63
64
|
zip_suffix: string | null
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
/**
|
|
67
|
-
* Split `address_primary` into a `(house_number, street)` pair.
|
|
68
|
-
*
|
|
69
|
-
* BDC's `address_primary` follows USPS Publication 28 conventions but with hand-entry drift. The canonical
|
|
70
|
-
* leading-digit prefix is the house number (`"123 Main St"`, `"6450 W Indian School Rd"`, even hyphenated forms `"40-12
|
|
71
|
-
* Bell Blvd"`). Anything that doesn't match the prefix shape (`"PO Box 1234"`, `"RR 2 Box 67"`, `"HC 1"`) is left as a
|
|
72
|
-
* single `street` value — the model sees the original surface form, and downstream classifiers/po-box handling can pick
|
|
73
|
-
* it up.
|
|
74
|
-
*
|
|
75
|
-
* The regex tolerates one trailing letter (`"123A Main St"`) and an optional hyphenated half (`"40-12"`) which is
|
|
76
|
-
* common in NYC + suburban garden-apartment numbering.
|
|
77
|
-
*/
|
|
78
|
-
const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
|
|
79
|
-
|
|
80
|
-
interface SplitAddress {
|
|
81
|
-
house_number?: string
|
|
82
|
-
street: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function splitAddressPrimary(address: string): SplitAddress | null {
|
|
86
|
-
const trimmed = address.trim()
|
|
87
|
-
|
|
88
|
-
if (!trimmed) return null
|
|
89
|
-
const match = HOUSE_NUMBER_PREFIX.exec(trimmed)
|
|
90
|
-
|
|
91
|
-
if (match) {
|
|
92
|
-
return { house_number: match[1], street: match[2]!.trim() }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { street: trimmed }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
67
|
/**
|
|
99
68
|
* Combine `zip` + optional `zip_suffix` into the canonical USPS postcode surface form.
|
|
100
69
|
*
|
|
@@ -150,7 +119,7 @@ export function createFccBdcAdapter(): CorpusAdapter {
|
|
|
150
119
|
|
|
151
120
|
if (opts.limit !== undefined && emitted >= opts.limit) return
|
|
152
121
|
|
|
153
|
-
const split =
|
|
122
|
+
const split = splitStreetLine(row.address_primary ?? "")
|
|
154
123
|
|
|
155
124
|
if (!split) continue
|
|
156
125
|
const state = lookupStateAbbreviation(row.state)
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
* License: stamped `"CC-BY-4.0"` per row (GeoNames' terms); provenance is the `geonames-<id>` key.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import {
|
|
32
|
-
import { dirname, join } from "node:path"
|
|
31
|
+
import { existsSync } from "node:fs"
|
|
33
32
|
|
|
34
|
-
import {
|
|
33
|
+
import { dirname, join } from "path-ts"
|
|
34
|
+
import { TSVSpliterator } from "spliterator"
|
|
35
35
|
|
|
36
36
|
import { stableSourceID } from "../../adapter.ts"
|
|
37
37
|
import { reconcileComponents } from "../../format.ts"
|
|
@@ -69,16 +69,14 @@ const NON_CURRENT_PPL = new Set(["PPLH", "PPLQ", "PPLW", "PPLCH"])
|
|
|
69
69
|
/**
|
|
70
70
|
* Load `admin1CodesASCII.txt` → Map("<CC>.<admin1>" → region name). Empty map if absent.
|
|
71
71
|
*/
|
|
72
|
-
function loadAdmin1(dir: string): Map<string, string
|
|
72
|
+
async function loadAdmin1(dir: string): Promise<Map<string, string>> {
|
|
73
73
|
const map = new Map<string, string>()
|
|
74
74
|
const fp = join(dir, "admin1CodesASCII.txt")
|
|
75
75
|
|
|
76
76
|
if (!existsSync(fp)) return map
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const cols = line.split("\t")
|
|
81
|
-
|
|
78
|
+
// `header: false` — the file is headerless, and the spliterator eats row 1 as a header otherwise.
|
|
79
|
+
for await (const cols of TSVSpliterator.fromAsync(fp, { header: false })) {
|
|
82
80
|
if (cols[0] && cols[1]) {
|
|
83
81
|
map.set(cols[0], cols[1])
|
|
84
82
|
}
|
|
@@ -90,15 +88,16 @@ function loadAdmin1(dir: string): Map<string, string> {
|
|
|
90
88
|
/**
|
|
91
89
|
* Load `countryInfo.txt` → Map(ISO → country name). Empty map if absent. The file is `#`-commented.
|
|
92
90
|
*/
|
|
93
|
-
function loadCountries(dir: string): Map<string, string
|
|
91
|
+
async function loadCountries(dir: string): Promise<Map<string, string>> {
|
|
94
92
|
const map = new Map<string, string>()
|
|
95
93
|
const fp = join(dir, "countryInfo.txt")
|
|
96
94
|
|
|
97
95
|
if (!existsSync(fp)) return map
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
// `header: false` — the file's header IS a `#` comment, so it falls out with the other comments
|
|
98
|
+
// rather than being consumed as column names.
|
|
99
|
+
for await (const cols of TSVSpliterator.fromAsync(fp, { header: false })) {
|
|
100
|
+
if (cols[0]?.startsWith("#")) continue
|
|
102
101
|
|
|
103
102
|
// ISO(0), ISO3(1), iso-numeric(2), fips(3), Country(4), ...
|
|
104
103
|
if (cols[0] && cols[4]) {
|
|
@@ -118,85 +117,78 @@ export function createGeonamesAdapter(): CorpusAdapter {
|
|
|
118
117
|
|
|
119
118
|
async *rows(opts: AdapterOptions): AsyncIterable<CanonicalRow> {
|
|
120
119
|
const dir = dirname(opts.inputPath)
|
|
121
|
-
const admin1 = loadAdmin1(dir)
|
|
122
|
-
const countries = loadCountries(dir)
|
|
123
|
-
|
|
124
|
-
const stream = createReadStream(opts.inputPath, { encoding: "utf8" })
|
|
120
|
+
const admin1 = await loadAdmin1(dir)
|
|
121
|
+
const countries = await loadCountries(dir)
|
|
125
122
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
123
|
+
// `header: false` — the per-country dump is headerless.
|
|
124
|
+
const rows = TSVSpliterator.fromAsync(opts.inputPath, { header: false })
|
|
129
125
|
|
|
130
126
|
let emitted = 0
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (opts.signal?.aborted) break
|
|
128
|
+
for await (const rec of rows) {
|
|
129
|
+
if (opts.signal?.aborted) break
|
|
135
130
|
|
|
136
|
-
|
|
131
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
137
132
|
|
|
138
|
-
|
|
133
|
+
if (rec[COL.featureClass] !== "P") continue
|
|
139
134
|
|
|
140
|
-
|
|
135
|
+
if (NON_CURRENT_PPL.has(rec[COL.featureCode] ?? "")) continue
|
|
141
136
|
|
|
142
|
-
|
|
137
|
+
const cc = (rec[COL.country] ?? "").trim()
|
|
143
138
|
|
|
144
|
-
|
|
139
|
+
if (!cc) continue
|
|
145
140
|
|
|
146
|
-
|
|
141
|
+
if (opts.country && cc !== opts.country) continue
|
|
147
142
|
|
|
148
|
-
|
|
143
|
+
const locality = (rec[COL.name] ?? "").trim()
|
|
149
144
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
if (!locality) continue
|
|
146
|
+
const geonameid = (rec[COL.geonameid] ?? "").trim()
|
|
147
|
+
const region = admin1.get(`${cc}.${(rec[COL.admin1] ?? "").trim()}`)
|
|
148
|
+
const country = countries.get(cc)
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
// Two hierarchy variants (domestic + international order) — but only emit the
|
|
151
|
+
// distinct ones the available names support.
|
|
152
|
+
const variants: Array<{ slot: string; comp: CanonicalRow["components"]; raw: string }> = []
|
|
158
153
|
|
|
159
|
-
|
|
160
|
-
|
|
154
|
+
if (region) {
|
|
155
|
+
variants.push({ slot: "lr", comp: { locality, region }, raw: `${locality}, ${region}` })
|
|
161
156
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
} else if (country) {
|
|
170
|
-
variants.push({ slot: "lc", comp: { locality, country }, raw: `${locality}, ${country}` })
|
|
171
|
-
} else {
|
|
172
|
-
variants.push({ slot: "l", comp: { locality }, raw: locality })
|
|
157
|
+
if (country) {
|
|
158
|
+
variants.push({
|
|
159
|
+
slot: "lrc",
|
|
160
|
+
comp: { locality, region, country },
|
|
161
|
+
raw: `${locality}, ${region}, ${country}`,
|
|
162
|
+
})
|
|
173
163
|
}
|
|
164
|
+
} else if (country) {
|
|
165
|
+
variants.push({ slot: "lc", comp: { locality, country }, raw: `${locality}, ${country}` })
|
|
166
|
+
} else {
|
|
167
|
+
variants.push({ slot: "l", comp: { locality }, raw: locality })
|
|
168
|
+
}
|
|
174
169
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
emitted++
|
|
170
|
+
for (const v of variants) {
|
|
171
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
172
|
+
const aligned = reconcileComponents(v.comp, v.raw)
|
|
173
|
+
|
|
174
|
+
if (!Object.keys(aligned).length) continue
|
|
175
|
+
|
|
176
|
+
const sourceID = geonameid
|
|
177
|
+
? `${GEONAMES_ADAPTER_ID}-${geonameid}-${v.slot}`
|
|
178
|
+
: stableSourceID(GEONAMES_ADAPTER_ID, aligned)
|
|
179
|
+
|
|
180
|
+
yield {
|
|
181
|
+
raw: v.raw,
|
|
182
|
+
components: aligned,
|
|
183
|
+
country: cc,
|
|
184
|
+
source: GEONAMES_ADAPTER_ID,
|
|
185
|
+
source_id: sourceID,
|
|
186
|
+
corpus_version: "",
|
|
187
|
+
license: GEONAMES_DEFAULT_LICENSE,
|
|
196
188
|
}
|
|
189
|
+
|
|
190
|
+
emitted++
|
|
197
191
|
}
|
|
198
|
-
} finally {
|
|
199
|
-
stream.destroy()
|
|
200
192
|
}
|
|
201
193
|
},
|
|
202
194
|
}
|
|
@@ -23,9 +23,7 @@
|
|
|
23
23
|
* `"CC-BY-4.0"` per row (attribute "GeoNames").
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
import { parse as csvParse } from "csv-parse"
|
|
26
|
+
import { TSVSpliterator } from "spliterator"
|
|
29
27
|
|
|
30
28
|
import { stableSourceID } from "../../adapter.ts"
|
|
31
29
|
import { reconcileComponents } from "../../format.ts"
|
|
@@ -55,67 +53,61 @@ export function createGeonamesPostalAdapter(): CorpusAdapter {
|
|
|
55
53
|
"GeoNames postal codes (CC-BY-4.0) — multi-locale postcode→locality→region, names inline; international postcode-first order.",
|
|
56
54
|
|
|
57
55
|
async *rows(opts: AdapterOptions): AsyncIterable<CanonicalRow> {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
csvParse({ delimiter: "\t", quote: false, relax_column_count: true, skip_empty_lines: true })
|
|
62
|
-
)
|
|
56
|
+
// `header: false` — the GeoNames postal dump is headerless, and the spliterator would
|
|
57
|
+
// otherwise consume row 1 as column names and lose its first postcode.
|
|
58
|
+
const rows = TSVSpliterator.fromAsync(opts.inputPath, { header: false })
|
|
63
59
|
|
|
64
60
|
let emitted = 0
|
|
65
61
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (opts.signal?.aborted) break
|
|
69
|
-
|
|
70
|
-
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
71
|
-
|
|
72
|
-
const cc = (rec[COL.country] ?? "").trim()
|
|
62
|
+
for await (const rec of rows as AsyncIterable<string[]>) {
|
|
63
|
+
if (opts.signal?.aborted) break
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
75
66
|
|
|
76
|
-
|
|
67
|
+
const cc = (rec[COL.country] ?? "").trim()
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
const locality = (rec[COL.place] ?? "").trim()
|
|
69
|
+
if (!cc) continue
|
|
80
70
|
|
|
81
|
-
|
|
82
|
-
const region = (rec[COL.admin1Name] ?? "").trim()
|
|
71
|
+
if (opts.country && cc !== opts.country) continue
|
|
83
72
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const variants: Array<{ slot: string; comp: CanonicalRow["components"]; raw: string }> = [
|
|
87
|
-
{ slot: "pl", comp: { postcode, locality }, raw: `${postcode} ${locality}` },
|
|
88
|
-
]
|
|
73
|
+
const postcode = (rec[COL.postcode] ?? "").trim()
|
|
74
|
+
const locality = (rec[COL.place] ?? "").trim()
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
slot: "plr",
|
|
93
|
-
comp: { postcode, locality, region },
|
|
94
|
-
raw: `${postcode} ${locality}, ${region}`,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const v of variants) {
|
|
99
|
-
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
100
|
-
const aligned = reconcileComponents(v.comp, v.raw)
|
|
76
|
+
if (!postcode || !locality) continue
|
|
77
|
+
const region = (rec[COL.admin1Name] ?? "").trim()
|
|
101
78
|
|
|
102
|
-
|
|
79
|
+
// Postcode-first (international) variants. Skip the region variant when admin1 just
|
|
80
|
+
// repeats the place (common for city-states / micro-admin) to avoid "X X" noise.
|
|
81
|
+
const variants: Array<{ slot: string; comp: CanonicalRow["components"]; raw: string }> = [
|
|
82
|
+
{ slot: "pl", comp: { postcode, locality }, raw: `${postcode} ${locality}` },
|
|
83
|
+
]
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
license: GEONAMES_POSTAL_DEFAULT_LICENSE,
|
|
112
|
-
}
|
|
85
|
+
if (region && region.toLowerCase() !== locality.toLowerCase()) {
|
|
86
|
+
variants.push({
|
|
87
|
+
slot: "plr",
|
|
88
|
+
comp: { postcode, locality, region },
|
|
89
|
+
raw: `${postcode} ${locality}, ${region}`,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
113
92
|
|
|
114
|
-
|
|
93
|
+
for (const v of variants) {
|
|
94
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
95
|
+
const aligned = reconcileComponents(v.comp, v.raw)
|
|
96
|
+
|
|
97
|
+
if (Object.keys(aligned).length < 2) continue
|
|
98
|
+
|
|
99
|
+
yield {
|
|
100
|
+
raw: v.raw,
|
|
101
|
+
components: aligned,
|
|
102
|
+
country: cc,
|
|
103
|
+
source: GEONAMES_POSTAL_ADAPTER_ID,
|
|
104
|
+
source_id: `${stableSourceID(GEONAMES_POSTAL_ADAPTER_ID, aligned)}-${v.slot}`,
|
|
105
|
+
corpus_version: "",
|
|
106
|
+
license: GEONAMES_POSTAL_DEFAULT_LICENSE,
|
|
115
107
|
}
|
|
108
|
+
|
|
109
|
+
emitted++
|
|
116
110
|
}
|
|
117
|
-
} finally {
|
|
118
|
-
stream.destroy()
|
|
119
111
|
}
|
|
120
112
|
},
|
|
121
113
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* line). Open G-NAF licence — attribute "Geoscape Australia".
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
+
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
31
32
|
import { TextSpliterator } from "spliterator"
|
|
32
33
|
|
|
33
34
|
import { stableSourceID } from "../../adapter.ts"
|
|
@@ -87,7 +88,7 @@ export function createGNAFAdapter(): CorpusAdapter {
|
|
|
87
88
|
|
|
88
89
|
// Input is the assembled component JSONL (one tuple per line). TextSpliterator auto-disposes on
|
|
89
90
|
// loop completion and on an early `break` (abort / limit), so the old explicit handle teardown is
|
|
90
|
-
// gone;
|
|
91
|
+
// gone; the parse tolerates a trailing CR on CRLF sources and the `!line.trim()` guard skips blanks.
|
|
91
92
|
// The render order rotates (i % 3), matching v1.9.1's rerender.
|
|
92
93
|
for await (const line of TextSpliterator.fromAsync(opts.inputPath)) {
|
|
93
94
|
if (opts.signal?.aborted) break
|
|
@@ -96,13 +97,9 @@ export function createGNAFAdapter(): CorpusAdapter {
|
|
|
96
97
|
|
|
97
98
|
if (!line.trim()) continue
|
|
98
99
|
|
|
99
|
-
|
|
100
|
+
const t = tryParsingJSON<GNAFTuple>(line)
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
t = JSON.parse(line) as GNAFTuple
|
|
103
|
-
} catch {
|
|
104
|
-
continue
|
|
105
|
-
}
|
|
102
|
+
if (t === null) continue
|
|
106
103
|
|
|
107
104
|
if (!t.house_number || !t.street || !t.locality || !t.postcode) continue
|
|
108
105
|
|
|
@@ -30,7 +30,8 @@ import { createWriteStream } from "node:fs"
|
|
|
30
30
|
import { readdir, readFile } from "node:fs/promises"
|
|
31
31
|
import { join } from "node:path"
|
|
32
32
|
|
|
33
|
-
import {
|
|
33
|
+
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
34
|
+
import { PSVSpliterator, TextSpliterator } from "spliterator"
|
|
34
35
|
|
|
35
36
|
export interface GNAFAssembleOptions {
|
|
36
37
|
/**
|
|
@@ -109,19 +110,14 @@ async function loadMap<V>(paths: string[], keyCol: string, pick: (r: Row) => V):
|
|
|
109
110
|
*/
|
|
110
111
|
async function loadHoldout(path: string): Promise<Set<string>> {
|
|
111
112
|
const keys = new Set<string>()
|
|
112
|
-
const text = await readFile(path, "utf8")
|
|
113
113
|
|
|
114
|
-
for (const line of
|
|
114
|
+
for await (const line of TextSpliterator.fromAsync(path)) {
|
|
115
115
|
if (!line.trim()) continue
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
const c = (JSON.parse(line) as { components?: Record<string, string> }).components
|
|
117
|
+
const c = tryParsingJSON<{ components?: Record<string, string> }>(line)?.components
|
|
119
118
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
/* skip malformed */
|
|
119
|
+
if (c?.street && c?.locality && c?.postcode) {
|
|
120
|
+
keys.add(gnafHoldoutKey(c.street, c.locality, c.postcode))
|
|
125
121
|
}
|
|
126
122
|
}
|
|
127
123
|
|