71 lines
1.9 KiB
Markdown
71 lines
1.9 KiB
Markdown
|
|
---
|
||
|
|
title: Decisions
|
||
|
|
---
|
||
|
|
|
||
|
|
# Decisions
|
||
|
|
|
||
|
|
```js
|
||
|
|
const decisions = await FileAttachment("data/decisions.json").json();
|
||
|
|
const data = Array.isArray(decisions) ? decisions : [];
|
||
|
|
const pending = data.filter(d => d.decision_type === "pending");
|
||
|
|
const made = data.filter(d => d.decision_type === "made");
|
||
|
|
```
|
||
|
|
|
||
|
|
```js
|
||
|
|
const tab = view(Inputs.select(["Pending", "Made"], { label: "View" }));
|
||
|
|
```
|
||
|
|
|
||
|
|
```js
|
||
|
|
const shown = tab === "Pending" ? pending : made;
|
||
|
|
|
||
|
|
display(Inputs.table(shown.map(d => ({
|
||
|
|
Title: d.title,
|
||
|
|
Status: d.status + (d.escalation_note ? " ⚠️" : ""),
|
||
|
|
Decided_by: d.decided_by ?? "—",
|
||
|
|
Deadline: d.deadline ? new Date(d.deadline).toLocaleDateString() : "—",
|
||
|
|
Rationale: (d.rationale ?? "").slice(0, 80),
|
||
|
|
Updated: new Date(d.updated_at).toLocaleDateString(),
|
||
|
|
})), { rows: 30 }));
|
||
|
|
```
|
||
|
|
|
||
|
|
## Resolution Velocity
|
||
|
|
|
||
|
|
```js
|
||
|
|
import * as Plot from "npm:@observablehq/plot";
|
||
|
|
|
||
|
|
const resolved = made.filter(d => d.decided_at);
|
||
|
|
const byMonth = resolved.reduce((acc, d) => {
|
||
|
|
const m = d.decided_at.slice(0, 7);
|
||
|
|
acc[m] = (acc[m] ?? 0) + 1;
|
||
|
|
return acc;
|
||
|
|
}, {});
|
||
|
|
|
||
|
|
display(Plot.plot({
|
||
|
|
title: "Decisions Resolved per Month",
|
||
|
|
x: { label: "Month", tickRotate: -30 },
|
||
|
|
y: { label: "Count", grid: true },
|
||
|
|
marks: [
|
||
|
|
Plot.barY(
|
||
|
|
Object.entries(byMonth).map(([month, count]) => ({ month, count })),
|
||
|
|
{ x: "month", y: "count", fill: "steelblue", tip: true }
|
||
|
|
),
|
||
|
|
Plot.ruleY([0]),
|
||
|
|
],
|
||
|
|
marginBottom: 60,
|
||
|
|
width: 700,
|
||
|
|
}));
|
||
|
|
```
|
||
|
|
|
||
|
|
```js
|
||
|
|
if (tab === "Pending" && pending.filter(d => d.escalation_note).length > 0) {
|
||
|
|
display(html`<div class="escalation-box">
|
||
|
|
<strong>⚠️ Escalated decisions require human approval before any action is taken (constitution §4).</strong>
|
||
|
|
<ul>${pending.filter(d => d.escalation_note).map(d => html`<li><b>${d.title}</b>: ${d.escalation_note}</li>`)}</ul>
|
||
|
|
</div>`);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
<style>
|
||
|
|
.escalation-box { background: #fff3cd; border: 2px solid orange; border-radius: 8px; padding: 1rem; margin-top: 1rem; }
|
||
|
|
</style>
|