/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Browser-side loader that pairs the existing `MailwomanTokenizer` (whose `loadFromBase64` path is * already browser-safe — it doesn't touch Node fs) with a fresh `WebONNXRunner`, and returns a * ready-to-use `NeuralAddressClassifier`. * * V1 strategy: fetch both `model.onnx` and `tokenizer.model` over HTTP from caller-provided URLs * (typically pointing at the same static-asset bundle that ships the resolver's slim WOF DB). The * neural weights package `@mailwoman/neural-weights-en-us` is the canonical source of those two * files; for a static deploy, copy them into the public bundle and pass the resulting URLs. */ import type { AnchorLookup } from "./anchor-inference.ts"; import { NeuralAddressClassifier, type NeuralAddressClassifierConfig } from "./classifier.ts"; import { PairIndexResolver } from "./pair-index-resolver.ts"; import type { PlacetypePairPriorOpts } from "./placetype-pair-prior.ts"; import { type WebONNXRunnerDiagnostics, type WebONNXRunnerOpts } from "./web-onnx-runner.ts"; export { type WebONNXRunnerDiagnostics } from "./web-onnx-runner.ts"; export interface LoadedPairIndex { /** * URL the binary was fetched from. */ url: string; /** * The header's ISO country code — the key the per-parse selection matches a detected country against. */ country: string; /** * The constructed, live resolver. The SAME instance the per-parse selection returns (and, for a posture-pinned load, * the classifier's config default). */ resolver: PairIndexResolver; } export interface LoadResult { classifier: NeuralAddressClassifier; diagnostics: WebONNXRunnerDiagnostics | null; /** * Labels actually applied to the classifier. `null` when no model-card was provided or its `labels` field was missing * — the classifier fell back to its built-in default (Stage 2). */ labels: readonly string[] | null; /** * The parsed postcode-anchor lookup (postcode → posterior + centroid), when anchor binaries were loaded. Exposed so * consumers (the demo's anchor-centroid map fallback) can reuse the SAME artifact the model channel feeds from — WOF * ships placeholder (0,0) for ~22% of US postcodes; this lookup has a real centroid for every covered ZIP. */ postcodeAnchorLookup?: AnchorLookup; /** * Every placetype-pair index that fetched + parsed, each with its header country and a live resolver — see * {@link LoadedPairIndex}. Empty when `pairIndexURLs` was omitted or every fetch failed. Exposed so consumers (the * demo's preset lighting) can see which countries' indexes are loaded and available to the per-parse selection. */ pairIndexes: readonly LoadedPairIndex[]; /** * Per-parse placetype-pair selection (#1278 phase 2) — the primary path. Runs `@mailwoman/query-shape` + * `@mailwoman/locale-gate` over `text` to derive a country subtag from its STRUCTURAL shape (postcode format / * script; never place-name dictionaries — bitter-lesson-safe), then returns the {@link LoadedPairIndex} resolver * whose header country matches, wrapped as a ready-to-spread `placetypePair` option. No matching index (or no indexes * loaded) → `undefined`, which a caller spreads as `placetypePair: undefined` → the classifier's `opts?.placetypePair * ?? this.cfg.placetypePair` resolution falls through to the config default (see {@link LoadFromURLsOptions.country}) * or, when none, the byte-stable no-prior decode. * * Intended call site (the demo, its own next step): * * ```ts * const tree = await classifier.parse(text, { ...baseOpts, placetypePair: result.selectPairIndexForText(text) }) * ``` * * `opts.country` (a locale "en-gb" or bare "gb") pins the selection for one call, bypassing detection — the escape * hatch for a preset that knows its own posture regardless of the text shape. */ selectPairIndexForText: (text: string, opts?: { country?: string; }) => PlacetypePairPriorOpts | undefined; } export interface LoadFromURLsOptions { /** * URL to the ONNX model file (e.g. `/static/mailwoman/model.onnx`). */ modelURL: string; /** * URL to the SentencePiece tokenizer model (e.g. `/static/mailwoman/tokenizer.model`). */ tokenizerURL: string; /** * URL to `model-card.json`. When provided, its `labels` field is threaded into the classifier so post-Stage-2 bundles * (33-label Stage 3 and beyond) decode correctly. Skip for legacy bundles whose cards predate the `labels` field — * the loader falls back to the built-in Stage 2 default. * * Required for any v0.6.x+ bundle: without it the classifier builds a 21×21 transition mask while the model emits 33 * logits and viterbi crashes with "Cannot read properties of undefined". */ modelCardURL?: string; /** * Runner options (WebGPU toggle, fixed sequence length, WASM path override). */ runner?: WebONNXRunnerOpts; /** * URLs to one or more PCB1 postcode binaries (`postcode-.bin`). For anchor-trained models (#239/#240) these are * decoded + merged into the postcode→anchor lookup the classifier feeds at inference, so the demo runs the model with * the anchor on. Pass the locales the model handles (e.g. US + DE). Omit for plain models — the runner then feeds the * anchor-off identity. */ postcodeBinaryURLs?: readonly string[]; /** * URLs to one or more PIX1 placetype-pair indexes (`pair-index-.bin`, placetype-pair-prior arc — the GB * dependent_locality retrieval channel, #1278). Each binary is OPTIONAL and fetched TOLERANTLY (the * `postcodeBinaryURLs` contract): a 404/network failure/corrupt file is skipped with a loud `console.warn` and never * blocks the classifier load — older HF release versions ship no pair indexes at all. * * **Phase 2 (#1278 locale-gate wiring) — load ALL, select per parse.** Every fetched index is constructed into a live * {@link PairIndexResolver} and retained ({@link LoadResult.pairIndexes}), tagged by its header country. The * selection of WHICH index biases a given parse is a per-parse decision — see * {@link LoadResult.selectPairIndexForText}, which runs locale-gate over the input text — because one loaded * classifier serves inputs from multiple countries and the country is a property of the text, not the load. (#1300's * load-time single-index country gate is superseded; the `country` load-option below survives as an optional * config-default posture pin.) */ pairIndexURLs?: readonly string[]; /** * OPTIONAL default posture for the placetype-pair prior — a locale ("en-gb") or bare ISO country code ("gb"), * case-insensitive (reduced to its country subtag via {@link resolvePairGateCountry}, the node `localeCountry` * derivation). When provided AND a fetched index carries a matching header country, that index becomes the * classifier's CONFIG-LEVEL `placetypePair` default — the posture a parse falls back to when the per-parse * {@link LoadResult.selectPairIndexForText} returns nothing (or the demo never calls it). This is the single-posture * "default/override" path: it pins one country the way #1300's demo did. * * OMITTED (the recommended shape for the multi-locale demo) sets NO config default — every parse's prior comes solely * from the per-parse selection, and an input that matches no loaded index decodes byte-stable (no prior). Note the * behavior change from #1300: omission no longer defaults to `"us"`/gates loading — it means "detect per parse." * * There is still no browser-side AUTO-detection at LOAD time (nothing here knows a locale before any text arrives); * detection happens per parse, on the actual input, in {@link LoadResult.selectPairIndexForText}. */ country?: string; /** * URL to the gazetteer-anchor lexicon JSON (`anchor-lexicon-v1.json`, #464 — the in-repo source is * `data/gazetteer/anchor-lexicon-v1.json`). Gazetteer-trained models (v4.2.0+, whose ONNX declares the * `gazetteer_features`/`gazetteer_confidence` inputs) REQUIRE this clue at inference: running them on the zero-filled * fallback is the measured train/inference mismatch that wrecks segmentation ("the zero-fill trap", * CONTRIBUTING_MODEL_WORK.mdx eval invariants). * * Defaults to `anchor-lexicon-v1.json` next to `modelURL`. A fetch miss (404 etc.) does NOT throw — older bundles * never shipped the file — but if the loaded model turns out to be gazetteer-trained the loader logs a loud * `console.error` naming the missing file and the model runs gazetteer-off (structurally valid, quality-degraded). * Pass `null` to skip the fetch entirely. */ gazetteerLexiconURL?: string | null; /** * URL to the country-surface lexicon JSON (`country-surface-lexicon-v1.json`, #1104 — the in-repo source is * `data/gazetteer/country-surface-lexicon-v1.json`). Country-channel models (v6.2.0+, whose ONNX declares the * `country_features`/`country_confidence` inputs) REQUIRE this clue at inference — same zero-fill trap as the * gazetteer. Defaults to `country-surface-lexicon-v1.json` next to `modelURL`; a fetch miss does NOT throw, but a * country-trained model with no lexicon runs country-off (loud `console.error`, structurally valid). Pass `null` to * skip. */ countryLexiconURL?: string | null; /** * URL to the street-type evidence lexicon (`street-type-lexicon-v3.json`, Option-A bundle — the in-repo source is * `data/gazetteer/street-type-lexicon-v3.json`). Bundle-trained models (6.7.0-bundle+, whose ONNX declares the * `street_type_features` input) feed this in fragmented-register parses. Defaults to a sibling of `modelURL`; a fetch * miss does NOT throw (channel runs off, loud `console.error`). Pass `null` to skip. */ streetTypeLexiconURL?: string | null; /** * URL to the locality-surface evidence lexicon (`locality-surface-lexicon-v6.json`, Option-A bundle — a data-root * artifact, ~7 MB; ships as a weights-package sibling). Same contract as {@link streetTypeLexiconURL}. */ localitySurfaceLexiconURL?: string | null; /** * Channel choreography (#464, v0.9.13 postcode fix): zero the gazetteer clue on pieces adjacent to a postcode-anchor * hit. Defaults to TRUE — it pairs with the train-time half on every gazetteer-trained bundle (v4.2.0+) and is inert * when either channel is absent. */ suppressGazetteerNearPostcode?: boolean; /** * Address-system conventions mode (#511 Tier A, v4.3.0+). Defaults to `"auto"` (read the model's locale head when * exported; inert on bundles without `locale_logits`). Pass a `SystemCode` to pin, or `null` to disable. */ addressSystemConventions?: NeuralAddressClassifierConfig["addressSystemConventions"] | null; /** * Span bridge (v4.4.0 declared behavior): merge same-tag spans split at intra-token punctuation ("P.O. Box"). * Defaults to TRUE per the v4.4.0 ship config (model-card.json: po_box 60.4 without, 89.1 with). Pass false to * disable for pre-bridge bundles where gate parity matters. */ bridgePunctuationGaps?: boolean; /** * Optional fetch override. Defaults to `globalThis.fetch`. */ fetchImpl?: typeof fetch; } /** * Reduce {@link LoadFromURLsOptions.country} to the bare country code the pair-index gate compares. A full locale * ("en-gb") yields its country subtag ("gb") — the node classifier's exact `localeCountry` derivation — and a bare code * ("gb") passes through unchanged (a browser-side widening: the node path only ever receives locales). Omitted = * `"en-us"` → `"us"`, the node default. */ export declare function resolvePairGateCountry(country: string | undefined): string; /** * Detect the placetype-pair country subtag for one input from its STRUCTURAL shape (#1278 phase 2). Runs the two * browser-safe Stage-2 modules the runtime pipeline uses — `@mailwoman/query-shape`'s `computeQueryShape` then * `@mailwoman/locale-gate`'s `detectLocaleSync` — and reduces the resulting `LocaleHint.locale` (e.g. "en-GB") to its * country subtag ("gb") via {@link resolvePairGateCountry}. * * The detection is bitter-lesson-safe by construction: locale-gate keys ONLY off universal cues (postcode format, * script class), never place-name dictionaries. So "10 Downing St, London SW1A 2AA" detects `gb` (UK postcode), but a * bare "Shoreditch London" — no postcode, Latin script — falls through to locale-gate's `en-US` fallback → `us`. The * pair prior is a soft, additive channel, so a conservative miss (no bias) is the safe failure mode. */ export declare function detectPairIndexCountry(text: string): string; /** * Select the placetype-pair prior for one parse (#1278 phase 2). Derives a country subtag — from an explicit * `opts.country` override when given, else {@link detectPairIndexCountry} over `text` — and returns the loaded index * whose header country matches, wrapped as a `placetypePair` option (`{ index }` alone: probe chain defaults to "auto", * `delta`/`transitionBeta` ride the resolver's header getters, exactly the node construction). No matching index → * `undefined` (the caller spreads `placetypePair: undefined` → byte-stable no-prior decode, or fall-through to a config * default). See {@link LoadResult.selectPairIndexForText} for the bound convenience + call-site example. */ export declare function resolvePairIndexForText(pairIndexes: readonly LoadedPairIndex[], text: string, opts?: { country?: string; }): PlacetypePairPriorOpts | undefined; /** * Default location of the gazetteer-anchor lexicon: `anchor-lexicon-v1.json` as a sibling of the model file. Matches * how release bundles lay out their version directory (model.onnx, tokenizer.model, model-card.json, postcode-*.bin, * anchor-lexicon-v1.json side by side). */ export declare function defaultGazetteerLexiconURL(modelURL: string): string; /** * Default location of the country-surface lexicon (#1104): `country-surface-lexicon-v1.json` as a sibling of the model * file — the release bundle lays it out beside anchor-lexicon-v1.json. */ export declare function defaultCountryLexiconURL(modelURL: string): string; /** * Convenience factory: fetch model + tokenizer, build the runner, return a classifier. The tokenizer is loaded via the * existing `loadFromBase64` path so this file shares zero Node-only code with `@mailwoman/neural/classifier`'s * `loadFromWeights`. * * The classifier is constructed with the v4.4.0 ship config by default (gazetteer lexicon + postcode anchor when their * assets resolve, `suppressGazetteerNearPostcode: true`, `addressSystemConventions: "auto"`, `bridgePunctuationGaps: * true`) — every knob is inert on bundles that predate the corresponding channel, so older versions keep decoding * unchanged. */ export declare function loadNeuralClassifierFromURLs(opts: LoadFromURLsOptions): Promise; //# sourceMappingURL=web-loader.d.ts.map