{"version":3,"file":"manifest.cjs","names":[],"sources":["../../src/tabular/manifest.ts"],"sourcesContent":["/**\n * Reading an edge package published by `tempest-fastapi-sdk`.\n *\n * `edge_pipeline` writes a directory — the graph, a gzipped copy, a drift\n * baseline and a `manifest.json` — and the browser is one of its two\n * intended consumers. The manifest is what turns a `.onnx` URL into\n * something a UI can use: the **column order** the model was trained on,\n * the class names behind `probabilities[2]`, and a version to compare\n * against what is already cached.\n *\n * That column order is the field worth loading a manifest for. A model fed\n * the right features in the wrong order answers confidently and wrongly,\n * and no runtime check catches it.\n *\n * The contract is pinned by `schema_version`. Unknown fields are ignored\n * rather than rejected, so a package written by a newer SDK still loads.\n */\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { CompactPredictor } from \"./compact\";\nimport { ModelFetchError } from \"./exceptions\";\nimport { TabularPredictor } from \"./predictor\";\nimport type { FeatureRow, TabularPrediction, TabularPredictorOptions } from \"./types\";\n\n/** Manifest schema version this reader was written against. */\nexport const SUPPORTED_MANIFEST_SCHEMA = 1;\n\n/** Fixed filename inside a package directory. */\nexport const MANIFEST_FILENAME = \"manifest.json\";\n\n/** The graph file and how to check you got it whole. */\nexport interface ManifestModelFile {\n    readonly file: string;\n    readonly sha256: string;\n    readonly bytes: number;\n    readonly gzip_file: string | null;\n    readonly gzip_bytes: number | null;\n    readonly opset: number;\n    readonly dtype: string;\n}\n\n/** What the graph expects per row. */\nexport interface ManifestInput {\n    readonly name: string;\n    readonly features: number;\n    /** Column order used at training time. */\n    readonly feature_names: readonly string[];\n}\n\n/** What the graph answers. */\nexport interface ManifestOutput {\n    readonly is_classifier: boolean;\n    readonly label_output: string;\n    readonly probability_output: string | null;\n    /** Class labels in score-column order. */\n    readonly classes: readonly string[];\n}\n\n/**\n * Where the packaged model came from, when it came from an existing\n * artifact.\n *\n * Present when the package was built with `edge_pipeline_from_pickle`: the\n * `.pkl` never reaches the browser (a pickle is a Python program, not\n * data), but its name and digest travel in the manifest, so a model\n * answering in a tab can be traced back to the file that produced it.\n */\nexport interface ManifestSource {\n    readonly file: string;\n    readonly kind: string;\n    readonly sha256: string;\n    readonly bytes: number;\n    readonly sklearn_version: string;\n    readonly warnings: readonly string[];\n}\n\n/**\n * One file in the package a runtime can load.\n *\n * A package may carry the same model twice — as ONNX, which any runtime\n * reads at the cost of a 25.6 MB WebAssembly download, and as the compact\n * format, which needs no runtime. The list is what lets the browser pick\n * by what it already ships.\n */\nexport interface ManifestRuntime {\n    readonly kind: \"onnx\" | \"compact\" | string;\n    readonly file: string;\n    readonly bytes: number;\n    readonly gzip_file: string | null;\n    readonly gzip_bytes: number | null;\n    readonly sha256: string;\n}\n\n/** The package manifest, as written by `edge_pipeline`. */\nexport interface EdgeManifest {\n    readonly schema_version: number;\n    readonly name: string;\n    readonly version: string;\n    readonly created_at: string;\n    readonly sdk_version: string;\n    readonly estimator: string;\n    readonly model: ManifestModelFile;\n    readonly input: ManifestInput;\n    readonly output: ManifestOutput;\n    readonly verified: boolean | null;\n    /** Every file a runtime can load. Absent on packages written before v0.194. */\n    readonly runtimes?: readonly ManifestRuntime[];\n    /** Absent on packages built straight from a fitted estimator. */\n    readonly source?: ManifestSource;\n    readonly baseline_file: string | null;\n    readonly baseline_samples: number;\n}\n\n/** Which reader served the package. */\nexport type TabularRuntime = \"onnx\" | \"compact\";\n\n/** A package loaded and ready to answer. */\nexport interface LoadedEdgePackage {\n    /** What was published. */\n    readonly manifest: EdgeManifest;\n    /** The running model, whichever runtime read it. */\n    readonly predictor: PredictorLike;\n    /** Which reader was used. */\n    readonly runtime: TabularRuntime;\n    /** Column order the rows must follow. */\n    readonly featureNames: readonly string[];\n    /** Class names behind each probability column. */\n    readonly classes: readonly string[];\n    /**\n     * Map a prediction's scores onto class names.\n     *\n     * @param probabilities One row of scores.\n     * @returns Name/score pairs, highest first.\n     */\n    readonly explain: (probabilities: readonly number[]) => { name: string; score: number }[];\n}\n\n/**\n * The shape both readers share.\n *\n * `TabularPredictor` (ONNX) and `CompactPredictor` (runtime-free) answer\n * with the same object, so an app can switch routes without touching a\n * line of its own code.\n */\nexport interface PredictorLike {\n    predict(rows: readonly FeatureRow[]): Promise<TabularPrediction>;\n    dispose(): Promise<void>;\n}\n\n/** Options for {@link loadEdgePackage}. */\nexport interface LoadEdgePackageOptions extends TabularPredictorOptions {\n    /** Cache the model bytes for offline use. `true` by default. */\n    readonly cache?: boolean | ModelCacheOptions;\n    /**\n     * Which reader to use.\n     *\n     * `\"auto\"` (the default) takes the compact form when the package has\n     * one, because it answers without downloading a WebAssembly runtime.\n     * Force `\"onnx\"` when the app already ships ONNX for something else —\n     * then the runtime is already paid for and ONNX covers more estimators.\n     */\n    readonly runtime?: TabularRuntime | \"auto\";\n}\n\n/**\n * Read a package's manifest.\n *\n * Cheap: it is a few hundred bytes, so an app can check for a new version\n * without downloading a model it may already have.\n *\n * @example\n * ```ts\n * const manifest = await fetchEdgeManifest(\"/models/risk/\");\n * if (manifest.version !== localStorage.getItem(\"risk-version\")) {\n *     // a new model was published\n * }\n * ```\n *\n * @param directoryUrl URL of the package directory, with or without a\n *   trailing slash. A full URL to the manifest file also works.\n * @param requestInit `fetch` options.\n * @returns The parsed manifest.\n * @throws {@link ModelFetchError} when the manifest cannot be read, or when\n *   its `schema_version` is newer than this reader understands — loading it\n *   anyway would risk misreading the field that defines column order.\n */\nexport async function fetchEdgeManifest(\n    directoryUrl: string,\n    requestInit?: RequestInit,\n): Promise<EdgeManifest> {\n    const url = manifestUrl(directoryUrl);\n    let response: Response;\n    try {\n        response = await fetch(url, requestInit);\n    } catch (error) {\n        throw new ModelFetchError(`Could not read the manifest at ${url}`, {\n            cause: error,\n        });\n    }\n    if (!response.ok) {\n        throw new ModelFetchError(\n            `Could not read the manifest at ${url}: ${response.status} ${response.statusText}`,\n        );\n    }\n\n    const manifest = (await response.json()) as EdgeManifest;\n    if (typeof manifest?.schema_version !== \"number\") {\n        throw new ModelFetchError(`${url} is not an edge package manifest (no schema_version).`);\n    }\n    if (manifest.schema_version > SUPPORTED_MANIFEST_SCHEMA) {\n        throw new ModelFetchError(\n            `The manifest at ${url} uses schema_version ${manifest.schema_version}, ` +\n                `and this SDK understands ${SUPPORTED_MANIFEST_SCHEMA}. Upgrade ` +\n                \"tempest-react-sdk before serving this package.\",\n        );\n    }\n    return manifest;\n}\n\n/**\n * Load a whole edge package: manifest, model, and the names to read it by.\n *\n * @example\n * ```ts\n * const pkg = await loadEdgePackage(\"/models/risk/\");\n *\n * console.log(pkg.featureNames); // [\"age\", \"income\", \"tenure\", \"score\", \"visits\"]\n *\n * const { probabilities } = await pkg.predictor.predict([[41, 5200, 3, 0.82, 12]]);\n * console.log(pkg.explain(probabilities[0]!)); // [{ name: \"approved\", score: 0.91 }, ...]\n * ```\n *\n * @param directoryUrl URL of the package directory.\n * @param options Predictor options plus caching.\n * @returns The loaded package.\n * @throws {@link ModelFetchError} when the manifest or model cannot be read.\n */\nexport async function loadEdgePackage(\n    directoryUrl: string,\n    options: LoadEdgePackageOptions = {},\n): Promise<LoadedEdgePackage> {\n    const manifest = await fetchEdgeManifest(directoryUrl);\n    const base = directoryUrl.endsWith(\"/\") ? directoryUrl : `${directoryUrl}/`;\n    const runtime = chooseRuntime(manifest, options.runtime ?? \"auto\");\n    const file = fileFor(manifest, runtime);\n    const modelUrl = `${base}${file}`;\n\n    const cache = options.cache ?? true;\n    const source =\n        cache === false\n            ? modelUrl\n            : await fetchModelBytes(modelUrl, typeof cache === \"object\" ? cache : {});\n\n    const predictor: PredictorLike =\n        runtime === \"compact\"\n            ? await CompactPredictor.create(source)\n            : await TabularPredictor.create(source, {\n                  providers: options.providers,\n                  warmup: options.warmup,\n                  sessionOptions: options.sessionOptions,\n              });\n\n    const classes = manifest.output.classes;\n    return {\n        manifest,\n        predictor,\n        runtime,\n        featureNames: manifest.input.feature_names,\n        classes,\n        explain: (probabilities: readonly number[]) =>\n            probabilities\n                .map((score, index) => ({\n                    name: classes[index] ?? String(index),\n                    score,\n                }))\n                .sort((a, b) => b.score - a.score),\n    };\n}\n\n/**\n * Decide which reader serves this package.\n *\n * @param manifest The package manifest.\n * @param requested What the caller asked for.\n * @returns The runtime to use.\n * @throws {@link ModelFetchError} when the package does not carry the\n *   requested form — asking for a compact model that was never written\n *   should say so, not silently download 25 MB of WebAssembly instead.\n */\nfunction chooseRuntime(manifest: EdgeManifest, requested: TabularRuntime | \"auto\"): TabularRuntime {\n    const available = manifest.runtimes ?? [];\n    const hasCompact = available.some((entry) => entry.kind === \"compact\");\n\n    if (requested === \"auto\") return hasCompact ? \"compact\" : \"onnx\";\n    if (requested === \"compact\" && !hasCompact) {\n        throw new ModelFetchError(\n            `This package carries no compact model (${manifest.name} lists ` +\n                `${available.map((entry) => entry.kind).join(\", \") || \"onnx\"}). ` +\n                \"Re-export it with edge_pipeline(compact=True), or load it as ONNX.\",\n        );\n    }\n    return requested;\n}\n\n/**\n * Find the file a runtime reads.\n *\n * @param manifest The package manifest.\n * @param runtime The chosen runtime.\n * @returns The filename inside the package directory.\n */\nfunction fileFor(manifest: EdgeManifest, runtime: TabularRuntime): string {\n    const entry = (manifest.runtimes ?? []).find((item) => item.kind === runtime);\n    return entry?.file ?? manifest.model.file;\n}\n\n/**\n * Resolve a directory URL to its manifest file.\n *\n * @param directoryUrl The package directory, or the manifest itself.\n * @returns The manifest URL.\n */\nfunction manifestUrl(directoryUrl: string): string {\n    if (directoryUrl.endsWith(\".json\")) return directoryUrl;\n    return directoryUrl.endsWith(\"/\")\n        ? `${directoryUrl}${MANIFEST_FILENAME}`\n        : `${directoryUrl}/${MANIFEST_FILENAME}`;\n}\n"],"mappings":"qHA4BA,IAAa,EAAoB,gBA8JjC,eAAsB,EAClB,EACA,EACqB,CACrB,IAAM,EAAM,EAAY,CAAY,EAChC,EACJ,GAAI,CACA,EAAW,MAAM,MAAM,EAAK,CAAW,CAC3C,OAAS,EAAO,CACZ,MAAM,IAAI,EAAA,gBAAgB,kCAAkC,IAAO,CAC/D,MAAO,CACX,CAAC,CACL,CACA,GAAI,CAAC,EAAS,GACV,MAAM,IAAI,EAAA,gBACN,kCAAkC,EAAI,IAAI,EAAS,OAAO,GAAG,EAAS,YAC1E,EAGJ,IAAM,EAAY,MAAM,EAAS,KAAK,EACtC,GAAI,OAAO,GAAU,gBAAmB,SACpC,MAAM,IAAI,EAAA,gBAAgB,GAAG,EAAI,sDAAsD,EAE3F,GAAI,EAAS,eAAA,EACT,MAAM,IAAI,EAAA,gBACN,mBAAmB,EAAI,uBAAuB,EAAS,eAAe,qFAG1E,EAEJ,OAAO,CACX,CAoBA,eAAsB,EAClB,EACA,EAAkC,CAAC,EACT,CAC1B,IAAM,EAAW,MAAM,EAAkB,CAAY,EAC/C,EAAO,EAAa,SAAS,GAAG,EAAI,EAAe,GAAG,EAAa,GACnE,EAAU,EAAc,EAAU,EAAQ,SAAW,MAAM,EAE3D,EAAW,GAAG,IADP,EAAQ,EAAU,CACJ,IAErB,EAAQ,EAAQ,OAAS,GACzB,EACF,IAAU,GACJ,EACA,MAAM,EAAA,gBAAgB,EAAU,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAC,EAE1E,EACF,IAAY,UACN,MAAM,EAAA,iBAAiB,OAAO,CAAM,EACpC,MAAM,EAAA,iBAAiB,OAAO,EAAQ,CAClC,UAAW,EAAQ,UACnB,OAAQ,EAAQ,OAChB,eAAgB,EAAQ,cAC5B,CAAC,EAEL,EAAU,EAAS,OAAO,QAChC,MAAO,CACH,WACA,YACA,UACA,aAAc,EAAS,MAAM,cAC7B,UACA,QAAU,GACN,EACK,KAAK,EAAO,KAAW,CACpB,KAAM,EAAQ,IAAU,OAAO,CAAK,EACpC,OACJ,EAAE,CAAC,CACF,MAAM,EAAG,IAAM,EAAE,MAAQ,EAAE,KAAK,CAC7C,CACJ,CAYA,SAAS,EAAc,EAAwB,EAAoD,CAC/F,IAAM,EAAY,EAAS,UAAY,CAAC,EAClC,EAAa,EAAU,KAAM,GAAU,EAAM,OAAS,SAAS,EAErE,GAAI,IAAc,OAAQ,OAAO,EAAa,UAAY,OAC1D,GAAI,IAAc,WAAa,CAAC,EAC5B,MAAM,IAAI,EAAA,gBACN,0CAA0C,EAAS,KAAK,SACjD,EAAU,IAAK,GAAU,EAAM,IAAI,CAAC,CAAC,KAAK,IAAI,GAAK,OAAO,sEAErE,EAEJ,OAAO,CACX,CASA,SAAS,EAAQ,EAAwB,EAAiC,CAEtE,OADe,EAAS,UAAY,CAAC,EAAA,CAAG,KAAM,GAAS,EAAK,OAAS,CAC9D,CAAA,EAAO,MAAQ,EAAS,MAAM,IACzC,CAQA,SAAS,EAAY,EAA8B,CAE/C,OADI,EAAa,SAAS,OAAO,EAAU,EACpC,EAAa,SAAS,GAAG,EAC1B,GAAG,IAAe,IAClB,GAAG,EAAa,GAAG,GAC7B"}