@mailwoman/corpus 4.16.2 → 5.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/out/src/adapter.d.ts +23 -26
- package/out/src/adapter.d.ts.map +1 -1
- package/out/src/adapter.js +25 -28
- package/out/src/adapter.js.map +1 -1
- package/out/src/adapters/ban/adapter.d.ts.map +1 -1
- package/out/src/adapters/ban/adapter.js +10 -10
- package/out/src/adapters/ban/adapter.js.map +1 -1
- package/out/src/adapters/ban/street-decompose.d.ts +2 -2
- package/out/src/adapters/ban/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/ban/street-decompose.js +2 -2
- package/out/src/adapters/ban/street-decompose.js.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.d.ts +2 -2
- package/out/src/adapters/fcc-bdc/adapter.d.ts.map +1 -1
- package/out/src/adapters/fcc-bdc/adapter.js +9 -9
- 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 +5 -5
- 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 +3 -3
- package/out/src/adapters/geonames-postal/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/adapter.d.ts +3 -3
- package/out/src/adapters/gnaf/adapter.d.ts.map +1 -1
- package/out/src/adapters/gnaf/adapter.js +11 -10
- package/out/src/adapters/gnaf/adapter.js.map +1 -1
- package/out/src/adapters/gnaf/assemble.d.ts +4 -5
- package/out/src/adapters/gnaf/assemble.d.ts.map +1 -1
- package/out/src/adapters/gnaf/assemble.js +1 -1
- package/out/src/adapters/gnaf/assemble.js.map +1 -1
- package/out/src/adapters/index.d.ts +9 -10
- package/out/src/adapters/index.d.ts.map +1 -1
- package/out/src/adapters/index.js +13 -14
- package/out/src/adapters/index.js.map +1 -1
- package/out/src/adapters/openaddresses/adapter.d.ts +8 -10
- package/out/src/adapters/openaddresses/adapter.d.ts.map +1 -1
- package/out/src/adapters/openaddresses/adapter.js +8 -8
- 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 +2 -2
- package/out/src/adapters/overture/adapter.js.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.d.ts +1 -1
- package/out/src/adapters/state-hi-schools/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-hi-schools/adapter.js +6 -6
- package/out/src/adapters/state-hi-schools/adapter.js.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.d.ts +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ia-contractors/adapter.js +6 -6
- package/out/src/adapters/state-ia-contractors/adapter.js.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.d.ts +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.d.ts.map +1 -1
- package/out/src/adapters/state-ny-notaries/adapter.js +6 -6
- 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 +7 -7
- package/out/src/adapters/state-tx-notaries/adapter.js.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.d.ts +9 -9
- package/out/src/adapters/synth-po-box/adapter.d.ts.map +1 -1
- package/out/src/adapters/synth-po-box/adapter.js +6 -6
- package/out/src/adapters/synth-po-box/adapter.js.map +1 -1
- package/out/src/adapters/tiger/adapter.d.ts.map +1 -1
- package/out/src/adapters/tiger/adapter.js +2 -2
- package/out/src/adapters/tiger/adapter.js.map +1 -1
- package/out/src/adapters/tiger/street-decompose.d.ts +2 -2
- package/out/src/adapters/tiger/street-decompose.d.ts.map +1 -1
- package/out/src/adapters/tiger/street-decompose.js +2 -2
- package/out/src/adapters/tiger/street-decompose.js.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js +15 -17
- package/out/src/adapters/usgov-hrsa-fqhc/adapter.js.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.d.ts +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-imls-pls/adapter.js +6 -6
- 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 +5 -5
- package/out/src/adapters/usgov-irs-bmf/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.d.ts +2 -2
- package/out/src/adapters/usgov-nad/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nad/adapter.js +6 -6
- package/out/src/adapters/usgov-nad/adapter.js.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.d.ts +3 -3
- package/out/src/adapters/usgov-nppes/adapter.d.ts.map +1 -1
- package/out/src/adapters/usgov-nppes/adapter.js +7 -7
- 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 +12 -12
- package/out/src/adapters/usgov-samhsa-treatment-locator/adapter.js.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.d.ts +3 -3
- package/out/src/adapters/wof-admin-jp/adapter.d.ts.map +1 -1
- package/out/src/adapters/wof-admin-jp/adapter.js +6 -6
- package/out/src/adapters/wof-admin-jp/adapter.js.map +1 -1
- package/out/src/adapters/wof-admin-json/adapter.d.ts +17 -19
- package/out/src/adapters/wof-admin-json/adapter.d.ts.map +1 -1
- package/out/src/adapters/wof-admin-json/adapter.js +25 -28
- package/out/src/adapters/wof-admin-json/adapter.js.map +1 -1
- package/out/src/adapters/wof-postalcode-json/adapter.d.ts +8 -8
- package/out/src/adapters/wof-postalcode-json/adapter.d.ts.map +1 -1
- package/out/src/adapters/wof-postalcode-json/adapter.js +11 -11
- package/out/src/adapters/wof-postalcode-json/adapter.js.map +1 -1
- package/out/src/align.d.ts +14 -16
- package/out/src/align.d.ts.map +1 -1
- package/out/src/align.js +15 -16
- package/out/src/align.js.map +1 -1
- package/out/src/build.d.ts +13 -15
- package/out/src/build.d.ts.map +1 -1
- package/out/src/build.js +4 -5
- package/out/src/build.js.map +1 -1
- package/out/src/codex/us-fips-state.d.ts +6 -8
- package/out/src/codex/us-fips-state.d.ts.map +1 -1
- package/out/src/codex/us-fips-state.js +6 -8
- package/out/src/codex/us-fips-state.js.map +1 -1
- package/out/src/golden.d.ts.map +1 -1
- package/out/src/golden.js +1 -1
- package/out/src/golden.js.map +1 -1
- package/out/src/index.d.ts +1 -0
- package/out/src/index.d.ts.map +1 -1
- package/out/src/index.js +1 -0
- package/out/src/index.js.map +1 -1
- package/out/src/license.d.ts +5 -6
- package/out/src/license.d.ts.map +1 -1
- package/out/src/license.js +5 -6
- package/out/src/license.js.map +1 -1
- package/out/src/parquet-wrapper/reader.d.ts.map +1 -1
- package/out/src/parquet-wrapper/reader.js.map +1 -1
- package/out/src/parquet-wrapper/writer.d.ts.map +1 -1
- package/out/src/parquet-wrapper/writer.js +2 -2
- package/out/src/parquet-wrapper/writer.js.map +1 -1
- package/out/src/parquet.d.ts +16 -18
- package/out/src/parquet.d.ts.map +1 -1
- package/out/src/parquet.js +22 -23
- package/out/src/parquet.js.map +1 -1
- package/out/src/runner.d.ts +10 -10
- package/out/src/runner.d.ts.map +1 -1
- package/out/src/runner.js +8 -8
- package/out/src/runner.js.map +1 -1
- package/out/src/shard-recipes/anchor-absorption.d.ts +16 -0
- package/out/src/shard-recipes/anchor-absorption.d.ts.map +1 -0
- package/out/src/shard-recipes/anchor-absorption.js +73 -0
- package/out/src/shard-recipes/anchor-absorption.js.map +1 -0
- package/out/src/shard-recipes/boundary-stress.d.ts +16 -0
- package/out/src/shard-recipes/boundary-stress.d.ts.map +1 -0
- package/out/src/shard-recipes/boundary-stress.js +77 -0
- package/out/src/shard-recipes/boundary-stress.js.map +1 -0
- package/out/src/shard-recipes/country-balanced.d.ts +28 -0
- package/out/src/shard-recipes/country-balanced.d.ts.map +1 -0
- package/out/src/shard-recipes/country-balanced.js +370 -0
- package/out/src/shard-recipes/country-balanced.js.map +1 -0
- package/out/src/shard-recipes/fr-admin-split.d.ts +34 -0
- package/out/src/shard-recipes/fr-admin-split.d.ts.map +1 -0
- package/out/src/shard-recipes/fr-admin-split.js +175 -0
- package/out/src/shard-recipes/fr-admin-split.js.map +1 -0
- package/out/src/shard-recipes/fr-bare-street.d.ts +24 -0
- package/out/src/shard-recipes/fr-bare-street.d.ts.map +1 -0
- package/out/src/shard-recipes/fr-bare-street.js +76 -0
- package/out/src/shard-recipes/fr-bare-street.js.map +1 -0
- package/out/src/shard-recipes/fr-order.d.ts +34 -0
- package/out/src/shard-recipes/fr-order.d.ts.map +1 -0
- package/out/src/shard-recipes/fr-order.js +238 -0
- package/out/src/shard-recipes/fr-order.js.map +1 -0
- package/out/src/shard-recipes/german.d.ts +19 -0
- package/out/src/shard-recipes/german.d.ts.map +1 -0
- package/out/src/shard-recipes/german.js +170 -0
- package/out/src/shard-recipes/german.js.map +1 -0
- package/out/src/shard-recipes/house-venue.d.ts +14 -0
- package/out/src/shard-recipes/house-venue.d.ts.map +1 -0
- package/out/src/shard-recipes/house-venue.js +66 -0
- package/out/src/shard-recipes/house-venue.js.map +1 -0
- package/out/src/shard-recipes/index.d.ts +18 -0
- package/out/src/shard-recipes/index.d.ts.map +1 -0
- package/out/src/shard-recipes/index.js +58 -0
- package/out/src/shard-recipes/index.js.map +1 -0
- package/out/src/shard-recipes/intersection.d.ts +38 -0
- package/out/src/shard-recipes/intersection.d.ts.map +1 -0
- package/out/src/shard-recipes/intersection.js +473 -0
- package/out/src/shard-recipes/intersection.js.map +1 -0
- package/out/src/shard-recipes/locale.d.ts +19 -0
- package/out/src/shard-recipes/locale.d.ts.map +1 -0
- package/out/src/shard-recipes/locale.js +257 -0
- package/out/src/shard-recipes/locale.js.map +1 -0
- package/out/src/shard-recipes/no-street.d.ts +14 -0
- package/out/src/shard-recipes/no-street.d.ts.map +1 -0
- package/out/src/shard-recipes/no-street.js +66 -0
- package/out/src/shard-recipes/no-street.js.map +1 -0
- package/out/src/shard-recipes/po-box-cedex.d.ts +34 -0
- package/out/src/shard-recipes/po-box-cedex.d.ts.map +1 -0
- package/out/src/shard-recipes/po-box-cedex.js +686 -0
- package/out/src/shard-recipes/po-box-cedex.js.map +1 -0
- package/out/src/shard-recipes/po-box.d.ts +13 -0
- package/out/src/shard-recipes/po-box.d.ts.map +1 -0
- package/out/src/shard-recipes/po-box.js +93 -0
- package/out/src/shard-recipes/po-box.js.map +1 -0
- package/out/src/shard-recipes/scaffold.d.ts +104 -0
- package/out/src/shard-recipes/scaffold.d.ts.map +1 -0
- package/out/src/shard-recipes/scaffold.js +76 -0
- package/out/src/shard-recipes/scaffold.js.map +1 -0
- package/out/src/shard-recipes/street-affix.d.ts +31 -0
- package/out/src/shard-recipes/street-affix.d.ts.map +1 -0
- package/out/src/shard-recipes/street-affix.js +408 -0
- package/out/src/shard-recipes/street-affix.js.map +1 -0
- package/out/src/shard-recipes/street-bare.d.ts +21 -0
- package/out/src/shard-recipes/street-bare.d.ts.map +1 -0
- package/out/src/shard-recipes/street-bare.js +82 -0
- package/out/src/shard-recipes/street-bare.js.map +1 -0
- package/out/src/shard-recipes/street.d.ts +11 -0
- package/out/src/shard-recipes/street.d.ts.map +1 -0
- package/out/src/shard-recipes/street.js +63 -0
- package/out/src/shard-recipes/street.js.map +1 -0
- package/out/src/shard-recipes/unit.d.ts +25 -0
- package/out/src/shard-recipes/unit.d.ts.map +1 -0
- package/out/src/shard-recipes/unit.js +255 -0
- package/out/src/shard-recipes/unit.js.map +1 -0
- package/out/src/split.d.ts +23 -29
- package/out/src/split.d.ts.map +1 -1
- package/out/src/split.js +22 -29
- package/out/src/split.js.map +1 -1
- package/out/src/synthesize-anchor-absorption.d.ts +2 -3
- package/out/src/synthesize-anchor-absorption.d.ts.map +1 -1
- package/out/src/synthesize-anchor-absorption.js +2 -2
- package/out/src/synthesize-anchor-absorption.js.map +1 -1
- package/out/src/synthesize-boundary-stress.d.ts +3 -4
- package/out/src/synthesize-boundary-stress.d.ts.map +1 -1
- package/out/src/synthesize-boundary-stress.js +3 -4
- package/out/src/synthesize-boundary-stress.js.map +1 -1
- package/out/src/synthesize-german.d.ts +15 -19
- package/out/src/synthesize-german.d.ts.map +1 -1
- package/out/src/synthesize-german.js +14 -17
- package/out/src/synthesize-german.js.map +1 -1
- package/out/src/synthesize-house-venue.d.ts +2 -3
- package/out/src/synthesize-house-venue.d.ts.map +1 -1
- package/out/src/synthesize-house-venue.js +2 -3
- package/out/src/synthesize-house-venue.js.map +1 -1
- package/out/src/synthesize-intersection.d.ts +2 -2
- package/out/src/synthesize-intersection.d.ts.map +1 -1
- package/out/src/synthesize-intersection.js +4 -4
- package/out/src/synthesize-intersection.js.map +1 -1
- package/out/src/synthesize-no-street.d.ts +5 -5
- package/out/src/synthesize-no-street.d.ts.map +1 -1
- package/out/src/synthesize-no-street.js +27 -23
- package/out/src/synthesize-no-street.js.map +1 -1
- package/out/src/synthesize-po-box.d.ts +15 -17
- package/out/src/synthesize-po-box.d.ts.map +1 -1
- package/out/src/synthesize-po-box.js +27 -27
- package/out/src/synthesize-po-box.js.map +1 -1
- package/out/src/synthesize-street.d.ts +8 -9
- package/out/src/synthesize-street.d.ts.map +1 -1
- package/out/src/synthesize-street.js +3 -3
- package/out/src/synthesize-street.js.map +1 -1
- package/out/src/synthesize.d.ts +57 -66
- package/out/src/synthesize.d.ts.map +1 -1
- package/out/src/synthesize.js +56 -65
- package/out/src/synthesize.js.map +1 -1
- package/out/src/tokenize.d.ts +5 -6
- package/out/src/tokenize.d.ts.map +1 -1
- package/out/src/tokenize.js +5 -6
- package/out/src/tokenize.js.map +1 -1
- package/out/src/types.d.ts +49 -51
- package/out/src/types.d.ts.map +1 -1
- package/out/src/wof-json.d.ts +32 -34
- package/out/src/wof-json.d.ts.map +1 -1
- package/out/src/wof-json.js +23 -25
- package/out/src/wof-json.js.map +1 -1
- package/package.json +14 -14
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `house-venue` shard recipe — synthetic house_number+venue+street co-occurrence rows: tuples →
|
|
7
|
+
* {@link synthesizeHouseVenueRow} → aligned LabeledRow. The v0.6.3 corrective companion to the
|
|
8
|
+
* no-street shard: every row carries BOTH house_number AND venue, restoring the house_number
|
|
9
|
+
* signal that no-street's distributional shift cost the model. Ported from
|
|
10
|
+
* scripts/build-house-venue-shard.mjs.
|
|
11
|
+
*/
|
|
12
|
+
import { synthesizeHouseVenueRow } from "../synthesize-house-venue.js";
|
|
13
|
+
import { alignAndWrite, makeLcg, readTuples, shardSourceID } from "./scaffold.js";
|
|
14
|
+
const LICENSE = "Synthetic — derived from CC-BY / public-domain input tuples";
|
|
15
|
+
export const houseVenueRecipe = {
|
|
16
|
+
name: "house-venue",
|
|
17
|
+
description: "House_number+venue co-occurrence rows: tuples → synthesizeHouseVenueRow → aligned LabeledRow",
|
|
18
|
+
mode: "tuples",
|
|
19
|
+
async run(opts, write) {
|
|
20
|
+
if (!opts.input)
|
|
21
|
+
throw new Error("house-venue recipe requires --input <tuples.jsonl>");
|
|
22
|
+
// Legacy build-house-venue-shard.mjs seeded the LCG via makeRandom(opts.seed) (s = seed).
|
|
23
|
+
const random = makeLcg(opts.seed);
|
|
24
|
+
const source = opts.sourceName ?? "synth-house-venue";
|
|
25
|
+
let read = 0;
|
|
26
|
+
let emitted = 0;
|
|
27
|
+
let skipped = 0;
|
|
28
|
+
for await (const tuple of readTuples(opts.input)) {
|
|
29
|
+
read++;
|
|
30
|
+
if (!tuple.locality || !tuple.region || !tuple.postcode || !tuple.country) {
|
|
31
|
+
skipped++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
for (let v = 0; v < opts.variants; v++) {
|
|
35
|
+
const synth = synthesizeHouseVenueRow(tuple, { random });
|
|
36
|
+
if (!synth) {
|
|
37
|
+
skipped++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const ok = alignAndWrite(write, {
|
|
41
|
+
raw: synth.raw,
|
|
42
|
+
components: synth.components,
|
|
43
|
+
country: tuple.country,
|
|
44
|
+
locale: synth.locale,
|
|
45
|
+
source,
|
|
46
|
+
source_id: shardSourceID(source, {
|
|
47
|
+
locality: tuple.locality,
|
|
48
|
+
region: tuple.region,
|
|
49
|
+
postcode: tuple.postcode,
|
|
50
|
+
country: tuple.country,
|
|
51
|
+
template: synth.template,
|
|
52
|
+
v: String(v),
|
|
53
|
+
}),
|
|
54
|
+
corpus_version: "0.4.0",
|
|
55
|
+
license: LICENSE,
|
|
56
|
+
}, synth.template);
|
|
57
|
+
if (ok)
|
|
58
|
+
emitted++;
|
|
59
|
+
else
|
|
60
|
+
skipped++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { read, emitted, skipped };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=house-venue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"house-venue.js","sourceRoot":"","sources":["../../../src/shard-recipes/house-venue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,uBAAuB,EAA4B,MAAM,8BAA8B,CAAA;AAChG,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAoB,MAAM,eAAe,CAAA;AAEnG,MAAM,OAAO,GAAG,6DAA6D,CAAA;AAE7E,MAAM,CAAC,MAAM,gBAAgB,GAAgB;IAC5C,IAAI,EAAE,aAAa;IACnB,WAAW,EAAE,8FAA8F;IAC3G,IAAI,EAAE,QAAQ;IACd,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK;QACpB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAA;QACtF,0FAA0F;QAC1F,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAA;QACrD,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,IAAI,OAAO,GAAG,CAAC,CAAA;QAEf,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,IAAI,EAAE,CAAA;YAEN,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBAC3E,OAAO,EAAE,CAAA;gBACT,SAAQ;YACT,CAAC;YAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;gBACxC,MAAM,KAAK,GAAG,uBAAuB,CAAC,KAA4B,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;gBAE/E,IAAI,CAAC,KAAK,EAAE,CAAC;oBACZ,OAAO,EAAE,CAAA;oBACT,SAAQ;gBACT,CAAC;gBACD,MAAM,EAAE,GAAG,aAAa,CACvB,KAAK,EACL;oBACC,GAAG,EAAE,KAAK,CAAC,GAAG;oBACd,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,MAAM;oBACN,SAAS,EAAE,aAAa,CAAC,MAAM,EAAE;wBAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;wBACxB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;qBACZ,CAAC;oBACF,cAAc,EAAE,OAAO;oBACvB,OAAO,EAAE,OAAO;iBAChB,EACD,KAAK,CAAC,QAAQ,CACd,CAAA;gBAED,IAAI,EAAE;oBAAE,OAAO,EAAE,CAAA;;oBACZ,OAAO,EAAE,CAAA;YACf,CAAC;QACF,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAClC,CAAC;CACD,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The synthetic-corpus SHARD RECIPE registry. Each recipe ({@link ShardRecipe}) is what one legacy
|
|
7
|
+
* `build-*-shard.mjs` script used to be; the `mailwoman corpus shard <recipe>` command dispatches
|
|
8
|
+
* to them. A new shard is a new entry here, not a new script.
|
|
9
|
+
*/
|
|
10
|
+
import type { ShardRecipe } from "./scaffold.js";
|
|
11
|
+
export * from "./scaffold.js";
|
|
12
|
+
/** Recipe name → recipe. */
|
|
13
|
+
export declare const SHARD_RECIPES: ReadonlyMap<string, ShardRecipe>;
|
|
14
|
+
/** Look up a recipe by its `<recipe>` name. */
|
|
15
|
+
export declare function getShardRecipe(name: string): ShardRecipe | undefined;
|
|
16
|
+
/** All recipes (for `--list` / help). */
|
|
17
|
+
export declare function listShardRecipes(): readonly ShardRecipe[];
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shard-recipes/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAMhD,cAAc,eAAe,CAAA;AAuB7B,4BAA4B;AAC5B,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAA4C,CAAA;AAEvG,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEpE;AAED,yCAAyC;AACzC,wBAAgB,gBAAgB,IAAI,SAAS,WAAW,EAAE,CAEzD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The synthetic-corpus SHARD RECIPE registry. Each recipe ({@link ShardRecipe}) is what one legacy
|
|
7
|
+
* `build-*-shard.mjs` script used to be; the `mailwoman corpus shard <recipe>` command dispatches
|
|
8
|
+
* to them. A new shard is a new entry here, not a new script.
|
|
9
|
+
*/
|
|
10
|
+
import { anchorAbsorptionRecipe } from "./anchor-absorption.js";
|
|
11
|
+
import { boundaryStressRecipe } from "./boundary-stress.js";
|
|
12
|
+
import { countryBalancedRecipe } from "./country-balanced.js";
|
|
13
|
+
import { frAdminSplitRecipe } from "./fr-admin-split.js";
|
|
14
|
+
import { frBareStreetRecipe } from "./fr-bare-street.js";
|
|
15
|
+
import { frOrderRecipe } from "./fr-order.js";
|
|
16
|
+
import { germanRecipe } from "./german.js";
|
|
17
|
+
import { houseVenueRecipe } from "./house-venue.js";
|
|
18
|
+
import { intersectionRecipe } from "./intersection.js";
|
|
19
|
+
import { localeRecipe } from "./locale.js";
|
|
20
|
+
import { noStreetRecipe } from "./no-street.js";
|
|
21
|
+
import { poBoxCedexRecipe } from "./po-box-cedex.js";
|
|
22
|
+
import { poBoxRecipe } from "./po-box.js";
|
|
23
|
+
import { streetAffixRecipe } from "./street-affix.js";
|
|
24
|
+
import { streetBareRecipe } from "./street-bare.js";
|
|
25
|
+
import { streetRecipe } from "./street.js";
|
|
26
|
+
import { unitRecipe } from "./unit.js";
|
|
27
|
+
export * from "./scaffold.js";
|
|
28
|
+
/** Every registered recipe, in display order. */
|
|
29
|
+
const RECIPES = [
|
|
30
|
+
streetRecipe,
|
|
31
|
+
streetBareRecipe,
|
|
32
|
+
streetAffixRecipe,
|
|
33
|
+
noStreetRecipe,
|
|
34
|
+
houseVenueRecipe,
|
|
35
|
+
poBoxRecipe,
|
|
36
|
+
poBoxCedexRecipe,
|
|
37
|
+
unitRecipe,
|
|
38
|
+
intersectionRecipe,
|
|
39
|
+
germanRecipe,
|
|
40
|
+
localeRecipe,
|
|
41
|
+
frOrderRecipe,
|
|
42
|
+
frAdminSplitRecipe,
|
|
43
|
+
frBareStreetRecipe,
|
|
44
|
+
countryBalancedRecipe,
|
|
45
|
+
boundaryStressRecipe,
|
|
46
|
+
anchorAbsorptionRecipe,
|
|
47
|
+
];
|
|
48
|
+
/** Recipe name → recipe. */
|
|
49
|
+
export const SHARD_RECIPES = new Map(RECIPES.map((r) => [r.name, r]));
|
|
50
|
+
/** Look up a recipe by its `<recipe>` name. */
|
|
51
|
+
export function getShardRecipe(name) {
|
|
52
|
+
return SHARD_RECIPES.get(name);
|
|
53
|
+
}
|
|
54
|
+
/** All recipes (for `--list` / help). */
|
|
55
|
+
export function listShardRecipes() {
|
|
56
|
+
return RECIPES;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shard-recipes/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAEtC,cAAc,eAAe,CAAA;AAE7B,iDAAiD;AACjD,MAAM,OAAO,GAA2B;IACvC,YAAY;IACZ,gBAAgB;IAChB,iBAAiB;IACjB,cAAc;IACd,gBAAgB;IAChB,WAAW;IACX,gBAAgB;IAChB,UAAU;IACV,kBAAkB;IAClB,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,kBAAkB;IAClB,kBAAkB;IAClB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;CACtB,CAAA;AAED,4BAA4B;AAC5B,MAAM,CAAC,MAAM,aAAa,GAAqC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAEvG,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,gBAAgB;IAC/B,OAAO,OAAO,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `intersection` shard recipe — the REAL-pair intersection training shard (#487). The model scored
|
|
7
|
+
* 0.0 on intersection_a/b because the training mix had ZERO intersection-labeled rows. This is
|
|
8
|
+
* the missing data. Ported from scripts/build-intersection-shard.mjs.
|
|
9
|
+
*
|
|
10
|
+
* STREET PAIRS ARE REAL: the same TIGER 2023 EDGES extraction as the eval builder
|
|
11
|
+
* (scripts/eval/build-intersection-real.ts) — a node where two road edges (MTFCC S1*) with
|
|
12
|
+
* distinct FULLNAMEs meet is a real crossing. Real pairs avoid teaching fake street-street
|
|
13
|
+
* co-occurrences.
|
|
14
|
+
*
|
|
15
|
+
* LEAKAGE POLICY (mirrors the affix shard's VT discipline):
|
|
16
|
+
*
|
|
17
|
+
* - TRAIN counties: Cook IL (grid city) + Morris NJ (suburb).
|
|
18
|
+
* - GOLDEN (`--golden`) county: Washington VT (rural) ONLY — the corpus defaultHoldout state.
|
|
19
|
+
* - Every crossing in data/eval/external/intersection-real.jsonl is excluded from BOTH modes, by node
|
|
20
|
+
* id AND by order-insensitive name pair (the eval shares all three counties).
|
|
21
|
+
*
|
|
22
|
+
* RENDERING: junction-format variety — padded/TIGHT `&` and `/`, `and`, `at`, `@`, leading-phrase
|
|
23
|
+
* `corner of` / `intersection of` — crossed with tails (bare / `, ST` / `, ST ZIP` / `, City, ST
|
|
24
|
+
* [ZIP]`) and case variants. ZIPs are the crossing's own TIGER edge ZIPL (real); the locality
|
|
25
|
+
* tail comes from the OA Cook-county ZIP→city majority map.
|
|
26
|
+
*
|
|
27
|
+
* AUDIT: every emitted row is label-checked on the RAW SURFACE via the #519 char-offset span
|
|
28
|
+
* triple. Any violation FAILS the build (throws). A JSON audit report lands next to the output.
|
|
29
|
+
*
|
|
30
|
+
* External inputs (`--edges-dir`, opts.edgesDir; both already on disk — do not re-download):
|
|
31
|
+
*
|
|
32
|
+
* - <edges-dir>/tl_2023_{17031,34027,50023}_edges.shp (unzipped TIGER 2023 EDGES; default
|
|
33
|
+
* /tmp/tiger-edges)
|
|
34
|
+
* - /tmp/oa-cache/us__il__cook.zip (ZIP→city tails)
|
|
35
|
+
*/
|
|
36
|
+
import { type ShardRecipe } from "./scaffold.js";
|
|
37
|
+
export declare const intersectionRecipe: ShardRecipe;
|
|
38
|
+
//# sourceMappingURL=intersection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intersection.d.ts","sourceRoot":"","sources":["../../../src/shard-recipes/intersection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAYH,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAsXhE,eAAO,MAAM,kBAAkB,EAAE,WAkKhC,CAAA"}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `intersection` shard recipe — the REAL-pair intersection training shard (#487). The model scored
|
|
7
|
+
* 0.0 on intersection_a/b because the training mix had ZERO intersection-labeled rows. This is
|
|
8
|
+
* the missing data. Ported from scripts/build-intersection-shard.mjs.
|
|
9
|
+
*
|
|
10
|
+
* STREET PAIRS ARE REAL: the same TIGER 2023 EDGES extraction as the eval builder
|
|
11
|
+
* (scripts/eval/build-intersection-real.ts) — a node where two road edges (MTFCC S1*) with
|
|
12
|
+
* distinct FULLNAMEs meet is a real crossing. Real pairs avoid teaching fake street-street
|
|
13
|
+
* co-occurrences.
|
|
14
|
+
*
|
|
15
|
+
* LEAKAGE POLICY (mirrors the affix shard's VT discipline):
|
|
16
|
+
*
|
|
17
|
+
* - TRAIN counties: Cook IL (grid city) + Morris NJ (suburb).
|
|
18
|
+
* - GOLDEN (`--golden`) county: Washington VT (rural) ONLY — the corpus defaultHoldout state.
|
|
19
|
+
* - Every crossing in data/eval/external/intersection-real.jsonl is excluded from BOTH modes, by node
|
|
20
|
+
* id AND by order-insensitive name pair (the eval shares all three counties).
|
|
21
|
+
*
|
|
22
|
+
* RENDERING: junction-format variety — padded/TIGHT `&` and `/`, `and`, `at`, `@`, leading-phrase
|
|
23
|
+
* `corner of` / `intersection of` — crossed with tails (bare / `, ST` / `, ST ZIP` / `, City, ST
|
|
24
|
+
* [ZIP]`) and case variants. ZIPs are the crossing's own TIGER edge ZIPL (real); the locality
|
|
25
|
+
* tail comes from the OA Cook-county ZIP→city majority map.
|
|
26
|
+
*
|
|
27
|
+
* AUDIT: every emitted row is label-checked on the RAW SURFACE via the #519 char-offset span
|
|
28
|
+
* triple. Any violation FAILS the build (throws). A JSON audit report lands next to the output.
|
|
29
|
+
*
|
|
30
|
+
* External inputs (`--edges-dir`, opts.edgesDir; both already on disk — do not re-download):
|
|
31
|
+
*
|
|
32
|
+
* - <edges-dir>/tl_2023_{17031,34027,50023}_edges.shp (unzipped TIGER 2023 EDGES; default
|
|
33
|
+
* /tmp/tiger-edges)
|
|
34
|
+
* - /tmp/oa-cache/us__il__cook.zip (ZIP→city tails)
|
|
35
|
+
*/
|
|
36
|
+
import { spawnSync } from "node:child_process";
|
|
37
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
38
|
+
import { repoRootPathBuilder } from "@mailwoman/core/utils";
|
|
39
|
+
import { stableSourceID } from "../adapter.js";
|
|
40
|
+
import { alignRow } from "../align.js";
|
|
41
|
+
import { makeMulberry32 } from "./scaffold.js";
|
|
42
|
+
const TRAIN_COUNTIES = [
|
|
43
|
+
{ fips: "17031", state: "IL", regime: "grid-city" },
|
|
44
|
+
{ fips: "34027", state: "NJ", regime: "suburb" },
|
|
45
|
+
];
|
|
46
|
+
const GOLDEN_COUNTIES = [{ fips: "50023", state: "VT", regime: "rural" }];
|
|
47
|
+
const EVAL_GOLD_PATH = String(repoRootPathBuilder("data", "eval", "external", "intersection-real.jsonl"));
|
|
48
|
+
const OA_COOK = { zip: "/tmp/oa-cache/us__il__cook.zip", csv: "us/il/cook.csv" };
|
|
49
|
+
const FORMS = [
|
|
50
|
+
{ id: "amp", w: 0.2, render: (a, b) => `${a} & ${b}` },
|
|
51
|
+
{ id: "and", w: 0.2, render: (a, b) => `${a} and ${b}` },
|
|
52
|
+
{ id: "at", w: 0.12, render: (a, b) => `${a} at ${b}` },
|
|
53
|
+
{ id: "slash", w: 0.08, render: (a, b) => `${a} / ${b}` },
|
|
54
|
+
{ id: "slash-tight", w: 0.06, render: (a, b) => `${a}/${b}` },
|
|
55
|
+
{ id: "amp-tight", w: 0.05, render: (a, b) => `${a}&${b}` },
|
|
56
|
+
{ id: "at-sign", w: 0.06, render: (a, b) => `${a} @ ${b}` },
|
|
57
|
+
{ id: "corner-of", w: 0.115, render: (a, b) => `corner of ${a} and ${b}` },
|
|
58
|
+
{ id: "intersection-of", w: 0.115, render: (a, b) => `intersection of ${a} and ${b}` },
|
|
59
|
+
];
|
|
60
|
+
const TAILS = [
|
|
61
|
+
{ id: "bare", w: 0.55 },
|
|
62
|
+
{ id: "region", w: 0.16 },
|
|
63
|
+
{ id: "region-zip", w: 0.09 },
|
|
64
|
+
{ id: "city-region", w: 0.08 },
|
|
65
|
+
{ id: "city-region-zip", w: 0.12 },
|
|
66
|
+
];
|
|
67
|
+
const CASES = [
|
|
68
|
+
{ id: "as-is", w: 0.82, apply: (s) => s },
|
|
69
|
+
{ id: "upper", w: 0.12, apply: (s) => s.toUpperCase() },
|
|
70
|
+
{ id: "lower", w: 0.06, apply: (s) => s.toLowerCase() },
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* Words a connector may contribute as O tokens. The audit rejects any O token outside this set — an unlabeled
|
|
74
|
+
* street/locality token would surface here.
|
|
75
|
+
*/
|
|
76
|
+
const CONNECTOR_O_TOKENS = new Set(["and", "at", "of", "corner", "intersection"]);
|
|
77
|
+
/**
|
|
78
|
+
* Street names that would make the connector ambiguous or break verbatim alignment: embedded connector punctuation, or
|
|
79
|
+
* a standalone "and"/"at" word.
|
|
80
|
+
*/
|
|
81
|
+
const BAD_NAME = /[,&@/]|\b(and|at)\b/i;
|
|
82
|
+
/** Punctuation a connector form may leave between spans (besides whitespace): `, & @ /`. */
|
|
83
|
+
const CONNECTOR_PUNCT_RE = /^[\s,&@/]*$/;
|
|
84
|
+
function weightedPick(items, random) {
|
|
85
|
+
const total = items.reduce((s, x) => s + x.w, 0);
|
|
86
|
+
let r = random() * total;
|
|
87
|
+
for (const item of items) {
|
|
88
|
+
r -= item.w;
|
|
89
|
+
if (r <= 0)
|
|
90
|
+
return item;
|
|
91
|
+
}
|
|
92
|
+
return items[items.length - 1];
|
|
93
|
+
}
|
|
94
|
+
/** Minimal RFC-4180-ish splitter (handles quoted fields) — same as the affix builder. */
|
|
95
|
+
function splitCSV(line) {
|
|
96
|
+
const out = [];
|
|
97
|
+
let cur = "";
|
|
98
|
+
let inQ = false;
|
|
99
|
+
for (let i = 0; i < line.length; i++) {
|
|
100
|
+
const c = line[i];
|
|
101
|
+
if (inQ) {
|
|
102
|
+
if (c === '"') {
|
|
103
|
+
if (line[i + 1] === '"') {
|
|
104
|
+
cur += '"';
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
inQ = false;
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
cur += c;
|
|
112
|
+
}
|
|
113
|
+
else if (c === '"')
|
|
114
|
+
inQ = true;
|
|
115
|
+
else if (c === ",") {
|
|
116
|
+
out.push(cur);
|
|
117
|
+
cur = "";
|
|
118
|
+
}
|
|
119
|
+
else
|
|
120
|
+
cur += c;
|
|
121
|
+
}
|
|
122
|
+
out.push(cur);
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/** Order-insensitive crossing key, for eval-leakage exclusion + pair dedup. */
|
|
126
|
+
const pairKey = (a, b) => [a.toLowerCase(), b.toLowerCase()].sort().join("\x1f");
|
|
127
|
+
/** Load the eval's crossings so neither train nor golden ever sees them. */
|
|
128
|
+
function readEvalExclusions() {
|
|
129
|
+
const nodes = new Set();
|
|
130
|
+
const pairs = new Set();
|
|
131
|
+
if (!existsSync(EVAL_GOLD_PATH)) {
|
|
132
|
+
console.error(` WARN: eval gold not found at ${EVAL_GOLD_PATH} — no eval-leakage exclusion applied`);
|
|
133
|
+
return { nodes, pairs };
|
|
134
|
+
}
|
|
135
|
+
for (const line of readFileSync(EVAL_GOLD_PATH, "utf8").split("\n")) {
|
|
136
|
+
if (!line)
|
|
137
|
+
continue;
|
|
138
|
+
const row = JSON.parse(line);
|
|
139
|
+
nodes.add(Number(row.node));
|
|
140
|
+
pairs.add(pairKey(row.components.intersection_a, row.components.intersection_b));
|
|
141
|
+
}
|
|
142
|
+
return { nodes, pairs };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extract real crossings from one county's TIGER EDGES shapefile. Same query shape as the eval builder (2 incident
|
|
146
|
+
* distinct S1* FULLNAMEs at a node, both names >=6 chars), plus the edge ZIPL so tails can carry the crossing's own
|
|
147
|
+
* ZIP. Hash-ordered for seed-stable determinism.
|
|
148
|
+
*/
|
|
149
|
+
async function extractCrossings(db, edgesDir, county, seed) {
|
|
150
|
+
const shp = `${edgesDir}/tl_2023_${county.fips}_edges.shp`;
|
|
151
|
+
const result = await db.runAndReadAll(`
|
|
152
|
+
WITH incidence AS (
|
|
153
|
+
SELECT TNIDF AS node, FULLNAME AS name, ZIPL AS zip
|
|
154
|
+
FROM ST_Read('${shp}') WHERE MTFCC LIKE 'S1%' AND FULLNAME IS NOT NULL
|
|
155
|
+
UNION ALL
|
|
156
|
+
SELECT TNIDT AS node, FULLNAME AS name, ZIPL AS zip
|
|
157
|
+
FROM ST_Read('${shp}') WHERE MTFCC LIKE 'S1%' AND FULLNAME IS NOT NULL
|
|
158
|
+
),
|
|
159
|
+
nodes AS (
|
|
160
|
+
SELECT node,
|
|
161
|
+
list_sort(list_distinct(list(name))) AS names,
|
|
162
|
+
max(zip) AS zip
|
|
163
|
+
FROM incidence GROUP BY node
|
|
164
|
+
HAVING len(list_distinct(list(name))) = 2
|
|
165
|
+
)
|
|
166
|
+
SELECT node, names[1] AS a, names[2] AS b, zip,
|
|
167
|
+
hash(node::VARCHAR || '${seed}') AS h
|
|
168
|
+
FROM nodes
|
|
169
|
+
WHERE len(names[1]) >= 6 AND len(names[2]) >= 6
|
|
170
|
+
ORDER BY h
|
|
171
|
+
`);
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const r of result.getRowObjects()) {
|
|
174
|
+
out.push({
|
|
175
|
+
a: String(r.a),
|
|
176
|
+
b: String(r.b),
|
|
177
|
+
zip: r.zip == null ? null : String(r.zip),
|
|
178
|
+
node: Number(r.node),
|
|
179
|
+
fips: county.fips,
|
|
180
|
+
state: county.state,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/** ZIP → majority city from the cached OA Cook-county CSV (real ZIP/city pairings). */
|
|
186
|
+
function buildZipCityMap() {
|
|
187
|
+
const r = spawnSync("unzip", ["-p", OA_COOK.zip, OA_COOK.csv], { maxBuffer: 1024 * 1024 * 1024, encoding: "buffer" });
|
|
188
|
+
if (r.status !== 0) {
|
|
189
|
+
console.error(` WARN: unzip failed for ${OA_COOK.zip} — city tails disabled`);
|
|
190
|
+
return new Map();
|
|
191
|
+
}
|
|
192
|
+
const lines = r.stdout.toString("utf8").split(/\r?\n/);
|
|
193
|
+
if (lines.length < 2)
|
|
194
|
+
return new Map();
|
|
195
|
+
const header = splitCSV(lines[0]).map((h) => h.trim().toLowerCase());
|
|
196
|
+
const iCity = header.indexOf("city");
|
|
197
|
+
const iPost = header.indexOf("postcode");
|
|
198
|
+
const counts = new Map();
|
|
199
|
+
// zip → Map(city → n)
|
|
200
|
+
for (let li = 1; li < lines.length; li++) {
|
|
201
|
+
if (!lines[li])
|
|
202
|
+
continue;
|
|
203
|
+
const cells = splitCSV(lines[li]);
|
|
204
|
+
const city = (cells[iCity] ?? "").trim();
|
|
205
|
+
const zip = (cells[iPost] ?? "").trim();
|
|
206
|
+
if (!city || !/^\d{5}$/.test(zip) || BAD_NAME.test(city))
|
|
207
|
+
continue;
|
|
208
|
+
let byCity = counts.get(zip);
|
|
209
|
+
if (!byCity)
|
|
210
|
+
counts.set(zip, (byCity = new Map()));
|
|
211
|
+
byCity.set(city, (byCity.get(city) ?? 0) + 1);
|
|
212
|
+
}
|
|
213
|
+
const map = new Map();
|
|
214
|
+
for (const [zip, byCity] of counts) {
|
|
215
|
+
let best = null;
|
|
216
|
+
for (const [city, n] of byCity)
|
|
217
|
+
if (!best || n > best.n)
|
|
218
|
+
best = { city, n };
|
|
219
|
+
if (best)
|
|
220
|
+
map.set(zip, best.city);
|
|
221
|
+
}
|
|
222
|
+
return map;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Render one crossing → { raw, components, formID, tailID, caseID }. Components are inserted in claim order (streets
|
|
226
|
+
* first) so alignment can't grab a region/postcode lookalike inside a street.
|
|
227
|
+
*/
|
|
228
|
+
function renderRow(random, crossing, zipCity) {
|
|
229
|
+
const form = weightedPick(FORMS, random);
|
|
230
|
+
const body = form.render(crossing.a, crossing.b);
|
|
231
|
+
let tail = weightedPick(TAILS, random);
|
|
232
|
+
const city = crossing.zip ? (zipCity.get(crossing.zip) ?? null) : null;
|
|
233
|
+
// Downgrade unsatisfiable tails (no ZIP on the edge / no city for the ZIP) to the region tail.
|
|
234
|
+
if ((tail.id === "region-zip" || tail.id === "city-region-zip") && !crossing.zip)
|
|
235
|
+
tail = TAILS[1];
|
|
236
|
+
if ((tail.id === "city-region" || tail.id === "city-region-zip") && !city)
|
|
237
|
+
tail = TAILS[1];
|
|
238
|
+
const components = { intersection_a: crossing.a, intersection_b: crossing.b };
|
|
239
|
+
let raw = body;
|
|
240
|
+
if (tail.id === "region") {
|
|
241
|
+
raw = `${body}, ${crossing.state}`;
|
|
242
|
+
components.region = crossing.state;
|
|
243
|
+
}
|
|
244
|
+
else if (tail.id === "region-zip") {
|
|
245
|
+
raw = `${body}, ${crossing.state} ${crossing.zip}`;
|
|
246
|
+
components.region = crossing.state;
|
|
247
|
+
components.postcode = crossing.zip;
|
|
248
|
+
}
|
|
249
|
+
else if (tail.id === "city-region") {
|
|
250
|
+
raw = `${body}, ${city}, ${crossing.state}`;
|
|
251
|
+
components.locality = city;
|
|
252
|
+
components.region = crossing.state;
|
|
253
|
+
}
|
|
254
|
+
else if (tail.id === "city-region-zip") {
|
|
255
|
+
raw = `${body}, ${city}, ${crossing.state} ${crossing.zip}`;
|
|
256
|
+
components.locality = city;
|
|
257
|
+
components.region = crossing.state;
|
|
258
|
+
components.postcode = crossing.zip;
|
|
259
|
+
}
|
|
260
|
+
const casing = weightedPick(CASES, random);
|
|
261
|
+
raw = casing.apply(raw);
|
|
262
|
+
// Components keep their original case; alignRow matches case-insensitively and labels the
|
|
263
|
+
// tokens of the (cased) raw — the parquet row carries tokens+labels only.
|
|
264
|
+
return { raw, components, formID: form.id, tailID: tail.id, caseID: casing.id };
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Label-correctness audit for one aligned row, on the RAW SURFACE via the #519 span triple. Returns a list of
|
|
268
|
+
* violations (empty = clean). Re-derives the span checks independent of `alignRow`'s own assertion, so a builder bug
|
|
269
|
+
* can't vouch for itself.
|
|
270
|
+
*/
|
|
271
|
+
function auditRow(row, components) {
|
|
272
|
+
const errors = [];
|
|
273
|
+
const { raw, tokens, labels, span_starts, span_ends, span_tags } = row;
|
|
274
|
+
if (tokens.length !== labels.length)
|
|
275
|
+
errors.push("tokens/labels length mismatch");
|
|
276
|
+
if (!span_starts || !span_ends || !span_tags) {
|
|
277
|
+
errors.push("missing the char-offset span triple (#519)");
|
|
278
|
+
return errors;
|
|
279
|
+
}
|
|
280
|
+
if (span_starts.length !== span_ends.length || span_starts.length !== span_tags.length) {
|
|
281
|
+
errors.push(`span triple not parallel: ${span_starts.length}/${span_ends.length}/${span_tags.length}`);
|
|
282
|
+
return errors;
|
|
283
|
+
}
|
|
284
|
+
for (let i = 0; i < span_starts.length; i++) {
|
|
285
|
+
if (!(span_starts[i] >= 0 && span_starts[i] < span_ends[i] && span_ends[i] <= raw.length)) {
|
|
286
|
+
errors.push(`span ${span_tags[i]}@[${span_starts[i]}, ${span_ends[i]}) out of bounds`);
|
|
287
|
+
}
|
|
288
|
+
if (i > 0 && span_starts[i] < span_ends[i - 1]) {
|
|
289
|
+
errors.push(`spans unsorted/overlapping at index ${i}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (errors.length > 0)
|
|
293
|
+
return errors;
|
|
294
|
+
const compCount = Object.keys(components).length;
|
|
295
|
+
if (span_tags.length !== compCount)
|
|
296
|
+
errors.push(`span count ${span_tags.length} != components ${compCount}`);
|
|
297
|
+
// Raw-surface reconstruction: each component's single span slices raw to the component verbatim.
|
|
298
|
+
for (const [tag, value] of Object.entries(components)) {
|
|
299
|
+
if (value == null)
|
|
300
|
+
continue;
|
|
301
|
+
const indices = span_tags.map((t, i) => (t === tag ? i : -1)).filter((i) => i >= 0);
|
|
302
|
+
if (indices.length !== 1) {
|
|
303
|
+
errors.push(`${tag}: expected 1 span, got ${indices.length}`);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const idx = indices[0];
|
|
307
|
+
const got = raw.slice(span_starts[idx], span_ends[idx]);
|
|
308
|
+
if (got.toLowerCase() !== value.toLowerCase())
|
|
309
|
+
errors.push(`${tag} span "${got}" != component "${value}"`);
|
|
310
|
+
}
|
|
311
|
+
// Negative space: every char outside the spans must be connector material.
|
|
312
|
+
let cursor = 0;
|
|
313
|
+
const uncovered = [];
|
|
314
|
+
for (let i = 0; i < span_starts.length; i++) {
|
|
315
|
+
if (span_starts[i] > cursor)
|
|
316
|
+
uncovered.push(raw.slice(cursor, span_starts[i]));
|
|
317
|
+
cursor = span_ends[i];
|
|
318
|
+
}
|
|
319
|
+
if (cursor < raw.length)
|
|
320
|
+
uncovered.push(raw.slice(cursor));
|
|
321
|
+
for (const segment of uncovered) {
|
|
322
|
+
const words = segment.match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
323
|
+
for (const word of words) {
|
|
324
|
+
if (!CONNECTOR_O_TOKENS.has(word.toLowerCase()))
|
|
325
|
+
errors.push(`illegal uncovered word "${word}"`);
|
|
326
|
+
}
|
|
327
|
+
const punctOnly = segment.replace(/[\p{L}\p{N}]+/gu, "");
|
|
328
|
+
if (!CONNECTOR_PUNCT_RE.test(punctOnly))
|
|
329
|
+
errors.push(`illegal uncovered punctuation in "${segment}"`);
|
|
330
|
+
}
|
|
331
|
+
return errors;
|
|
332
|
+
}
|
|
333
|
+
export const intersectionRecipe = {
|
|
334
|
+
name: "intersection",
|
|
335
|
+
description: "Real-pair intersection rows (US): TIGER 2023 EDGES crossings → audited intersection_a/b labels",
|
|
336
|
+
mode: "generate",
|
|
337
|
+
options: [{ flag: "--edges-dir <dir>", description: "Unzipped TIGER 2023 EDGES dir. Default /tmp/tiger-edges" }],
|
|
338
|
+
async run(opts, write) {
|
|
339
|
+
// Legacy build-intersection-shard.mjs seeded `mulberry32(opts.seed)`.
|
|
340
|
+
const random = makeMulberry32(opts.seed);
|
|
341
|
+
const count = opts.count ?? 40000;
|
|
342
|
+
const source = opts.sourceName ?? "synth-intersection";
|
|
343
|
+
const edgesDir = opts.edgesDir ?? "/tmp/tiger-edges";
|
|
344
|
+
const counties = opts.golden ? GOLDEN_COUNTIES : TRAIN_COUNTIES;
|
|
345
|
+
const exclusions = readEvalExclusions();
|
|
346
|
+
console.error(` eval exclusions: ${exclusions.nodes.size} nodes, ${exclusions.pairs.size} pairs`);
|
|
347
|
+
const { DuckDBInstance } = await import("@duckdb/node-api");
|
|
348
|
+
const instance = await DuckDBInstance.create();
|
|
349
|
+
const db = await instance.connect();
|
|
350
|
+
await db.run("INSTALL spatial; LOAD spatial;");
|
|
351
|
+
// Pool real crossings: eval-excluded, connector-safe names, one crossing per distinct pair.
|
|
352
|
+
const pool = [];
|
|
353
|
+
const seenPairs = new Set();
|
|
354
|
+
const stats = { evalExcluded: 0, badName: 0, dupPair: 0 };
|
|
355
|
+
for (const county of counties) {
|
|
356
|
+
const crossings = await extractCrossings(db, edgesDir, county, opts.seed);
|
|
357
|
+
let kept = 0;
|
|
358
|
+
for (const c of crossings) {
|
|
359
|
+
const key = pairKey(c.a, c.b);
|
|
360
|
+
if (exclusions.nodes.has(c.node) || exclusions.pairs.has(key)) {
|
|
361
|
+
stats.evalExcluded++;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (BAD_NAME.test(c.a) || BAD_NAME.test(c.b) || c.a.includes(c.b) || c.b.includes(c.a)) {
|
|
365
|
+
stats.badName++;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (seenPairs.has(key)) {
|
|
369
|
+
stats.dupPair++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
seenPairs.add(key);
|
|
373
|
+
pool.push(c);
|
|
374
|
+
kept++;
|
|
375
|
+
}
|
|
376
|
+
console.error(` ${county.fips} (${county.state}, ${county.regime}): ${crossings.length} crossings, ${kept} kept`);
|
|
377
|
+
}
|
|
378
|
+
if (pool.length === 0) {
|
|
379
|
+
throw new Error(`No crossings found — are the TIGER EDGES shapefiles present in ${edgesDir}?`);
|
|
380
|
+
}
|
|
381
|
+
const zipCity = opts.golden ? new Map() : buildZipCityMap();
|
|
382
|
+
if (!opts.golden)
|
|
383
|
+
console.error(` zip→city map: ${zipCity.size} ZIPs (OA Cook)`);
|
|
384
|
+
let emitted = 0;
|
|
385
|
+
let skipped = 0;
|
|
386
|
+
let guard = 0;
|
|
387
|
+
const formCounts = {};
|
|
388
|
+
const tailCounts = {};
|
|
389
|
+
const caseCounts = {};
|
|
390
|
+
const countyCounts = {};
|
|
391
|
+
const usedCrossings = new Set();
|
|
392
|
+
const seenRaw = new Set();
|
|
393
|
+
const auditErrors = [];
|
|
394
|
+
const samples = [];
|
|
395
|
+
while (emitted < count && guard++ < count * 10) {
|
|
396
|
+
const crossing = pool[Math.floor(random() * pool.length)];
|
|
397
|
+
const { raw, components, formID, tailID, caseID } = renderRow(random, crossing, zipCity);
|
|
398
|
+
if (seenRaw.has(raw)) {
|
|
399
|
+
skipped++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (opts.golden) {
|
|
403
|
+
seenRaw.add(raw);
|
|
404
|
+
write(JSON.stringify({ raw, components, country: "US", form: formID }) + "\n");
|
|
405
|
+
formCounts[formID] = (formCounts[formID] ?? 0) + 1;
|
|
406
|
+
tailCounts[tailID] = (tailCounts[tailID] ?? 0) + 1;
|
|
407
|
+
caseCounts[caseID] = (caseCounts[caseID] ?? 0) + 1;
|
|
408
|
+
countyCounts[crossing.fips] = (countyCounts[crossing.fips] ?? 0) + 1;
|
|
409
|
+
usedCrossings.add(crossing.node);
|
|
410
|
+
emitted++;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const canonical = {
|
|
414
|
+
raw,
|
|
415
|
+
components,
|
|
416
|
+
country: "US",
|
|
417
|
+
locale: "en-US",
|
|
418
|
+
source,
|
|
419
|
+
source_id: stableSourceID(source, components),
|
|
420
|
+
corpus_version: "0.4.0",
|
|
421
|
+
license: "TIGER/Line 2023 EDGES (US Census, public domain) real street pairs; OA Cook IL zip-to-city tails",
|
|
422
|
+
};
|
|
423
|
+
// Verbatim-only alignment: raw is built from the component values, so a fuzzy fallback could
|
|
424
|
+
// only ever mislabel (e.g. claim a lookalike window for a near-duplicate street).
|
|
425
|
+
const aligned = alignRow(canonical, { maxEditDistance: 0 });
|
|
426
|
+
if (aligned.kind !== "labeled" || !aligned.row) {
|
|
427
|
+
skipped++;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const violations = auditRow(aligned.row, components);
|
|
431
|
+
if (violations.length > 0) {
|
|
432
|
+
auditErrors.push({ raw, violations });
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
seenRaw.add(raw);
|
|
436
|
+
write(JSON.stringify({ ...aligned.row, synth_method: "intersection", synth_base_id: null }) + "\n");
|
|
437
|
+
formCounts[formID] = (formCounts[formID] ?? 0) + 1;
|
|
438
|
+
tailCounts[tailID] = (tailCounts[tailID] ?? 0) + 1;
|
|
439
|
+
caseCounts[caseID] = (caseCounts[caseID] ?? 0) + 1;
|
|
440
|
+
countyCounts[crossing.fips] = (countyCounts[crossing.fips] ?? 0) + 1;
|
|
441
|
+
usedCrossings.add(crossing.node);
|
|
442
|
+
if (samples.length < FORMS.length && !samples.some((s) => s.form === formID)) {
|
|
443
|
+
samples.push({ form: formID, raw, tokens: aligned.row.tokens, labels: aligned.row.labels });
|
|
444
|
+
}
|
|
445
|
+
emitted++;
|
|
446
|
+
}
|
|
447
|
+
const report = {
|
|
448
|
+
mode: opts.golden ? "golden" : "train",
|
|
449
|
+
rows: emitted,
|
|
450
|
+
skipped,
|
|
451
|
+
pool: { crossings: pool.length, used: usedCrossings.size, ...stats },
|
|
452
|
+
per_county: countyCounts,
|
|
453
|
+
forms: formCounts,
|
|
454
|
+
tails: tailCounts,
|
|
455
|
+
cases: caseCounts,
|
|
456
|
+
audit: { errors: auditErrors.length, examples: auditErrors.slice(0, 10) },
|
|
457
|
+
seed: opts.seed,
|
|
458
|
+
source: "TIGER2023 EDGES via DuckDB ST_Read; node = 2 distinct S1* FULLNAMEs; eval crossings excluded",
|
|
459
|
+
samples,
|
|
460
|
+
};
|
|
461
|
+
writeFileSync(opts.output.replace(/\.jsonl$/, ".report.json"), JSON.stringify(report, null, "\t"));
|
|
462
|
+
console.error(`Done: emitted ${emitted} rows (skipped ${skipped}) from ${usedCrossings.size}/${pool.length} real crossings. → ${opts.output}\n` +
|
|
463
|
+
` forms: ${JSON.stringify(formCounts)}\n` +
|
|
464
|
+
` tails: ${JSON.stringify(tailCounts)}\n` +
|
|
465
|
+
` cases: ${JSON.stringify(caseCounts)}\n` +
|
|
466
|
+
` audit: ${auditErrors.length} violation(s)`);
|
|
467
|
+
if (auditErrors.length > 0) {
|
|
468
|
+
throw new Error(`AUDIT FAILED — first violation: ${JSON.stringify(auditErrors[0])}`);
|
|
469
|
+
}
|
|
470
|
+
return { emitted, skipped };
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
//# sourceMappingURL=intersection.js.map
|