mailwoman 7.8.1 → 8.0.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.
Files changed (70) hide show
  1. package/commands/eval/oa-resolver.tsx +1 -1
  2. package/commands/parse.tsx +72 -19
  3. package/commands/release/hf.tsx +6 -0
  4. package/eval-harness/baselines.json +577 -0
  5. package/eval-harness/gates/v0.5.0-bridge.json +33 -0
  6. package/eval-harness/gates/v1.6.0-boundary-stress.json +29 -0
  7. package/eval-harness/gates/v1.7.0-boundary-stress.json +31 -0
  8. package/eval-harness/gates/v2.0.0-width.json +21 -0
  9. package/eval-harness/gates/v2.3.0-nl-postcode.json +34 -0
  10. package/eval-harness/gates/v4.15.0-boundary.json +29 -0
  11. package/eval-harness/gates/v4.2.0-ship.json +21 -0
  12. package/eval-harness/gates/v4.3.0-relabel.json +23 -0
  13. package/eval-harness/gates/v4.4.0-boundary.json +29 -0
  14. package/eval-harness/gates/v5.2.0-nordic.json +30 -0
  15. package/eval-harness/gates/v5.3.0-family.json +33 -0
  16. package/eval-harness/gates/v6.0.0-shipped-baseline.json +37 -0
  17. package/eval-harness/gauntlet/build-regression-db.ts +1 -0
  18. package/eval-harness/gauntlet/cases/regression.ts +301 -2
  19. package/eval-harness/gauntlet/harness.ts +28 -5
  20. package/eval-harness/gauntlet/regression.ts +19 -2
  21. package/eval-harness/gauntlet/schema.ts +5 -1
  22. package/eval-harness/oa-resolver-eval.ts +1 -3
  23. package/geocode-core.ts +18 -1
  24. package/out/commands/eval/oa-resolver.d.ts +0 -1
  25. package/out/commands/eval/oa-resolver.d.ts.map +1 -1
  26. package/out/commands/eval/oa-resolver.js +0 -1
  27. package/out/commands/eval/oa-resolver.js.map +1 -1
  28. package/out/commands/parse.d.ts.map +1 -1
  29. package/out/commands/parse.js +64 -17
  30. package/out/commands/parse.js.map +1 -1
  31. package/out/commands/release/hf.d.ts +1 -0
  32. package/out/commands/release/hf.d.ts.map +1 -1
  33. package/out/commands/release/hf.js +4 -0
  34. package/out/commands/release/hf.js.map +1 -1
  35. package/out/eval-harness/gauntlet/build-regression-db.d.ts.map +1 -1
  36. package/out/eval-harness/gauntlet/build-regression-db.js +1 -1
  37. package/out/eval-harness/gauntlet/build-regression-db.js.map +1 -1
  38. package/out/eval-harness/gauntlet/cases/regression.d.ts +2 -0
  39. package/out/eval-harness/gauntlet/cases/regression.d.ts.map +1 -1
  40. package/out/eval-harness/gauntlet/cases/regression.js +296 -2
  41. package/out/eval-harness/gauntlet/cases/regression.js.map +1 -1
  42. package/out/eval-harness/gauntlet/harness.d.ts +10 -2
  43. package/out/eval-harness/gauntlet/harness.d.ts.map +1 -1
  44. package/out/eval-harness/gauntlet/harness.js +16 -4
  45. package/out/eval-harness/gauntlet/harness.js.map +1 -1
  46. package/out/eval-harness/gauntlet/regression.d.ts.map +1 -1
  47. package/out/eval-harness/gauntlet/regression.js +18 -2
  48. package/out/eval-harness/gauntlet/regression.js.map +1 -1
  49. package/out/eval-harness/gauntlet/schema.d.ts +4 -2
  50. package/out/eval-harness/gauntlet/schema.d.ts.map +1 -1
  51. package/out/eval-harness/gauntlet/schema.js +2 -0
  52. package/out/eval-harness/gauntlet/schema.js.map +1 -1
  53. package/out/eval-harness/oa-resolver-eval.d.ts +0 -2
  54. package/out/eval-harness/oa-resolver-eval.d.ts.map +1 -1
  55. package/out/eval-harness/oa-resolver-eval.js +1 -1
  56. package/out/eval-harness/oa-resolver-eval.js.map +1 -1
  57. package/out/geocode-core.d.ts.map +1 -1
  58. package/out/geocode-core.js +15 -1
  59. package/out/geocode-core.js.map +1 -1
  60. package/out/release-tools/publish-hf.d.ts +3 -2
  61. package/out/release-tools/publish-hf.d.ts.map +1 -1
  62. package/out/release-tools/publish-hf.js +65 -15
  63. package/out/release-tools/publish-hf.js.map +1 -1
  64. package/out/runtime-pipeline.d.ts +13 -12
  65. package/out/runtime-pipeline.d.ts.map +1 -1
  66. package/out/runtime-pipeline.js +73 -2
  67. package/out/runtime-pipeline.js.map +1 -1
  68. package/package.json +26 -24
  69. package/release-tools/publish-hf.ts +77 -18
  70. package/runtime-pipeline.ts +99 -4
