@validation-os/dashboard 0.15.4 → 0.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +302 -126
- package/dist/index.js.map +1 -1
- package/dist/styles.css +148 -70
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,14 +9,14 @@ var REGISTER_LABEL = {
|
|
|
9
9
|
// One container-agnostic lens now Goal is unified into Experiment (OPS-1299 /
|
|
10
10
|
// OPS-1287 story 22): all plans browse in one place under this label.
|
|
11
11
|
experiments: "Evidence plans",
|
|
12
|
-
readings: "
|
|
12
|
+
readings: "Evidence",
|
|
13
13
|
decisions: "Decisions",
|
|
14
14
|
glossary: "Glossary"
|
|
15
15
|
};
|
|
16
16
|
var REGISTER_SINGULAR = {
|
|
17
17
|
assumptions: "Assumption",
|
|
18
18
|
experiments: "Evidence plan",
|
|
19
|
-
readings: "
|
|
19
|
+
readings: "Evidence",
|
|
20
20
|
decisions: "Decision",
|
|
21
21
|
glossary: "Glossary term"
|
|
22
22
|
};
|
|
@@ -37,7 +37,7 @@ var REGISTER_SUBTITLE = {
|
|
|
37
37
|
var REGISTER_ICON = {
|
|
38
38
|
assumptions: "\u25CE",
|
|
39
39
|
experiments: "\u2697",
|
|
40
|
-
readings: "\
|
|
40
|
+
readings: "\u2726",
|
|
41
41
|
decisions: "\xA7",
|
|
42
42
|
glossary: "A"
|
|
43
43
|
};
|
|
@@ -50,7 +50,7 @@ var REGISTER_GROUPS = [
|
|
|
50
50
|
var WORKFLOW_NAV = [
|
|
51
51
|
{ route: "assumptions", label: "Assumptions", icon: "\u25CE", isDefault: true },
|
|
52
52
|
{ route: "experiments", label: "Experiments", icon: "\u2697" },
|
|
53
|
-
{ route: "readings", label: "
|
|
53
|
+
{ route: "readings", label: "Evidence", icon: "\u2726" }
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
// src/assumption-detail.tsx
|
|
@@ -218,6 +218,47 @@ function buildEvidenceComposition(assumption, readings) {
|
|
|
218
218
|
totalContribution: Math.round((total + Number.EPSILON) * 100) / 100
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
|
+
function readingContributions(assumption, readings) {
|
|
222
|
+
const id = str(assumption.id) ?? "";
|
|
223
|
+
const inputs = [];
|
|
224
|
+
for (const r of readings) {
|
|
225
|
+
const belief2 = readingBeliefFor(r, id);
|
|
226
|
+
if (!belief2) continue;
|
|
227
|
+
const result = str(belief2.Result) ?? "Inconclusive";
|
|
228
|
+
if (result === "Inconclusive") continue;
|
|
229
|
+
const rung = str(r.Rung) ?? "Talk";
|
|
230
|
+
inputs.push({
|
|
231
|
+
id: str(r.id) ?? "",
|
|
232
|
+
source: str(r.Source) ?? null,
|
|
233
|
+
rung,
|
|
234
|
+
result,
|
|
235
|
+
representativeness: Number(r.Representativeness) || 1,
|
|
236
|
+
credibility: Number(r.Credibility) || 1,
|
|
237
|
+
date: str(r.Date),
|
|
238
|
+
magnitudeBand: r.magnitudeBand,
|
|
239
|
+
experimentId: str(r.experimentId)
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
const winners = scoreAndDedupe(inputs);
|
|
243
|
+
const winnerIds = new Set(winners.map((w) => w.input.id));
|
|
244
|
+
const rungsPresent = new Set(winners.map((x) => x.input.rung));
|
|
245
|
+
let den = 0;
|
|
246
|
+
for (const rung of rungsPresent) den += w0ForRung(rung);
|
|
247
|
+
for (const x of winners) den += x.weight;
|
|
248
|
+
return inputs.map((input) => {
|
|
249
|
+
const w = winners.find((x) => x.input.id === input.id);
|
|
250
|
+
const used = winnerIds.has(input.id);
|
|
251
|
+
const strength = w ? w.strength : (input.result === "Validated" ? 1 : input.result === "Invalidated" ? -1 : 0) * (RUNG_CAPS[input.rung] ?? 0);
|
|
252
|
+
const weight = w ? w.weight : 0;
|
|
253
|
+
return {
|
|
254
|
+
id: input.id,
|
|
255
|
+
contribution: w && den > 0 ? Math.round(w.weight * w.strength / den * 100) / 100 : 0,
|
|
256
|
+
weight,
|
|
257
|
+
strength,
|
|
258
|
+
used
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
221
262
|
|
|
222
263
|
// src/confidence-explainer.ts
|
|
223
264
|
import { RUNG_ANCHOR, W0_BY_RUNG } from "@validation-os/core/derivation";
|
|
@@ -1004,10 +1045,26 @@ function readingBeliefVerdicts(reading, assumptions = []) {
|
|
|
1004
1045
|
linked: hit != null,
|
|
1005
1046
|
result: b.Result ?? null,
|
|
1006
1047
|
strength,
|
|
1007
|
-
justification: typeof b["Grading justification"] === "string" ? b["Grading justification"] : ""
|
|
1048
|
+
justification: typeof b["Grading justification"] === "string" ? b["Grading justification"] : "",
|
|
1049
|
+
excerpt: typeof b.excerpt === "string" && b.excerpt !== "" ? b.excerpt : snippetFromBody(String(reading.body ?? ""), b.assumptionId)
|
|
1008
1050
|
};
|
|
1009
1051
|
});
|
|
1010
1052
|
}
|
|
1053
|
+
function snippetFromBody(body, cue) {
|
|
1054
|
+
if (!body) return "";
|
|
1055
|
+
const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
|
|
1056
|
+
if (quoteMatch) {
|
|
1057
|
+
const q = quoteMatch[1].trim();
|
|
1058
|
+
return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
|
|
1059
|
+
}
|
|
1060
|
+
const sentences = body.split(/(?<=[.!?])\s+/);
|
|
1061
|
+
const cueLower = cue.toLowerCase();
|
|
1062
|
+
for (const s of sentences) {
|
|
1063
|
+
if (s.toLowerCase().includes(cueLower)) return s.trim();
|
|
1064
|
+
}
|
|
1065
|
+
const first = sentences[0]?.trim() ?? "";
|
|
1066
|
+
return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
|
|
1067
|
+
}
|
|
1011
1068
|
function readingBeliefSummary(reading, assumptions = []) {
|
|
1012
1069
|
const verdicts = readingBeliefVerdicts(reading, assumptions);
|
|
1013
1070
|
let validated = 0;
|
|
@@ -1554,64 +1611,118 @@ function AssumptionDetail({
|
|
|
1554
1611
|
);
|
|
1555
1612
|
})
|
|
1556
1613
|
] }) : null,
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
const id = String(r.id ?? "");
|
|
1566
|
-
const belief2 = readingBeliefFor(r, assumptionId);
|
|
1567
|
-
if (!belief2) return null;
|
|
1568
|
-
const result = String(belief2.Result ?? "Inconclusive");
|
|
1569
|
-
const justification = String(belief2["Grading justification"] ?? "");
|
|
1570
|
-
const rung = String(r.Rung ?? "");
|
|
1571
|
-
const source = String(r.Source ?? "");
|
|
1572
|
-
const expId = r.experimentId ? String(r.experimentId) : null;
|
|
1573
|
-
return /* @__PURE__ */ jsxs4(
|
|
1574
|
-
"button",
|
|
1575
|
-
{
|
|
1576
|
-
type: "button",
|
|
1577
|
-
className: `vos-linked-row vos-linked-reading vos-verdict-${verdictTone(result)}`,
|
|
1578
|
-
onClick: () => onNavigate({ name: "reading", id }),
|
|
1579
|
-
children: [
|
|
1580
|
-
/* @__PURE__ */ jsxs4("div", { className: "vos-reading-head", children: [
|
|
1581
|
-
/* @__PURE__ */ jsx4("span", { className: "vos-reading-date vos-num", children: String(r.Date ?? "") }),
|
|
1582
|
-
/* @__PURE__ */ jsx4("span", { className: "vos-reading-title", children: String(r.Title ?? id) }),
|
|
1583
|
-
/* @__PURE__ */ jsx4("span", { className: `vos-pill vos-pill-${verdictTone(result)}`, children: result }),
|
|
1584
|
-
/* @__PURE__ */ jsx4("span", { className: "vos-rung-tag", children: rung })
|
|
1585
|
-
] }),
|
|
1586
|
-
justification ? /* @__PURE__ */ jsxs4("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone(result)}`, children: [
|
|
1587
|
-
/* @__PURE__ */ jsx4("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
1588
|
-
justification
|
|
1589
|
-
] }) : null,
|
|
1590
|
-
/* @__PURE__ */ jsxs4("div", { className: "vos-reading-source", children: [
|
|
1591
|
-
source,
|
|
1592
|
-
expId ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1593
|
-
" \xB7 from ",
|
|
1594
|
-
/* @__PURE__ */ jsx4(
|
|
1595
|
-
"span",
|
|
1596
|
-
{
|
|
1597
|
-
className: "vos-link",
|
|
1598
|
-
onClick: (ev) => {
|
|
1599
|
-
ev.stopPropagation();
|
|
1600
|
-
onNavigate({ name: "experiment", id: expId });
|
|
1601
|
-
},
|
|
1602
|
-
children: expId
|
|
1603
|
-
}
|
|
1604
|
-
)
|
|
1605
|
-
] }) : null
|
|
1606
|
-
] })
|
|
1607
|
-
]
|
|
1608
|
-
},
|
|
1609
|
-
id
|
|
1610
|
-
);
|
|
1611
|
-
})
|
|
1612
|
-
] }) : null
|
|
1614
|
+
/* @__PURE__ */ jsx4(
|
|
1615
|
+
EvidenceList,
|
|
1616
|
+
{
|
|
1617
|
+
assumptionId,
|
|
1618
|
+
readings: readings.records ?? [],
|
|
1619
|
+
onNavigate
|
|
1620
|
+
}
|
|
1621
|
+
)
|
|
1613
1622
|
] });
|
|
1614
1623
|
}
|
|
1624
|
+
function EvidenceList({
|
|
1625
|
+
assumptionId,
|
|
1626
|
+
readings,
|
|
1627
|
+
onNavigate
|
|
1628
|
+
}) {
|
|
1629
|
+
const linkedReadings = readings.filter((r) => readingBeliefs(r).some((b) => b.assumptionId === assumptionId)).sort((a, b) => String(b.Date ?? "").localeCompare(String(a.Date ?? "")));
|
|
1630
|
+
const contribById = useMemo(() => {
|
|
1631
|
+
const rec = readings.find((a) => String(a.id) === assumptionId);
|
|
1632
|
+
if (!rec) return /* @__PURE__ */ new Map();
|
|
1633
|
+
const rows = readingContributions(rec, readings);
|
|
1634
|
+
return new Map(rows.map((r) => [r.id, r]));
|
|
1635
|
+
}, [readings, assumptionId]);
|
|
1636
|
+
if (linkedReadings.length === 0) return null;
|
|
1637
|
+
return /* @__PURE__ */ jsxs4("div", { className: "vos-card vos-detail-section", children: [
|
|
1638
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-detail-section-label", children: [
|
|
1639
|
+
"Evidence \xB7 ",
|
|
1640
|
+
linkedReadings.length,
|
|
1641
|
+
" piece",
|
|
1642
|
+
linkedReadings.length === 1 ? "" : "s",
|
|
1643
|
+
" of evidence"
|
|
1644
|
+
] }),
|
|
1645
|
+
linkedReadings.map((r) => {
|
|
1646
|
+
const id = String(r.id ?? "");
|
|
1647
|
+
const belief2 = readingBeliefFor(r, assumptionId);
|
|
1648
|
+
if (!belief2) return null;
|
|
1649
|
+
const result = String(belief2.Result ?? "Inconclusive");
|
|
1650
|
+
const justification = String(belief2["Grading justification"] ?? "");
|
|
1651
|
+
const excerpt = typeof belief2.excerpt === "string" && belief2.excerpt !== "" ? belief2.excerpt : snippetFromBody2(String(r.body ?? ""), assumptionId);
|
|
1652
|
+
const rung = String(r.Rung ?? "");
|
|
1653
|
+
const source = String(r.Source ?? "");
|
|
1654
|
+
const expId = r.experimentId ? String(r.experimentId) : null;
|
|
1655
|
+
const c = contribById.get(id);
|
|
1656
|
+
return /* @__PURE__ */ jsxs4(
|
|
1657
|
+
"button",
|
|
1658
|
+
{
|
|
1659
|
+
type: "button",
|
|
1660
|
+
className: `vos-evidence-row vos-verdict-${verdictTone(result)}`,
|
|
1661
|
+
onClick: () => onNavigate({ name: "reading", id }),
|
|
1662
|
+
children: [
|
|
1663
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-evidence-row-head", children: [
|
|
1664
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-evidence-date vos-num", children: String(r.Date ?? "") }),
|
|
1665
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-evidence-title", children: String(r.Title ?? id) }),
|
|
1666
|
+
/* @__PURE__ */ jsx4("span", { className: `vos-pill vos-pill-${verdictTone(result)}`, children: result }),
|
|
1667
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-rung-tag", children: rung }),
|
|
1668
|
+
c && c.used ? /* @__PURE__ */ jsxs4("span", { className: `vos-evidence-score vos-num vos-text-${contributionTone(c.contribution)}`, children: [
|
|
1669
|
+
formatSigned(c.contribution),
|
|
1670
|
+
" confidence"
|
|
1671
|
+
] }) : null
|
|
1672
|
+
] }),
|
|
1673
|
+
excerpt ? /* @__PURE__ */ jsxs4("div", { className: "vos-evidence-excerpt", children: [
|
|
1674
|
+
"\u201C",
|
|
1675
|
+
excerpt,
|
|
1676
|
+
"\u201D"
|
|
1677
|
+
] }) : null,
|
|
1678
|
+
justification ? /* @__PURE__ */ jsxs4("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone(result)}`, children: [
|
|
1679
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
1680
|
+
justification
|
|
1681
|
+
] }) : null,
|
|
1682
|
+
/* @__PURE__ */ jsxs4("div", { className: "vos-evidence-source", children: [
|
|
1683
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-evidence-source-link", title: source, children: source }),
|
|
1684
|
+
expId ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1685
|
+
" \xB7 from experiment ",
|
|
1686
|
+
/* @__PURE__ */ jsx4(
|
|
1687
|
+
"span",
|
|
1688
|
+
{
|
|
1689
|
+
className: "vos-link",
|
|
1690
|
+
onClick: (ev) => {
|
|
1691
|
+
ev.stopPropagation();
|
|
1692
|
+
onNavigate({ name: "experiment", id: expId });
|
|
1693
|
+
},
|
|
1694
|
+
children: expId
|
|
1695
|
+
}
|
|
1696
|
+
)
|
|
1697
|
+
] }) : null
|
|
1698
|
+
] })
|
|
1699
|
+
]
|
|
1700
|
+
},
|
|
1701
|
+
id
|
|
1702
|
+
);
|
|
1703
|
+
})
|
|
1704
|
+
] });
|
|
1705
|
+
}
|
|
1706
|
+
function contributionTone(v) {
|
|
1707
|
+
if (v > 0) return "good";
|
|
1708
|
+
if (v < 0) return "crit";
|
|
1709
|
+
return "neutral";
|
|
1710
|
+
}
|
|
1711
|
+
function snippetFromBody2(body, cue) {
|
|
1712
|
+
if (!body) return "";
|
|
1713
|
+
const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
|
|
1714
|
+
if (quoteMatch) {
|
|
1715
|
+
const q = quoteMatch[1].trim();
|
|
1716
|
+
return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
|
|
1717
|
+
}
|
|
1718
|
+
const sentences = body.split(/(?<=[.!?])\s+/);
|
|
1719
|
+
const cueLower = cue.toLowerCase();
|
|
1720
|
+
for (const s of sentences) {
|
|
1721
|
+
if (s.toLowerCase().includes(cueLower)) return s.trim();
|
|
1722
|
+
}
|
|
1723
|
+
const first = sentences[0]?.trim() ?? "";
|
|
1724
|
+
return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
|
|
1725
|
+
}
|
|
1615
1726
|
function ScoreCard({
|
|
1616
1727
|
label,
|
|
1617
1728
|
value,
|
|
@@ -1781,16 +1892,24 @@ function EvidenceCompositionView({
|
|
|
1781
1892
|
" (adds to Confidence)"
|
|
1782
1893
|
] }),
|
|
1783
1894
|
comp.rungs.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "vos-muted", children: "No lens set \u2014 evidence composition needs a lens." }) : comp.rungs.map((r) => {
|
|
1784
|
-
const
|
|
1895
|
+
const abs = Math.abs(r.contribution);
|
|
1896
|
+
const pct2 = r.cap > 0 ? Math.min(100, abs / r.cap * 100) : 0;
|
|
1785
1897
|
const isEmpty = r.count === 0;
|
|
1898
|
+
const tone = r.contribution > 0 ? "good" : r.contribution < 0 ? "crit" : "accent";
|
|
1786
1899
|
return /* @__PURE__ */ jsxs4("div", { className: "vos-comp-row", children: [
|
|
1787
1900
|
/* @__PURE__ */ jsx4("span", { className: `vos-comp-rung ${isEmpty ? "is-empty" : ""}`, children: r.rung }),
|
|
1788
|
-
/* @__PURE__ */ jsx4("div", { className: "vos-comp-bar", children: /* @__PURE__ */ jsx4("i", { className:
|
|
1789
|
-
/* @__PURE__ */ jsx4("span", { className: "vos-comp-val vos-num", children: isEmpty ? "\u2014" :
|
|
1901
|
+
/* @__PURE__ */ jsx4("div", { className: "vos-comp-bar", children: /* @__PURE__ */ jsx4("i", { className: `vos-comp-fill vos-fill-${tone}`, style: { width: `${isEmpty ? 0 : Math.max(4, pct2)}%` } }) }),
|
|
1902
|
+
/* @__PURE__ */ jsx4("span", { className: "vos-comp-val vos-num", children: isEmpty ? "\u2014" : /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1903
|
+
/* @__PURE__ */ jsx4("span", { className: `vos-text-${tone}`, children: formatSigned(r.contribution) }),
|
|
1904
|
+
/* @__PURE__ */ jsxs4("span", { style: { color: "var(--vos-faint)" }, children: [
|
|
1905
|
+
" \xB7 cap ",
|
|
1906
|
+
r.cap
|
|
1907
|
+
] })
|
|
1908
|
+
] }) }),
|
|
1790
1909
|
/* @__PURE__ */ jsxs4("span", { className: "vos-comp-count vos-num", children: [
|
|
1791
1910
|
r.count,
|
|
1792
|
-
"
|
|
1793
|
-
r.count === 1 ? "" : "
|
|
1911
|
+
" ",
|
|
1912
|
+
r.count === 1 ? "source" : "sources"
|
|
1794
1913
|
] })
|
|
1795
1914
|
] }, r.rung);
|
|
1796
1915
|
})
|
|
@@ -2099,9 +2218,14 @@ function weekOverWeekDelta(assumptions, readings, now) {
|
|
|
2099
2218
|
}
|
|
2100
2219
|
|
|
2101
2220
|
// src/recommended-experiments.ts
|
|
2102
|
-
var MAX_RECOMMENDED =
|
|
2221
|
+
var MAX_RECOMMENDED = 3;
|
|
2103
2222
|
var MAX_CLUSTER_SIZE = 3;
|
|
2104
2223
|
var MAX_NEEDS_FRAMING = 3;
|
|
2224
|
+
var LENS_COLOUR = {
|
|
2225
|
+
Consumer: "#8b83f5",
|
|
2226
|
+
Commercial: "#38c793",
|
|
2227
|
+
Investor: "#e6a23c"
|
|
2228
|
+
};
|
|
2105
2229
|
function buildNeedsFraming(assumptions) {
|
|
2106
2230
|
const live = assumptions.filter((a) => {
|
|
2107
2231
|
const status = str(a.Status);
|
|
@@ -2116,7 +2240,8 @@ function buildNeedsFraming(assumptions) {
|
|
|
2116
2240
|
const stage = str(a.Stage) ?? "\u2014";
|
|
2117
2241
|
const title = str(a.Title) ?? id;
|
|
2118
2242
|
const hint = framingHint(a, completeness);
|
|
2119
|
-
|
|
2243
|
+
const lensColour = LENS_COLOUR[lens] ?? LENS_COLOUR["Investor"] ?? "#6b7484";
|
|
2244
|
+
return { id, title, risk, completeness, lens, stage, hint, lensColour };
|
|
2120
2245
|
}).filter((a) => a.completeness < 100);
|
|
2121
2246
|
const byLens = /* @__PURE__ */ new Map();
|
|
2122
2247
|
for (const item of items) {
|
|
@@ -2166,22 +2291,28 @@ function buildRecommendedExperiments(assumptions, experiments) {
|
|
|
2166
2291
|
if (bucket) bucket.push(a);
|
|
2167
2292
|
else clusters.set(key, [a]);
|
|
2168
2293
|
}
|
|
2169
|
-
const
|
|
2294
|
+
const rankedClusters = [...clusters.entries()].map(([key, cluster]) => {
|
|
2170
2295
|
const ranked = cluster.sort((a, b) => (derivedNum(b, "risk") ?? 0) - (derivedNum(a, "risk") ?? 0)).slice(0, MAX_CLUSTER_SIZE);
|
|
2171
2296
|
const maxRisk = Math.max(...ranked.map((a) => derivedNum(a, "risk") ?? 0), 0);
|
|
2172
2297
|
return { key, cluster: ranked, maxRisk };
|
|
2173
|
-
}).sort((a, b) => b.maxRisk - a.maxRisk)
|
|
2298
|
+
}).sort((a, b) => b.maxRisk - a.maxRisk);
|
|
2299
|
+
const byLens = /* @__PURE__ */ new Map();
|
|
2300
|
+
for (const entry of rankedClusters) {
|
|
2301
|
+
const lens = entry.key.split("\xD7")[0] ?? "\u2014";
|
|
2302
|
+
if (!byLens.has(lens)) byLens.set(lens, entry);
|
|
2303
|
+
}
|
|
2304
|
+
const clusterEntries = [...byLens.values()].sort((a, b) => b.maxRisk - a.maxRisk).slice(0, MAX_RECOMMENDED);
|
|
2174
2305
|
const recs = [];
|
|
2175
2306
|
for (const { key, cluster, maxRisk } of clusterEntries) {
|
|
2176
2307
|
const lens = key.split("\xD7")[0] ?? "\u2014";
|
|
2177
2308
|
const stage = key.split("\xD7")[1] ?? "\u2014";
|
|
2178
2309
|
const assumptionIds = cluster.map((a) => str(a.id) ?? "").filter(Boolean).sort();
|
|
2179
2310
|
const type = LENS_TO_TYPE[lens ?? ""] ?? "Desk research";
|
|
2180
|
-
const titles = cluster.map((a) => str(a.Title) ?? str(a.id) ?? "");
|
|
2181
2311
|
const title = cluster.length === 1 ? `Test ${assumptionIds[0]}` : `Test ${cluster.length} ${lens} \xB7 ${stage} beliefs`;
|
|
2182
2312
|
const rationale = cluster.length === 1 ? `One belief (${assumptionIds[0]}) at ${Math.round(maxRisk)} risk with no live test. Designing an experiment here would buy down the most risk.` : `${cluster.length} beliefs share ${lens} \xB7 ${stage}, the riskiest at ${Math.round(maxRisk)} risk. One experiment can address them all.`;
|
|
2183
2313
|
const barPreview = `The riskiest belief moves out of the kill zone, or stays in it.`;
|
|
2184
2314
|
const body = generateExperimentBody(type, lens, stage, cluster, maxRisk);
|
|
2315
|
+
const lensColour = LENS_COLOUR[lens] ?? LENS_COLOUR["Investor"] ?? "#6b7484";
|
|
2185
2316
|
recs.push({
|
|
2186
2317
|
id: assumptionIds.join("+"),
|
|
2187
2318
|
type,
|
|
@@ -2190,7 +2321,9 @@ function buildRecommendedExperiments(assumptions, experiments) {
|
|
|
2190
2321
|
maxRisk,
|
|
2191
2322
|
rationale,
|
|
2192
2323
|
barPreview,
|
|
2193
|
-
body
|
|
2324
|
+
body,
|
|
2325
|
+
lens,
|
|
2326
|
+
lensColour
|
|
2194
2327
|
});
|
|
2195
2328
|
}
|
|
2196
2329
|
recs.sort(
|
|
@@ -2679,10 +2812,7 @@ function NextMovesSection({
|
|
|
2679
2812
|
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-head", children: "Next moves" }),
|
|
2680
2813
|
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-cols", children: [
|
|
2681
2814
|
/* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
|
|
2682
|
-
/* @__PURE__ */
|
|
2683
|
-
"Needs framing \xB7 top ",
|
|
2684
|
-
needsFraming.length
|
|
2685
|
-
] }),
|
|
2815
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-col-label", children: "Needs framing \xB7 one per lens" }),
|
|
2686
2816
|
needsFraming.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every belief is fully framed." }) : needsFraming.map((a) => /* @__PURE__ */ jsxs5(
|
|
2687
2817
|
"button",
|
|
2688
2818
|
{
|
|
@@ -2691,13 +2821,24 @@ function NextMovesSection({
|
|
|
2691
2821
|
onClick: () => onOpenAssumption(a.id),
|
|
2692
2822
|
children: [
|
|
2693
2823
|
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-item-head", children: [
|
|
2694
|
-
/* @__PURE__ */ jsx5(
|
|
2824
|
+
/* @__PURE__ */ jsx5(
|
|
2825
|
+
"span",
|
|
2826
|
+
{
|
|
2827
|
+
className: "vos-next-moves-lens",
|
|
2828
|
+
style: { background: `${a.lensColour}20`, color: a.lensColour, borderColor: `${a.lensColour}40` },
|
|
2829
|
+
children: a.lens
|
|
2830
|
+
}
|
|
2831
|
+
),
|
|
2695
2832
|
/* @__PURE__ */ jsxs5("span", { className: `vos-next-moves-item-risk vos-num vos-text-${riskToneClass(a.risk)}`, children: [
|
|
2696
2833
|
Math.round(a.risk),
|
|
2697
2834
|
" risk"
|
|
2698
2835
|
] })
|
|
2699
2836
|
] }),
|
|
2700
|
-
/* @__PURE__ */
|
|
2837
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-item-title", children: [
|
|
2838
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-next-moves-item-id vos-num", children: a.id }),
|
|
2839
|
+
" \xB7 ",
|
|
2840
|
+
a.title
|
|
2841
|
+
] }),
|
|
2701
2842
|
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-item-hint", children: a.hint })
|
|
2702
2843
|
]
|
|
2703
2844
|
},
|
|
@@ -2705,39 +2846,36 @@ function NextMovesSection({
|
|
|
2705
2846
|
))
|
|
2706
2847
|
] }),
|
|
2707
2848
|
/* @__PURE__ */ jsxs5("div", { className: "vos-card vos-next-moves-col", children: [
|
|
2708
|
-
/* @__PURE__ */
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2849
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-col-label", children: "Proposed experiments \xB7 one per lens" }),
|
|
2850
|
+
recs.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "vos-muted vos-next-moves-empty", children: "Every risk has a live test." }) : recs.map((rec) => /* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-rec", children: [
|
|
2851
|
+
/* @__PURE__ */ jsxs5("div", { className: "vos-next-moves-rec-head", children: [
|
|
2852
|
+
/* @__PURE__ */ jsx5(
|
|
2853
|
+
"span",
|
|
2854
|
+
{
|
|
2855
|
+
className: "vos-next-moves-lens",
|
|
2856
|
+
style: { background: `${rec.lensColour}20`, color: rec.lensColour, borderColor: `${rec.lensColour}40` },
|
|
2857
|
+
children: rec.lens
|
|
2858
|
+
}
|
|
2859
|
+
),
|
|
2860
|
+
/* @__PURE__ */ jsx5("span", { className: "vos-next-moves-rec-type", children: rec.type }),
|
|
2861
|
+
/* @__PURE__ */ jsxs5("span", { className: "vos-next-moves-rec-risk vos-num", children: [
|
|
2717
2862
|
Math.round(rec.maxRisk),
|
|
2718
2863
|
" risk"
|
|
2719
2864
|
] })
|
|
2720
2865
|
] }),
|
|
2721
|
-
/* @__PURE__ */
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
/* @__PURE__ */ jsx5("strong", { children: "Right if:" }),
|
|
2735
|
-
" ",
|
|
2736
|
-
rec.barPreview
|
|
2737
|
-
] }),
|
|
2738
|
-
/* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-body", children: /* @__PURE__ */ jsx5(EvidenceBody, { text: rec.body }) }),
|
|
2739
|
-
/* @__PURE__ */ jsx5("div", { className: "vos-pipe-rec-actions", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "vos-btn vos-btn-sm vos-btn-accent", children: "Accept & create experiment" }) })
|
|
2740
|
-
] })
|
|
2866
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-title", children: rec.title }),
|
|
2867
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-rationale", children: rec.rationale }),
|
|
2868
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-chips", children: rec.assumptionIds.map((id) => /* @__PURE__ */ jsx5(
|
|
2869
|
+
"button",
|
|
2870
|
+
{
|
|
2871
|
+
type: "button",
|
|
2872
|
+
className: "vos-next-moves-rec-chip",
|
|
2873
|
+
onClick: () => onOpenAssumption(id),
|
|
2874
|
+
children: id
|
|
2875
|
+
},
|
|
2876
|
+
id
|
|
2877
|
+
)) }),
|
|
2878
|
+
/* @__PURE__ */ jsx5("div", { className: "vos-next-moves-rec-actions", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "vos-btn vos-btn-sm", children: "Accept & create experiment" }) })
|
|
2741
2879
|
] }, rec.id))
|
|
2742
2880
|
] })
|
|
2743
2881
|
] })
|
|
@@ -2766,8 +2904,8 @@ function ConfidenceDonut({
|
|
|
2766
2904
|
value,
|
|
2767
2905
|
size = 56
|
|
2768
2906
|
}) {
|
|
2769
|
-
const stroke = size > 60 ?
|
|
2770
|
-
const r = (size - stroke) / 2;
|
|
2907
|
+
const stroke = size > 60 ? 4 : 3;
|
|
2908
|
+
const r = (size - stroke * 3) / 2;
|
|
2771
2909
|
const cx = size / 2;
|
|
2772
2910
|
const cy = size / 2;
|
|
2773
2911
|
const circumference = 2 * Math.PI * r;
|
|
@@ -2783,7 +2921,7 @@ function ConfidenceDonut({
|
|
|
2783
2921
|
cy,
|
|
2784
2922
|
r,
|
|
2785
2923
|
fill: "none",
|
|
2786
|
-
stroke: "var(--vos-
|
|
2924
|
+
stroke: "var(--vos-border)",
|
|
2787
2925
|
strokeWidth: stroke
|
|
2788
2926
|
}
|
|
2789
2927
|
),
|
|
@@ -2807,7 +2945,7 @@ function ConfidenceDonut({
|
|
|
2807
2945
|
"span",
|
|
2808
2946
|
{
|
|
2809
2947
|
className: "vos-donut-num vos-num",
|
|
2810
|
-
style: { fontSize: size > 60 ?
|
|
2948
|
+
style: { fontSize: size > 60 ? 22 : 15 },
|
|
2811
2949
|
children: Math.round(value)
|
|
2812
2950
|
}
|
|
2813
2951
|
)
|
|
@@ -2948,11 +3086,11 @@ function ExperimentDetail({
|
|
|
2948
3086
|
/* @__PURE__ */ jsxs7("div", { className: "vos-detail-section-label", children: [
|
|
2949
3087
|
"Evidence \xB7 ",
|
|
2950
3088
|
expReadings.length,
|
|
2951
|
-
"
|
|
3089
|
+
" piece",
|
|
2952
3090
|
expReadings.length === 1 ? "" : "s",
|
|
2953
3091
|
" from this experiment"
|
|
2954
3092
|
] }),
|
|
2955
|
-
expReadings.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-muted vos-empty", children: "No
|
|
3093
|
+
expReadings.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "vos-muted vos-empty", children: "No evidence yet \u2014 experiment is running." }) : expReadings.map((r) => {
|
|
2956
3094
|
const beliefs = readingBeliefs(r);
|
|
2957
3095
|
const rDate = String(r.Date ?? "");
|
|
2958
3096
|
const rTitle = String(r.Title ?? r.id);
|
|
@@ -2999,10 +3137,20 @@ function ExperimentDetail({
|
|
|
2999
3137
|
/* @__PURE__ */ jsx7("span", { className: `vos-pill vos-pill-${verdictTone2(b.Result)}`, children: b.Result }),
|
|
3000
3138
|
/* @__PURE__ */ jsx7("span", { className: "vos-rung-tag", children: String(r.Rung ?? "") })
|
|
3001
3139
|
] }),
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3140
|
+
typeof b.excerpt === "string" && b.excerpt !== "" ? /* @__PURE__ */ jsxs7("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone2(b.Result)}`, children: [
|
|
3141
|
+
"\u201C",
|
|
3142
|
+
b.excerpt,
|
|
3143
|
+
"\u201D"
|
|
3144
|
+
] }) : justification ? /* @__PURE__ */ jsxs7(
|
|
3145
|
+
"div",
|
|
3146
|
+
{
|
|
3147
|
+
className: `vos-belief-rationale vos-verdict-border-${verdictTone2(b.Result)}`,
|
|
3148
|
+
children: [
|
|
3149
|
+
/* @__PURE__ */ jsx7("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
3150
|
+
justification
|
|
3151
|
+
]
|
|
3152
|
+
}
|
|
3153
|
+
) : null,
|
|
3006
3154
|
bl ? /* @__PURE__ */ jsxs7("div", { className: "vos-belief-bar", children: [
|
|
3007
3155
|
/* @__PURE__ */ jsx7("div", { className: "vos-belief-bar-label", children: "Pre-registered bar" }),
|
|
3008
3156
|
/* @__PURE__ */ jsxs7("div", { children: [
|
|
@@ -3032,7 +3180,7 @@ function ExperimentDetail({
|
|
|
3032
3180
|
unstartedBars.length,
|
|
3033
3181
|
" bar",
|
|
3034
3182
|
unstartedBars.length === 1 ? "" : "s",
|
|
3035
|
-
" with no
|
|
3183
|
+
" with no evidence"
|
|
3036
3184
|
] }),
|
|
3037
3185
|
unstartedBars.map((bl) => {
|
|
3038
3186
|
const a = (assumptions.records ?? []).find((x) => String(x.id) === bl.assumptionId);
|
|
@@ -3049,7 +3197,7 @@ function ExperimentDetail({
|
|
|
3049
3197
|
),
|
|
3050
3198
|
/* @__PURE__ */ jsx7("span", { className: "vos-belief-title", children: String(a?.Title ?? bl.assumptionId) }),
|
|
3051
3199
|
/* @__PURE__ */ jsx7("span", { className: "vos-rung-tag", children: String(bl.plannedRung ?? "") }),
|
|
3052
|
-
/* @__PURE__ */ jsx7("span", { className: "vos-muted", children: "\u25CC no
|
|
3200
|
+
/* @__PURE__ */ jsx7("span", { className: "vos-muted", children: "\u25CC no evidence" })
|
|
3053
3201
|
] }),
|
|
3054
3202
|
/* @__PURE__ */ jsxs7("div", { className: "vos-belief-bar", children: [
|
|
3055
3203
|
/* @__PURE__ */ jsxs7("div", { children: [
|
|
@@ -3168,15 +3316,15 @@ function ReadingDetail({
|
|
|
3168
3316
|
);
|
|
3169
3317
|
if (loading) {
|
|
3170
3318
|
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
3171
|
-
/* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "
|
|
3172
|
-
/* @__PURE__ */ jsx9("p", { className: "vos-muted", children: "Loading
|
|
3319
|
+
/* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Evidence", route: { name: "readings" } }], onNavigate }),
|
|
3320
|
+
/* @__PURE__ */ jsx9("p", { className: "vos-muted", children: "Loading evidence\u2026" })
|
|
3173
3321
|
] });
|
|
3174
3322
|
}
|
|
3175
3323
|
if (!reading) {
|
|
3176
3324
|
return /* @__PURE__ */ jsxs9("div", { children: [
|
|
3177
|
-
/* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "
|
|
3325
|
+
/* @__PURE__ */ jsx9(Breadcrumb, { trail: [{ label: "Evidence", route: { name: "readings" } }], onNavigate }),
|
|
3178
3326
|
/* @__PURE__ */ jsxs9("p", { className: "vos-error", children: [
|
|
3179
|
-
"
|
|
3327
|
+
"Evidence not found: ",
|
|
3180
3328
|
readingId
|
|
3181
3329
|
] })
|
|
3182
3330
|
] });
|
|
@@ -3193,7 +3341,7 @@ function ReadingDetail({
|
|
|
3193
3341
|
Breadcrumb,
|
|
3194
3342
|
{
|
|
3195
3343
|
trail: [
|
|
3196
|
-
{ label: "
|
|
3344
|
+
{ label: "Evidence", route: { name: "readings" } },
|
|
3197
3345
|
{ label: readingId, route: { name: "reading", id: readingId } }
|
|
3198
3346
|
],
|
|
3199
3347
|
onNavigate
|
|
@@ -3217,7 +3365,7 @@ function ReadingDetail({
|
|
|
3217
3365
|
beliefs.length === 1 ? "" : "s",
|
|
3218
3366
|
" \xB7 each with its own quote"
|
|
3219
3367
|
] }),
|
|
3220
|
-
beliefs.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "vos-muted", children: "No beliefs scored in this
|
|
3368
|
+
beliefs.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "vos-muted", children: "No beliefs scored in this evidence." }) : beliefs.map((b) => {
|
|
3221
3369
|
const a = (assumptions.records ?? []).find((x) => String(x.id) === b.assumptionId);
|
|
3222
3370
|
const bl = experiment && Array.isArray(experiment.barLines) ? experiment.barLines.find((x) => x?.assumptionId === b.assumptionId) : null;
|
|
3223
3371
|
const result = String(b.Result ?? "Inconclusive");
|
|
@@ -3237,11 +3385,19 @@ function ReadingDetail({
|
|
|
3237
3385
|
/* @__PURE__ */ jsx9("span", { className: `vos-pill vos-pill-${verdictTone3(result)}`, children: result }),
|
|
3238
3386
|
/* @__PURE__ */ jsx9("span", { className: "vos-rung-tag", children: rung })
|
|
3239
3387
|
] }),
|
|
3388
|
+
typeof b.excerpt === "string" && b.excerpt !== "" ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone3(result)}`, children: [
|
|
3389
|
+
"\u201C",
|
|
3390
|
+
b.excerpt,
|
|
3391
|
+
"\u201D"
|
|
3392
|
+
] }) : body ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-excerpt vos-verdict-border-${verdictTone3(result)}`, children: [
|
|
3393
|
+
"\u201C",
|
|
3394
|
+
snippetFromReadingBody(body, b.assumptionId),
|
|
3395
|
+
"\u201D"
|
|
3396
|
+
] }) : null,
|
|
3240
3397
|
justification ? /* @__PURE__ */ jsxs9("div", { className: `vos-belief-rationale vos-verdict-border-${verdictTone3(result)}`, children: [
|
|
3241
3398
|
/* @__PURE__ */ jsx9("span", { className: "vos-belief-rationale-label", children: "grading rationale:" }),
|
|
3242
3399
|
justification
|
|
3243
3400
|
] }) : null,
|
|
3244
|
-
body ? /* @__PURE__ */ jsx9("div", { className: "vos-belief-context-link vos-muted", children: "see context above for the full quote" }) : null,
|
|
3245
3401
|
bl ? /* @__PURE__ */ jsxs9("div", { className: "vos-belief-bar", children: [
|
|
3246
3402
|
/* @__PURE__ */ jsx9("div", { className: "vos-belief-bar-label", children: "Pre-registered bar" }),
|
|
3247
3403
|
/* @__PURE__ */ jsxs9("div", { children: [
|
|
@@ -3288,6 +3444,21 @@ function verdictTone3(verdict) {
|
|
|
3288
3444
|
if (verdict === "Invalidated") return "crit";
|
|
3289
3445
|
return "neutral";
|
|
3290
3446
|
}
|
|
3447
|
+
function snippetFromReadingBody(body, cue) {
|
|
3448
|
+
if (!body) return "";
|
|
3449
|
+
const quoteMatch = body.match(/## Quote\n+([\s\S]*?)(?=\n## |\n##$|$)/i);
|
|
3450
|
+
if (quoteMatch) {
|
|
3451
|
+
const q = quoteMatch[1].trim();
|
|
3452
|
+
return q.length > 220 ? q.slice(0, 217).trim() + "\u2026" : q;
|
|
3453
|
+
}
|
|
3454
|
+
const sentences = body.split(/(?<=[.!?])\s+/);
|
|
3455
|
+
const cueLower = cue.toLowerCase();
|
|
3456
|
+
for (const s of sentences) {
|
|
3457
|
+
if (s.toLowerCase().includes(cueLower)) return s.trim();
|
|
3458
|
+
}
|
|
3459
|
+
const first = sentences[0]?.trim() ?? "";
|
|
3460
|
+
return first.length > 220 ? first.slice(0, 217).trim() + "\u2026" : first;
|
|
3461
|
+
}
|
|
3291
3462
|
|
|
3292
3463
|
// src/readings-surface.tsx
|
|
3293
3464
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
@@ -5119,6 +5290,11 @@ function BeliefVerdicts({
|
|
|
5119
5290
|
"Strength ",
|
|
5120
5291
|
formatSigned(v.strength)
|
|
5121
5292
|
] }) }) : null,
|
|
5293
|
+
v.excerpt ? /* @__PURE__ */ jsxs17("p", { className: "vos-verdict-quote", children: [
|
|
5294
|
+
"\u201C",
|
|
5295
|
+
v.excerpt,
|
|
5296
|
+
"\u201D"
|
|
5297
|
+
] }) : null,
|
|
5122
5298
|
v.justification ? /* @__PURE__ */ jsx17("p", { className: "vos-verdict-why", children: v.justification }) : null
|
|
5123
5299
|
]
|
|
5124
5300
|
},
|