@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
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
* locality-hash holdout (hash%10===0).
|
|
21
21
|
*
|
|
22
22
|
* Prerequisites (read once, before the generation loop — these do NOT consume `random`): the cached
|
|
23
|
-
* OA zips in /
|
|
24
|
-
* GeoNames POSTAL-CODE dumps for AU/NZ
|
|
25
|
-
*
|
|
23
|
+
* OA zips in `$MAILWOMAN_DATA_ROOT/oa-cache`, the GeoNames Canada dump at
|
|
24
|
+
* `$MAILWOMAN_DATA_ROOT/geonames/CA.zip`, and the GeoNames POSTAL-CODE dumps for AU/NZ
|
|
25
|
+
* (`$MAILWOMAN_DATA_ROOT/geonames-postal/{AU,NZ}.zip`).
|
|
26
26
|
*
|
|
27
27
|
* Byte-fidelity: the legacy script seeded its own mulberry32 from `--seed`
|
|
28
28
|
* (`mulberry32(opts.seed)`); this recipe re-creates the SAME generator
|
|
@@ -35,8 +35,10 @@ import { spawnSync } from "node:child_process"
|
|
|
35
35
|
import { isAuDeliveryService, isAuPostcode, isAuStateAbbreviation } from "@mailwoman/codex/au"
|
|
36
36
|
import { FSA_LETTER_TO_PROVINCE, normalizeCaPostalCode } from "@mailwoman/codex/ca"
|
|
37
37
|
import { isCedex } from "@mailwoman/codex/fr"
|
|
38
|
-
import {
|
|
38
|
+
import { isNZDeliveryService, isNZPostcode } from "@mailwoman/codex/nz"
|
|
39
39
|
import { isPOBox } from "@mailwoman/codex/us"
|
|
40
|
+
import { dataRootPath } from "@mailwoman/core/utils"
|
|
41
|
+
import { TextSpliterator } from "spliterator"
|
|
40
42
|
|
|
41
43
|
import { alignRow } from "../align.ts"
|
|
42
44
|
import {
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
synthesizeMilitaryPoBoxRow,
|
|
46
48
|
type LocaleTemplate,
|
|
47
49
|
} from "../synthesize-po-box.ts"
|
|
48
|
-
import { makeMulberry32, shardSourceID, type CanonicalShardRow, type ShardRecipe } from "./scaffold.ts"
|
|
50
|
+
import { makeMulberry32, readCSVRecords, shardSourceID, type CanonicalShardRow, type ShardRecipe } from "./scaffold.ts"
|
|
49
51
|
|
|
50
52
|
// ── Base-skeleton sources ────────────────────────────────────────────────────────────────────────
|
|
51
53
|
// Same OA cache as the unit/affix shards. US train = every NON-Vermont state; US eval = Vermont (the
|
|
@@ -58,20 +60,29 @@ import { makeMulberry32, shardSourceID, type CanonicalShardRow, type ShardRecipe
|
|
|
58
60
|
extracted as named constants above. */
|
|
59
61
|
|
|
60
62
|
const US_TRAIN_SOURCES = [
|
|
61
|
-
{ zip: "
|
|
62
|
-
{ zip: "
|
|
63
|
-
{ zip: "
|
|
64
|
-
{ zip: "
|
|
65
|
-
{ zip: "
|
|
66
|
-
{ zip: "
|
|
67
|
-
{ zip: "
|
|
63
|
+
{ zip: dataRootPath("oa-cache", "us__ca__berkeley.zip"), csv: "us/ca/berkeley.csv", region: "CA" },
|
|
64
|
+
{ zip: dataRootPath("oa-cache", "us__ca__marin.zip"), csv: "us/ca/marin.csv", region: "CA" },
|
|
65
|
+
{ zip: dataRootPath("oa-cache", "us__dc__statewide.zip"), csv: "us/dc/statewide.csv", region: "DC" },
|
|
66
|
+
{ zip: dataRootPath("oa-cache", "us__ia__statewide.zip"), csv: "us/ia/statewide.csv", region: "IA" },
|
|
67
|
+
{ zip: dataRootPath("oa-cache", "us__il__cook.zip"), csv: "us/il/cook.csv", region: "IL" },
|
|
68
|
+
{ zip: dataRootPath("oa-cache", "us__mt__statewide.zip"), csv: "us/mt/statewide.csv", region: "MT" },
|
|
69
|
+
{ zip: dataRootPath("oa-cache", "us__sd__statewide.zip"), csv: "us/sd/statewide.csv", region: "SD" },
|
|
68
70
|
]
|
|
69
71
|
|
|
70
|
-
const US_EVAL_SOURCE = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const US_EVAL_SOURCE = {
|
|
73
|
+
zip: dataRootPath("oa-cache", "us__vt__statewide.zip"),
|
|
74
|
+
csv: "us/vt/statewide.csv",
|
|
75
|
+
region: "VT",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const FR_SOURCE = { zip: dataRootPath("oa-cache", "fr__countrywide.zip"), csv: "fr/countrywide.csv" }
|
|
79
|
+
// GeoNames has two per-country exports with different schemas, and this recipe reads both. The MAIN
|
|
80
|
+
// dump (feature class + population columns) lives under `geonames/`; the POSTAL-CODE dump lives
|
|
81
|
+
// under `geonames-postal/`, where the directory carries the "postal" distinction the filename used
|
|
82
|
+
// to.
|
|
83
|
+
const GEONAMES_CA = dataRootPath("geonames", "CA.zip")
|
|
84
|
+
const GEONAMES_POSTAL_AU = { zip: dataRootPath("geonames-postal", "AU.zip"), txt: "AU.txt" }
|
|
85
|
+
const GEONAMES_POSTAL_NZ = { zip: dataRootPath("geonames-postal", "NZ.zip"), txt: "NZ.txt" }
|
|
75
86
|
|
|
76
87
|
/**
|
|
77
88
|
* ── Surface vocabulary (codex + corpus templates — see the header) ──────────────────────────────.
|
|
@@ -172,20 +183,20 @@ interface USTuple {
|
|
|
172
183
|
postcode: string
|
|
173
184
|
}
|
|
174
185
|
|
|
175
|
-
interface
|
|
186
|
+
interface FRTuple {
|
|
176
187
|
house_number: string
|
|
177
188
|
street: string
|
|
178
189
|
locality: string
|
|
179
190
|
postcode: string
|
|
180
191
|
}
|
|
181
192
|
|
|
182
|
-
interface
|
|
193
|
+
interface AUTuple {
|
|
183
194
|
locality: string
|
|
184
195
|
region: string
|
|
185
196
|
postcode: string
|
|
186
197
|
}
|
|
187
198
|
|
|
188
|
-
interface
|
|
199
|
+
interface NZTuple {
|
|
189
200
|
locality: string
|
|
190
201
|
postcode: string
|
|
191
202
|
}
|
|
@@ -214,44 +225,6 @@ function localityHash(name: string): number {
|
|
|
214
225
|
|
|
215
226
|
const isHoldoutLocality = (name: string): boolean => localityHash(name) % 10 === 0
|
|
216
227
|
|
|
217
|
-
/**
|
|
218
|
-
* Minimal RFC-4180-ish splitter (handles quoted fields).
|
|
219
|
-
*/
|
|
220
|
-
function splitCSV(line: string): string[] {
|
|
221
|
-
const out: string[] = []
|
|
222
|
-
let cur = ""
|
|
223
|
-
let inQ = false
|
|
224
|
-
|
|
225
|
-
for (let i = 0; i < line.length; i++) {
|
|
226
|
-
const c = line[i]!
|
|
227
|
-
|
|
228
|
-
if (inQ) {
|
|
229
|
-
if (c === '"') {
|
|
230
|
-
if (line[i + 1] === '"') {
|
|
231
|
-
cur += '"'
|
|
232
|
-
|
|
233
|
-
i++
|
|
234
|
-
} else {
|
|
235
|
-
inQ = false
|
|
236
|
-
}
|
|
237
|
-
} else {
|
|
238
|
-
cur += c
|
|
239
|
-
}
|
|
240
|
-
} else if (c === '"') {
|
|
241
|
-
inQ = true
|
|
242
|
-
} else if (c === ",") {
|
|
243
|
-
out.push(cur)
|
|
244
|
-
cur = ""
|
|
245
|
-
} else {
|
|
246
|
-
cur += c
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
out.push(cur)
|
|
251
|
-
|
|
252
|
-
return out
|
|
253
|
-
}
|
|
254
|
-
|
|
255
228
|
const MAX_LOCALITY_LENGTH = 40
|
|
256
229
|
|
|
257
230
|
const cleanLocality = (loc: string) =>
|
|
@@ -269,38 +242,24 @@ function readUsTuples(source: { zip: string; csv: string; region: string }): UST
|
|
|
269
242
|
return []
|
|
270
243
|
}
|
|
271
244
|
|
|
272
|
-
const lines = r.stdout.toString("utf8").split(/\r?\n/)
|
|
273
|
-
|
|
274
|
-
if (lines.length < 2) return []
|
|
275
|
-
const header = splitCSV(lines[0]!).map((h) => h.trim().toLowerCase())
|
|
276
|
-
const idx = (name: string) => header.indexOf(name)
|
|
277
|
-
|
|
278
|
-
const iNum = idx("number"),
|
|
279
|
-
iStreet = idx("street"),
|
|
280
|
-
iCity = idx("city"),
|
|
281
|
-
iPost = idx("postcode")
|
|
282
|
-
|
|
283
|
-
const get = (cells: string[], i: number) => (i >= 0 && i < cells.length ? (cells[i] ?? "").trim() : "")
|
|
284
245
|
const tuples: USTuple[] = []
|
|
285
246
|
const seen = new Set<string>()
|
|
286
247
|
|
|
287
|
-
for (
|
|
288
|
-
|
|
289
|
-
const cells = splitCSV(lines[li]!)
|
|
290
|
-
const locality = get(cells, iCity)
|
|
248
|
+
for (const row of readCSVRecords(r.stdout)) {
|
|
249
|
+
const locality = row.city ?? ""
|
|
291
250
|
|
|
292
251
|
if (!cleanLocality(locality)) continue
|
|
293
252
|
const key = locality.toLowerCase()
|
|
294
253
|
|
|
295
|
-
const street =
|
|
296
|
-
house_number =
|
|
254
|
+
const street = row.street ?? "",
|
|
255
|
+
house_number = row.number ?? ""
|
|
297
256
|
|
|
298
257
|
// One tuple per (locality, street) pair keeps the pool varied without ballooning memory.
|
|
299
258
|
const pairKey = `${key}|${street}`.toLowerCase()
|
|
300
259
|
|
|
301
260
|
if (seen.has(pairKey)) continue
|
|
302
261
|
seen.add(pairKey)
|
|
303
|
-
tuples.push({ house_number, street, locality, region: source.region, postcode:
|
|
262
|
+
tuples.push({ house_number, street, locality, region: source.region, postcode: row.postcode ?? "" })
|
|
304
263
|
}
|
|
305
264
|
|
|
306
265
|
return tuples
|
|
@@ -308,10 +267,13 @@ function readUsTuples(source: { zip: string; csv: string; region: string }): UST
|
|
|
308
267
|
|
|
309
268
|
/**
|
|
310
269
|
* Stride-sampled FR tuples (number/street/city/postcode). The countrywide CSV is 2.5 GB and insee-ordered; `awk NR%K`
|
|
311
|
-
* strides the whole country instead of reading one département.
|
|
312
|
-
*
|
|
270
|
+
* strides the whole country instead of reading one département.
|
|
271
|
+
*
|
|
272
|
+
* The stride counts PHYSICAL lines, so a record carrying a newline inside a quoted field is two lines to awk and can be
|
|
273
|
+
* cut in half by the stride. That is a sampling artefact of the awk pre-filter, not of the parse: whichever lines
|
|
274
|
+
* survive are re-assembled into records by {@link readCSVRecords}, and a halved record fails the field checks below.
|
|
313
275
|
*/
|
|
314
|
-
function readFrTuples(limit: number):
|
|
276
|
+
function readFrTuples(limit: number): FRTuple[] {
|
|
315
277
|
const r = spawnSync(
|
|
316
278
|
"bash",
|
|
317
279
|
["-c", `unzip -p "${FR_SOURCE.zip}" "${FR_SOURCE.csv}" | awk 'NR==1 || NR%211==3' | head -n ${limit + 1}`],
|
|
@@ -324,29 +286,14 @@ function readFrTuples(limit: number): FrTuple[] {
|
|
|
324
286
|
return []
|
|
325
287
|
}
|
|
326
288
|
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
if (lines.length < 2) return []
|
|
330
|
-
const header = splitCSV(lines[0]!).map((h) => h.trim().toLowerCase())
|
|
331
|
-
const idx = (n: string) => header.indexOf(n)
|
|
332
|
-
|
|
333
|
-
const iNum = idx("number"),
|
|
334
|
-
iStreet = idx("street"),
|
|
335
|
-
iCity = idx("city"),
|
|
336
|
-
iPost = idx("postcode")
|
|
337
|
-
|
|
338
|
-
const get = (cells: string[], i: number) => (i >= 0 && i < cells.length ? (cells[i] ?? "").trim() : "")
|
|
339
|
-
const tuples: FrTuple[] = []
|
|
289
|
+
const tuples: FRTuple[] = []
|
|
340
290
|
const seen = new Set<string>()
|
|
341
291
|
|
|
342
|
-
for (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
postcode = get(cells, iPost),
|
|
348
|
-
street = get(cells, iStreet),
|
|
349
|
-
house_number = get(cells, iNum)
|
|
292
|
+
for (const row of readCSVRecords(r.stdout)) {
|
|
293
|
+
const locality = row.city ?? "",
|
|
294
|
+
postcode = row.postcode ?? "",
|
|
295
|
+
street = row.street ?? "",
|
|
296
|
+
house_number = row.number ?? ""
|
|
350
297
|
|
|
351
298
|
if (!cleanLocality(locality) || !/^\d{5}$/.test(postcode) || !street || !house_number) continue
|
|
352
299
|
const key = `${locality}|${street}`.toLowerCase()
|
|
@@ -379,7 +326,7 @@ function readCaLocalities(admin1: string): string[] {
|
|
|
379
326
|
return []
|
|
380
327
|
}
|
|
381
328
|
|
|
382
|
-
return [...new Set(r.stdout
|
|
329
|
+
return [...new Set([...TextSpliterator.from(r.stdout)].filter(cleanLocality))]
|
|
383
330
|
}
|
|
384
331
|
|
|
385
332
|
/**
|
|
@@ -392,7 +339,7 @@ function readCaLocalities(admin1: string): string[] {
|
|
|
392
339
|
function readPostalTuples(
|
|
393
340
|
source: { zip: string; txt: string },
|
|
394
341
|
opts: { withState: boolean }
|
|
395
|
-
): Array<
|
|
342
|
+
): Array<AUTuple | NZTuple> {
|
|
396
343
|
const r = spawnSync("unzip", ["-p", source.zip, source.txt], { maxBuffer: 1024 * 1024 * 64, encoding: "utf8" })
|
|
397
344
|
|
|
398
345
|
if (r.status !== 0) {
|
|
@@ -401,12 +348,14 @@ function readPostalTuples(
|
|
|
401
348
|
return []
|
|
402
349
|
}
|
|
403
350
|
|
|
404
|
-
const tuples: Array<
|
|
351
|
+
const tuples: Array<AUTuple | NZTuple> = []
|
|
405
352
|
const seen = new Set<string>()
|
|
406
|
-
const validPostcode = opts.withState ? isAuPostcode :
|
|
353
|
+
const validPostcode = opts.withState ? isAuPostcode : isNZPostcode
|
|
407
354
|
|
|
408
|
-
for (const line of r.stdout
|
|
355
|
+
for (const line of TextSpliterator.from(r.stdout)) {
|
|
409
356
|
if (!line) continue
|
|
357
|
+
|
|
358
|
+
// oxlint-disable-next-line mailwoman/prefer-spliterator -- One row already streamed off the line above.
|
|
410
359
|
const cols = line.split("\t")
|
|
411
360
|
const postcode = (cols[1] ?? "").trim()
|
|
412
361
|
const locality = (cols[2] ?? "").trim()
|
|
@@ -514,7 +463,7 @@ function makeCedex(random: () => number): string {
|
|
|
514
463
|
/**
|
|
515
464
|
* Compose an AU or NZ delivery-service phrase from the codex-sourced leaders. Same contract as makePoBoxPhrase: a
|
|
516
465
|
* phrase built from a codex-known designator and a clean id must round-trip the codex matcher (isAuDeliveryService /
|
|
517
|
-
*
|
|
466
|
+
* isNZDeliveryService) — a failure is a generation bug, loud. Noisy ids (commas / embedded spaces) are corpus-designed
|
|
518
467
|
* adversarial forms, exempt.
|
|
519
468
|
*/
|
|
520
469
|
function makeAuNzPoBoxPhrase(
|
|
@@ -532,14 +481,14 @@ function makeAuNzPoBoxPhrase(
|
|
|
532
481
|
let num = maybeNoisifyBoxNumber(pickBoxNumber(random), random)
|
|
533
482
|
|
|
534
483
|
// NZ CMB identifiers are alpha-led per the ADV358 example ("CMB B99").
|
|
535
|
-
if (leader === "CMB" && validate ===
|
|
484
|
+
if (leader === "CMB" && validate === isNZDeliveryService) {
|
|
536
485
|
num = `B${num}`
|
|
537
486
|
}
|
|
538
487
|
|
|
539
488
|
const phrase = `${caseDial(random, leader)} ${num}`
|
|
540
489
|
// The "clean id" shape differs per system: ADV358 identifiers carry no separators at all, the AU
|
|
541
490
|
// AMAS id (like the US one) tolerates dashes. Noisy ids outside the clean shape are exempt.
|
|
542
|
-
const cleanID = validate ===
|
|
491
|
+
const cleanID = validate === isNZDeliveryService ? /^[\dA-Za-z]+$/ : /^[\dA-Za-z][\dA-Za-z-]*$/
|
|
543
492
|
|
|
544
493
|
if (cleanID.test(num) && !validate(phrase)) {
|
|
545
494
|
throw new Error(`generated a phrase the codex matcher rejects: "${phrase}"`)
|
|
@@ -614,7 +563,7 @@ function renderPmbUs(random: () => number, t: USTuple): Rendered {
|
|
|
614
563
|
return { fmt: "pmb-bare", raw: `${road} ${phrase}`, components: { house_number: hn, street, po_box: phrase } }
|
|
615
564
|
}
|
|
616
565
|
|
|
617
|
-
function renderBpFr(random: () => number, t:
|
|
566
|
+
function renderBpFr(random: () => number, t: FRTuple): Rendered {
|
|
618
567
|
const phrase = makePoBoxPhrase(random, FR_LEADERS)
|
|
619
568
|
const { locality, postcode: pc } = t
|
|
620
569
|
const upper = random() < 0.5
|
|
@@ -651,7 +600,7 @@ function renderBpFr(random: () => number, t: FrTuple): Rendered {
|
|
|
651
600
|
}
|
|
652
601
|
}
|
|
653
602
|
|
|
654
|
-
function renderCedexFr(random: () => number, t:
|
|
603
|
+
function renderCedexFr(random: () => number, t: FRTuple): Rendered {
|
|
655
604
|
const cedex = makeCedex(random)
|
|
656
605
|
const { house_number: hn, street, locality, postcode: pc } = t
|
|
657
606
|
const loc = random() < 0.6 ? locality.toUpperCase() : locality
|
|
@@ -703,7 +652,7 @@ function renderCaEn(random: () => number, loc: string): Rendered {
|
|
|
703
652
|
return { fmt: "ca-en-bare", raw: phrase, components: { po_box: phrase } }
|
|
704
653
|
}
|
|
705
654
|
|
|
706
|
-
function
|
|
655
|
+
function renderAUPoBox(random: () => number, t: AUTuple): Rendered {
|
|
707
656
|
const phrase = makeAuNzPoBoxPhrase(random, AU_LEADERS_CURRENT, AU_LEADERS_LEGACY, isAuDeliveryService)
|
|
708
657
|
const { locality, region: reg, postcode: pc } = t
|
|
709
658
|
const r = random()
|
|
@@ -737,8 +686,8 @@ function renderAuPoBox(random: () => number, t: AuTuple): Rendered {
|
|
|
737
686
|
return { fmt: "au-venue", raw: `${v}, ${phrase}, ${loc} ${reg} ${pc}`, components: { venue: v, ...base } }
|
|
738
687
|
}
|
|
739
688
|
|
|
740
|
-
function
|
|
741
|
-
const phrase = makeAuNzPoBoxPhrase(random, NZ_LEADERS_COMMON, NZ_LEADERS_RARE,
|
|
689
|
+
function renderNZPoBox(random: () => number, t: NZTuple): Rendered {
|
|
690
|
+
const phrase = makeAuNzPoBoxPhrase(random, NZ_LEADERS_COMMON, NZ_LEADERS_RARE, isNZDeliveryService)
|
|
742
691
|
const { locality, postcode: pc } = t
|
|
743
692
|
// NZ addresses are written mixed-case ("PO Box 4099, Timaru 7942") — no region line (ADV358).
|
|
744
693
|
const base = { po_box: phrase, locality, postcode: pc }
|
|
@@ -818,8 +767,8 @@ export const poBoxCedexRecipe: ShardRecipe = {
|
|
|
818
767
|
console.error(` GeoNames CA: QC ${qcAll.length}→${qcPool.length}, ON ${onAll.length}→${onPool.length}`)
|
|
819
768
|
|
|
820
769
|
// AU/NZ pools: same stable locality-hash holdout as FR/CA.
|
|
821
|
-
const auAll = readPostalTuples(GEONAMES_POSTAL_AU, { withState: true }) as
|
|
822
|
-
const nzAll = readPostalTuples(GEONAMES_POSTAL_NZ, { withState: false }) as
|
|
770
|
+
const auAll = readPostalTuples(GEONAMES_POSTAL_AU, { withState: true }) as AUTuple[]
|
|
771
|
+
const nzAll = readPostalTuples(GEONAMES_POSTAL_NZ, { withState: false }) as NZTuple[]
|
|
823
772
|
const auPool = auAll.filter((t) => isHoldoutLocality(t.locality) === opts.golden)
|
|
824
773
|
const nzPool = nzAll.filter((t) => isHoldoutLocality(t.locality) === opts.golden)
|
|
825
774
|
|
|
@@ -827,7 +776,7 @@ export const poBoxCedexRecipe: ShardRecipe = {
|
|
|
827
776
|
|
|
828
777
|
if (!usPool.length || !frPool.length || !qcPool.length || !onPool.length || !auPool.length || !nzPool.length) {
|
|
829
778
|
throw new Error(
|
|
830
|
-
|
|
779
|
+
`A base pool is empty — check ${dataRootPath("oa-cache")}, ${GEONAMES_CA}, ${GEONAMES_POSTAL_AU.zip}, and ${GEONAMES_POSTAL_NZ.zip}.`
|
|
831
780
|
)
|
|
832
781
|
}
|
|
833
782
|
|
|
@@ -877,11 +826,11 @@ export const poBoxCedexRecipe: ShardRecipe = {
|
|
|
877
826
|
country = "CA"
|
|
878
827
|
locale = "fr-CA"
|
|
879
828
|
} else if (cls === "po-box-au") {
|
|
880
|
-
rendered =
|
|
829
|
+
rendered = renderAUPoBox(random, pick(random, auPool))
|
|
881
830
|
country = "AU"
|
|
882
831
|
locale = "en-AU"
|
|
883
832
|
} else if (cls === "po-box-nz") {
|
|
884
|
-
rendered =
|
|
833
|
+
rendered = renderNZPoBox(random, pick(random, nzPool))
|
|
885
834
|
country = "NZ"
|
|
886
835
|
locale = "en-NZ"
|
|
887
836
|
} else if (cls === "po-box-us-military") {
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* only its synthesis + filter; the `mailwoman corpus shard <recipe>` command supplies the I/O.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
13
|
+
import { CSVSpliterator } from "spliterator"
|
|
14
|
+
|
|
12
15
|
import { stableSourceID } from "../adapter.ts"
|
|
13
16
|
import { alignRow } from "../align.ts"
|
|
14
17
|
|
|
@@ -33,38 +36,65 @@ export interface ShardTuple {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
* The
|
|
37
|
-
*
|
|
39
|
+
* The two seeded generators the legacy `build-*-shard` scripts used, re-exported from their home in
|
|
40
|
+
* `@mailwoman/core/utils` so a recipe keeps importing everything it needs from this one scaffold module.
|
|
41
|
+
*
|
|
42
|
+
* - `makeLcg` (`s = s*1664525 + 1013904223 mod 2^32`) — what the street/po-box/anchor builders seeded.
|
|
43
|
+
* - `makeMulberry32` — what the MAJORITY of them used (german, locale, boundary-stress, unit, fr-order, country-balanced,
|
|
44
|
+
* intersection, fr-admin-split, street-affix, street-bare, po-box-cedex).
|
|
45
|
+
*
|
|
46
|
+
* A recipe must seed the same one its `.mjs` did, the same way it did (usually `seed`, but some derive a per-stream
|
|
47
|
+
* seed), or `--seed N` stops being byte-reproducible.
|
|
38
48
|
*/
|
|
39
|
-
export
|
|
40
|
-
let s = seed >>> 0
|
|
41
|
-
|
|
42
|
-
return () => {
|
|
43
|
-
s = (s * 1_664_525 + 1_013_904_223) % 4_294_967_296
|
|
44
|
-
|
|
45
|
-
return s / 4_294_967_296
|
|
46
|
-
}
|
|
47
|
-
}
|
|
49
|
+
export { makeLcg, mulberry32 as makeMulberry32, makeLcg as makeRandom } from "@mailwoman/core/utils"
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
|
-
*
|
|
52
|
+
* One CSV record, keyed by its lower-cased header name, every value trimmed. A column the header does not declare reads
|
|
53
|
+
* as `undefined`; a column the header declares but this record does not reach reads as `""`.
|
|
51
54
|
*/
|
|
52
|
-
export
|
|
55
|
+
export type CSVRecord = Record<string, string | undefined>
|
|
53
56
|
|
|
54
57
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
+
* Read an in-memory CSV (an `unzip -p` buffer, say) as header-keyed records.
|
|
59
|
+
*
|
|
60
|
+
* Quote handling spans the ROW split, not only the column split: a newline inside a quoted field belongs to a single
|
|
61
|
+
* record, so the record boundaries can only be found by a scanner that already knows where the quotes are. Find the
|
|
62
|
+
* lines first and unquote afterwards and such a record splits in two — the first half short by however many columns
|
|
63
|
+
* followed the newline, which is how a `street` value comes to be read as `city`.
|
|
64
|
+
*
|
|
65
|
+
* Header names are lower-cased on the way in. `normalizeKeys` will not do it: it leaves an ALL CAPS header alone, and
|
|
66
|
+
* OpenAddresses ships `LON,LAT,NUMBER,STREET` while other extracts ship the same names lower-case. A recipe names its
|
|
67
|
+
* columns in lower case either way.
|
|
68
|
+
*
|
|
69
|
+
* Line breaks inside a value become single spaces. A quote-aware parse is the first thing here able to return a value
|
|
70
|
+
* CONTAINING one — `us/ia/statewide.csv` has 12, all unit designators like `"#2\n#2"` — and every consumer synthesizes
|
|
71
|
+
* one-line address text from these cells with no guard, because until that parse landed no value could carry one.
|
|
72
|
+
* Collapsing keeps the record (the address is fine; the source's line break is not part of it) without emitting a
|
|
73
|
+
* training row with a newline inside it.
|
|
74
|
+
*
|
|
75
|
+
* Only `\r` and `\n`, deliberately — NOT `\s`. Runs of spaces and tabs pass through exactly as the source wrote them
|
|
76
|
+
* (OA's IA extract writes `NORTH`, three spaces, `MAIN STREET`), because those could always appear and every shard
|
|
77
|
+
* built to date contains them. Widening this to `\s+` silently rewrites values on rows with no line break at all.
|
|
78
|
+
* `scaffold.test.ts` pins both halves.
|
|
58
79
|
*/
|
|
59
|
-
export function
|
|
60
|
-
let
|
|
80
|
+
export function* readCSVRecords(source: Uint8Array): Generator<CSVRecord> {
|
|
81
|
+
let header: string[] | null = null
|
|
82
|
+
|
|
83
|
+
// `header: false` keeps the first record in the stream so this reader owns the lower-casing.
|
|
84
|
+
for (const cells of CSVSpliterator.from(source, { header: false, enableQuoteHandling: true })) {
|
|
85
|
+
if (!header) {
|
|
86
|
+
header = cells.map((name) => name.trim().toLowerCase())
|
|
87
|
+
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
61
90
|
|
|
62
|
-
|
|
63
|
-
a = (a + 0x6d_2b_79_f5) | 0
|
|
64
|
-
let t = Math.imul(a ^ (a >>> 15), 1 | a)
|
|
65
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
|
91
|
+
const record: CSVRecord = {}
|
|
66
92
|
|
|
67
|
-
|
|
93
|
+
for (let i = 0; i < header.length; i++) {
|
|
94
|
+
record[header[i]!] = (cells[i] ?? "").replaceAll(/[\r\n]+/g, " ").trim()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
yield record
|
|
68
98
|
}
|
|
69
99
|
}
|
|
70
100
|
|
|
@@ -73,7 +103,7 @@ export function makeMulberry32(seed: number): () => number {
|
|
|
73
103
|
*/
|
|
74
104
|
export async function* readTuples(input: string): AsyncGenerator<ShardTuple> {
|
|
75
105
|
// TextSpliterator (not JSONSpliterator) so a malformed line is SKIPPED, not thrown — the
|
|
76
|
-
// per-line
|
|
106
|
+
// per-line tryParsingJSON below is the tolerance this reader has always had.
|
|
77
107
|
const { TextSpliterator } = await import("spliterator")
|
|
78
108
|
|
|
79
109
|
for await (const line of TextSpliterator.fromAsync(input)) {
|
|
@@ -81,10 +111,10 @@ export async function* readTuples(input: string): AsyncGenerator<ShardTuple> {
|
|
|
81
111
|
|
|
82
112
|
if (!trimmed) continue
|
|
83
113
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
const tuple = tryParsingJSON<ShardTuple>(trimmed)
|
|
115
|
+
|
|
116
|
+
if (tuple !== null) {
|
|
117
|
+
yield tuple
|
|
88
118
|
}
|
|
89
119
|
}
|
|
90
120
|
}
|
|
@@ -178,6 +208,30 @@ export interface ShardRecipeOpts {
|
|
|
178
208
|
* `no-fragment` knob 3: minimum digit count for a number to count as "long" and be boosted. Default 3.
|
|
179
209
|
*/
|
|
180
210
|
longNumberMinDigits?: number
|
|
211
|
+
/**
|
|
212
|
+
* `sub-venue`: the sub-venue lexicon JSON. Default = the committed `corpus/data/sub-venue-lexicon.json`, resolved
|
|
213
|
+
* through the package manifest so it works from the source tree and from `out/`.
|
|
214
|
+
*/
|
|
215
|
+
lexicon?: string
|
|
216
|
+
/**
|
|
217
|
+
* `sub-venue`: directory of `sub-venue-extract` JSONLs, one per region. Default
|
|
218
|
+
* `$MAILWOMAN_DATA_ROOT/sub-venue/extracts`.
|
|
219
|
+
*/
|
|
220
|
+
extractsDir?: string
|
|
221
|
+
/**
|
|
222
|
+
* `sub-venue`: the `poi.db` spatial layer, read for the en-US and fr-FR venue + confound pools (the two of poi.db's
|
|
223
|
+
* four countries this shard has legs for). Default `$MAILWOMAN_DATA_ROOT/poi/poi.db`.
|
|
224
|
+
*/
|
|
225
|
+
poiDb?: string
|
|
226
|
+
/**
|
|
227
|
+
* `sub-venue`: GB/US/FR address-context tuples JSONL. Default the house-venue v3 tuples
|
|
228
|
+
* (`$MAILWOMAN_DATA_ROOT/corpus/intermediate/house-venue-tuples-v3.jsonl`); DE and ES read OpenAddresses directly.
|
|
229
|
+
*/
|
|
230
|
+
subVenueTuples?: string
|
|
231
|
+
/**
|
|
232
|
+
* `sub-venue`: share of emitted rows that are confound NEGATIVES. Default 0.3.
|
|
233
|
+
*/
|
|
234
|
+
negativeFraction?: number
|
|
181
235
|
}
|
|
182
236
|
|
|
183
237
|
/**
|