/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Golden-set street-suffix relabel — v0.1.2 → v0.1.3. * * ## Why this exists * * The golden answer key and the training corpus disagreed about ONE thing, and the v9.0.0 * promotion gate read the disagreement as a model regression (`us.street` 87.4 vs a floor of * 87.8). The corpus SPLITS a US street into `street` + `street_suffix` — TIGER's adapter * decomposes at `corpus/src/adapters/tiger/street-decompose.ts`, the `street-affix` shard recipe * teaches it from USPS Pub-28, and `ComponentTag` carries `street_suffix` as a first-class tag. * The golden set FOLDED it: 2,216 US rows carry a `street`, and exactly 2 of them label a * `street_suffix`. Operator ruling, 2026-08-06: **the split is canonical**; the golden is the * stale side. This tool moves the answer key onto the corpus convention. * * ## The instrument * * `matchTrailingSuffix` from `@mailwoman/codex/us` — the USPS Pub-28 Appendix C table, which is * also what the corpus shard recipe splits on. The table is NOT re-implemented here, and the * libpostal dictionary TIGER reads is deliberately not used: measured on this golden set the two * disagree on 51 US rows, and the disagreements run in the codex table's favour (libpostal's * `directionals.txt` lists `center|c`, so TIGER reads the `C` of "C STREET" as a directional * prefix and then emits no suffix at all). * * ## What it changes, and what it refuses to * * Applied only to rows whose `country` is `US`. Three branches, mirroring the SHAPE of TIGER's * `decomposeStreet` on codex tables: * * - **street type** — the last whitespace-separated word is a Pub-28 suffix, and something is left * over: "Main St" → `street: "Main"`, `street_suffix: "St"` (1,559 rows). * - **street type + post-directional** — the last word is a directional AND the one before it is a * Pub-28 suffix: "Pennsylvania Avenue NW" → `street: "Pennsylvania"`, * `street_suffix: "Avenue NW"` (347 rows). The post-directional joins the suffix rather than * becoming a tag of its own, because that is what the corpus adapter emits; there is no * `street_postfix` tag to move it to. * - **everything else is left folded** and reported. In particular a BARE post-directional tail * ("Seymour East", "BROADWAY N" — 16 rows) is NOT split: a directional is not a Pub-28 suffix, * and the observed rows in that class are unit-contaminated ("1ST AVE SW BOX E", where the * trailing "E" is a box letter). * * FR rows are untouched, deliberately and permanently as far as this tool is concerned. French * street typology puts the type FIRST ("Rue de la Paix") and the golden labels only 7 of 665 FR * street rows with a `street_prefix`; whether FR should split at all is a different question with * a different table behind it, and nothing here should be read as having answered it. * * ## Surface bytes * * The split is a cut at a whitespace run in the ORIGINAL string — no trimming, no case * normalization, no re-joining of tokens. `street + gap + street_suffix` reconstructs the input * byte-for-byte, so the whitespace between them belongs to neither span (the same shape the corpus * adapter's spans have). The tool asserts this per row and refuses to write a file if it ever * fails. */ /** * A golden-set row, as stored one-per-line in `us.jsonl` / `fr.jsonl` / `adversarial.jsonl`. Only the fields this tool * reads are modeled; every other key rides through untouched. */ export interface GoldenStreetRow { raw: string; components: Record; country?: string; source?: string; notes?: string; [key: string]: unknown; } /** * What the tool decided about one row. Every value except the two `split-*` classes means "left folded". */ export type GoldenRelabelClass = "split-suffix" | "split-suffix-postdirectional" | "split-prefix-only" | "already-split" | "single-token" | "suffix-only-street" | "postdirectional-tail-only" | "no-suffix-match" | "no-street" | "not-us" | "untrimmed-street"; /** * A review trigger on a row the tool DID change. A flag is never an adjudication — it marks the row for the operator's * deck, and the split is applied either way. */ export interface GoldenRelabelFlag { kind: "name-prone-suffix" | "venue-context" | "remainder-is-affix"; detail: string; } /** * Row-level relabel outcome. */ export interface GoldenRelabelResult { /** * The row to write. Identical object reference when nothing changed. */ row: GoldenStreetRow; changed: boolean; rowClass: GoldenRelabelClass; flags: GoldenRelabelFlag[]; /** * A leading directional was lifted into `street_prefix` on this row. */ prefixSplit: boolean; /** * The street span as it stood in the parent version — recorded for the review deck. */ beforeStreet?: string; } /** * Options for {@linkcode relabelGoldenStreetRow}. */ export interface RelabelStreetRowOptions { /** * Also lift a folded LEADING directional out into `street_prefix`. Default true. * * On by default because the fold cuts both ways and the answer key has to be corrected on both, or the correction is * not a correction: 207 of the 1,682 split dev rows (12.3%) still opened with a directional after the suffix move — * "N Desmet Avenue" would have graded `street: "N Desmet"` against a model that says `street_prefix: "N", street: * "Desmet"`. Turn it OFF only to measure what the prefix fold alone costs. */ splitPrefix?: boolean; } /** * Decide, and apply, the US street-span split for ONE golden row. Pure: never mutates its argument, and returns the * same object reference when the row is left alone. */ export declare function relabelGoldenStreetRow(row: GoldenStreetRow, options?: RelabelStreetRowOptions): GoldenRelabelResult; /** * Per-class row counts for one relabelled file. */ export type GoldenRelabelCounts = Record; /** * One line of the review deck: what a changed (or notably unchanged) row looked like before and after. */ export interface GoldenRelabelDeckEntry { file: string; line: number; raw: string; rowClass: GoldenRelabelClass; before: Record; after: Record; flags: GoldenRelabelFlag[]; } /** * Options for {@linkcode relabelGoldenDirectory}. */ export interface RelabelGoldenOptions { /** * Parent golden version dir (read-only), e.g. `data/eval/golden/v0.1.2`. */ input: string; /** * Output golden version dir. Created; never overwritten in place. */ output: string; /** * Review-deck JSONL path. Default `/REVIEW-DECK.jsonl`. */ deck?: string; /** * Parent version label recorded in the manifest. Default: the input dir's basename. */ parentLabel?: string; /** * Tool provenance recorded in the manifest — the commit the relabel ran at. */ commit?: string; /** * Passed through to {@linkcode relabelGoldenStreetRow}. Default true. */ splitPrefix?: boolean; } /** * Aggregate outcome for a whole golden version. */ export interface RelabelGoldenReport { files: Record; deckPath: string; outputDir: string; totalChanged: number; totalFlagged: number; } /** * Relabel every `.jsonl` in a golden version dir, writing a new version dir plus a review deck and a MANIFEST that * records the convention, the parent, and the counts. Non-JSONL siblings (README, split manifests) are copied forward * so the new version is self-contained; nested split dirs (`dev/`, `test/`) are relabelled recursively. */ export declare function relabelGoldenDirectory(options: RelabelGoldenOptions, report?: (line: string) => void): Promise; /** * Every relabel class that means the row was left folded, for callers that want to report the residue. */ export declare function isLeftFolded(rowClass: GoldenRelabelClass): boolean; /** * True when a golden dir declares the US-split convention — i.e. it is safe to grade it with an UNFOLDED scorer. */ export declare function goldenDeclaresSplitStreets(dir: string): boolean; //# sourceMappingURL=golden-relabel-street.d.ts.map