Skip to content

Commit 2b5dbbe

Browse files
committed
Point 4: the clinical / evidence-synthesis form vocabulary
`design-by-topic.md` has listed "Medicine / biotech / clinical" as a first-class domain for a long time, and EVERY row in that table is an aesthetic row — which preset, which anti-pick. None of them changes the FORM vocabulary. Measured by grep across references/ + scripts/ + agents/ before this commit: zero hits for kaplan-meier, forest plot, CONSORT, PRISMA, Bland-Altman, ROC. The two vocabularies that did exist are business-analytics (designed_charts: waterfall, marimekko, pareto, radar) and undergraduate-physics (schematic-diagrams: free-body, optics, circuits). So the skill could STYLE a clinical deck and could not DRAW one, and the author's only option was a hand-rolled scatter. Five forms, each owning a rule that is wrong BY DEFAULT when hand-rolled — which matters because a wrong chart is a perfectly well-formed set of shapes and no geometry lint in this repo can see one: deckkit.consort_flow participant flow whose ARITHMETIC IS ENFORCED — every stage's count minus its documented exclusions must equal the next stage's, and it raises with the sum spelled out. This is the only claim of its kind in the skill: the lints measure geometry, the critic reads pixels, and both pass a diagram that loses fourteen patients between two boxes. designed_charts.forest_plot ratios on a LOG axis (a linear axis draws a ratio-symmetric CI lopsided) with plain-number ticks, since matplotlib renders the null as "10^0" and a reader should not decode an exponent to find the line at ONE. Also the general form for "the same comparison measured several times" — subgroups, regression coefficients, per-segment A/B lifts. designed_charts.km_curve a STEP from raw (times, events) — the estimator runs here — plus censoring ticks and numbers at risk. designed_charts.bland_altman bias +/- 1.96*SD of the DIFFERENCES. Two methods can correlate almost perfectly and still disagree by a fatal constant, so a correlation is the classic wrong answer. designed_charts.roc_curve square canvas, chance diagonal, AUC in the legend. A confusion matrix is routed to the existing heat_matrix rather than given a new component, and the reference states what it does NOT supply (phylogeny, reaction scheme, theorem-proof) instead of implying coverage. Estimators checked against hand-computable answers, not just "it runs": the KM ladder reproduces the textbook 6-MP series exactly (0.6667 / 0.5556 / 0.4167 / 0.2778 / 0.1389 / 0), the AUC reproduces the published worked example (0.7500), the Bland-Altman bias and SD match statistics.mean/stdev. Every render was then LOOKED at, which is what caught two defects numbers could not: scientific-notation ticks on the forest axis, and the KM risk-table labels colliding with the first count ("Control20") because the tick at x=0 is centred on the spine. Routed from SKILL.md (layer 1, which is what a non-Claude runtime reads), from form-selection.md (where the form is chosen), data-viz.md (where the chart type is chosen) and design-by-topic.md — where the LOOK-vs-FORM limit is now named explicitly, because a matched preset reads as "the deck fits the domain" and does not. component_audit can name a guarantee for all five, so a hand-rolled one is reportable. 37 assertions, wired into CI. ALSO: the fonts gate is now REPORT-ONLY on both paths. As a blocker it broke four CI runs across three unrelated suites, and the reason is conceptual, not a bug — it runs on the machine doing the GATING, which need not be the machine that did the MEASURING, and nothing in a .pptx says which was which. A deck authored on a Mac with Helvetica Neue was measured correctly; re-gating it on Linux would refuse a deck with nothing wrong with it. The finding still prints on both paths and rides in --json (it was a print() in lint_layout before, gating nothing); --strict makes the CLI fatal for a caller that owns both the build and the gate.
1 parent a3396fe commit 2b5dbbe

14 files changed

