/* Static-deployment shim. * * Hugging Face static Spaces serve files, not Python, so the page has no * backend. This intercepts the app's own API routes and answers them in the * browser: NAB is fetched straight from the upstream GitHub raw endpoint * (which sends `access-control-allow-origin: *`) and every detector and metric * is recomputed locally by web/outliar.js — the same code the Python↔Node * parity test pins to the NumPy implementation. * * Two detectors do not survive the trip and the page says so rather than * quietly serving different numbers: `iforest` needs scikit-learn's fitted * trees, and `random` uses NumPy's PCG64, which a JS PRNG cannot reproduce * bit-for-bit (it is still uniform noise, which is the entire point of it). */ (function () { "use strict"; const NAB = "https://raw.githubusercontent.com/numenta/NAB/master"; const REAL_CORPORA = new Set([ "realAWSCloudwatch", "realAdExchange", "realKnownCause", "realTraffic", "realTweets", ]); const N_GRID = 120; const cache = { labels: null, series: new Map(), scores: new Map(), sweeps: new Map() }; const SAMPLES = [ { series: "realKnownCause/nyc_taxi.csv", detector: "random", note: "Uniform noise on NYC taxi demand, at its own point-adjusted optimum.", }, { series: "realKnownCause/machine_temperature_system_failure.csv", detector: "window_pca", note: "A real detector on a real machine failure — the honest case.", }, { series: "realAWSCloudwatch/ec2_cpu_utilization_5f5533.csv", detector: "random", note: "Noise again, on EC2 CPU utilisation.", }, { series: "realTraffic/speed_7578.csv", detector: "seasonal", note: "A seasonal-profile detector on highway speeds.", }, ]; async function labels() { if (!cache.labels) { const response = await fetch(`${NAB}/labels/combined_windows.json`); if (!response.ok) throw new Error(`could not reach NAB (${response.status})`); cache.labels = await response.json(); } return cache.labels; } async function series(key) { if (cache.series.has(key)) return cache.series.get(key); const [all, response] = await Promise.all([ labels(), fetch(`${NAB}/data/${key}`), ]); if (!response.ok) throw new Error(`could not load ${key} (${response.status})`); const parsed = outliar.parseCsv(await response.text()); const windows = outliar.windowsToIndices(parsed.timestamps, all[key] || []); const value = { key, name: key.split("/")[1].replace(/\.csv$/, ""), corpus: key.split("/")[0], timestamps: parsed.timestamps, values: parsed.values, windows, n: parsed.values.length, samplingMinutes: outliar.samplingMinutes(parsed.timestamps), }; cache.series.set(key, value); return value; } function scores(s, detector) { const id = `${s.key}::${detector}`; if (!cache.scores.has(id)) { cache.scores.set(id, outliar.DETECTORS[detector](s, 7)); } return cache.scores.get(id); } async function evaluate(key, detector, threshold) { const s = await series(key); const raw = scores(s, detector); const probation = outliar.probationLength(s.n); const scored = Array.from(raw.slice(probation)); const yTrue = outliar.pointLabels(s.n, s.windows).slice(probation); const sweepId = `${key}::${detector}`; if (!cache.sweeps.has(sweepId)) { cache.sweeps.set(sweepId, outliar.sweep(yTrue, scored, N_GRID)); } const best = cache.sweeps.get(sweepId); const paOptimal = best.pa[0]; if (threshold === null || threshold === undefined) threshold = paOptimal; const yPred = scored.map((v) => v >= threshold); const protocols = outliar.allProtocols(yTrue, yPred); const [caught, missed] = outliar.eventRecall(yTrue, yPred); let alarms = 0, inside = 0; for (let i = 0; i < yPred.length; i++) { if (yPred[i]) { alarms++; if (yTrue[i]) inside++; } } const credited = outliar.adjustPredictions(yTrue, yPred).reduce((a, b) => a + (b ? 1 : 0), 0); const step = Math.max(1, Math.floor(s.n / 2400)); const timestamps = [], values = [], drawn = []; for (let i = 0; i < s.n; i += step) { timestamps.push(s.timestamps[i]); values.push(s.values[i]); drawn.push(raw[i]); } const finite = scored.filter(Number.isFinite); return { series: key, name: s.name, detector, is_control: outliar.CONTROLS.has(detector), description: outliar.DESCRIPTIONS[detector], threshold, pa_optimal_threshold: paOptimal, composite_optimal_threshold: best.composite[0], score_range: [Math.min(...finite), outliar.quantile(finite, 0.999)], probation, n: s.n, timestamps, values, scores: drawn, windows: s.windows, metrics: protocols, alarms, alarms_inside: inside, pa_credited: credited, windows_caught: caught, windows_total: caught + missed, false_alarms_per_day: outliar.falseAlarmsPerDay(yTrue, yPred, s.samplingMinutes), }; } async function catalog() { const all = await labels(); const keys = Object.keys(all).filter((k) => REAL_CORPORA.has(k.split("/")[0])).sort(); return { series: keys.map((k) => ({ key: k, name: k.split("/")[1].replace(/\.csv$/, ""), corpus: k.split("/")[0], windows: all[k].length, })), detectors: Object.keys(outliar.DETECTORS).map((name) => ({ name, description: outliar.DESCRIPTIONS[name], is_control: outliar.CONTROLS.has(name), })), n_samples: SAMPLES.length, }; } function json(payload) { return new Response(JSON.stringify(payload), { status: 200, headers: { "content-type": "application/json" }, }); } const original = window.fetch.bind(window); window.fetch = async function (input, init) { const url = typeof input === "string" ? input : input.url; // Only the app's own routes are intercepted; NAB requests pass through. if (!/^\/(api\/|sample|healthz)/.test(url)) return original(input, init); const parsed = new URL(url, location.origin); const path = parsed.pathname; const query = parsed.searchParams; try { if (path === "/api/catalog") return json(await catalog()); if (path === "/healthz") return json({ status: "ok", static: true }); if (path === "/api/findings") { const response = await original("findings.json"); if (!response.ok) throw new Error("findings.json missing"); return json(await response.json()); } if (path === "/api/evaluate") { const threshold = query.has("threshold") ? parseFloat(query.get("threshold")) : null; return json(await evaluate(query.get("series"), query.get("detector"), threshold)); } if (path === "/sample") { const choice = SAMPLES[(parseInt(query.get("index") || "0", 10) || 0) % SAMPLES.length]; const payload = await evaluate(choice.series, choice.detector, null); payload.note = choice.note; return json(payload); } } catch (err) { return new Response(String(err && err.message ? err.message : err), { status: 502 }); } return new Response("not found", { status: 404 }); }; })();