--- title: Decisions --- ```js const API = "http://127.0.0.1:8000"; const POLL = 15_000; ``` ```js // Fetch decisions + topics (for domain context) in parallel const decState = (async function*() { while (true) { let data = [], ok = false; try { const [rd, rt] = await Promise.all([ fetch(`${API}/decisions/?limit=500`), fetch(`${API}/topics/`), ]); ok = rd.ok && rt.ok; if (ok) { const [decisions, topics] = await Promise.all([rd.json(), rt.json()]); const topicMap = Object.fromEntries(topics.map(t => [t.id, t])); data = decisions .map(d => ({ ...d, domain: topicMap[d.topic_id]?.domain ?? null, topic_title: topicMap[d.topic_id]?.title ?? null, })) .sort((a, b) => { // escalated first, then open, then resolved/superseded; within group by deadline asc const rank = {escalated: 0, open: 1, resolved: 2, superseded: 3}; const dr = (rank[a.status] ?? 9) - (rank[b.status] ?? 9); if (dr !== 0) return dr; if (a.deadline && b.deadline) return a.deadline.localeCompare(b.deadline); return a.deadline ? -1 : b.deadline ? 1 : 0; }); } } catch {} yield {data, ok, ts: new Date()}; await new Promise(res => setTimeout(res, POLL)); } })(); ``` ```js const data = decState.data ?? []; const _ok = decState.ok ?? false; const _ts = decState.ts; ``` # Decisions ```js display(html`
`); ``` ```js import {MultiSelect} from "./components/multiselect.js"; const filters = view(Inputs.form( { type: MultiSelect(["pending", "made"], {label: "Type", placeholder: "All types"}), status: MultiSelect(["open", "escalated", "resolved", "superseded"], {label: "Status", placeholder: "All statuses"}), search: Inputs.text({placeholder: "Search title…", style: "width:160px"}), }, { template: ({type, status, search}) => html``, } )); ``` ```js const filtered = data.filter(d => (filters.type.length === 0 || filters.type.includes(d.decision_type)) && (filters.status.length === 0 || filters.status.includes(d.status)) && (!filters.search || d.title.toLowerCase().includes(filters.search.toLowerCase())) ); const escalated = filtered.filter(d => d.escalation_note); const STATUS_BORDER = {open: "steelblue", escalated: "#f59e0b", resolved: "#22c55e", superseded: "#aaa"}; const TYPE_CLASS = {pending: "badge-pending", made: "badge-made"}; function fmtDate(iso) { return iso ? new Date(iso).toLocaleDateString(undefined, {day: "numeric", month: "short", year: "numeric"}) : null; } function isOverdue(iso) { return iso && new Date(iso) < new Date(); } if (filtered.length === 0) { display(html`No decisions match the current filter.
`); } else { display(html`No resolved decisions yet.
` : Plot.plot({ title: "Decisions resolved per month", x: {label: "Month", tickRotate: -30}, y: {label: "Count", grid: true}, marks: [ Plot.barY(byMonth, {x: "month", y: "count", fill: "steelblue", tip: true}), Plot.ruleY([0]), ], marginBottom: 60, width: 700, }) ); ```