Lines changed: 927 additions & 18 deletions

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,17 @@ jobs:
679679
grep -qE "^[0-9]+ passed, 0 failed" /tmp/renderpar.log || {
680680
echo "::error::render-equivalence suite did not run to completion"; exit 1; }
681681
682+
# The clinical / evidence-synthesis forms. Each owns a rule that is wrong BY DEFAULT when
683+
# hand-rolled (a linear axis for ratios, a line instead of a step, a correlation instead of
684+
# limits of agreement) — and a wrong chart is a well-formed set of shapes, so no geometry
685+
# lint can see one. The estimators are checked against hand-computable answers.
686+
- name: Clinical / evidence forms (estimators, guards, routing)
687+
run: |
688+
set -o pipefail
689+
python tests/test_clinical_forms.py | tee /tmp/clin.log
690+
grep -qE "^[0-9]+ passed, 0 failed" /tmp/clin.log || {
691+
echo "::error::clinical forms suite did not run to completion"; exit 1; }
692+
682693
# The template BRANCH — roughly half of what this skill builds — had three holes that every
683694
# gate reported clean through. All three were found on one real institutional-template deck.
684695
# Script-style (matching test_lint_regressions.py), so pytest would collect zero: invoke

skills/slide-maker/SKILL.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ every **🔴 CHECKPOINT** is a hard stop.
275275
|---|---|
276276
| The craft / the "why" (contrast · hierarchy · C.R.A.P. · layout safety) | `references/design-principles.md` |
277277
| Per-purpose look (defense vs exec vs lecture …) | `references/design-by-purpose.md` |
278-
| Per-TOPIC look (domain → apt presets → ANTI-PICK + cliché guard — the topic-adapted pick) | `references/design-by-topic.md` |
278+
| Per-TOPIC look (domain → apt presets → ANTI-PICK + cliché guard — the topic-adapted pick) | `references/design-by-topic.md` — 🔴 it adapts the LOOK only; a domain that needs its own FORM vocabulary is routed on the two rows below |
279279
| Bespoke registers invented from a subject's world (verified library to ADAPT + grow) | `references/bespoke-registers.md` |
280280
| Content — deep read + per-slide message (Step 1) | `agents/content-planner.md` |
281281
| Input formats — Word/Office · image · video (ingest routes + the vision/audio fidelity floor) | `agents/content-planner.md` §1 (Input formats) · `scripts/ingest.py` |
@@ -288,6 +288,7 @@ every **🔴 CHECKPOINT** is a hard stop.
288288
| Charts (which type · editable-native vs raster) | `references/data-viz.md` |
289289
| Choropleth map (value per country / province — europe · world · china) | `deckkit.choropleth()` · `scripts/maps.py` · `references/data-viz.md` |
290290
| Science schematics (force / ray / circuit / apparatus …) | `references/schematic-diagrams.md` |
291+
| Clinical / evidence figures (participant flow · forest · survival · agreement · ROC) | `references/clinical-evidence-figures.md` |
291292
| Generated + sourced imagery (when/how · text-free · topical · REFERENT RULE + source tokens) | `references/image-generation.md` |
292293
| Generated-template branch (hero + shallow bg + frosted blocks) | `references/generated-template.md` |
293294
| Icons (one family · recolored · treatments) | `references/icons.md` |
@@ -1487,6 +1488,22 @@ The helper set, by job:
14871488
ordinal), `concept_equation` (ZINE=MAGAZINE word-equation), `pull_quote`/`standfirst`, `cta_button`/
14881489
`cta_pair`, `status_stamp`/`corner_tab`, `spec_card`, `year_badge`, `gradient_rule` (2-stop brand rule),
14891490
`catalogue_frame` (double-line specimen frame — museum/eastern presets).
1491+
- **Clinical / evidence-synthesis forms — reach for these before hand-rolling a scatter** (full
1492+
rules + when each is the wrong answer: `references/clinical-evidence-figures.md`):
1493+
**`deckkit.consort_flow`** (participant flow whose ARITHMETIC IS ENFORCED — every stage's count
1494+
minus its documented exclusions must equal the next stage's, or it raises with the sum spelled
1495+
out; no other gate in this skill can see a wrong number, because a wrong number is a perfectly
1496+
well-formed shape) · **`designed_charts.forest_plot`** (effect + CI per row against a null line;
1497+
ratios forced onto a LOG axis with plain-number ticks, since a linear axis draws a symmetric CI
1498+
lopsided — and it is the general form for *the same comparison measured several times*, so it
1499+
serves subgroup effects, regression coefficients and per-segment A/B lifts too) ·
1500+
**`designed_charts.km_curve`** (survival as a STEP from raw `(times, events)`, censoring ticks,
1501+
numbers-at-risk table) · **`designed_charts.bland_altman`** (method agreement as bias ± 1.96·SD
1502+
of the DIFFERENCES — two methods can correlate almost perfectly and still disagree by a fatal
1503+
constant, so a correlation is the classic wrong answer) · **`designed_charts.roc_curve`**
1504+
(square canvas + chance diagonal + AUC in the legend). A **confusion matrix** needs no new
1505+
component — it is `heat_matrix`, with the axes labelled *Predicted*/*Actual* and the caption
1506+
saying whether the cells are counts or row-normalised.
14901507
- **Sample data / overlap:** **`designed_charts.distribution`** (SPREAD, not just the average —
14911508
`groups=[(label,[v,…]),…]`; `kind="auto"` gives a box plot at n≥5, mean ± error at n=3–4, and
14921509
**refuses n<3**; every observation overlaid; `err="sd"|"se"|"ci95"` is printed ON the figure).
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Clinical and evidence-synthesis figures
2+
3+
The forms a trial, a meta-analysis, a diagnostic-accuracy study or a method-comparison paper is
4+
*made of* — and the fidelity rules each one carries.
5+
6+
## Why this file exists
7+
8+
`design-by-topic.md` lists **Medicine / biotech / clinical** as a first-class domain and tells you
9+
which PRESET to dress it in. Every row in that table is an *aesthetic* row: it changes the palette
10+
and the type, and **none of them changes the FORM vocabulary**. Measured by grep across
11+
`references/`, `scripts/` and `agents/` before these components existed, there were zero hits for
12+
*kaplan-meier*, *forest plot*, *CONSORT*, *PRISMA*, *Bland-Altman* or *ROC*.
13+
14+
The two vocabularies the skill already had are both real and both from somewhere else:
15+
16+
| existing | what it actually covers |
17+
|---|---|
18+
| `designed_charts` (waterfall · marimekko · pareto · radar · slope · dumbbell) | **business analytics** |
19+
| `schematic-diagrams.md` (free-body · optics · circuit · apparatus · vector · wave) | **undergraduate physics** |
20+
21+
So the skill could *style* a clinical deck and could not *draw* one, and the author's only option
22+
was a hand-rolled scatter — which is exactly where the rules below get broken silently, because a
23+
wrong chart is a perfectly well-formed set of shapes and no geometry lint can see it.
24+
25+
## Pick by the question the slide answers
26+
27+
| the question | form | call |
28+
|---|---|---|
29+
| Who got in, who dropped out, who was analysed? | participant flow | `deckkit.consort_flow` |
30+
| Does the same effect hold across studies / subgroups? | forest plot | `designed_charts.forest_plot` |
31+
| Do these two groups differ in time-to-event? | survival curve | `designed_charts.km_curve` |
32+
| Do these two methods MEASURE the same thing? | agreement plot | `designed_charts.bland_altman` |
33+
| How well does this classifier separate two classes? | ROC | `designed_charts.roc_curve` |
34+
| Which classes does the model confuse? | confusion matrix | `deckkit.heat_matrix` (see below) |
35+
| Is this value a mean of MEASUREMENTS, not a count? | distribution | `designed_charts.distribution` |
36+
37+
**A confusion matrix needs no new component.** `heat_matrix(values, row_labels, col_labels,
38+
scale="seq", cell_labels=…)` is one, with two conventions to set yourself: label the axes
39+
*Predicted* (columns) and *Actual* (rows) so the reader knows which way round it is, and say in the
40+
caption whether the cells are COUNTS or ROW-NORMALISED rates — the same matrix tells opposite
41+
stories under the two readings, and an unlabelled one is unreadable on an imbalanced dataset.
42+
43+
## The rules each component owns
44+
45+
These are the ones that are wrong *by default* when the form is hand-rolled. Each is enforced in
46+
code, so you get it right by reaching for the component rather than by remembering.
47+
48+
- **`consort_flow` — the arithmetic is ENFORCED.** Every stage's count minus its documented
49+
exclusions must equal the next stage's count; it raises with the sum spelled out when it does
50+
not. A flow diagram whose numbers do not balance is the commonest error in trial and review
51+
reporting and the first thing a referee adds up by hand. 🔴 **Nothing else in this skill can
52+
check a claim of this kind** — the lints measure geometry, the critic reads pixels, and both pass
53+
a diagram that loses fourteen patients between two boxes.
54+
- **`forest_plot` — ratios live on a LOG axis.** An OR/RR/HR is symmetric in log space (0.5 and 2
55+
are the same effect in opposite directions), so on a linear axis the left half of every CI is
56+
squashed and the right stretched, and the eye reads an asymmetry that is not in the data. `log`
57+
defaults to `null == 1`; pass `null=0` for a difference scale. Ticks are forced to plain numbers
58+
— matplotlib's log formatter renders the null as `10⁰`, and a reader looking for the line at ONE
59+
should not have to decode an exponent. A non-positive bound on a log axis raises.
60+
- **`km_curve` — survival is a STEP, and the numbers at risk are part of the figure.** The estimate
61+
changes only AT an observed event; a straight line between observations asserts deaths on days
62+
nobody was seen. Censoring ticks are what separate "we stopped watching" from "nothing happened".
63+
The at-risk table is the part reviewers ask for first: a curve whose right tail rests on two
64+
patients looks identical to one resting on two hundred. Pass raw `(times, events)` — the
65+
estimator runs here, so there is nothing to get wrong by hand.
66+
- **`bland_altman` — agreement is not correlation.** Two methods can correlate almost perfectly and
67+
still disagree by a clinically fatal constant, so a scatter of A against B with an r value is the
68+
classic wrong answer. The limits are `bias ± 1.96·SD of the DIFFERENCES`, drawn and labelled with
69+
their values, because the number a reader judges is "where do 95% of individual disagreements
70+
fall, and can my application tolerate that?". A funnel shape means the disagreement grows with
71+
magnitude — switch to `percent=True`.
72+
- **`roc_curve` — square canvas, chance diagonal, AUC in the legend.** On a stretched axis every
73+
classifier looks better than it is, and without the diagonal there is no visual anchor for "no
74+
better than guessing". AUC is printed because the curve alone does not let a reader rank two
75+
models that cross. Pass raw `(y_true, y_score)`; the curve and the AUC are computed here.
76+
77+
## What this file does NOT cover
78+
79+
No component is supplied for a **phylogenetic tree**, a **chemical reaction scheme**, or a
80+
**theorem–proof structure**. `deckkit.org_tree` is a tidy hierarchy and will draw a small clade
81+
legibly, but it is not a phylogeny (no branch lengths, no support values) — say so rather than
82+
implying one. For a reaction scheme, `schematic-diagrams.md`'s image-tool path with native labels
83+
is the honest route today.
84+
85+
🔴 **And the fidelity floor is unchanged and outranks all of this.** These components make the
86+
*form* correct; they cannot make the *numbers* true. Every count, estimate, interval and p-value
87+
still traces to the source under the never-invent rule, and a plausible-looking curve drawn from
88+
numbers nobody checked is worse than no figure, because it is harder to doubt.

skills/slide-maker/references/data-viz.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,21 @@ dk.takeaway_rail(s, band[0]+band[2]*0.66, band[1]+0.3, band[2]*0.34,
248248
Short status updates, qualitative talks, or a slide with a single obvious number (use a hero stat /
249249
`scorecard`, not a chart). Reserve the richer roster for data/report/market/strategy decks that have
250250
many distinct quantitative relationships to show.
251+
252+
## Clinical / evidence-synthesis charts
253+
254+
Routed in full by `references/clinical-evidence-figures.md`. They are NOT in the roster above
255+
because that roster is a business-analytics vocabulary (waterfall, marimekko, pareto, radar) —
256+
which is the whole reason the clinical file exists.
257+
258+
- `designed_charts.forest_plot` — effect + CI per row against a null line. Ratios on a LOG axis.
259+
- `designed_charts.km_curve` — survival as a STEP, censoring ticks, numbers at risk.
260+
- `designed_charts.bland_altman` — method agreement: bias ± 1.96·SD of the DIFFERENCES.
261+
- `designed_charts.roc_curve` — square canvas, chance diagonal, AUC in the legend.
262+
- `deckkit.consort_flow` — participant flow whose arithmetic is enforced.
263+
- a confusion matrix is `deckkit.heat_matrix`, axes labelled Predicted / Actual.
264+
265+
🔴 **The chart anti-patterns above apply here unchanged**, and one of them bites hardest in this
266+
family: a bar chart of per-subject MEANS hides n, the spread and the outliers. If the value is a
267+
mean of measurements — per-subject Dice, per-run latency, per-rater score — the form is
268+
`designed_charts.distribution`, not a bar.

skills/slide-maker/references/design-by-topic.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,29 @@ low-contrast / high-texture spine under a data-dense deck).
7171
| Legal / policy / government | `editorial_report` · `swiss` · `consulting` | `synthwave` / `memphis` — undermines authority |
7272
| Chinese cultural / literary / heritage | `ink_wash` · `eastern_traditional` · `museum_memorial` | `dark_tech` / `glassmorphism` — screen-glossy, off the register 留白 wants |
7373

74+
## 🔴 This table adapts the LOOK. Some domains also need their own FORM vocabulary.
75+
76+
Every row above is an *aesthetic* row — which preset, which anti-pick. That is deliberate and it is
77+
also a real limit worth naming, because it is easy to read a matched preset as "the deck now fits
78+
the domain". It does not: a clinical deck dressed in `editorial_paper` is still drawing business
79+
charts unless someone changes the forms.
80+
81+
Measured before the clinical components existed: `design-by-topic` had listed **Medicine /
82+
biotech / clinical** as a first-class domain for a long time, while a grep across `references/`,
83+
`scripts/` and `agents/` returned zero hits for kaplan-meier, forest plot, CONSORT, PRISMA,
84+
Bland-Altman and ROC. The skill knew how to STYLE a clinical deck and could not DRAW one.
85+
86+
| domain | its own FORM vocabulary lives in |
87+
|---|---|
88+
| Medicine / biotech / clinical · any trial, meta-analysis, diagnostic-accuracy or method-comparison deck | `references/clinical-evidence-figures.md` |
89+
| Physics / chemistry / engineering — mechanisms and apparatus | `references/schematic-diagrams.md` |
90+
| Everything else | `references/form-selection.md` + `references/data-viz.md` |
91+
92+
**When a domain you are working in has no row here, that is information, not permission.** Say so
93+
in the design plan rather than reaching for the nearest business chart: a bar of per-subject means
94+
and a correlation between two measurement methods are both well-formed pictures and both the wrong
95+
answer, and no gate in this skill can see either.
96+
7497
## Guardrail VETOES (a style must never fight the content)
7598

7699
- **SOMBER / SENSITIVE** — death, disease, disaster, layoffs, war, safety-critical medicine forbid

0 commit comments

Comments
 (0)