@mailwoman/corpus 8.2.0 → 8.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"house-venue.d.ts","sourceRoot":"","sources":["../../../src/shard-recipes/house-venue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAqD,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAInG;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,WA+D9B,CAAA"}
1
+ {"version":3,"file":"house-venue.d.ts","sourceRoot":"","sources":["../../../src/shard-recipes/house-venue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAqD,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAInG;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAiE9B,CAAA"}
@@ -31,7 +31,9 @@ export const houseVenueRecipe = {
31
31
  let skipped = 0;
32
32
  for await (const tuple of readTuples(opts.input)) {
33
33
  read++;
34
- if (!tuple.locality || !tuple.region || !tuple.postcode || !tuple.country) {
34
+ // FR renders without a region (postcode-before-locality tail the run-2 contingency), so
35
+ // an empty region is valid there and stays required everywhere else.
36
+ if (!tuple.locality || !tuple.postcode || !tuple.country || (!tuple.region && tuple.country !== "FR")) {
35
37
  skipped++;
36
38
  continue;
37
39
  }
@@ -1 +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;;;GAGG;AACH,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;gBAET,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;oBAET,SAAQ;gBACT,CAAC;gBAED,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,EAAE,CAAC;oBACR,OAAO,EAAE,CAAA;gBACV,CAAC;qBAAM,CAAC;oBACP,OAAO,EAAE,CAAA;gBACV,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAClC,CAAC;CACD,CAAA"}
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;;;GAGG;AACH,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,0FAA0F;YAC1F,qEAAqE;YACrE,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;gBACvG,OAAO,EAAE,CAAA;gBAET,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;oBAET,SAAQ;gBACT,CAAC;gBAED,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,EAAE,CAAC;oBACR,OAAO,EAAE,CAAA;gBACV,CAAC;qBAAM,CAAC;oBACP,OAAO,EAAE,CAAA;gBACV,CAAC;YACF,CAAC;QACF,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAClC,CAAC;CACD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"synthesize-house-venue.d.ts","sourceRoot":"","sources":["../../src/synthesize-house-venue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,kBAAkB,GAC3B,oBAAoB,GACpB,qBAAqB,CAAA;AAIxB,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,kBAAkB,CAAA;CAClC;AAED,MAAM,WAAW,wBAAwB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,CAAA;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,kBAAkB,CAAA;CAC5B;AAiGD,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,mBAAmB,EACzB,IAAI,GAAE,uBAA4B,GAChC,wBAAwB,GAAG,IAAI,CA8BjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,OAAO,CAEtF"}
1
+ {"version":3,"file":"synthesize-house-venue.d.ts","sourceRoot":"","sources":["../../src/synthesize-house-venue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,MAAM,kBAAkB,GAC3B,oBAAoB,GACpB,qBAAqB,CAAA;AAIxB,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,kBAAkB,CAAA;CAClC;AAED,MAAM,WAAW,wBAAwB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,CAAA;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,kBAAkB,CAAA;CAC5B;AAkLD,wBAAgB,uBAAuB,CACtC,IAAI,EAAE,mBAAmB,EACzB,IAAI,GAAE,uBAA4B,GAChC,wBAAwB,GAAG,IAAI,CAmEjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,OAAO,CAEtF"}
@@ -40,6 +40,17 @@ const PLAIN_VENUES = [
40
40
  "Maple Leaf Cafe",
41
41
  "Riverside Garden Center",
42
42
  "Tech Solutions Inc",
43
+ // FR-flavored venue names (the run-2 contingency): the failing gauntlet fixtures carry
44
+ // international/English names at FR addresses, but native forms must appear too — the register
45
+ // mixes both in real Paris data.
46
+ "Café de la Poste",
47
+ "Boulangerie Saint-Michel",
48
+ "Le Petit Bistrot",
49
+ "Brasserie du Marché",
50
+ "Chez Marcel",
51
+ "La Belle Époque",
52
+ "Restaurant du Port",
53
+ "Pharmacie Centrale",
43
54
  "Pacific Industries",
44
55
  "Atlantic Holdings",
45
56
  "Stellar Consulting",
@@ -54,6 +65,42 @@ const PLAIN_VENUES = [
54
65
  "Pinecrest Veterinary",
55
66
  "Westwood Realty",
56
67
  ];
68
+ /**
69
+ * GB-flavored venue names (#1366): institutional forms (Club/Centre/House/Arms/Station), the "Ye" archaic register, and
70
+ * brand–dash–place compounds — INCLUDING directional-led names, because the target class is venues that open with
71
+ * compass words ("New North Health Centre", "Southfields Station") and the base model reads those as locality/street
72
+ * evidence. The six #1366 gauntlet fixtures' own venue names are deliberately ABSENT — the fixtures stay held-out.
73
+ */
74
+ const GB_VENUES = [
75
+ "Ye Olde Cheshire Cheese",
76
+ "Ye Old Mitre",
77
+ "The Red Lion",
78
+ "The Crown & Anchor",
79
+ "The King's Arms",
80
+ "The White Hart",
81
+ "The Royal Oak",
82
+ "North End Road Market",
83
+ "Northfields Community Centre",
84
+ "South Bank Tavern",
85
+ "Southgate Dental Practice",
86
+ "West End Barbers",
87
+ "East Street Pharmacy",
88
+ "New Cross Learning Centre",
89
+ "Old Street Works",
90
+ "Upper Crust - Waterloo",
91
+ "Pret a Manger - Leadenhall",
92
+ "Greggs - Camden High Street",
93
+ "The Ivy - Chelsea Garden",
94
+ "Marks & Spencer Simply Food",
95
+ "Chapel Market Fishmongers",
96
+ "Victoria Coach Station",
97
+ "Highbury Fields Tennis Club",
98
+ "The Carpenters Arms",
99
+ "St Bride's Institute",
100
+ "Albion House",
101
+ "Imperial Dry Cleaners",
102
+ "Golden Dragon 金龍饭店",
103
+ ];
57
104
  //#endregion
58
105
  //#region Fallback street pool
59
106
  /**
@@ -109,28 +156,88 @@ function countryToLocale(country) {
109
156
  }
110
157
  //#endregion
111
158
  //#region Synthesis
159
+ /**
160
+ * Fraction of GB rows drawing from {@link GB_VENUES} instead of the shared pool. 0.7 mirrors the register mix in real
161
+ * GB listings data (institutional names dominate, international/generic names still appear) — pre-registered in the
162
+ * #1366 memo.
163
+ */
164
+ const GB_VENUE_POOL_RATE = 0.7;
165
+ /**
166
+ * Fraction of GB rows whose house number widens into a range ("287-293"). Real GB venue addresses frequently span
167
+ * buildings; 0.15 keeps ranges a minority register — pre-registered in the #1366 memo.
168
+ */
169
+ const GB_RANGE_NUMBER_RATE = 0.15;
170
+ /**
171
+ * Fraction of rows (EVERY template order) rendered with a trailing country surface, tagged `country`. The 2026-08-01
172
+ * operator probe set proved the mechanism: the FR control row ("…, 75004 Paris, France") fails on a model trained only
173
+ * on country-less venue rows while its country-less twin passes — a trailing country makes the whole template OOD
174
+ * (Addendum 3 of the #1366 pre-registration). 0.3 keeps the country-less register dominant.
175
+ */
176
+ const COUNTRY_APPEND_RATE = 0.3;
177
+ /**
178
+ * Trailing country surfaces by tuple country — the register mixes formal and short forms where both are common.
179
+ */
180
+ const COUNTRY_SURFACES = {
181
+ US: ["United States", "USA"],
182
+ GB: ["United Kingdom", "United Kingdom", "United Kingdom", "UK"],
183
+ FR: ["France"],
184
+ CA: ["Canada"],
185
+ AU: ["Australia"],
186
+ DE: ["Germany", "Deutschland"],
187
+ };
112
188
  export function synthesizeHouseVenueRow(base, opts = {}) {
113
189
  const random = opts.random ?? Math.random;
114
190
  const locale = countryToLocale(base.country);
115
191
  const template = opts.forceTemplate ?? (random() < 0.5 ? "venue-after-street" : "venue-before-street");
116
- const venue = pick(PLAIN_VENUES, random);
192
+ // FR renders postcode-before-locality with NO region ("MR & MRS CRAB, 20 Rue de la Huchette,
193
+ // 75005 Paris" — the v4.0.0 gauntlet's venue-led failure family, the run-2 contingency's exact
194
+ // target shape). GB (#1366) renders locality-then-postcode with NO region and NO comma between
195
+ // them ("Ye Three Lords, 27 Minories, London EC3N 1DE" — the third tail the shard must teach).
196
+ // Every other country keeps the original US-order tail.
197
+ const frOrder = base.country === "FR";
198
+ const gbOrder = base.country === "GB";
199
+ // GB rows draw from the GB pool 70% of the time (institutional/archaic/brand-dash-place forms,
200
+ // incl. directional-led names — the #1366 target class) and the shared pool otherwise; real GB
201
+ // registers mix both. Other locales keep the shared pool (which already carries the FR flavor).
202
+ const venue = gbOrder && random() < GB_VENUE_POOL_RATE ? pick(GB_VENUES, random) : pick(PLAIN_VENUES, random);
117
203
  const street = base.street ?? pick(FALLBACK_STREETS, random);
118
- const houseNumber = base.houseNumber ?? randomHouseNumber(random);
204
+ let houseNumber = base.houseNumber ?? randomHouseNumber(random);
205
+ // GB range numbers ("287-293 New N Rd"): real GB venue addresses frequently span buildings.
206
+ // 15% of GB rows widen the number into a range (same parity, small span — the register's real
207
+ // shape). Pre-registered in the #1366 memo; the base pool's no-ranges stance stays for other
208
+ // locales.
209
+ if (gbOrder && random() < GB_RANGE_NUMBER_RATE && /^\d+$/.test(houseNumber)) {
210
+ const start = Number.parseInt(houseNumber, 10);
211
+ const span = (1 + Math.floor(random() * 4)) * 2;
212
+ houseNumber = `${start}-${start + span}`;
213
+ }
119
214
  const components = {
120
215
  house_number: houseNumber,
121
216
  street,
122
217
  venue,
123
218
  locality: base.locality,
124
- region: base.region,
219
+ ...(frOrder || gbOrder ? {} : { region: base.region }),
125
220
  postcode: base.postcode,
126
221
  };
222
+ let tail = frOrder
223
+ ? `${base.postcode} ${base.locality}`
224
+ : gbOrder
225
+ ? `${base.locality} ${base.postcode}`
226
+ : `${base.locality}, ${base.region} ${base.postcode}`;
227
+ // Trailing country surface (Addendum 3): appended AFTER the tail in every order, tagged.
228
+ const countrySurfaces = COUNTRY_SURFACES[base.country];
229
+ if (countrySurfaces && random() < COUNTRY_APPEND_RATE) {
230
+ const countrySurface = pick(countrySurfaces, random);
231
+ components.country = countrySurface;
232
+ tail = `${tail}, ${countrySurface}`;
233
+ }
127
234
  let raw;
128
235
  switch (template) {
129
236
  case "venue-after-street":
130
- raw = `${houseNumber} ${street}, ${venue}, ${base.locality}, ${base.region} ${base.postcode}`;
237
+ raw = `${houseNumber} ${street}, ${venue}, ${tail}`;
131
238
  break;
132
239
  case "venue-before-street":
133
- raw = `${venue}, ${houseNumber} ${street}, ${base.locality}, ${base.region} ${base.postcode}`;
240
+ raw = `${venue}, ${houseNumber} ${street}, ${tail}`;
134
241
  break;
135
242
  }
136
243
  return { raw, components, locale, template };
@@ -1 +1 @@
1
- {"version":3,"file":"synthesize-house-venue.js","sourceRoot":"","sources":["../../src/synthesize-house-venue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"synthesize-house-venue.js","sourceRoot":"","sources":["../../src/synthesize-house-venue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mailwoman/corpus",
3
- "version": "8.2.0",
3
+ "version": "8.4.0",
4
4
  "description": "Mailwoman corpus pipeline: BIO-labeled dataset builder for the neural classifier.",
5
5
  "license": "AGPL-3.0-only OR LicenseRef-Commercial",
6
6
  "repository": {
@@ -91,10 +91,10 @@
91
91
  },
92
92
  "dependencies": {
93
93
  "@dsnp/parquetjs": "1.8.9",
94
- "@mailwoman/ban": "8.2.0",
95
- "@mailwoman/codex": "8.2.0",
96
- "@mailwoman/core": "8.2.0",
97
- "@mailwoman/formatter": "8.2.0",
94
+ "@mailwoman/ban": "8.4.0",
95
+ "@mailwoman/codex": "8.4.0",
96
+ "@mailwoman/core": "8.4.0",
97
+ "@mailwoman/formatter": "8.4.0",
98
98
  "csv-parse": "^7.0.1",
99
99
  "fast-glob": "^3.3.3",
100
100
  "fastest-levenshtein": "^1.0.16",
@@ -35,7 +35,9 @@ export const houseVenueRecipe: ShardRecipe = {
35
35
  for await (const tuple of readTuples(opts.input)) {
36
36
  read++
37
37
 
38
- if (!tuple.locality || !tuple.region || !tuple.postcode || !tuple.country) {
38
+ // FR renders without a region (postcode-before-locality tail the run-2 contingency), so
39
+ // an empty region is valid there and stays required everywhere else.
40
+ if (!tuple.locality || !tuple.postcode || !tuple.country || (!tuple.region && tuple.country !== "FR")) {
39
41
  skipped++
40
42
 
41
43
  continue
@@ -71,6 +71,17 @@ const PLAIN_VENUES: ReadonlyArray<string> = [
71
71
  "Maple Leaf Cafe",
72
72
  "Riverside Garden Center",
73
73
  "Tech Solutions Inc",
74
+ // FR-flavored venue names (the run-2 contingency): the failing gauntlet fixtures carry
75
+ // international/English names at FR addresses, but native forms must appear too — the register
76
+ // mixes both in real Paris data.
77
+ "Café de la Poste",
78
+ "Boulangerie Saint-Michel",
79
+ "Le Petit Bistrot",
80
+ "Brasserie du Marché",
81
+ "Chez Marcel",
82
+ "La Belle Époque",
83
+ "Restaurant du Port",
84
+ "Pharmacie Centrale",
74
85
  "Pacific Industries",
75
86
  "Atlantic Holdings",
76
87
  "Stellar Consulting",
@@ -86,6 +97,43 @@ const PLAIN_VENUES: ReadonlyArray<string> = [
86
97
  "Westwood Realty",
87
98
  ]
88
99
 
100
+ /**
101
+ * GB-flavored venue names (#1366): institutional forms (Club/Centre/House/Arms/Station), the "Ye" archaic register, and
102
+ * brand–dash–place compounds — INCLUDING directional-led names, because the target class is venues that open with
103
+ * compass words ("New North Health Centre", "Southfields Station") and the base model reads those as locality/street
104
+ * evidence. The six #1366 gauntlet fixtures' own venue names are deliberately ABSENT — the fixtures stay held-out.
105
+ */
106
+ const GB_VENUES: ReadonlyArray<string> = [
107
+ "Ye Olde Cheshire Cheese",
108
+ "Ye Old Mitre",
109
+ "The Red Lion",
110
+ "The Crown & Anchor",
111
+ "The King's Arms",
112
+ "The White Hart",
113
+ "The Royal Oak",
114
+ "North End Road Market",
115
+ "Northfields Community Centre",
116
+ "South Bank Tavern",
117
+ "Southgate Dental Practice",
118
+ "West End Barbers",
119
+ "East Street Pharmacy",
120
+ "New Cross Learning Centre",
121
+ "Old Street Works",
122
+ "Upper Crust - Waterloo",
123
+ "Pret a Manger - Leadenhall",
124
+ "Greggs - Camden High Street",
125
+ "The Ivy - Chelsea Garden",
126
+ "Marks & Spencer Simply Food",
127
+ "Chapel Market Fishmongers",
128
+ "Victoria Coach Station",
129
+ "Highbury Fields Tennis Club",
130
+ "The Carpenters Arms",
131
+ "St Bride's Institute",
132
+ "Albion House",
133
+ "Imperial Dry Cleaners",
134
+ "Golden Dragon 金龍饭店",
135
+ ]
136
+
89
137
  //#endregion
90
138
 
91
139
  //#region Fallback street pool
@@ -152,6 +200,39 @@ function countryToLocale(country: string): string {
152
200
 
153
201
  //#region Synthesis
154
202
 
203
+ /**
204
+ * Fraction of GB rows drawing from {@link GB_VENUES} instead of the shared pool. 0.7 mirrors the register mix in real
205
+ * GB listings data (institutional names dominate, international/generic names still appear) — pre-registered in the
206
+ * #1366 memo.
207
+ */
208
+ const GB_VENUE_POOL_RATE = 0.7
209
+
210
+ /**
211
+ * Fraction of GB rows whose house number widens into a range ("287-293"). Real GB venue addresses frequently span
212
+ * buildings; 0.15 keeps ranges a minority register — pre-registered in the #1366 memo.
213
+ */
214
+ const GB_RANGE_NUMBER_RATE = 0.15
215
+
216
+ /**
217
+ * Fraction of rows (EVERY template order) rendered with a trailing country surface, tagged `country`. The 2026-08-01
218
+ * operator probe set proved the mechanism: the FR control row ("…, 75004 Paris, France") fails on a model trained only
219
+ * on country-less venue rows while its country-less twin passes — a trailing country makes the whole template OOD
220
+ * (Addendum 3 of the #1366 pre-registration). 0.3 keeps the country-less register dominant.
221
+ */
222
+ const COUNTRY_APPEND_RATE = 0.3
223
+
224
+ /**
225
+ * Trailing country surfaces by tuple country — the register mixes formal and short forms where both are common.
226
+ */
227
+ const COUNTRY_SURFACES: Readonly<Record<string, ReadonlyArray<string>>> = {
228
+ US: ["United States", "USA"],
229
+ GB: ["United Kingdom", "United Kingdom", "United Kingdom", "UK"],
230
+ FR: ["France"],
231
+ CA: ["Canada"],
232
+ AU: ["Australia"],
233
+ DE: ["Germany", "Deutschland"],
234
+ }
235
+
155
236
  export function synthesizeHouseVenueRow(
156
237
  base: HouseVenueBaseTuple,
157
238
  opts: HouseVenueSynthesisOpts = {}
@@ -160,27 +241,64 @@ export function synthesizeHouseVenueRow(
160
241
  const locale = countryToLocale(base.country)
161
242
  const template = opts.forceTemplate ?? (random() < 0.5 ? "venue-after-street" : "venue-before-street")
162
243
 
163
- const venue = pick(PLAIN_VENUES, random)
244
+ // FR renders postcode-before-locality with NO region ("MR & MRS CRAB, 20 Rue de la Huchette,
245
+ // 75005 Paris" — the v4.0.0 gauntlet's venue-led failure family, the run-2 contingency's exact
246
+ // target shape). GB (#1366) renders locality-then-postcode with NO region and NO comma between
247
+ // them ("Ye Three Lords, 27 Minories, London EC3N 1DE" — the third tail the shard must teach).
248
+ // Every other country keeps the original US-order tail.
249
+ const frOrder = base.country === "FR"
250
+ const gbOrder = base.country === "GB"
251
+
252
+ // GB rows draw from the GB pool 70% of the time (institutional/archaic/brand-dash-place forms,
253
+ // incl. directional-led names — the #1366 target class) and the shared pool otherwise; real GB
254
+ // registers mix both. Other locales keep the shared pool (which already carries the FR flavor).
255
+ const venue = gbOrder && random() < GB_VENUE_POOL_RATE ? pick(GB_VENUES, random) : pick(PLAIN_VENUES, random)
164
256
  const street = base.street ?? pick(FALLBACK_STREETS, random)
165
- const houseNumber = base.houseNumber ?? randomHouseNumber(random)
257
+ let houseNumber = base.houseNumber ?? randomHouseNumber(random)
258
+
259
+ // GB range numbers ("287-293 New N Rd"): real GB venue addresses frequently span buildings.
260
+ // 15% of GB rows widen the number into a range (same parity, small span — the register's real
261
+ // shape). Pre-registered in the #1366 memo; the base pool's no-ranges stance stays for other
262
+ // locales.
263
+ if (gbOrder && random() < GB_RANGE_NUMBER_RATE && /^\d+$/.test(houseNumber)) {
264
+ const start = Number.parseInt(houseNumber, 10)
265
+ const span = (1 + Math.floor(random() * 4)) * 2
266
+
267
+ houseNumber = `${start}-${start + span}`
268
+ }
166
269
 
167
270
  const components: CanonicalRow["components"] = {
168
271
  house_number: houseNumber,
169
272
  street,
170
273
  venue,
171
274
  locality: base.locality,
172
- region: base.region,
275
+ ...(frOrder || gbOrder ? {} : { region: base.region }),
173
276
  postcode: base.postcode,
174
277
  }
175
278
 
279
+ let tail = frOrder
280
+ ? `${base.postcode} ${base.locality}`
281
+ : gbOrder
282
+ ? `${base.locality} ${base.postcode}`
283
+ : `${base.locality}, ${base.region} ${base.postcode}`
284
+
285
+ // Trailing country surface (Addendum 3): appended AFTER the tail in every order, tagged.
286
+ const countrySurfaces = COUNTRY_SURFACES[base.country]
287
+
288
+ if (countrySurfaces && random() < COUNTRY_APPEND_RATE) {
289
+ const countrySurface = pick(countrySurfaces, random)
290
+ components.country = countrySurface
291
+ tail = `${tail}, ${countrySurface}`
292
+ }
293
+
176
294
  let raw: string
177
295
 
178
296
  switch (template) {
179
297
  case "venue-after-street":
180
- raw = `${houseNumber} ${street}, ${venue}, ${base.locality}, ${base.region} ${base.postcode}`
298
+ raw = `${houseNumber} ${street}, ${venue}, ${tail}`
181
299
  break
182
300
  case "venue-before-street":
183
- raw = `${venue}, ${houseNumber} ${street}, ${base.locality}, ${base.region} ${base.postcode}`
301
+ raw = `${venue}, ${houseNumber} ${street}, ${tail}`
184
302
  break
185
303
  }
186
304