@mailwoman/neural-weights-fr-fr 8.3.0 → 8.5.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/model-card.json +46 -8
- package/package.json +3 -2
- package/pair-index-fr.bin +0 -0
- package/scripts/link-dev-weights.ts +104 -0
package/model-card.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neural-weights-fr-fr",
|
|
3
3
|
"version": "6.5.0",
|
|
4
|
-
"model_lineage": "Ships the en-us v310 (v3.1.0-fr-fragment, #1143 T2) model
|
|
5
|
-
"phase": "Stage 3
|
|
4
|
+
"model_lineage": "Ships the en-us v310 (v3.1.0-fr-fragment, #1143 T2) model \u2014 init_from v264 plus the synth-fr-fragment BAN shard @ 12.0; tokenizer v0.9.0-multisplice (73,143) UNCHANGED. One model serves both locales; see the en-us card for the full lineage + grade. NOTE for this locale specifically: the shard is BAN-derived (Base Adresse Nationale, Licence Ouverte 2.0) and the gains are measured on French \u2014 bare-street 0.215\u21920.715, street-particle 0.273\u21920.855, admin-street-homonym 0.087\u21920.517, with the bare-locality counter-guard holding exactly. This is the largest FR-facing model change since 5.9.0.",
|
|
5
|
+
"phase": "Stage 3 \u2014 multi-locale (FR served by the shared en-us v310 model, shipped as 6.4.0)",
|
|
6
6
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
7
7
|
"locale": "fr-fr",
|
|
8
8
|
"training": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"note": "Identical artifact to neural-weights-en-us 6.4.0 / v310 fr-fragment (one multi-locale model serves both, copied at publish time). See that card's training/recipe for full provenance."
|
|
14
14
|
},
|
|
15
15
|
"requires": {
|
|
16
|
-
"$comment": "SHIP-CONFIG mirrored from neural-weights-en-us (#718/#721): this package ships en-us's model, so its required channels are identical
|
|
16
|
+
"$comment": "SHIP-CONFIG mirrored from neural-weights-en-us (#718/#721): this package ships en-us's model, so its required channels are identical \u2014 including `country` (the #1104 dedicated country soft-feed channel added in v263). createScorer / loadFromWeights read this and feed anchor + gazetteer + country + the conventions mask; running channel-OFF is the #566/#685 OOD crater.",
|
|
17
17
|
"anchor": {
|
|
18
18
|
"required": true
|
|
19
19
|
},
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"I-intersection_b"
|
|
87
87
|
],
|
|
88
88
|
"eval": {
|
|
89
|
-
"$comment": "SUPERSEDED / STALE (#721). The per_component + calibration numbers below are the OLD v0.9.7-step-20000 checkpoint measured ANCHOR-OFF (country 0.21, postcode 0.69
|
|
89
|
+
"$comment": "SUPERSEDED / STALE (#721). The per_component + calibration numbers below are the OLD v0.9.7-step-20000 checkpoint measured ANCHOR-OFF (country 0.21, postcode 0.69 \u2014 the #566/#685 OOD crater, NOT this package's real quality). Since the unified release CI ships en-us's v1.5.0 model for fr-fr too, the authoritative FR numbers are en-us v1.5.0-fr-order's, anchor-ON: assembled FR coord p50 1.5km / p90 5.3km, locality-match 76.3%; golden per-tag fr.house_number 99.3, fr.region 41.8 (open gap #330), fr.country 62.7, fr.postcode 99.5. See docs/articles/evals/2026-06-18-v150-vs-v170-head-to-head.md and neural-weights-en-us/model-card.json. Refresh or remove these stale fields per #721.",
|
|
90
90
|
"n_entries": 4535,
|
|
91
91
|
"full_parse_exact_match": 0.08180815876515987,
|
|
92
92
|
"mean_token_confidence": 0.8062812768727202,
|
|
@@ -213,14 +213,35 @@
|
|
|
213
213
|
"n": 30517,
|
|
214
214
|
"acc": 0.6440344725890488
|
|
215
215
|
}
|
|
216
|
-
]
|
|
216
|
+
],
|
|
217
|
+
"r6_fr_lieudit": {
|
|
218
|
+
"$comment": "Pre-registered bars from docs/superpowers/plans/2026-08-01-r6-fr-lieudit-preregistration.md.",
|
|
219
|
+
"golden_board": {
|
|
220
|
+
"n": 80,
|
|
221
|
+
"emit_before": 0,
|
|
222
|
+
"emit_after": 80,
|
|
223
|
+
"tag_correct_before": 0,
|
|
224
|
+
"tag_correct_after": 76,
|
|
225
|
+
"tag_correct_pct": 95.0,
|
|
226
|
+
"bar_pct": 70.0,
|
|
227
|
+
"note": "mailwoman/eval-harness/fixtures/fr-lieudit-golden.jsonl. Prior-OFF leg on identical bytes stays 0/80. The 4 residual misses are span-boundary (dropped particles), not retrieval \u2014 the prior fired on all 80. DISCLOSURE: 80/80 of the board's pairs are in the index (both are BAN-derived), so this measures mechanism efficacy where the gazetteer has data, not generalization."
|
|
228
|
+
},
|
|
229
|
+
"venue_confound_board": {
|
|
230
|
+
"n": 60,
|
|
231
|
+
"false_positives": 0,
|
|
232
|
+
"fp_pct": 0.0,
|
|
233
|
+
"bar_pct": 2.0,
|
|
234
|
+
"note": "40 article-led lieu-dit surfaces (La/Le/Les/Saint\u2026, from 84,582 available) + 20 others, each opening a venue name"
|
|
235
|
+
},
|
|
236
|
+
"gauntlet": "PASS after both decode-path fixes; neural 498/498, mailwoman 521/521"
|
|
237
|
+
}
|
|
217
238
|
},
|
|
218
239
|
"known_failure_modes": [
|
|
219
240
|
"underperforms on Hawaiian addresses (sparse in training corpus)",
|
|
220
241
|
"particle-honorific kryptonite (e.g. FR 'Saint-Just-Saint-Rambert') if not in synth set",
|
|
221
242
|
"non-Latin scripts (CJK, Cyrillic) fall through to byte-fallback tokens; F1 unknown"
|
|
222
243
|
],
|
|
223
|
-
"notes": "v0.4.0
|
|
244
|
+
"notes": "v0.4.0 \u2014 issue #116. Same encoder geometry as v0.3.0 (8.87M params, 6L/256H/4-heads, 21 BIO labels, linear-chain CRF). Issue proposed (1) per-token CRF NLL normalization + (3) class-weighted CE biased toward coarse labels + (4) source-weight rebalance. Empirical iteration found that \u00a71 and \u00a73 destabilize sustained training at every LR tested (5e-4, 3e-4, 1.5e-4 \u2014 even the v0.3.0-safe LR), and on the golden v0.1.2 eval (4535 entries) they slightly REGRESS country/postcode F1 vs v0.3.0. SHIPPED recipe is the \u00a74-only ablation (v0.3.0 dual-loss + v0.4.0 source-weight rebalance) at lr=1.5e-4, step 2200. Modest fine-label gains (street +0.03, house_number +0.01), modest coarse-F1 regression (country -0.07, postcode -0.07). \u00a71/\u00a73 deferred to v0.4.1 corpus-side investigation per issue's '2K divergence' clause. Full ablation matrix retrospective in LOG.md.",
|
|
224
245
|
"format": {
|
|
225
246
|
"model": "ONNX int8 dynamic",
|
|
226
247
|
"tokenizer": "SentencePiece unigram, byte_fallback=true, vocab_size=16000",
|
|
@@ -230,7 +251,24 @@
|
|
|
230
251
|
"files": {
|
|
231
252
|
"model": "model.onnx",
|
|
232
253
|
"tokenizer": "tokenizer.model",
|
|
233
|
-
"model_card": "model-card.json"
|
|
254
|
+
"model_card": "model-card.json",
|
|
255
|
+
"pair_index": "pair-index-fr.bin"
|
|
234
256
|
},
|
|
235
|
-
"base_relpath": "/data/models/checkpoints/v0_4_0-stableLR-source-only/step-002200"
|
|
257
|
+
"base_relpath": "/data/models/checkpoints/v0_4_0-stableLR-source-only/step-002200",
|
|
258
|
+
"fr_artifacts": {
|
|
259
|
+
"$comment": "Hierarchy campaign R6 (2026-08-01). The FR instance of the placetype-pair prior.",
|
|
260
|
+
"pair_index_fr_bin": {
|
|
261
|
+
"format": "PIX1 placetype-pair index \u2014 a retrieval-augmented (lieu-dit, commune) dependent_locality prior, e.g. (\"Pinsonnac\", \"Montpeyroux\")",
|
|
262
|
+
"source": "BAN \u2014 Base Adresse Nationale (DINUM/IGN, Licence Ouverte 2.0), raw adresses-<dept>.csv `nom_ld` field read through ban/sdk's cleanLieuDit filter. 101 d\u00e9partements, 1,684,844 rows carrying a clean lieu-dit. NOT WOF: WOF's French neighbourhood records are Paris quartiers, cartographic subdivisions that never appear in a postal address (the arrondissement is encoded in the postcode). The lieu-dit is the line French addresses actually carry \u2014 La Poste's line 5.",
|
|
263
|
+
"pairs": 199282,
|
|
264
|
+
"bytes": 5839518,
|
|
265
|
+
"md5": "f0999f1d5cd52a642ca7129157659578",
|
|
266
|
+
"delta_calibration": "\u03b4=10, transitionBeta=5 \u2014 inherited from the GB calibration, per R5's finding that the classifier's dependent_locality deficit is uniform across locales rather than GB-specific. The R6 bars were measured at exactly this pair.",
|
|
267
|
+
"country_gate": "hard-gated on the header's country (fr).",
|
|
268
|
+
"not_committed": "derived artifact, .gitignore'd \u2014 rebuilt via `mailwoman gazetteer pair-index --out neural-weights-fr-fr --country fr --delta 10 --transition-beta 5 --ban-dir <corpus/sources/ban>`",
|
|
269
|
+
"required_decode_fixes": "This artifact is INERT without two locale-general probe-chain fixes that shipped alongside it: a LEADING postcode strip on parent-candidate keys (France writes '12210 Montpeyroux'; the probe previously stripped only trailing postcodes, the Anglo/NZ convention) and NEWLINE as a segment boundary (computeGroupSegments scanned for commas only, so multi-line addresses collapsed to one segment and the segment path went structurally inert). With the artifact but without them the golden board stayed at 0/80.",
|
|
270
|
+
"$comment_md5": "REPRODUCIBILITY: a PIX1 header embeds `buildDate`, so a rebuild from identical sources produces different bytes and a different md5. This md5 therefore identifies the artifact STAGED TO HUGGING FACE for this release \u2014 it is a provenance record of the shipped bytes, NOT a check a local rebuild can reproduce. A mismatch after rebuilding is expected and is not corruption; verify pair COUNT and header delta/beta instead.",
|
|
271
|
+
"provenance_gap": "The PIX1 header's sourceMD5s is EMPTY for this artifact: the BAN source is a directory of 101 d\u00e9partement CSVs, and the build hashes individual files only. Hashing the full dump costs more than the provenance is worth at build time, so a BAN refresh is a deliberate act \u2014 rebuild with the artifact deleted rather than trusting a guard to notice."
|
|
272
|
+
}
|
|
273
|
+
}
|
|
236
274
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/neural-weights-fr-fr",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"description": "Mailwoman neural-classifier weights for locale 'fr-fr'. Data-only overlay — shares the base model.onnx + tokenizer.model from @mailwoman/neural-weights-en-us (they are byte-identical); ships only the fr-specific data siblings. Loaded by @mailwoman/neural at runtime.",
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
6
|
"repository": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"model-card.json",
|
|
13
13
|
"postcode-fr.bin",
|
|
14
|
+
"pair-index-fr.bin",
|
|
14
15
|
"fst-fr-fr.bin",
|
|
15
16
|
"fst-street-morphology.bin",
|
|
16
17
|
"anchor-lexicon-v1.json",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"access": "public"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@mailwoman/neural-weights-en-us": "8.
|
|
35
|
+
"@mailwoman/neural-weights-en-us": "8.5.0"
|
|
35
36
|
},
|
|
36
37
|
"mailwoman": {
|
|
37
38
|
"baseWeights": "@mailwoman/neural-weights-en-us"
|
|
Binary file
|
|
@@ -180,3 +180,107 @@ if (existsSync(MORPHOLOGY_SRC)) {
|
|
|
180
180
|
`WARNING: missing ${MORPHOLOGY_SRC} — the street-context gate falls back to the per-process dictionary build.`
|
|
181
181
|
)
|
|
182
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Placetype-pair index (hierarchy campaign R6, 2026-08-01): build `pair-index-fr.bin` from the raw BAN dump so
|
|
186
|
+
* `resolveWeights` surfaces `pairIndexPath` in dev and the placetype-pair prior is live for fr-fr.
|
|
187
|
+
*
|
|
188
|
+
* The FR source is BAN's `nom_ld` (lieu-dit), read through `ban/sdk`'s `cleanLieuDit` — NOT WOF. WOF's French
|
|
189
|
+
* neighbourhood records are Paris quartiers, which never appear in a postal address; the lieu-dit is the line French
|
|
190
|
+
* addresses actually carry. See `mailwoman/gazetteer-pipeline/lieudit-pairs.ts`.
|
|
191
|
+
*
|
|
192
|
+
* Freshness guard: the build streams ~26M BAN rows across 101 département files, so an unconditional rebuild would make
|
|
193
|
+
* every test run unusable. Peek the header and rebuild only when the calibrated delta or transition beta has moved.
|
|
194
|
+
* Unlike en-gb this does NOT md5 its source — BAN is a directory of 101 files, and hashing all of them costs more than
|
|
195
|
+
* the guard saves; a BAN refresh is a deliberate act, so re-run with the artifact deleted after one.
|
|
196
|
+
*/
|
|
197
|
+
const PAIR_INDEX_BIN_DEST = resolve(PKG_DIR, "pair-index-fr.bin")
|
|
198
|
+
/**
|
|
199
|
+
* Calibrated soft-prior magnitudes — the pair the R6 bars were measured at (board 0/80 → 76/80, 0/60 confound FPs).
|
|
200
|
+
*/
|
|
201
|
+
const PAIR_INDEX_DELTA = 10
|
|
202
|
+
const PAIR_INDEX_TRANSITION_BETA = 5
|
|
203
|
+
const BAN_DIR = dataRootPath("corpus", "sources", "ban")
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Minimal PIX1 header reader — magic + header block only, reimplemented rather than imported so this data-only package
|
|
207
|
+
* gains no dependency on `@mailwoman/neural`. `neural/pair-index-resolver.ts` owns the format.
|
|
208
|
+
*/
|
|
209
|
+
function peekPairIndexHeaderFields(path: string): { delta: number; transitionBeta: number | undefined } {
|
|
210
|
+
const bytes = readFileSync(path)
|
|
211
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
212
|
+
// "PIX1" little-endian.
|
|
213
|
+
const MAGIC = 0x31_58_49_50
|
|
214
|
+
|
|
215
|
+
if (view.getUint32(0, true) !== MAGIC) {
|
|
216
|
+
throw new Error(`pair index: bad magic reading ${path}`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const headerLen = view.getUint32(4, true)
|
|
220
|
+
const header = JSON.parse(Buffer.from(bytes.subarray(8, 8 + headerLen)).toString("utf8")) as {
|
|
221
|
+
delta: number
|
|
222
|
+
transitionBeta?: number
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { delta: header.delta, transitionBeta: header.transitionBeta }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!existsSync(CLI)) {
|
|
229
|
+
console.error(`WARNING: ${CLI} not built — run \`yarn compile\` first, then re-run for pair-index-fr.bin.`)
|
|
230
|
+
} else if (!existsSync(String(BAN_DIR))) {
|
|
231
|
+
console.error(
|
|
232
|
+
`WARNING: missing ${BAN_DIR} — pair-index-fr.bin not built; the placetype-pair prior stays inert for FR.`
|
|
233
|
+
)
|
|
234
|
+
} else {
|
|
235
|
+
let needsRebuild = true
|
|
236
|
+
|
|
237
|
+
if (existsSync(PAIR_INDEX_BIN_DEST)) {
|
|
238
|
+
try {
|
|
239
|
+
const header = peekPairIndexHeaderFields(PAIR_INDEX_BIN_DEST)
|
|
240
|
+
|
|
241
|
+
if (header.delta !== PAIR_INDEX_DELTA) {
|
|
242
|
+
console.log(`rebuilding pair-index-fr.bin — delta ${header.delta} → ${PAIR_INDEX_DELTA}`)
|
|
243
|
+
} else if (header.transitionBeta !== PAIR_INDEX_TRANSITION_BETA) {
|
|
244
|
+
console.log(
|
|
245
|
+
`rebuilding pair-index-fr.bin — transitionBeta ${header.transitionBeta} → ${PAIR_INDEX_TRANSITION_BETA}`
|
|
246
|
+
)
|
|
247
|
+
} else {
|
|
248
|
+
needsRebuild = false
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log(`rebuilding pair-index-fr.bin — header unreadable (${(error as Error).message})`)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!needsRebuild) {
|
|
256
|
+
console.log(`skipped pair-index-fr.bin build — ${PAIR_INDEX_BIN_DEST} is current`)
|
|
257
|
+
} else {
|
|
258
|
+
const result = spawnSync(
|
|
259
|
+
process.execPath,
|
|
260
|
+
[
|
|
261
|
+
CLI,
|
|
262
|
+
"gazetteer",
|
|
263
|
+
"pair-index",
|
|
264
|
+
"--out",
|
|
265
|
+
PKG_DIR,
|
|
266
|
+
"--country",
|
|
267
|
+
"fr",
|
|
268
|
+
"--delta",
|
|
269
|
+
String(PAIR_INDEX_DELTA),
|
|
270
|
+
"--transition-beta",
|
|
271
|
+
String(PAIR_INDEX_TRANSITION_BETA),
|
|
272
|
+
"--ban-dir",
|
|
273
|
+
String(BAN_DIR),
|
|
274
|
+
],
|
|
275
|
+
{ stdio: "inherit" }
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if (result.status !== 0 || !existsSync(PAIR_INDEX_BIN_DEST)) {
|
|
279
|
+
console.error(`FAILED: gazetteer pair-index --country fr (exit ${result.status})`)
|
|
280
|
+
|
|
281
|
+
process.exit(1)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log(`built pair-index-fr.bin ← ${BAN_DIR}`)
|
|
285
|
+
}
|
|
286
|
+
}
|