@mailwoman/corpus 8.5.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
|
@@ -28,11 +28,9 @@
|
|
|
28
28
|
* distribution terms.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import {
|
|
31
|
+
import { CSVSpliterator } from "spliterator"
|
|
32
32
|
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
import { stableSourceID } from "../../adapter.ts"
|
|
33
|
+
import { splitStreetLine, stableSourceID } from "../../adapter.ts"
|
|
36
34
|
import { lookupStateAbbreviation } from "../../codex/us-fips-state.ts"
|
|
37
35
|
import { reconcileComponents } from "../../format.ts"
|
|
38
36
|
import type { AdapterOptions, CanonicalRow, CorpusAdapter } from "../../types.ts"
|
|
@@ -66,19 +64,6 @@ interface SamhsaSiteRow {
|
|
|
66
64
|
frid?: string
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
const HOUSE_NUMBER_PREFIX = /^(\d+(?:-\d+)?[A-Za-z]?)\s+(.+)$/
|
|
70
|
-
|
|
71
|
-
function splitAddress(address: string): { house_number?: string; street: string } | null {
|
|
72
|
-
const trimmed = address.trim()
|
|
73
|
-
|
|
74
|
-
if (!trimmed) return null
|
|
75
|
-
const m = HOUSE_NUMBER_PREFIX.exec(trimmed)
|
|
76
|
-
|
|
77
|
-
if (m) return { house_number: m[1], street: m[2]!.trim() }
|
|
78
|
-
|
|
79
|
-
return { street: trimmed }
|
|
80
|
-
}
|
|
81
|
-
|
|
82
67
|
/**
|
|
83
68
|
* Join the SAMHSA two-line street: primary street + optional secondary line (suite / unit / floor / "behind main
|
|
84
69
|
* building") on `", "`. The combined value is the `street` component surface form. Phase 1 does not break this out into
|
|
@@ -148,76 +133,65 @@ export function createUsgovSamhsaTreatmentLocatorAdapter(): CorpusAdapter {
|
|
|
148
133
|
throw new Error(`usgov-samhsa adapter: only US supported, got country=${opts.country}`)
|
|
149
134
|
}
|
|
150
135
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
skip_empty_lines: true,
|
|
157
|
-
relax_quotes: true,
|
|
158
|
-
relax_column_count: true,
|
|
159
|
-
})
|
|
160
|
-
)
|
|
136
|
+
const rows = CSVSpliterator.fromAsync(opts.inputPath, {
|
|
137
|
+
mode: "object",
|
|
138
|
+
normalizeKeys: false,
|
|
139
|
+
enableQuoteHandling: true,
|
|
140
|
+
})
|
|
161
141
|
|
|
162
142
|
let emitted = 0
|
|
163
143
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (opts.signal?.aborted) break
|
|
167
|
-
|
|
168
|
-
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
144
|
+
for await (const record of rows as AsyncIterable<SamhsaSiteRow>) {
|
|
145
|
+
if (opts.signal?.aborted) break
|
|
169
146
|
|
|
170
|
-
|
|
171
|
-
const street = joinTwoLineStreet(record.street1 ?? "", record.street2)
|
|
172
|
-
const split = splitAddress(street)
|
|
173
|
-
const city = (record.city ?? "").trim()
|
|
174
|
-
const stateAbbr = (record.state ?? "").trim()
|
|
175
|
-
const postcode = (record.zip ?? "").trim()
|
|
147
|
+
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
176
148
|
|
|
177
|
-
|
|
178
|
-
|
|
149
|
+
const venue = composeVenue(record.name1 ?? "", record.name2)
|
|
150
|
+
const street = joinTwoLineStreet(record.street1 ?? "", record.street2)
|
|
151
|
+
const split = splitStreetLine(street)
|
|
152
|
+
const city = (record.city ?? "").trim()
|
|
153
|
+
const stateAbbr = (record.state ?? "").trim()
|
|
154
|
+
const postcode = (record.zip ?? "").trim()
|
|
179
155
|
|
|
180
|
-
|
|
156
|
+
if (!venue || !split || !city || !postcode) continue
|
|
157
|
+
const state = lookupStateAbbreviation(stateAbbr)
|
|
181
158
|
|
|
182
|
-
|
|
183
|
-
const components: CanonicalRow["components"] = {
|
|
184
|
-
venue,
|
|
185
|
-
...(split.house_number ? { house_number: split.house_number } : {}),
|
|
186
|
-
street: split.street,
|
|
187
|
-
locality: city,
|
|
188
|
-
region: state.abbreviation,
|
|
189
|
-
postcode,
|
|
190
|
-
}
|
|
159
|
+
if (!state) continue
|
|
191
160
|
|
|
192
|
-
|
|
161
|
+
// venue first — same kryptonite-defending insertion order as HRSA.
|
|
162
|
+
const components: CanonicalRow["components"] = {
|
|
163
|
+
venue,
|
|
164
|
+
...(split.house_number ? { house_number: split.house_number } : {}),
|
|
165
|
+
street: split.street,
|
|
166
|
+
locality: city,
|
|
167
|
+
region: state.abbreviation,
|
|
168
|
+
postcode,
|
|
169
|
+
}
|
|
193
170
|
|
|
194
|
-
|
|
171
|
+
const raw = composeRaw(venue, split.house_number, split.street, city, state.abbreviation, postcode)
|
|
195
172
|
|
|
196
|
-
|
|
173
|
+
if (!raw) continue
|
|
197
174
|
|
|
198
|
-
|
|
175
|
+
const aligned = reconcileComponents(components, raw)
|
|
199
176
|
|
|
200
|
-
|
|
177
|
+
if (!Object.keys(aligned).length) continue
|
|
201
178
|
|
|
202
|
-
|
|
203
|
-
? `${USGOV_SAMHSA_ADAPTER_ID}-${frID}`
|
|
204
|
-
: stableSourceID(USGOV_SAMHSA_ADAPTER_ID, aligned)
|
|
179
|
+
const frID = (record.frid ?? "").trim()
|
|
205
180
|
|
|
206
|
-
|
|
207
|
-
raw,
|
|
208
|
-
components: aligned,
|
|
209
|
-
country: "US",
|
|
210
|
-
locale: "en-US",
|
|
211
|
-
source: USGOV_SAMHSA_ADAPTER_ID,
|
|
212
|
-
source_id: sourceID,
|
|
213
|
-
corpus_version: "",
|
|
214
|
-
license: USGOV_SAMHSA_DEFAULT_LICENSE,
|
|
215
|
-
}
|
|
181
|
+
const sourceID = frID ? `${USGOV_SAMHSA_ADAPTER_ID}-${frID}` : stableSourceID(USGOV_SAMHSA_ADAPTER_ID, aligned)
|
|
216
182
|
|
|
217
|
-
|
|
183
|
+
yield {
|
|
184
|
+
raw,
|
|
185
|
+
components: aligned,
|
|
186
|
+
country: "US",
|
|
187
|
+
locale: "en-US",
|
|
188
|
+
source: USGOV_SAMHSA_ADAPTER_ID,
|
|
189
|
+
source_id: sourceID,
|
|
190
|
+
corpus_version: "",
|
|
191
|
+
license: USGOV_SAMHSA_DEFAULT_LICENSE,
|
|
218
192
|
}
|
|
219
|
-
|
|
220
|
-
|
|
193
|
+
|
|
194
|
+
emitted++
|
|
221
195
|
}
|
|
222
196
|
},
|
|
223
197
|
}
|
|
@@ -59,15 +59,21 @@ interface NameRow {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Walk parent chain up to 6 levels.
|
|
62
|
+
* Walk parent chain up to 6 levels, over a place table already resident in memory.
|
|
63
|
+
*
|
|
64
|
+
* `resolve` is the escape hatch for a parent outside the preloaded set (a chain that leaves the country); it is a point
|
|
65
|
+
* query, so keeping the common case out of it is the whole point.
|
|
63
66
|
*/
|
|
64
|
-
function chainOf(
|
|
65
|
-
|
|
67
|
+
function chainOf(
|
|
68
|
+
byID: Map<number, PlaceRow>,
|
|
69
|
+
resolve: (id: number) => PlaceRow | undefined,
|
|
70
|
+
startID: number
|
|
71
|
+
): PlaceRow[] {
|
|
66
72
|
const out: PlaceRow[] = []
|
|
67
73
|
let id = startID
|
|
68
74
|
|
|
69
75
|
for (let i = 0; i < MAX_ANCESTRY_DEPTH && id > 0; i++) {
|
|
70
|
-
const row =
|
|
76
|
+
const row = byID.get(id) ?? resolve(id)
|
|
71
77
|
|
|
72
78
|
if (!row) break
|
|
73
79
|
out.push(row)
|
|
@@ -151,9 +157,29 @@ export function createWOFAdminJpAdapter(): CorpusAdapter {
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
// One read of the JP place table instead of a fresh `prepare` + up to six point queries per
|
|
161
|
+
// seed; there are tens of thousands of neighbourhood seeds.
|
|
162
|
+
const byID = new Map<number, PlaceRow>()
|
|
163
|
+
|
|
164
|
+
for (const row of db
|
|
165
|
+
.prepare(`SELECT id, name, placetype, parent_id, country FROM spr WHERE country='JP'`)
|
|
166
|
+
.all() as unknown as PlaceRow[]) {
|
|
167
|
+
byID.set(row.id, row)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const outsideStmt = db.prepare(`SELECT id, name, placetype, parent_id, country FROM spr WHERE id = ?`)
|
|
171
|
+
|
|
172
|
+
const resolveOutside = (id: number): PlaceRow | undefined => {
|
|
173
|
+
const row = outsideStmt.get(id) as unknown as PlaceRow | undefined
|
|
174
|
+
|
|
175
|
+
if (row) {
|
|
176
|
+
byID.set(id, row)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return row
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const seeds = [...byID.values()].filter((row) => row.placetype === "neighbourhood")
|
|
157
183
|
|
|
158
184
|
let emitted = 0
|
|
159
185
|
|
|
@@ -162,7 +188,7 @@ export function createWOFAdminJpAdapter(): CorpusAdapter {
|
|
|
162
188
|
|
|
163
189
|
if (opts.limit !== undefined && emitted >= opts.limit) break
|
|
164
190
|
|
|
165
|
-
const chain = chainOf(
|
|
191
|
+
const chain = chainOf(byID, resolveOutside, seed.id)
|
|
166
192
|
const synth = synthesizeJpAddress(chain, jpnNames)
|
|
167
193
|
|
|
168
194
|
if (!synth) continue
|
package/src/build.ts
CHANGED
|
@@ -52,6 +52,7 @@ import { mkdir, writeFile } from "node:fs/promises"
|
|
|
52
52
|
import { join } from "node:path"
|
|
53
53
|
|
|
54
54
|
import { $public } from "@mailwoman/core/env"
|
|
55
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
55
56
|
import { JSONSpliterator } from "spliterator"
|
|
56
57
|
|
|
57
58
|
import { defaultAdapterRegistry } from "./adapter.ts"
|
|
@@ -188,7 +189,7 @@ export async function buildCorpus(opts: BuildCorpusOptions): Promise<BuildCorpus
|
|
|
188
189
|
existsSync(cachedManifest) &&
|
|
189
190
|
existsSync(join(adapterDir, "canonical.jsonl"))
|
|
190
191
|
) {
|
|
191
|
-
const cached =
|
|
192
|
+
const cached = parseJSONStrict<AdapterRunManifest>(readFileSync(cachedManifest, "utf8"))
|
|
192
193
|
opts.onProgress?.("adapter-run", `resumed ${adapter.id} (reused ${cached.yielded} canonical rows)`)
|
|
193
194
|
adapterRuns.push(cached)
|
|
194
195
|
|
package/src/golden.ts
CHANGED
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
* creep in over time as new entries land.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { readdir
|
|
23
|
-
import { extname, join } from "node:path"
|
|
22
|
+
import { readdir } from "node:fs/promises"
|
|
24
23
|
|
|
25
24
|
import { COMPONENT_TAGS, type ComponentTag } from "@mailwoman/core/types"
|
|
25
|
+
import { join } from "path-ts"
|
|
26
|
+
import { TextSpliterator } from "spliterator"
|
|
26
27
|
|
|
27
28
|
import { reconcileComponents } from "./format.ts"
|
|
28
29
|
|
|
@@ -61,6 +62,9 @@ export interface GoldenReport {
|
|
|
61
62
|
* Parse a single JSONL line into a `GoldenEntry`. Throws on schema violations.
|
|
62
63
|
*/
|
|
63
64
|
export function parseGoldenLine(line: string): GoldenEntry {
|
|
65
|
+
// The throw IS the result: `validateGoldenFile` catches it and records the message against the line
|
|
66
|
+
// number, so a tolerant parse would report a corrupt row as valid.
|
|
67
|
+
// oxlint-disable-next-line no-restricted-properties
|
|
64
68
|
const obj = JSON.parse(line) as Partial<GoldenEntry> & Record<string, unknown>
|
|
65
69
|
|
|
66
70
|
if (typeof obj.raw !== "string" || !obj.raw.length) {
|
|
@@ -112,16 +116,23 @@ export function unreachableComponents(entry: GoldenEntry): ComponentTag[] {
|
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
118
|
* Validate one `.jsonl` file end-to-end, returning a list of issues.
|
|
119
|
+
*
|
|
120
|
+
* Parses line by line over `TextSpliterator` rather than `JSONSpliterator`: every issue this returns carries the LINE
|
|
121
|
+
* NUMBER it was found on, and a malformed line has to be REPORTED rather than thrown. `JSONSpliterator` parses each row
|
|
122
|
+
* for you and throws on the first bad one — correct for consumers that want the rows, wrong for the validator whose
|
|
123
|
+
* whole job is locating the bad ones.
|
|
115
124
|
*/
|
|
116
125
|
export async function validateGoldenFile(path: string): Promise<GoldenIssue[]> {
|
|
117
|
-
const text = await readFile(path, "utf8")
|
|
118
|
-
const lines = text.split("\n")
|
|
119
126
|
const issues: GoldenIssue[] = []
|
|
127
|
+
// Counted over EVERY row including blanks, so the number matches what an editor shows.
|
|
128
|
+
let lineNumber = 0
|
|
120
129
|
|
|
121
|
-
for (
|
|
122
|
-
|
|
130
|
+
for await (const raw of TextSpliterator.fromAsync(path, { skipEmpty: false })) {
|
|
131
|
+
lineNumber++
|
|
132
|
+
const line = raw.trim()
|
|
123
133
|
|
|
124
134
|
if (!line) continue
|
|
135
|
+
const i = lineNumber - 1
|
|
125
136
|
|
|
126
137
|
try {
|
|
127
138
|
const entry = parseGoldenLine(line)
|
|
@@ -146,7 +157,7 @@ export async function validateGoldenFile(path: string): Promise<GoldenIssue[]> {
|
|
|
146
157
|
* Validate every `.jsonl` in a golden directory.
|
|
147
158
|
*/
|
|
148
159
|
export async function validateGoldenDir(dir: string): Promise<GoldenReport> {
|
|
149
|
-
const files = (await readdir(dir)).filter((n) =>
|
|
160
|
+
const files = (await readdir(dir)).filter((n) => n.endsWith(".jsonl")).toSorted()
|
|
150
161
|
const issues: GoldenIssue[] = []
|
|
151
162
|
let entries = 0
|
|
152
163
|
|
|
@@ -154,8 +165,12 @@ export async function validateGoldenDir(dir: string): Promise<GoldenReport> {
|
|
|
154
165
|
const fullPath = join(dir, name)
|
|
155
166
|
const fileIssues = await validateGoldenFile(fullPath)
|
|
156
167
|
issues.push(...fileIssues)
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
|
|
169
|
+
for await (const line of TextSpliterator.fromAsync(fullPath)) {
|
|
170
|
+
if (line.trim()) {
|
|
171
|
+
entries++
|
|
172
|
+
}
|
|
173
|
+
}
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
return { entries, files: files.length, issues }
|
|
@@ -59,6 +59,25 @@ export class ParquetReader<T extends ParquetRecordLike> extends BaseParquetReade
|
|
|
59
59
|
return super[Symbol.asyncIterator]() as AsyncGenerator<T, void, unknown>
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Iterate a subset of the columns, narrowed to the keys asked for.
|
|
64
|
+
*
|
|
65
|
+
* Parquet is columnar, so a projection is read avoided rather than read-then-discarded: on a 14-column corpus shard,
|
|
66
|
+
* two columns cost 93 ms against 253 ms for the whole row. The default iterator reads every column, which is what a
|
|
67
|
+
* caller wanting the whole record should use.
|
|
68
|
+
*/
|
|
69
|
+
public async *project<K extends keyof T>(...columns: K[]): AsyncGenerator<Pick<T, K>, void, unknown> {
|
|
70
|
+
const cursor = this.getCursor(columns.map((column) => [column as string]))
|
|
71
|
+
|
|
72
|
+
for (;;) {
|
|
73
|
+
const row = (await cursor.next()) as Pick<T, K> | null
|
|
74
|
+
|
|
75
|
+
if (!row) return
|
|
76
|
+
|
|
77
|
+
yield row
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
62
81
|
public async [Symbol.asyncDispose]() {
|
|
63
82
|
return this.close()
|
|
64
83
|
}
|
|
@@ -24,6 +24,10 @@ import { LRUCache } from "lru-cache"
|
|
|
24
24
|
/**
|
|
25
25
|
* A Parquet record-like object, i.e. a record with string keys and JSON-serializable values.
|
|
26
26
|
*/
|
|
27
|
+
/**
|
|
28
|
+
* Shape a row type must satisfy. Declare yours as a `type` alias — TypeScript gives an alias an implicit index
|
|
29
|
+
* signature and an `interface` none, so an otherwise-identical interface does not satisfy this.
|
|
30
|
+
*/
|
|
27
31
|
export type ParquetRecordLike = Record<string, unknown | undefined>
|
|
28
32
|
|
|
29
33
|
/**
|
package/src/parquet.ts
CHANGED
|
@@ -37,11 +37,12 @@
|
|
|
37
37
|
* close — cheap relative to writing it).
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
import { createHash } from "node:crypto"
|
|
41
40
|
import { createReadStream } from "node:fs"
|
|
42
41
|
import { mkdir, stat, writeFile } from "node:fs/promises"
|
|
43
42
|
import { join } from "node:path"
|
|
44
43
|
|
|
44
|
+
import { sha256File } from "@mailwoman/core/utils"
|
|
45
|
+
|
|
45
46
|
import { ParquetWriter, type ParquetSchemaDefinition } from "./parquet-wrapper/index.ts"
|
|
46
47
|
import type { SplitName } from "./split.ts"
|
|
47
48
|
import type { LabeledRow } from "./types.ts"
|
|
@@ -311,7 +312,7 @@ export async function writeShards(perSplit: PerSplitRows, opts: WriteShardsOptio
|
|
|
311
312
|
|
|
312
313
|
if (shardRows > 0) {
|
|
313
314
|
const fileStat = await stat(path)
|
|
314
|
-
const sha256 = await
|
|
315
|
+
const sha256 = await sha256File(path)
|
|
315
316
|
|
|
316
317
|
shards.push({
|
|
317
318
|
split,
|
|
@@ -375,17 +376,3 @@ export async function writeShards(perSplit: PerSplitRows, opts: WriteShardsOptio
|
|
|
375
376
|
|
|
376
377
|
return manifest
|
|
377
378
|
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Single-pass SHA-256 over the file at `path`. Cheap relative to Parquet write throughput.
|
|
381
|
-
*/
|
|
382
|
-
async function hashFile(path: string): Promise<string> {
|
|
383
|
-
const hash = createHash("sha256")
|
|
384
|
-
const stream = createReadStream(path)
|
|
385
|
-
|
|
386
|
-
for await (const chunk of stream) {
|
|
387
|
-
hash.update(chunk as Buffer)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return hash.digest("hex")
|
|
391
|
-
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import { readFileSync } from "node:fs"
|
|
16
16
|
|
|
17
|
+
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
17
18
|
import { dataRootPath } from "@mailwoman/core/utils"
|
|
18
19
|
|
|
19
20
|
import { stableSourceID } from "../adapter.ts"
|
|
@@ -31,7 +32,7 @@ const ANCHOR_LOOKUP = dataRootPath("anchor", "pilot-anchor-lookup.json")
|
|
|
31
32
|
* The real US ZIPs in the anchor lookup (entries whose value is a `[{ US: … }]` candidate list).
|
|
32
33
|
*/
|
|
33
34
|
function loadRealUsZips(path: string): string[] {
|
|
34
|
-
const d =
|
|
35
|
+
const d = parseJSONStrict<Record<string, unknown>>(readFileSync(path, "utf8"))
|
|
35
36
|
const zips: string[] = []
|
|
36
37
|
|
|
37
38
|
for (const [pc, v] of Object.entries(d)) {
|
|
@@ -28,11 +28,12 @@ import { spawnSync } from "node:child_process"
|
|
|
28
28
|
|
|
29
29
|
import { COUNTRY_SURFACE_FORMS, CountryNames } from "@mailwoman/codex/country"
|
|
30
30
|
import type { ComponentTag } from "@mailwoman/core/types"
|
|
31
|
+
import { dataRootPath } from "@mailwoman/core/utils"
|
|
31
32
|
|
|
32
33
|
import { stableSourceID } from "../adapter.ts"
|
|
33
34
|
import { alignRow } from "../align.ts"
|
|
34
35
|
import type { CanonicalRow } from "../types.ts"
|
|
35
|
-
import { makeMulberry32, type ShardRecipe } from "./scaffold.ts"
|
|
36
|
+
import { makeMulberry32, readCSVRecords, type ShardRecipe } from "./scaffold.ts"
|
|
36
37
|
|
|
37
38
|
// v2: the country TOKEN is decoupled from the skeleton's locale and drawn from a BROAD pool — every
|
|
38
39
|
// ISO canonical name + every curated surface form (endonyms/abbrevs). Surface forms are over-weighted
|
|
@@ -72,24 +73,72 @@ interface CountrySource {
|
|
|
72
73
|
* countrywide extracts (FR/IT/NL) read region from the CSV when present.
|
|
73
74
|
*/
|
|
74
75
|
const SOURCES: readonly CountrySource[] = [
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
{
|
|
77
|
+
zip: dataRootPath("oa-cache", "us__ia__statewide.zip"),
|
|
78
|
+
csv: "us/ia/statewide.csv",
|
|
79
|
+
iso2: "US",
|
|
80
|
+
region: "IA",
|
|
81
|
+
order: "us",
|
|
82
|
+
},
|
|
83
|
+
{ zip: dataRootPath("oa-cache", "us__il__cook.zip"), csv: "us/il/cook.csv", iso2: "US", region: "IL", order: "us" },
|
|
84
|
+
{
|
|
85
|
+
zip: dataRootPath("oa-cache", "us__mt__statewide.zip"),
|
|
86
|
+
csv: "us/mt/statewide.csv",
|
|
87
|
+
iso2: "US",
|
|
88
|
+
region: "MT",
|
|
89
|
+
order: "us",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
zip: dataRootPath("oa-cache", "us__sd__statewide.zip"),
|
|
93
|
+
csv: "us/sd/statewide.csv",
|
|
94
|
+
iso2: "US",
|
|
95
|
+
region: "SD",
|
|
96
|
+
order: "us",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
zip: dataRootPath("oa-cache", "de__sn__statewide.zip"),
|
|
100
|
+
csv: "de/sn/statewide.csv",
|
|
101
|
+
iso2: "DE",
|
|
102
|
+
region: "",
|
|
103
|
+
order: "eu",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
zip: dataRootPath("oa-cache", "fr__countrywide.zip"),
|
|
107
|
+
csv: "fr/countrywide.csv",
|
|
108
|
+
iso2: "FR",
|
|
109
|
+
region: "",
|
|
110
|
+
order: "fr",
|
|
111
|
+
},
|
|
81
112
|
// ES uses the Spanish IGN schema, not the OA standard columns — skipped here (codex still recognizes
|
|
82
113
|
// "España"/"Spain"). A dedicated IGN adapter is a follow-up.
|
|
83
|
-
{
|
|
84
|
-
|
|
114
|
+
{
|
|
115
|
+
zip: dataRootPath("oa-cache", "it__countrywide.zip"),
|
|
116
|
+
csv: "it/countrywide.csv",
|
|
117
|
+
iso2: "IT",
|
|
118
|
+
region: "",
|
|
119
|
+
order: "eu",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
zip: dataRootPath("oa-cache", "nl__countrywide.zip"),
|
|
123
|
+
csv: "nl/countrywide.csv",
|
|
124
|
+
iso2: "NL",
|
|
125
|
+
region: "",
|
|
126
|
+
order: "eu",
|
|
127
|
+
},
|
|
85
128
|
]
|
|
86
129
|
|
|
87
130
|
/**
|
|
88
131
|
* Held-out for --golden: Vermont (US holdout) + Berlin (DE holdout) — geographic split, never trained.
|
|
89
132
|
*/
|
|
90
133
|
const EVAL_SOURCES: readonly CountrySource[] = [
|
|
91
|
-
{
|
|
92
|
-
|
|
134
|
+
{
|
|
135
|
+
zip: dataRootPath("oa-cache", "us__vt__statewide.zip"),
|
|
136
|
+
csv: "us/vt/statewide.csv",
|
|
137
|
+
iso2: "US",
|
|
138
|
+
region: "VT",
|
|
139
|
+
order: "us",
|
|
140
|
+
},
|
|
141
|
+
{ zip: dataRootPath("oa-cache", "de__berlin.zip"), csv: "de/berlin.csv", iso2: "DE", region: "", order: "eu" },
|
|
93
142
|
]
|
|
94
143
|
|
|
95
144
|
/**
|
|
@@ -105,45 +154,9 @@ interface CountryTuple {
|
|
|
105
154
|
order: string
|
|
106
155
|
}
|
|
107
156
|
|
|
108
|
-
function splitCSV(line: string): string[] {
|
|
109
|
-
const out: string[] = []
|
|
110
|
-
|
|
111
|
-
let cur = "",
|
|
112
|
-
inQ = false
|
|
113
|
-
|
|
114
|
-
for (let i = 0; i < line.length; i++) {
|
|
115
|
-
const c = line[i]
|
|
116
|
-
|
|
117
|
-
if (inQ) {
|
|
118
|
-
if (c === '"') {
|
|
119
|
-
if (line[i + 1] === '"') {
|
|
120
|
-
cur += '"'
|
|
121
|
-
|
|
122
|
-
i++
|
|
123
|
-
} else {
|
|
124
|
-
inQ = false
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
cur += c
|
|
128
|
-
}
|
|
129
|
-
} else if (c === '"') {
|
|
130
|
-
inQ = true
|
|
131
|
-
} else if (c === ",") {
|
|
132
|
-
out.push(cur)
|
|
133
|
-
cur = ""
|
|
134
|
-
} else {
|
|
135
|
-
cur += c
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
out.push(cur)
|
|
140
|
-
|
|
141
|
-
return out
|
|
142
|
-
}
|
|
143
|
-
|
|
144
157
|
function readTuples(source: CountrySource, limit: number): CountryTuple[] {
|
|
145
158
|
// countrywide extracts (FR/IT/NL) are GB-scale — cap the bytes with `head` (read ~8 lines per wanted
|
|
146
|
-
// tuple to survive dedup/skips) so the
|
|
159
|
+
// tuple to survive dedup/skips) so the whole extract never has to be held in memory.
|
|
147
160
|
const maxLines = Math.max(limit * 8, 20_000) + 1
|
|
148
161
|
|
|
149
162
|
const r = spawnSync("bash", ["-c", `unzip -p "${source.zip}" "${source.csv}" | head -n ${maxLines}`], {
|
|
@@ -157,29 +170,15 @@ function readTuples(source: CountrySource, limit: number): CountryTuple[] {
|
|
|
157
170
|
return []
|
|
158
171
|
}
|
|
159
172
|
|
|
160
|
-
const lines = r.stdout.toString("utf8").split(/\r?\n/)
|
|
161
|
-
|
|
162
|
-
if (lines.length < 2) return []
|
|
163
|
-
const header = splitCSV(lines[0]!).map((h) => h.trim().toLowerCase())
|
|
164
|
-
const idx = (n: string): number => header.indexOf(n)
|
|
165
|
-
|
|
166
|
-
const iNum = idx("number"),
|
|
167
|
-
iStreet = idx("street"),
|
|
168
|
-
iCity = idx("city"),
|
|
169
|
-
iRegion = idx("region"),
|
|
170
|
-
iPost = idx("postcode")
|
|
171
|
-
|
|
172
|
-
const get = (cells: string[], i: number): string => (i >= 0 && i < cells.length ? (cells[i] ?? "").trim() : "")
|
|
173
173
|
const tuples: CountryTuple[] = []
|
|
174
174
|
const seen = new Set<string>()
|
|
175
175
|
|
|
176
|
-
for (
|
|
177
|
-
if (
|
|
178
|
-
const cells = splitCSV(lines[li]!)
|
|
176
|
+
for (const row of readCSVRecords(r.stdout)) {
|
|
177
|
+
if (tuples.length >= limit) break
|
|
179
178
|
|
|
180
|
-
const street =
|
|
181
|
-
locality =
|
|
182
|
-
house_number =
|
|
179
|
+
const street = row.street ?? "",
|
|
180
|
+
locality = row.city ?? "",
|
|
181
|
+
house_number = row.number ?? ""
|
|
183
182
|
|
|
184
183
|
if (!street || !locality || !house_number) continue
|
|
185
184
|
const key = `${house_number}|${street}|${locality}`.toLowerCase()
|
|
@@ -191,8 +190,8 @@ function readTuples(source: CountrySource, limit: number): CountryTuple[] {
|
|
|
191
190
|
house_number,
|
|
192
191
|
street,
|
|
193
192
|
locality,
|
|
194
|
-
region:
|
|
195
|
-
postcode:
|
|
193
|
+
region: row.region || source.region,
|
|
194
|
+
postcode: row.postcode ?? "",
|
|
196
195
|
iso2: source.iso2,
|
|
197
196
|
order: source.order,
|
|
198
197
|
})
|
|
@@ -460,7 +459,7 @@ export const countryBalancedRecipe: ShardRecipe = {
|
|
|
460
459
|
}
|
|
461
460
|
|
|
462
461
|
if (!pool.length) {
|
|
463
|
-
throw new Error(
|
|
462
|
+
throw new Error(`No tuples — are the cached OA zips present in ${dataRootPath("oa-cache")}?`)
|
|
464
463
|
}
|
|
465
464
|
|
|
466
465
|
let emitted = 0
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
* blow-up). Disable that mask for any run including this shard.
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
|
-
import {
|
|
71
|
+
import { TextSpliterator } from "spliterator"
|
|
72
72
|
|
|
73
73
|
import { decomposeFrStreet } from "../adapters/ban/street-decompose.ts"
|
|
74
74
|
import { alignAndWrite, makeMulberry32, readTuples, type ShardRecipe, shardSourceID } from "./scaffold.ts"
|
|
@@ -187,12 +187,15 @@ export const frFragmentRecipe: ShardRecipe = {
|
|
|
187
187
|
)
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
const excluded = new Set(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
const excluded = new Set<string>()
|
|
191
|
+
|
|
192
|
+
for await (const line of TextSpliterator.fromAsync(excludePath)) {
|
|
193
|
+
const trimmed = line.trim()
|
|
194
|
+
|
|
195
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
196
|
+
excluded.add(trimmed)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
196
199
|
|
|
197
200
|
if (!excluded.size) throw new Error(`fr-fragment: --exclude-surfaces "${excludePath}" listed no surfaces`)
|
|
198
201
|
|