--- title: Progress --- ```js import {POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; ``` ```js const progState = (async function*() { let failures = 0; while (true) { let data = [], tokenEvents = [], ok = false; try { const [r1, r2] = await Promise.all([ apiFetch("/progress/?limit=500"), apiFetch("/token-events/?limit=1000"), ]); ok = r1.ok; data = ok ? await r1.json() : []; tokenEvents = r2.ok ? await r2.json() : []; } catch {} failures = ok ? 0 : failures + 1; yield {data, tokenEvents, ok, ts: new Date()}; await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures})); } })(); ``` ```js const data = progState.data ?? []; const tokenEvents = progState.tokenEvents ?? []; const _ok = progState.ok ?? false; const _ts = progState.ts; ``` # Progress Log ```js import {injectTocTop} from "./components/toc-sidebar.js"; import {withDocHelp} from "./components/doc-overlay.js"; const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()} · ${data.length} events total` : html`Offline — run: make api`}
`; withDocHelp(_liveEl, "/docs/live-data"); injectTocTop("live-indicator", _liveEl); const _h1 = document.querySelector("#observablehq-main h1"); if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/progress-log"); } ``` ## Event Volume (Last 30 Days) ```js import * as Plot from "npm:@observablehq/plot"; const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const byDay = Object.entries( data .filter(e => new Date(e.created_at) >= cutoff) .reduce((acc, e) => { const d = e.created_at.slice(0, 10); acc[d] = (acc[d] ?? 0) + 1; return acc; }, {}) ).sort().map(([day, count]) => ({day, count})); const tokensByDay = Object.entries( tokenEvents .filter(e => new Date(e.created_at) >= cutoff) .reduce((acc, e) => { const d = e.created_at.slice(0, 10); acc[d] = (acc[d] ?? 0) + (e.tokens_in || 0) + (e.tokens_out || 0); return acc; }, {}) ).sort().map(([day, tokens]) => ({day, tokens})); // Scale tokens onto the events axis for dual-axis display const maxEvents = Math.max(1, ...byDay.map(d => d.count)); const maxTokens = Math.max(1, ...tokensByDay.map(d => d.tokens)); const ratio = maxTokens / maxEvents; const fmtTokens = v => { const t = v * ratio; return t >= 1e6 ? (t/1e6).toFixed(1)+"M" : t >= 1e3 ? Math.round(t/1e3)+"k" : String(Math.round(t)); }; display(byDay.length === 0 ? html`

No events in the last 30 days.

` : Plot.plot({ title: "Progress events & tokens per day (30-day window)", x: {label: "Date", tickRotate: -30}, y: {label: "← Events", grid: true}, marginBottom: 60, marginRight: tokensByDay.length > 0 ? 70 : 20, width: 750, marks: [ Plot.areaY(byDay, {x: "day", y: "count", fill: "steelblue", fillOpacity: 0.25}), Plot.lineY(byDay, {x: "day", y: "count", stroke: "steelblue", strokeWidth: 1.5}), Plot.tip(byDay, Plot.pointerX({x: "day", y: "count", title: d => `${d.day}\n${d.count} event${d.count === 1 ? "" : "s"}`})), ...(tokensByDay.length > 0 ? [ Plot.areaY(tokensByDay, {x: "day", y: d => d.tokens / ratio, fill: "#f28e2b", fillOpacity: 0.2}), Plot.lineY(tokensByDay, {x: "day", y: d => d.tokens / ratio, stroke: "#f28e2b", strokeWidth: 1.5}), Plot.tip(tokensByDay, Plot.pointerX({x: "day", y: d => d.tokens / ratio, title: d => `${d.day}\n${d.tokens.toLocaleString()} tokens`})), Plot.axisY({anchor: "right", label: "Tokens →", tickFormat: fmtTokens}), ] : []), Plot.ruleY([0]), ], }) ); if (byDay.length > 0) display(html`
Events (left axis) ${tokensByDay.length > 0 ? html` Tokens (right axis)` : html`No token data yet`}
`); ``` ## Event Log ```js const authorOpts = ["(all)", ...new Set(data.map(e => e.author ?? "unknown"))].sort(); const typeOpts = ["(all)", ...new Set(data.map(e => e.event_type))].sort(); const authorFilter = view(Inputs.select(authorOpts, {label: "Author"})); const typeFilter = view(Inputs.select(typeOpts, {label: "Event type"})); const sinceFilter = view(Inputs.date({label: "Since"})); ``` ```js const filtered = data.filter(e => (authorFilter === "(all)" || (e.author ?? "unknown") === authorFilter) && (typeFilter === "(all)" || e.event_type === typeFilter) && (!sinceFilter || new Date(e.created_at) >= sinceFilter) ); display(html`

${filtered.length} events shown (append-only, no deletions).

`); display(Inputs.table(filtered.map(e => ({ Time: new Date(e.created_at).toLocaleString(), Type: e.event_type, Author: e.author ?? "—", Summary: e.summary, })), {rows: 50})); ```