@mailwoman/corpus 7.2.0 → 7.3.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/package.json +59 -27
- package/src/adapter.ts +153 -0
- package/src/adapters/ban/adapter.ts +189 -0
- package/src/adapters/ban/street-decompose.ts +98 -0
- package/src/adapters/fcc-bdc/adapter.ts +188 -0
- package/src/adapters/geonames/adapter.ts +185 -0
- package/src/adapters/geonames-postal/adapter.ts +111 -0
- package/src/adapters/gnaf/adapter.ts +139 -0
- package/src/adapters/gnaf/assemble.ts +200 -0
- package/src/adapters/index.ts +135 -0
- package/src/adapters/openaddresses/adapter.ts +237 -0
- package/src/adapters/overture/adapter.ts +149 -0
- package/src/adapters/state-hi-schools/adapter.ts +171 -0
- package/src/adapters/state-ia-contractors/adapter.ts +148 -0
- package/src/adapters/state-ny-notaries/adapter.ts +169 -0
- package/src/adapters/state-tx-notaries/adapter.ts +160 -0
- package/src/adapters/synth-po-box/adapter.ts +197 -0
- package/src/adapters/tiger/adapter.ts +232 -0
- package/src/adapters/tiger/street-decompose.ts +120 -0
- package/src/adapters/usgov-hrsa-fqhc/adapter.ts +189 -0
- package/src/adapters/usgov-imls-pls/adapter.ts +152 -0
- package/src/adapters/usgov-irs-bmf/adapter.ts +155 -0
- package/src/adapters/usgov-nad/adapter.ts +338 -0
- package/src/adapters/usgov-nppes/adapter.ts +169 -0
- package/src/adapters/usgov-samhsa-treatment-locator/adapter.ts +211 -0
- package/src/adapters/wof-admin-jp/adapter.ts +176 -0
- package/src/adapters/wof-admin-json/adapter.ts +280 -0
- package/src/adapters/wof-postalcode-json/adapter.ts +209 -0
- package/src/align.ts +317 -0
- package/src/build.ts +348 -0
- package/src/codex/us-fips-state.ts +109 -0
- package/src/format.ts +14 -0
- package/src/golden.ts +147 -0
- package/src/index.ts +28 -0
- package/src/license.ts +48 -0
- package/src/parquet-wrapper/index.ts +9 -0
- package/src/parquet-wrapper/reader.ts +67 -0
- package/src/parquet-wrapper/schema.ts +80 -0
- package/src/parquet-wrapper/writer.ts +102 -0
- package/src/parquet.ts +360 -0
- package/src/runner.ts +283 -0
- package/src/shard-recipes/anchor-absorption.ts +86 -0
- package/src/shard-recipes/boundary-stress.ts +87 -0
- package/src/shard-recipes/country-balanced.ts +471 -0
- package/src/shard-recipes/cz-pcfirst-preposition.ts +83 -0
- package/src/shard-recipes/fr-admin-split.ts +212 -0
- package/src/shard-recipes/fr-bare-street.ts +85 -0
- package/src/shard-recipes/fr-fragment.ts +304 -0
- package/src/shard-recipes/fr-order.ts +283 -0
- package/src/shard-recipes/german.ts +206 -0
- package/src/shard-recipes/house-venue.ts +78 -0
- package/src/shard-recipes/index.ts +76 -0
- package/src/shard-recipes/intersection.ts +617 -0
- package/src/shard-recipes/locale.ts +415 -0
- package/src/shard-recipes/nl-postcode.ts +105 -0
- package/src/shard-recipes/no-fragment.ts +206 -0
- package/src/shard-recipes/no-street-led.ts +130 -0
- package/src/shard-recipes/no-street.ts +78 -0
- package/src/shard-recipes/po-box-cedex.ts +853 -0
- package/src/shard-recipes/po-box.ts +111 -0
- package/src/shard-recipes/scaffold.ts +187 -0
- package/src/shard-recipes/si-bare-village.ts +88 -0
- package/src/shard-recipes/street-affix.ts +549 -0
- package/src/shard-recipes/street-bare.ts +90 -0
- package/src/shard-recipes/street.ts +74 -0
- package/src/shard-recipes/unit.ts +315 -0
- package/src/split.ts +251 -0
- package/src/synthesize-anchor-absorption.ts +263 -0
- package/src/synthesize-boundary-stress.ts +504 -0
- package/src/synthesize-german.ts +227 -0
- package/src/synthesize-house-venue.ts +192 -0
- package/src/synthesize-intersection.ts +205 -0
- package/src/synthesize-no-street.ts +341 -0
- package/src/synthesize-po-box.ts +307 -0
- package/src/synthesize-street.ts +264 -0
- package/src/synthesize.ts +872 -0
- package/src/tokenize.ts +68 -0
- package/src/tools/align-shard.ts +67 -0
- package/src/tools/audit.ts +352 -0
- package/src/tools/corpus-stats.ts +183 -0
- package/src/tools/fetch/ban.ts +288 -0
- package/src/tools/fetch/download.ts +119 -0
- package/src/tools/fetch/hrsa.ts +65 -0
- package/src/tools/fetch/imls-pls.ts +162 -0
- package/src/tools/fetch/index.ts +121 -0
- package/src/tools/fetch/nad.ts +304 -0
- package/src/tools/fetch/nppes.ts +177 -0
- package/src/tools/fetch/openaddresses.ts +380 -0
- package/src/tools/fetch/state-hi-schools.ts +217 -0
- package/src/tools/fetch/state-sources.ts +142 -0
- package/src/tools/fetch/tiger-full.ts +318 -0
- package/src/tools/golden-expand.ts +524 -0
- package/src/tools/golden-promote.ts +304 -0
- package/src/tools/index.ts +24 -0
- package/src/tools/ingest-csv.ts +420 -0
- package/src/tools/jsonl-to-parquet.ts +200 -0
- package/src/tools/lint-shard-vocab.ts +357 -0
- package/src/tools/lint-shard.ts +502 -0
- package/src/tools/overlay-manifest.ts +141 -0
- package/src/tools/shard-kryptonite.ts +131 -0
- package/src/tools/shard-translit.ts +244 -0
- package/src/types.ts +213 -0
- 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
|
+
}
|