@mailwoman/corpus 7.2.0 → 7.2.1

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.
Files changed (103) hide show
  1. package/package.json +59 -27
  2. package/src/adapter.ts +153 -0
  3. package/src/adapters/ban/adapter.ts +189 -0
  4. package/src/adapters/ban/street-decompose.ts +98 -0
  5. package/src/adapters/fcc-bdc/adapter.ts +188 -0
  6. package/src/adapters/geonames/adapter.ts +185 -0
  7. package/src/adapters/geonames-postal/adapter.ts +111 -0
  8. package/src/adapters/gnaf/adapter.ts +139 -0
  9. package/src/adapters/gnaf/assemble.ts +200 -0
  10. package/src/adapters/index.ts +135 -0
  11. package/src/adapters/openaddresses/adapter.ts +237 -0
  12. package/src/adapters/overture/adapter.ts +149 -0
  13. package/src/adapters/state-hi-schools/adapter.ts +171 -0
  14. package/src/adapters/state-ia-contractors/adapter.ts +148 -0
  15. package/src/adapters/state-ny-notaries/adapter.ts +169 -0
  16. package/src/adapters/state-tx-notaries/adapter.ts +160 -0
  17. package/src/adapters/synth-po-box/adapter.ts +197 -0
  18. package/src/adapters/tiger/adapter.ts +232 -0
  19. package/src/adapters/tiger/street-decompose.ts +120 -0
  20. package/src/adapters/usgov-hrsa-fqhc/adapter.ts +189 -0
  21. package/src/adapters/usgov-imls-pls/adapter.ts +152 -0
  22. package/src/adapters/usgov-irs-bmf/adapter.ts +155 -0
  23. package/src/adapters/usgov-nad/adapter.ts +338 -0
  24. package/src/adapters/usgov-nppes/adapter.ts +169 -0
  25. package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +211 -0
  26. package/src/adapters/wof-admin-jp/adapter.ts +176 -0
  27. package/src/adapters/wof-admin-json/adapter.ts +280 -0
  28. package/src/adapters/wof-postalcode-json/adapter.ts +209 -0
  29. package/src/align.ts +317 -0
  30. package/src/build.ts +348 -0
  31. package/src/codex/us-fips-state.ts +109 -0
  32. package/src/format.ts +14 -0
  33. package/src/golden.ts +147 -0
  34. package/src/index.ts +28 -0
  35. package/src/license.ts +48 -0
  36. package/src/parquet-wrapper/index.ts +9 -0
  37. package/src/parquet-wrapper/reader.ts +67 -0
  38. package/src/parquet-wrapper/schema.ts +80 -0
  39. package/src/parquet-wrapper/writer.ts +102 -0
  40. package/src/parquet.ts +360 -0
  41. package/src/runner.ts +283 -0
  42. package/src/shard-recipes/anchor-absorption.ts +86 -0
  43. package/src/shard-recipes/boundary-stress.ts +87 -0
  44. package/src/shard-recipes/country-balanced.ts +471 -0
  45. package/src/shard-recipes/cz-pcfirst-preposition.ts +83 -0
  46. package/src/shard-recipes/fr-admin-split.ts +212 -0
  47. package/src/shard-recipes/fr-bare-street.ts +85 -0
  48. package/src/shard-recipes/fr-fragment.ts +304 -0
  49. package/src/shard-recipes/fr-order.ts +283 -0
  50. package/src/shard-recipes/german.ts +206 -0
  51. package/src/shard-recipes/house-venue.ts +78 -0
  52. package/src/shard-recipes/index.ts +76 -0
  53. package/src/shard-recipes/intersection.ts +617 -0
  54. package/src/shard-recipes/locale.ts +415 -0
  55. package/src/shard-recipes/nl-postcode.ts +105 -0
  56. package/src/shard-recipes/no-fragment.ts +206 -0
  57. package/src/shard-recipes/no-street-led.ts +130 -0
  58. package/src/shard-recipes/no-street.ts +78 -0
  59. package/src/shard-recipes/po-box-cedex.ts +853 -0
  60. package/src/shard-recipes/po-box.ts +111 -0
  61. package/src/shard-recipes/scaffold.ts +187 -0
  62. package/src/shard-recipes/si-bare-village.ts +88 -0
  63. package/src/shard-recipes/street-affix.ts +549 -0
  64. package/src/shard-recipes/street-bare.ts +90 -0
  65. package/src/shard-recipes/street.ts +74 -0
  66. package/src/shard-recipes/unit.ts +315 -0
  67. package/src/split.ts +251 -0
  68. package/src/synthesize-anchor-absorption.ts +263 -0
  69. package/src/synthesize-boundary-stress.ts +504 -0
  70. package/src/synthesize-german.ts +227 -0
  71. package/src/synthesize-house-venue.ts +192 -0
  72. package/src/synthesize-intersection.ts +205 -0
  73. package/src/synthesize-no-street.ts +341 -0
  74. package/src/synthesize-po-box.ts +307 -0
  75. package/src/synthesize-street.ts +264 -0
  76. package/src/synthesize.ts +872 -0
  77. package/src/tokenize.ts +68 -0
  78. package/src/tools/align-shard.ts +67 -0
  79. package/src/tools/audit.ts +352 -0
  80. package/src/tools/corpus-stats.ts +183 -0
  81. package/src/tools/fetch/ban.ts +288 -0
  82. package/src/tools/fetch/download.ts +119 -0
  83. package/src/tools/fetch/hrsa.ts +65 -0
  84. package/src/tools/fetch/imls-pls.ts +162 -0
  85. package/src/tools/fetch/index.ts +121 -0
  86. package/src/tools/fetch/nad.ts +304 -0
  87. package/src/tools/fetch/nppes.ts +177 -0
  88. package/src/tools/fetch/openaddresses.ts +380 -0
  89. package/src/tools/fetch/state-hi-schools.ts +217 -0
  90. package/src/tools/fetch/state-sources.ts +142 -0
  91. package/src/tools/fetch/tiger-full.ts +318 -0
  92. package/src/tools/golden-expand.ts +524 -0
  93. package/src/tools/golden-promote.ts +304 -0
  94. package/src/tools/index.ts +24 -0
  95. package/src/tools/ingest-csv.ts +420 -0
  96. package/src/tools/jsonl-to-parquet.ts +200 -0
  97. package/src/tools/lint-shard-vocab.ts +357 -0
  98. package/src/tools/lint-shard.ts +502 -0
  99. package/src/tools/overlay-manifest.ts +141 -0
  100. package/src/tools/shard-kryptonite.ts +131 -0
  101. package/src/tools/shard-translit.ts +244 -0
  102. package/src/types.ts +213 -0
  103. package/src/wof-json.ts +236 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `po-box` shard recipe — synthetic PO box rows: tuples → {@link synthesizePoBoxRow} → aligned
