Spaces:
Running
Running
Download js/util.js from Chennzi/agentic-forecasting: direct link, hf CLI and curl.
- Browser
- Download file 8.54 kB
-
https://huggingface.co/spaces/Chennzi/agentic-forecasting/resolve/main/js/util.js
- Command line
-
hf download hf://spaces/Chennzi/agentic-forecasting/js/util.js
-
curl -L -o util.js https://huggingface.co/spaces/Chennzi/agentic-forecasting/resolve/main/js/util.js
8.54 kB
| // DOM, formatting and color helpers, and components several pages share. | |
| // Data strings only ever enter the DOM as text nodes. | |
| export function h(tag, attrs = {}, ...children) { | |
| const el = document.createElement(tag); | |
| for (const [key, value] of Object.entries(attrs || {})) { | |
| if (value == null || value === false) continue; | |
| if (key === "class") el.className = value; | |
| else if (key === "style" && typeof value === "object") Object.assign(el.style, value); | |
| else if (key.startsWith("on")) el.addEventListener(key.slice(2), value); | |
| else el.setAttribute(key, value === true ? "" : value); | |
| } | |
| for (const child of children.flat(Infinity)) { | |
| if (child == null || child === false) continue; | |
| el.append(child instanceof Node ? child : document.createTextNode(String(child))); | |
| } | |
| return el; | |
| } | |
| // Chart renderers re-run on resize and theme changes; pages register them here. | |
| export const redraws = []; | |
| export const parseDay = d3.utcParse("%Y-%m-%d"); | |
| export const fmt = { | |
| int: d3.format(",.0f"), | |
| ratio: (v) => (v == null ? "β" : d3.format(".3f")(v)), | |
| pct: (v) => (v == null ? "β" : d3.format(".0%")(v)), | |
| // whole numbers for large targets (MW), two decimals for small ones (river stage, AQHI) | |
| value: (v) => (v == null ? "β" : d3.format(Math.abs(v) < 100 ? ",.2f" : ",.0f")(v)), | |
| compact: (v) => (v == null ? "β" : d3.format(".3~s")(v).replace("G", "B")), | |
| bytes: (v) => (v == null ? "β" : `${d3.format(".2~s")(v)}B`), | |
| usd: (v) => (v == null ? "β" : `$${d3.format(".2f")(v)}`), | |
| day: d3.utcFormat("%b %-d"), | |
| dow: d3.utcFormat("%a %b %-d"), | |
| long: d3.utcFormat("%a, %b %-d, %Y"), | |
| }; | |
| export const dayLabel = (iso, style = "day") => (iso ? fmt[style](parseDay(iso)) : "β"); | |
| export function timeIn(iso, timeZone, withDate = false) { | |
| if (!iso) return "β"; | |
| const options = { timeZone, hour: "2-digit", minute: "2-digit", hour12: false, timeZoneName: "short" }; | |
| if (withDate) Object.assign(options, { weekday: "short", month: "short", day: "numeric" }); | |
| return new Intl.DateTimeFormat("en-US", options).format(new Date(iso)); | |
| } | |
| export function duration(seconds) { | |
| if (seconds == null) return "β"; | |
| const m = Math.round(seconds / 60); | |
| return m < 60 ? `${m} min` : `${Math.floor(m / 60)} h ${m % 60} min`; | |
| } | |
| export function host(url) { | |
| try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return null; } | |
| } | |
| // Color follows the system, never its rank: fixed slots for known systems, stable spill-over | |
| // slots for new ones, and recessive gray for the seasonal-naive reference. | |
| const SLOTS = { | |
| "codex-cli-gpt-6.1-sol": 1, | |
| "claude-code-claude-sonnet-5": 2, | |
| "chronos-2": 3, | |
| "chronos-2-univariate": 4, | |
| "timesfm-3": 5, | |
| "chronos-2-expert": 6, | |
| "timesfm-3-expert": 8, | |
| }; | |
| const SPARE = [6, 8]; | |
| let systemIndex = new Map(); | |
| export function setSystems(systems) { | |
| systemIndex = new Map(systems.map((s) => [s.id, s])); | |
| let spare = 0; | |
| for (const s of systems) { | |
| if (s.id in SLOTS || s.kind === "reference" || s.kind === "expert") continue; | |
| // a continual agent takes its fresh twin's slot: the same agent, carrying on | |
| const twin = s.id.replace(/-continual$/, ""); | |
| SLOTS[s.id] = twin in SLOTS ? SLOTS[twin] : SPARE[spare++ % SPARE.length]; | |
| } | |
| } | |
| export const system = (id) => systemIndex.get(id) || { id, kind: "agent", label: id, description: "" }; | |
| // every task's expert forecast shares one slot; a task page shows only its own | |
| export const color = (id) => { | |
| const kind = system(id).kind; | |
| if (kind === "reference") return "var(--muted)"; | |
| return `var(--series-${kind === "expert" ? 7 : SLOTS[id] || 8})`; | |
| }; | |
| export const KINDS = ["agent", "expert", "tsfm", "reference"]; | |
| export const KIND_GROUP = { agent: "Agents", expert: "Expert forecast", tsfm: "TSFM baselines", reference: "Reference" }; | |
| export const KIND_LABEL = { agent: "Agent", expert: "Expert", tsfm: "TSFM", reference: "Reference" }; | |
| export const bySystem = (a, b) => | |
| KINDS.indexOf(system(a).kind) - KINDS.indexOf(system(b).kind) || system(a).label.localeCompare(system(b).label); | |
| export const keyLine = (id) => h("span", { class: "key-line", style: { background: color(id) } }); | |
| export const systemCell = (id) => h("span", { class: "system-cell", title: system(id).description }, keyLine(id), system(id).label); | |
| export function segmented(options, current, onChange, label) { | |
| return h("div", { class: "segmented", role: "group", "aria-label": label }, | |
| options.map(([value, text]) => | |
| h("button", { type: "button", "aria-pressed": String(value === current), onclick: () => onChange(value) }, text))); | |
| } | |
| export function sectionHead(id, title, text, ...extra) { | |
| return h("div", { class: "section-head", id }, h("div", {}, h("h2", {}, title), text ? h("p", {}, text) : null), ...extra); | |
| } | |
| export const empty = (text) => h("div", { class: "empty" }, text); | |
| /** Text whose $β¦$ spans are inline TeX, e.g. md("frozen as $X_d$"); for h() children. */ | |
| export const md = (text) => text.split(/\$([^$]+)\$/).map((part, i) => (i % 2 ? tex(part) : part)); | |
| /** TeX typeset by KaTeX when it loaded, else the source in a code span. */ | |
| export function tex(source, display = false) { | |
| const el = h(display ? "div" : "span", { class: display ? "tex-display" : "tex" }); | |
| if (window.katex) window.katex.render(source, el, { displayMode: display, throwOnError: false, output: "html" }); | |
| else el.append(h("code", {}, source)); | |
| return el; | |
| } | |
| export function more(label, ...body) { | |
| return h("details", { class: "more" }, h("summary", {}, label), h("div", { class: "body" }, ...body)); | |
| } | |
| export function sourceLink(source) { | |
| return source?.url | |
| ? h("a", { href: source.url, target: "_blank", rel: "noopener" }, source.name || host(source.url)) | |
| : source?.name || "β"; | |
| } | |
| export function scoreBar(value, id, max) { | |
| if (value == null) return h("span", { class: "muted" }, "β"); | |
| return h("div", { class: "bar-cell" }, | |
| h("div", { class: "bar-track" }, | |
| h("div", { class: "bar-fill", style: { width: `${(Math.min(value, max) / max) * 100}%`, background: color(id) } }), | |
| max >= 1 ? h("div", { class: "bar-tick", style: { left: `${(1 / max) * 100}%` }, title: "seasonal naive = 1" }) : null), | |
| h("span", { class: "bar-value" }, fmt.ratio(value))); | |
| } | |
| function coverage(value) { | |
| if (value == null) return h("span", { class: "muted small" }, "point"); | |
| return h("span", { class: "cov", title: "share of actual values inside the 10β90% interval; 80% is calibrated" }, | |
| h("span", { class: "cov-track" }, | |
| h("span", { class: "bar-tick", style: { left: "80%" } }), | |
| h("span", { class: "cov-dot", style: { left: `${value * 100}%` } })), | |
| fmt.pct(value)); | |
| } | |
| /** Leaderboard rows grouped by kind: agents, expert forecast, TSFM baselines, reference. */ | |
| export function leaderboard(rows, unit) { | |
| if (!rows.length) return empty("No forecast has been scored yet."); | |
| const max = Math.max(1.05, d3.max(rows, (r) => r.scaled_crps) || 1); | |
| const groups = d3.group([...rows].sort((a, b) => (a.scaled_crps ?? 9) - (b.scaled_crps ?? 9)), (r) => system(r.system).kind); | |
| return h("div", { class: "table-wrap" }, h("table", {}, | |
| h("thead", {}, h("tr", {}, | |
| h("th", {}, "System"), h("th", {}, "Scaled CRPS β"), h("th", { class: "num" }, "Scaled MAE"), | |
| h("th", {}, "80% coverage"), h("th", { class: "num" }, `CRPS (${unit})`), h("th", { class: "num" }, "Pairs"))), | |
| h("tbody", {}, KINDS.filter((k) => groups.has(k)).map((kind) => [ | |
| h("tr", { class: "group" }, h("td", { colspan: 6 }, KIND_GROUP[kind])), | |
| groups.get(kind).map((r) => h("tr", {}, | |
| h("td", {}, systemCell(r.system)), | |
| h("td", {}, scoreBar(r.scaled_crps, r.system, max)), | |
| h("td", { class: "num" }, fmt.ratio(r.scaled_mae)), | |
| h("td", {}, coverage(r.coverage80)), | |
| h("td", { class: "num" }, fmt.value(r.crps)), | |
| h("td", { class: "num" }, String(r.n)))), | |
| ])))); | |
| } | |
| const tooltip = () => document.getElementById("tooltip"); | |
| export function showTooltip(event, content) { | |
| const el = tooltip(); | |
| el.replaceChildren(content); | |
| el.hidden = false; | |
| const { innerWidth, innerHeight } = window; | |
| const rect = el.getBoundingClientRect(); | |
| let x = event.clientX + 16, y = event.clientY + 16; | |
| if (x + rect.width > innerWidth - 8) x = event.clientX - rect.width - 16; | |
| if (y + rect.height > innerHeight - 8) y = event.clientY - rect.height - 16; | |
| el.style.left = `${Math.max(8, x)}px`; | |
| el.style.top = `${Math.max(8, y)}px`; | |
| } | |
| export const hideTooltip = () => { tooltip().hidden = true; }; | |