@@ -44,7 +44,7 @@ const OptionsSchema = zod.object({
44
44
  // scripts/eval shim the probes spawn) keep the original two-key contract.
45
45
  adminCoherenceOff: zod.boolean().default(false).describe("#895 tri-state pin: force adminCoherence OFF"),
46
46
  hierarchyCompletion: zod.boolean().default(false).describe("#405: recover the dual-role-place locality"),
47
- cityStateFallback: zod.boolean().default(false).describe("#387 alias for --hierarchy-completion"),
47
+
48
48
  // coordinate tiers
49
49
  postcodeAnchor: zod.boolean().default(false).describe("Add the neural+anchor row (anchor-centroid coordinate)"),
50
50
  postcodeShards: zod.string().optional().describe("Postcode shards for the anchor rows (comma-separated)"),
@@ -11,10 +11,10 @@ import { collectProposals, filterByPolicy, InMemoryPolicyRegistry, type PolicyMo
11
11
  import type { ComponentTag, Section } from "@mailwoman/core/types"
12
12
  import { createNeuralProposalClassifier, NeuralAddressClassifier } from "@mailwoman/neural"
13
13
  import { weightsPackageName } from "@mailwoman/neural/weights"
14
- import { createWOFResolver, type Resolver, type ResolverBackend } from "@mailwoman/resolver"
14
+ import { createWOFResolver, type Resolver } from "@mailwoman/resolver"
15
15
  import { Text } from "ink"
16
16
  import { createRuntimePipeline } from "mailwoman"
17
- import React from "react"
17
+ import type React from "react"
18
18
  import zod from "zod"
19
19
 
20
20
  import { type CommandComponent, commandError, useCommandTask } from "../cli-kit/index.ts"
@@ -396,17 +396,27 @@ function serializeTree(
396
396
  }
397
397
 
398
398
  /**
399
- * Encoder-less structural parse (plan 3): the REAL pipeline stages (normalize query-shape locale-gate → kind →
400
- * grouper fast-paths) with no neural classifier. The tree carries what the structural stages can prove (postcode_only /
401
- * locality_only fast-paths populate it; free-form addresses may yield an empty tree). Banner goes to stderr so stdout
402
- * stays machine-parseable.
399
+ * The generic degraded-mode banner (stderr stdout stays machine-parseable). Emitted when the guard hands back a
400
+ * `declined` outcome (interactive "n" / `--degraded` / a failed download) or the explicit `--no-neural` skip paths
401
+ * that never attempt an encoder load, so `tryLoadNeural`'s precise absent-vs-load-error warning didn't fire. The
402
+ * attempted-and-failed case is announced by `tryLoadNeural` instead (see #1108), so this banner is deliberately NOT
403
+ * emitted there (it would double up).
403
404
  */
404
- async function runDegraded(input: string, options: zod.infer<typeof ParseConfigSchema>): Promise<string> {
405
+ function emitDegradedBanner(options: zod.infer<typeof ParseConfigSchema>): void {
405
406
  console.error(
406
407
  "⚠ degraded parse: the neural encoder is not loaded — output carries structural-pipeline results only.\n" +
407
408
  ` Upgrade: npm install ${weightsPackageName(options.locale)} or mailwoman parse --download-weights <address>`
408
409
  )
410
+ }
409
411
 
412
+ /**
413
+ * Encoder-less structural parse (plan 3), WITHOUT the banner: the REAL pipeline stages (normalize → query-shape →
414
+ * locale-gate → kind → grouper fast-paths) with no neural classifier. The tree carries what the structural stages can
415
+ * prove (postcode_only / locality_only fast-paths populate it; free-form addresses may yield an empty tree). The caller
416
+ * owns the degraded notice — either {@link emitDegradedBanner} or the precise absent/load-error warning `tryLoadNeural`
417
+ * already printed — so no path degrades silently, and none double-warns.
418
+ */
419
+ async function runStructuralPipeline(input: string, options: zod.infer<typeof ParseConfigSchema>): Promise<string> {
410
420
  const pipeline = createRuntimePipeline({ poiQueryKind: options.poi })
411
421
  const result = await pipeline(input, { locale: options.locale })
412
422
 
@@ -415,20 +425,37 @@ async function runDegraded(input: string, options: zod.infer<typeof ParseConfigS
415
425
  : serializeTree(result.tree, options.format, { includeAlternatives: false })
416
426
  }
417
427
 
428
+ /**
429
+ * Structural parse fronted by the generic degraded banner — the guard's `declined` / `--no-neural` entry point (the
430
+ * caller here did NOT attempt an encoder load, so it owns the notice).
431
+ */
432
+ async function runDegraded(input: string, options: zod.infer<typeof ParseConfigSchema>): Promise<string> {
433
+ emitDegradedBanner(options)
434
+
435
+ return runStructuralPipeline(input, options)
436
+ }
437
+
418
438
  /**
419
439
  * Default path: runtime pipeline. Lazy-loads the neural classifier + optional resolver. Returns the parsed tree
420
440
  * serialized in the requested format. When the encoder is unavailable, degrades to the structural-pipeline stages
421
441
  * (normalize → query-shape → kind → grouper fast-paths) rather than any rules parser.
422
442
  */
423
443
  async function runPipeline(input: string, options: zod.infer<typeof ParseConfigSchema>): Promise<string> {
424
- const classifier = options.noNeural ? undefined : await tryLoadNeural(options)
425
-
426
- // When the encoder isn't loaded and there's no resolver/debug work to do, the full pipeline can
427
- // only emit QueryShape fast-path structure. Route to the degraded structural path (with its banner)
428
- // so the CLI still produces useful output for the fast-path kinds (postcode_only, locality_only).
429
- // `--debug` stays on the pipeline so the operator gets the requested PipelineResult JSON shape.
444
+ // `tryLoadNeural` emits its own precise (absent vs. corrupt/load-error) stderr warning when the load
445
+ // fails, so an attempted-but-failed encoder load is NEVER silent — including on the --resolve/--debug
446
+ // paths, which don't route through the degraded banner below (#1108). The explicit --no-neural skip
447
+ // attempts no load (and gets no such warning); the degraded banner covers it instead.
448
+ const attemptedNeural = !options.noNeural
449
+ const classifier = attemptedNeural ? await tryLoadNeural(options) : undefined
450
+
451
+ // When the encoder isn't loaded and there's no resolver/debug work to do, the full pipeline can only
452
+ // emit QueryShape fast-path structure. Route to the structural path so the CLI still produces useful
453
+ // output for the fast-path kinds (postcode_only, locality_only). `--debug` stays on the pipeline so
454
+ // the operator gets the requested PipelineResult JSON shape.
430
455
  if (!classifier && !options.resolve && !options.debug) {
431
- return runDegraded(input, options)
456
+ // tryLoadNeural already warned when the load was attempted; only the explicit --no-neural skip
457
+ // still needs the generic degraded banner (it never called tryLoadNeural).
458
+ return attemptedNeural ? runStructuralPipeline(input, options) : runDegraded(input, options)
432
459
  }
433
460
 
434
461
  const wantAlternatives = options.candidates !== undefined
@@ -624,7 +651,18 @@ async function runBenchmark(
624
651
  return lines.join("\n")
625
652
  }
626
653
 
627
- /** Try to load the neural classifier; return undefined (with stderr note) if weights are absent. */
654
+ /**
655
+ * Try to load the neural classifier. NEVER throws — on failure it emits a LOUD one-line stderr warning and returns
656
+ * `undefined` so the caller degrades to the structural pipeline (postcode_only / locality_only fast-paths still
657
+ * resolve; `npx mailwoman parse …` always produces output). Distinguishes two failure modes (#1108) so a consumer can't
658
+ * attribute silently-degraded output to the neural parser:
659
+ *
660
+ * - Weights ABSENT (package not installed / carries no binaries) → an install hint, no scary error text.
661
+ * - Weights present but the encoder FAILED to load (corrupt / partial bundle) → the underlying error is surfaced, not
662
+ * swallowed.
663
+ *
664
+ * The warning goes to STDERR, never STDOUT, so piped stdout parsing is unaffected.
665
+ */
628
666
  async function tryLoadNeural(
629
667
  options: zod.infer<typeof ParseConfigSchema>
630
668
  ): Promise<NeuralAddressClassifier | undefined> {
@@ -634,10 +672,25 @@ async function tryLoadNeural(
634
672
  modelPath: options.model,
635
673
  tokenizerPath: options.tokenizer,
636
674
  })
637
- } catch {
638
- // Graceful degradation: pipeline runs normalize + queryShape + kind + resolver only.
639
- // The caller sees `tree.roots` populated from QueryShape fast-paths (postcode_only,
640
- // locality_only) but nothing from the encoder.
675
+ } catch (error) {
676
+ // Graceful degradation: pipeline runs normalize + queryShape + kind (+ resolver) only. The caller
677
+ // sees `tree.roots` populated from QueryShape fast-paths but nothing from the encoder — so we warn.
678
+ const message = error instanceof Error ? error.message : String(error)
679
+ // "Absent" = the weights package simply isn't installed (the resolver's not-found signal). Every OTHER
680
+ // failure means the weights DID resolve but the encoder couldn't load them — a partial/metadata-only
681
+ // bundle ("missing model files"), a bad explicit --model/--tokenizer path, or a corrupt artifact — so we
682
+ // surface the underlying error verbatim rather than mislabel it "not installed" and swallow the cause.
683
+ const absent = /Could not resolve/iu.test(message)
684
+
685
+ if (absent) {
686
+ console.error(
687
+ `⚠ neural weights not found — running a degraded structural parse; ` +
688
+ `install ${weightsPackageName(options.locale)} for full accuracy.`
689
+ )
690
+ } else {
691
+ console.error(`⚠ neural weights failed to load — running a degraded structural parse. Encoder error: ${message}`)
692
+ }
693
+
641
694
  return undefined
642
695
  }
643
696
  }
@@ -42,6 +42,12 @@ const OptionsSchema = zod.object({
42
42
  .describe(
43
43
  "Comma-separated placetype-pair-index soft-feed binaries (pair-index-gb.bin,…; placetype-pair-prior arc, Task 8)"
44
44
  ),
45
+ fsts: zod
46
+ .string()
47
+ .optional()
48
+ .describe(
49
+ "Comma-separated per-locale FST gazetteers for the npm packages (fst-en-us.bin,fst-fr-fr.bin,fst-en-gb.bin; #1318). Staged flat by lowercase basename; publish.yml fetches these into each weights workspace."
50
+ ),
45
51
  gazetteerLexicon: zod
46
52
  .string()
47
53
  .optional()