7
+ * LabeledRow, plus optional self-contained US military/diplomatic rows (#517) at
8
+ * `--military-ratio`. Region is required EXCEPT region-less locales (NZ). Ported from
9
+ * scripts/build-po-box-shard.mjs.
10
+ */
11
+
12
+ import { synthesizeMilitaryPoBoxRow, synthesizePoBoxRow, type PoBoxBaseTuple } from "../synthesize-po-box.ts"
13
+ import { alignAndWrite, makeLcg, readTuples, shardSourceID, type ShardRecipe } from "./scaffold.ts"
14
+
15
+ const LICENSE = "Synthetic — derived from CC-BY / public-domain input tuples"
16
+
17
+ export const poBoxRecipe: ShardRecipe = {
18
+ name: "po-box",
19
+ description: "PO box rows: tuples → synthesizePoBoxRow (+ optional US military/diplomatic rows)",
20
+ mode: "tuples",
21
+ options: [
22
+ { flag: "--pmb-ratio <p>", description: "P(private-mailbox layout). Default 0.15" },
23
+ { flag: "--military-ratio <p>", description: "P(emit one US military/diplomatic row per input, #517). Default 0" },
24
+ ],
25
+ async run(opts, write) {
26
+ if (!opts.input) throw new Error("po-box recipe requires --input <tuples.jsonl>")
27
+ const random = makeLcg(opts.seed)
28
+ const pmbRatio = opts.pmbRatio ?? 0.15
29
+ const militaryRatio = opts.militaryRatio ?? 0
30
+ let read = 0
31
+ let emitted = 0
32
+ let skipped = 0
33
+
34
+ for await (const tuple of readTuples(opts.input)) {
35
+ read++
36
+ // Region required EXCEPT region-less locales (NZ: "Private Bag 12, Auckland 1010", #517).
37
+ const regionOptional = ["NZ", "NZL", "NEW ZEALAND"].includes(String(tuple.country || "").toUpperCase())
38
+
39
+ if (!tuple.locality || !tuple.postcode || !tuple.country || (!tuple.region && !regionOptional)) {
40
+ skipped++
41
+ continue
42
+ }
43
+
44
+ for (let v = 0; v < opts.variants; v++) {
45
+ const synth = synthesizePoBoxRow(tuple as PoBoxBaseTuple, { random, pmbRatio })
46
+
47
+ if (!synth) continue
48
+ const ok = alignAndWrite(
49
+ write,
50
+ {
51
+ raw: synth.raw,
52
+ components: synth.components,
53
+ country: tuple.country,
54
+ locale: synth.locale,
55
+ source: "synth-po-box",
56
+ source_id: shardSourceID("synth-po-box", {
57
+ locality: tuple.locality,
58
+ region: tuple.region,
59
+ postcode: tuple.postcode,
60
+ country: tuple.country,
61
+ v: String(v),
62
+ }),
63
+ corpus_version: "0.4.0",
64
+ license: LICENSE,
65
+ },
66
+ synth.template
67
+ )
68
+
69
+ if (ok) {
70
+ emitted++
71
+ } else {
72
+ skipped++
73
+ }
74
+ }
75
+
76
+ // US military/diplomatic rows (#517): self-contained, one per input line at --military-ratio.
77
+ // Default 0 → byte-stable (random() not called when off). US-only.
78
+ if (militaryRatio > 0 && random() < militaryRatio) {
79
+ const mil = synthesizeMilitaryPoBoxRow({ random })
80
+ const ok = alignAndWrite(
81
+ write,
82
+ {
83
+ raw: mil.raw,
84
+ components: mil.components,
85
+ country: "US",
86
+ locale: mil.locale,
87
+ source: "synth-po-box",
88
+ source_id: shardSourceID("synth-po-box", {
89
+ po_box: mil.components.po_box,
90
+ locality: mil.components.locality,
91
+ region: mil.components.region,
92
+ postcode: mil.components.postcode,
93
+ v: `mil${emitted}`,
94
+ }),
95
+ corpus_version: "0.4.0",
96
+ license: LICENSE,
97
+ },
98
+ mil.template
99
+ )
100
+
101
+ if (ok) {
102
+ emitted++
103
+ } else {
104
+ skipped++
105
+ }
106
+ }
107
+ }
108
+
109
+ return { read, emitted, skipped }
110
+ },
111
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Shared scaffolding for the synthetic-corpus SHARD RECIPES — the common bits the 16
7
+ * `build-*-shard.mjs` scripts each re-implemented: the seeded LCG PRNG, the tuple reader, and the
8
+ * canonical → `alignRow` → `LabeledRow` JSONL emit step. A recipe ({@link ShardRecipe}) supplies
9
+ * only its synthesis + filter; the `mailwoman corpus shard <recipe>` command supplies the I/O.
10
+ */
11
+
12
+ import { stableSourceID } from "../adapter.ts"
13
+ import { alignRow } from "../align.ts"
14
+
15
+ /**
16
+ * {@link stableSourceID}, but accepting arbitrary disambiguator keys (e.g. a variant index `v`) that aren't
17
+ * `ComponentTag`s. `stableSourceID` sorts + hashes EVERY key it's given, so passing extra keys is how the legacy
18
+ * builders kept per-variant ids unique — the strict typing is just too narrow for that. Centralizes the one cast.
19
+ */
20
+ export function shardSourceID(adapterID: string, parts: Record<string, string | undefined>): string {
21
+ return stableSourceID(adapterID, parts as unknown as Parameters<typeof stableSourceID>[1])
22
+ }
23
+
24
+ /** A (locality, region, postcode, country) source tuple — the input to tuples-mode recipes. */
25
+ export interface ShardTuple {
26
+ locality?: string
27
+ region?: string
28
+ postcode?: string
29
+ country?: string
30
+ [k: string]: unknown
31
+ }
32
+
33
+ /**
34
+ * The deterministic LCG (`s = s*1664525 + 1013904223 mod 2^32`) the street/po-box/anchor builders used. A recipe whose
35
+ * legacy `.mjs` seeded this must create it here so `--seed N` is byte-reproducible.
36
+ */
37
+ export function makeLcg(seed: number): () => number {
38
+ let s = seed >>> 0
39
+
40
+ return () => {
41
+ s = (s * 1664525 + 1013904223) % 4294967296
42
+
43
+ return s / 4294967296
44
+ }
45
+ }
46
+
47
+ /** Back-compat alias for {@link makeLcg}. */
48
+ export const makeRandom = makeLcg
49
+
50
+ /**
51
+ * Mulberry32 — the PRNG the MAJORITY of the legacy `build-*-shard` scripts used (german, locale, boundary-stress, unit,
52
+ * fr-order, country-balanced, intersection, fr-admin-split, street-affix, street-bare, po-box-cedex). A recipe must
53
+ * seed it EXACTLY as its `.mjs` did (usually `seed`, but some derive a per-stream seed) to stay byte-reproducible.
54
+ */
55
+ export function makeMulberry32(seed: number): () => number {
56
+ let a = seed >>> 0
57
+
58
+ return () => {
59
+ a = (a + 0x6d2b79f5) | 0
60
+ let t = Math.imul(a ^ (a >>> 15), 1 | a)
61
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
62
+
63
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
64
+ }
65
+ }
66
+
67
+ /** Stream-parse a tuples JSONL file, yielding each parsed object (blank/invalid lines skipped). */
68
+ export async function* readTuples(input: string): AsyncGenerator<ShardTuple> {
69
+ // TextSpliterator (not JSONSpliterator) so a malformed line is SKIPPED, not thrown — the
70
+ // per-line try/catch below is the tolerance this reader has always had.
71
+ const { TextSpliterator } = await import("spliterator")
72
+
73
+ for await (const line of TextSpliterator.fromAsync(input)) {
74
+ const trimmed = line.trim()
75
+
76
+ if (!trimmed) continue
77
+
78
+ try {
79
+ yield JSON.parse(trimmed) as ShardTuple
80
+ } catch {
81
+ // skip malformed line
82
+ }
83
+ }
84
+ }
85
+
86
+ /** A canonical row as the recipes assemble it, before `alignRow` turns it into a `LabeledRow`. */
87
+ export interface CanonicalShardRow {
88
+ raw: string
89
+ components: Record<string, string>
90
+ country: string
91
+ locale?: string
92
+ source: string
93
+ source_id: string
94
+ corpus_version?: string
95
+ license?: string
96
+ }
97
+
98
+ /**
99
+ * Run a canonical row through `alignRow` and, on success, write the `LabeledRow` (+ `synth_method` / `synth_base_id`)
100
+ * as one JSONL line. Returns true if emitted, false if alignment quarantined it.
101
+ */
102
+ export function alignAndWrite(
103
+ write: (line: string) => void,
104
+ canonical: CanonicalShardRow,
105
+ synthMethod: string,
106
+ synthBaseID: string | null = null
107
+ ): boolean {
108
+ const aligned = alignRow(canonical as Parameters<typeof alignRow>[0])
109
+
110
+ if (!aligned.row) return false
111
+ write(JSON.stringify({ ...aligned.row, synth_method: synthMethod, synth_base_id: synthBaseID }) + "\n")
112
+
113
+ return true
114
+ }
115
+
116
+ /** Parsed options a recipe's `run` receives. Common fields + the union of recipe-specific flags. */
117
+ export interface ShardRecipeOpts {
118
+ output: string
119
+ seed: number
120
+ variants: number
121
+ input?: string
122
+ count?: number
123
+ golden?: boolean
124
+ sourceName?: string
125
+ // recipe-specific (each recipe reads only what it needs):
126
+ houseNumberProb?: number
127
+ pmbRatio?: number
128
+ militaryRatio?: number
129
+ reversedFraction?: number
130
+ edgesDir?: string
131
+ country?: string
132
+ intlFraction?: number
133
+ bareProb?: number
134
+ hnProb?: number
135
+ communes?: string
136
+ multilocaleCount?: number
137
+ /**
138
+ * `fr-fragment` / `no-fragment` / `no-street-led`: the eval board's reserved street-surface list. REQUIRED for those
139
+ * recipes — a shard that trains on its own eval set measures memorization. See their docstrings.
140
+ */
141
+ excludeSurfaces?: string
142
+ /** `no-fragment`: share of rows that are counter-distribution (bare locality OR bare postcode). */
143
+ counterProb?: number
144
+ /**
145
+ * `no-fragment` knob 3: emit N copies of each street+number row whose number has >= longNumberMinDigits digits
146
+ * (oversample the failing long-number class). Default 1 = no boost.
147
+ */
148
+ longNumberBoost?: number
149
+ /** `no-fragment` knob 3: minimum digit count for a number to count as "long" and be boosted. Default 3. */
150
+ longNumberMinDigits?: number
151
+ }
152
+
153
+ /** Tally a recipe returns. */
154
+ export interface ShardStats {
155
+ read?: number
156
+ emitted: number
157
+ skipped: number
158
+ /**
159
+ * Rows dropped because their street SURFACE is reserved by an eval board (`--exclude-surfaces`). Separate from
160
+ * `skipped` on purpose: a nonzero value is the audit trail that the train/eval split actually fired. Zero when a
161
+ * recipe has no board split.
162
+ */
163
+ contaminated?: number
164
+ }
165
+
166
+ /** A single declared recipe-specific option flag (for the command's --help). */
167
+ export interface ShardRecipeOption {
168
+ flag: string
169
+ description: string
170
+ }
171
+
172
+ /** A shard recipe: its identity, input mode, and its synthesis `run`. */
173
+ export interface ShardRecipe {
174
+ /** Recipe id, e.g. "street", "po-box" — the `<recipe>` positional. */
175
+ name: string
176
+ /** One-line description for `--list` / help. */
177
+ description: string
178
+ /** `tuples` reads `--input` JSONL; `generate` self-generates `--count` rows. */
179
+ mode: "tuples" | "generate"
180
+ /** Recipe-specific flags this recipe honors (documentation only). */
181
+ options?: ShardRecipeOption[]
182
+ /**
183
+ * Do the build: create the recipe's PRNG from `opts.seed` (its LEGACY generator — `makeLcg` or `makeMulberry32` — for
184
+ * byte-reproducibility), synthesize, and emit each row via `write`.
185
+ */
186
+ run(opts: ShardRecipeOpts, write: (line: string) => void): Promise<ShardStats>
187
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `si-bare-village` — the Slovenian no-street counter-shard (#901 run-2). Slovenia's rural
7
+ * addressing has NO street line: the village name is the street-level token and repeats as the
8
+ * locality ("Zabiče 8, 6250 Zabiče"). The v1.9.8 gate FALSIFIED the fr-bare-street shard alone on
9
+ * exactly this class (SI resolve −3.4pp; "Apače 108" split into street "Apače 10" + house "8") —
10
+ * the bare-street boundary lesson generalizes onto a form where the leading name must keep its
11
+ * number whole and the trailing mention must stay locality-bound. This shard is the paired
12
+ * counter-distribution: same lesson ("name before number, comma, then admin"), opposite polarity
13
+ * on the trailing mention.
14
+ *
15
+ * Three real-order templates cycle per tuple (mirrors the coord-golden orders so the eval and the
16
+ * training distribution agree):
17
+ *
18
+ * 1. canonical "«V» «n», «pc» «V»"
19
+ * 2. bare "«V» «n», «V»" (no postcode — the anchor-free form, the fr-bare lesson)
20
+ * 3. pc-first "«pc» «V», «V» «n»"
21
+ *
22
+ * Gold spans: leading «V» = street (matches OA ground truth — the village IS the address line),
23
+ * «n» = house_number (NEVER split mid-digits), «pc» = postcode (never swallowing the neighbor),
24
+ * trailing «V» = locality (the binding the resolver needs).
25
+ */
26
+
27
+ import { alignAndWrite, makeMulberry32, readTuples, type ShardRecipe, shardSourceID } from "./scaffold.ts"
28
+
29
+ export const siBareVillageRecipe: ShardRecipe = {
30
+ name: "si-bare-village",
31
+ description: "SI no-street village form (#901 run-2): '«V» «n», «pc» «V»' — the fr-bare-street counter-distribution",
32
+ mode: "tuples",
33
+ async run(opts, write) {
34
+ makeMulberry32(opts.seed)
35
+ let read = 0
36
+ let emitted = 0
37
+ let skipped = 0
38
+
39
+ for await (const t of readTuples(opts.input!)) {
40
+ read++
41
+ const village = String(t.locality ?? "").trim()
42
+ const number = String(t.number ?? "").trim()
43
+ const postcode = String(t.postcode ?? "").trim()
44
+
45
+ if (!village || !number || !postcode) {
46
+ skipped++
47
+ continue
48
+ }
49
+ const order = read % 3
50
+ let raw: string
51
+ const components: Record<string, string> = {
52
+ street: village,
53
+ house_number: number,
54
+ locality: village,
55
+ }
56
+
57
+ if (order === 0) {
58
+ components.postcode = postcode
59
+ raw = `${village} ${number}, ${postcode} ${village}`
60
+ } else if (order === 1) {
61
+ raw = `${village} ${number}, ${village}`
62
+ } else {
63
+ components.postcode = postcode
64
+ raw = `${postcode} ${village}, ${village} ${number}`
65
+ }
66
+ const source_id = shardSourceID("synth-si-bare-village", { ...components, o: String(order), v: String(read) })
67
+ const canonical = {
68
+ raw,
69
+ components,
70
+ country: "SI",
71
+ locale: "sl-SI",
72
+ source: "synth-si-bare-village",
73
+ source_id,
74
+ corpus_version: "0.9.9",
75
+ license:
76
+ "Synthetic — si-bare-village; (village, number, postcode) from OpenAddresses SI (per-source attribution in the model card)",
77
+ }
78
+
79
+ if (alignAndWrite(write, canonical, "si-bare-village")) {
80
+ emitted++
81
+ } else {
82
+ skipped++
83
+ }
84
+ }
85
+
86
+ return { read, emitted, skipped }
87
+ },
88
+ }