/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * The canonical ProductionScorer (#718) — the ONE place that constructs a `NeuralAddressClassifier` * the way the SHIPPED model expects to be fed. Every eval, harness, and (eventually) the serving * path should route through here instead of re-deriving the anchor/gazetteer/conventions feed * from per-script flags. The history this closes: the #566/#685 trap — a model TRAINED with a * channel, scored WITHOUT it, silently goes out-of-distribution and the eval grades a handicapped * model. The flat per-script construction (`score-country-homograph.ts`, `per-locale-f1.ts`) each * re-invented the feed and each could silently drop a channel. * * The fix: the model-card declares its required channels (`requires` block — see * `readRequiredChannels`), and `createScorer` FAILS CLOSED when a declared channel isn't actually * fed. Deliberate ablations are still legal — pass an explicit `override` and the scorer warns * loudly instead of throwing (silent OOD is the bug, not the ablation). * * **Node-only.** Reads the model card, anchor lookup, and gazetteer lexicon from disk and * constructs the `ONNXRunner` (onnxruntime-node). Subpath `./scorer`; never import from the * browser bundle. */ import { NeuralAddressClassifier } from "./classifier.ts"; /** * Delta threshold for the capability-manifest gate (#718/#719): a conventions row may forbid a tag only if the mask * does NOT provably destroy a real capability — i.e. `maskOffF1 − maskOnF1 ≤ 5pp`. A DELTA, not an absolute floor: a * tag the model emits at 0.80 is protected if the mask drops it to 0.0, but a tag the mask leaves intact (small/zero * delta) is legal regardless of its absolute F1. */ export declare const CAPABILITY_DELTA_THRESHOLD = 0.05; /** * Default postcode→anchor lookup (the pilot lookup the shipped en-us model trained against). */ export declare const DEFAULT_ANCHOR_LOOKUP: string; /** * Default gazetteer-anchor lexicon (codex-generated, repo-relative). */ export declare const DEFAULT_GAZETTEER_LEXICON = "data/gazetteer/anchor-lexicon-v1.json"; /** * Default country-surface lexicon (codex-generated, repo-relative, #1104). */ export declare const DEFAULT_COUNTRY_LEXICON = "data/gazetteer/country-surface-lexicon-v1.json"; /** * Per-channel overrides for a deliberate, DECLARED ablation. Setting any of these to a value diverts the scorer from * the model-card's declared SHIP-CONFIG; the scorer honors it but emits a loud `console.error` warning (a stated * ablation is legal — silent OOD is not, #566/#685). */ export interface ScorerOverrides { /** * `false` to ablate the anchor channel even when the card declares it required. */ anchor?: boolean; /** * `false` to ablate the gazetteer channel even when the card declares it required. */ gazetteer?: boolean; /** * `false` to ablate the street-type evidence channel (Option-A bundle). */ streetType?: boolean; /** * `false` to ablate the locality-surface evidence channel (Option-A bundle). */ localitySurface?: boolean; /** * `false` to ablate the country-lexicon channel even when the card declares it required (#1104). */ country?: boolean; /** * Pin / disable the conventions mode (`"auto"` | a `SystemCode` | `false` to disable) regardless of the card's * declaration. */ conventions?: "auto" | string | false; /** * Override the bridge declaration. */ bridge?: boolean; /** * Override the near-postcode gazetteer choreography. */ suppressGazetteerNearPostcode?: boolean; } export interface CreateScorerOpts { /** * Path to the `model.onnx`. */ modelPath: string; /** * Path to the `tokenizer.model`. */ tokenizerPath: string; /** * Path to the `model-card.json` (label vocab + the `requires` ship-config). */ modelCardPath: string; /** * Per-locale FST gazetteer path (`fst-.bin`), surfaced on the built classifier as * {@link NeuralAddressClassifier.fstPath} so a caller assembling `createRuntimePipeline` gets the decode-time * gazetteer bias. * * PATH ONLY — `neural` carries no `resolver-wof-sqlite` dependency, so the deserialize happens in the caller's layer, * exactly as it does for the `loadFromWeights` route. * * WHY THIS EXISTS (#1497). `loadFromWeights` sets `fstPath` from the resolved weights package; `createScorer` builds * a classifier from EXPLICIT artifact paths and had no way to say which FST goes with them. Every eval that pins a * candidate model goes through this constructor, so every one of them was assembling a pipeline whose gazetteer prior * was silently OFF — which is why an FST change could not be measured by any eval in the tree. */ fstPath?: string; /** * Postcode→anchor lookup path — a JSON pilot lookup, or a PCB1 `.bin` (recognized by extension, so a candidate's own * `postcode-.bin` can be pinned; before that it was JSON-only and pointing at a binary threw a parse error). * * Default: {@link DEFAULT_ANCHOR_LOOKUP} when it exists, else the soft-feed sibling shipped in the * `@mailwoman/neural-weights-` package (#718 D1) — EXCEPT for a card declaring `span_mode: "shaped"`, which * inverts the order. See {@link resolveAnchorSource}. */ anchorLookupPath?: string; /** * Gazetteer-anchor lexicon path. Default {@link DEFAULT_GAZETTEER_LEXICON} when it exists, else the soft-feed sibling * shipped in the weights package (#718 D1). */ gazetteerLexiconPath?: string; /** * Street-type evidence lexicon path (Option-A bundle). Default: repo artifact, else the weights sibling. */ streetTypeLexiconPath?: string; /** * Locality-surface evidence lexicon path (Option-A bundle). Default: the weights-package sibling. */ localitySurfaceLexiconPath?: string; /** * Country-surface lexicon path (#1104). Default {@link DEFAULT_COUNTRY_LEXICON} when it exists, else the soft-feed * sibling shipped in the weights package. */ countryLexiconPath?: string; /** * Locale tag (e.g. `"en-us"`) used to resolve the weights-package soft-feed siblings when the default `/mnt` / * repo-relative paths are absent (#718 D1). Only consulted for that fallback; the model/tokenizer/card are always * explicit on this path. */ locale?: string; /** * Fail CLOSED (throw) when the model-card declares a channel required but it isn't actually fed. Default `true`. Set * `false` only for throwaway debugging — a below-config scorer is the trap this module exists to catch. */ strict?: boolean; /** * Serving tier whose certified capabilities the load-time delta-gate (#718/#719) reads from the card's `capabilities` * block: `"server"` (anchor+gazetteer — the production default) or `"pocket"` (anchor-only). Default `"server"`. A * tier the card doesn't certify → the gate has no capability claims to consult and is a no-op (legal). */ tier?: string; /** * Deliberate, DECLARED ablations (warn-not-throw). See {@link ScorerOverrides}. */ overrides?: ScorerOverrides; } /** * Construct a `NeuralAddressClassifier` wired to the model-card's declared SHIP-CONFIG (anchor + gazetteer + * conventions + bridge + near-postcode suppression), failing closed in `strict` mode when a declared channel can't * actually be fed. * * Resolution of "what's required": the card's `requires` block when present; otherwise INFERRED from the ONNX graph's * input names (back-compat for every pre-#718 bundle). Explicit `overrides` divert from the declaration with a loud * warning rather than a throw. */ export declare function createScorer(opts: CreateScorerOpts): Promise; //# sourceMappingURL=scorer.d.ts.map