feat: AritmoLab AI presentation deck (work in progress)

Reveal.js deck + prototypes + speaker scripts for the talk
'Role of AI in the Analysis of Unstructured Clinical Databases'
(AritmoLab — Policlinico San Donato, 2-3 October 2026).

Lives on its own branch while in progress; not for main until ready.
This commit is contained in:
Codex
2026-09-06 23:06:28 +02:00
parent cffa60772e
commit 0f1391e1b7
140 changed files with 36007 additions and 0 deletions
@@ -0,0 +1,228 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · Content proposals — Role of AI in Unstructured Clinical Databases</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,400&family=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bordeaux: #cb333b; --bordeaux-tint: #f4dfe1; --bordeaux-deep: #8f1204;
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
--ink: #2a2426; --ink-muted: #6b6568;
--line: #e3e0e1; --line-strong: #c9c5c6;
--sans: 'Manrope', system-ui, sans-serif; --display: 'Fraunces', Georgia, serif;
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--surface-sunken); font-family: var(--sans); padding: 0 0 80px; color: var(--ink); }
.sheet { max-width: 1280px; width: calc(100% - 32px); margin: 40px auto 0; background: var(--surface);
border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 1px 2px rgba(0,0,0,.03);
overflow: hidden; }
.head { flex: 0 0 104px; height: 104px; display: flex; align-items: center; justify-content: space-between;
padding: 0 40px; background: var(--surface); border-bottom: 1px solid var(--line); }
.head img { height: 80px; display: block; }
.head-org { font: 700 11.5px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted); }
.band { height: 46px; display: flex; align-items: center; justify-content: space-between;
padding: 0 40px; background: var(--bordeaux); color: var(--surface); }
.band .band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
.band .tabs { display: flex; gap: 28px; }
.band .tabs a { font: 600 12px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
color: rgba(255,255,255,.6); padding: 17px 0 13px; border-bottom: 3px solid transparent;
text-decoration: none; cursor: pointer; }
.band .tabs a.on { color: #fff; border-bottom-color: #fff; }
.lede { padding: 40px 48px 30px; border-bottom: 1px solid var(--line); }
.kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
color: var(--bordeaux); margin-bottom: 14px; }
.lede h1 { font: 600 34px/1.15 var(--display); letter-spacing: -.01em; }
.lede p { margin-top: 14px; font: 400 15.5px/1.55 var(--sans); color: var(--ink-muted); max-width: 88ch; }
.meta { display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap; }
.meta span { font: 700 11.5px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
color: var(--ink-muted); border: 1px solid var(--line-strong); border-radius: 9999px; padding: 8px 14px; }
.meta span b { color: var(--bordeaux); }
.story { padding: 30px 48px 44px; display: flex; flex-direction: column; gap: 12px; }
.sl { display: flex; align-items: center; gap: 20px; padding: 16px 20px;
border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.sl.sp { background: var(--bordeaux-tint); border-color: #e7c3c6; }
.sl.demo { border-style: dashed; border-color: var(--line-strong); }
.sl .no { flex: 0 0 40px; font: 600 26px/1 var(--display); color: var(--bordeaux); }
.sl .tx { flex: 1; min-width: 0; }
.sl .tx b { display: block; font: 700 16px/1.3 var(--sans); }
.sl .tx span { display: block; margin-top: 4px; font: 400 13.5px/1.45 var(--sans); color: var(--ink-muted); }
.sl .side { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; }
.chip { font: 700 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase;
color: var(--ink-muted); border: 1px solid var(--line-strong); border-radius: 9999px; padding: 5px 10px; }
.chip.speaker { color: #fff; background: var(--bordeaux); border-color: var(--bordeaux); }
.sl.sp .chip.speaker { background: var(--bordeaux-deep); border-color: var(--bordeaux-deep); }
.chip.tbd { color: var(--bordeaux-deep); border-color: var(--bordeaux); background: #fff; }
/* compare view */
.cmp { display: grid; grid-template-columns: 44px repeat(3, 1fr); gap: 4px 10px; padding: 26px 48px 44px; align-items: stretch; }
.cmp .h { font: 800 10.5px/1.4 var(--mono); letter-spacing: .1em; text-transform: uppercase;
color: var(--bordeaux-deep); padding: 6px 6px 8px; border-bottom: 2px solid var(--bordeaux); }
.cmp .h span { display: block; color: var(--ink-muted); font-weight: 400; text-transform: none; letter-spacing: 0; }
.cmp .no { font: 600 15px/1.2 var(--display); color: var(--bordeaux); padding: 8px 2px 6px;
border-bottom: 1px solid var(--line); }
.cmp .cell { padding: 7px 9px; border-bottom: 1px solid var(--line); border-radius: 6px; }
.cmp .cell b { display: block; font: 700 12.5px/1.3 var(--sans); color: var(--ink); }
.cmp .cell .s { display: block; margin-top: 3px; font: 600 9.5px/1 var(--mono); letter-spacing: .05em;
color: var(--ink-muted); text-transform: uppercase; }
.cmp .cell.same { border-bottom-color: transparent; }
.cmp .cell.same b { color: var(--ink-muted); font-weight: 600; }
.cmp .cell.diff { background: var(--bordeaux-tint); border-bottom-color: #e7c3c6; }
</style>
</head>
<body>
<div class="sheet">
<header class="head">
<img src="logo.png" alt="Policlinico San Donato">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">Content proposals · light layout</span>
<span class="tabs" id="tabs"></span>
</nav>
<section class="lede" id="lede"></section>
<section class="story" id="story"></section>
</div>
<script>
const proposals = {
1: {
tab: 'A · Three roles',
name: 'A — The three roles of AI',
principle: 'Organized by what the AI does: it reads the text, it co-engineers the platform, it answers questions. Three acts, one per role, with the clinical text-analysis block as the centerpiece.',
fit: 'Best for a mixed technical audience · the band navigation maps 1:1 to the acts',
risk: 'Engineering-first framing — the patient story stays implicit',
meta: [['Slides', '14'], ['Talk time', '≈ 12.3 min'], ['Sara’s block', '4 slides'], ['Demo', 'star schema + ThothII']],
slides: [
[01,'Title','Title, speakers, event, venue & date (clean — no diagram)','title','MP','40s'],
[02,'The project at a glance','Project flow: 3 sources + future → staging → integration 🧠 → star schema → datamarts 🧠 → AritmoLab (DWH + portal)','architecture','MP','75s'],
[03,'The problem — truth locked in free text','One real sentence from a discharge letter: diagnosis, history, test outcome — all prose','concept','MP','60s'],
[04,'What the text miner reads','58,438 letters · 73,389 pathologies · 10,908 drug-challenge tests · 2,307 Brugada','stats','SP','60s'],
[05,'The hard problems of clinical NLP','Negation, family attribution, bilingual IT/EN, abbreviations, clause splitting','concept','SP','75s'],
[06,'Building the clinical ontology','12 TIER1 + 5 TIER2 categories, clinical precedence ordering, semver patterns','concept','SP','75s'],
[07,'Trust the text: validation & human review','≥95% / ≥85% gates on 100 manual reviews, the v1.3.1 false-positive fix','concept','SP','75s'],
[08,'AI as co-engineer','19,764 lines of mapping YAML drafted by LLM agents, reviewed by humans, versioned in git','artifact','MP','70s'],
[09,'Genetic data — many sources, one patient','9 Kokoro genetic tables + genetic visits + file imports; genotype ↔ phenotype bridges','architecture','MP','60s','tbd'],
[10,'ThothII: ask the warehouse in plain English','NL → SQL over the star schema, generates the final datamarts','concept','MP','75s'],
[11,'From datamarts to prediction','Curated cohorts feed predictive statistics and ML models','concept','MP','60s'],
[12,'Live demo','Star schema, Superset dashboards, ThothII answering a new question','divider','MP','~2 min live'],
[13,'Lessons learned','What we would do again, what we changed','concept','MP','60s'],
[14,'Thank you + questions','Substack deep-dive videos pointer','closing','MP','25s'],
],
},
2: {
tab: 'B · One patient',
name: 'B — One patient’s journey',
principle: 'The same content, organized around a single (composite, anonymized) Brugada patient: from his admission letter to his cohort, his genetics, his datamart, a prediction. The platform is told through him.',
fit: 'Best when clinicians outnumber engineers · the most memorable arc',
risk: 'Platform and engineering content gets compressed; the two speakers must interlock tightly. Needs a clear “composite case” disclaimer.',
meta: [['Slides', '13'], ['Talk time', '≈ 12.4 min'], ['Sara’s block', '4 slides'], ['Demo', 'merged into the journey']],
slides: [
[01,'Title','Title, speakers, event, venue & date (clean — no diagram)','title','MP','40s'],
[02,'The project at a glance','Project flow: 3 sources + future → staging → integration 🧠 → star schema → datamarts 🧠 → AritmoLab (DWH + portal)','architecture','MP','75s'],
[03,'Meet the patient','Syncope, ECG with ST elevation, positive flecainide test — one letter tells it all','concept','MP','60s'],
[04,'Where his story lives','Hospital systems keep the truth in free-text columns','concept','MP','60s'],
[05,'Reading his letter','The hard NLP problems: negation, family, bilingual, clauses','concept','SP','75s'],
[06,'His diagnosis, in our language','The clinical ontology: categories, precedence, versions','concept','SP','75s'],
[07,'His test, his cohort','Positive drug challenge → one of 2,307 confirmed Brugada patients','stats','SP','60s'],
[08,'Trusting what we read','Validation protocol, manual reviews, clinician dashboards','concept','SP','60s'],
[09,'His genetics','Many sources, one patient: reconciling genetic data','architecture','MP','60s','tbd'],
[10,'His data, queryable','Star schema + dashboards','divider','MP','~2 min live'],
[11,'Ask in plain English','ThothII over the warehouse: datamarts on demand','concept','MP','75s'],
[12,'From his cohort to prediction','Cohorts → predictive statistics → ML models','concept','MP','60s'],
[13,'Lessons, questions, thanks','Substack deep-dives pointer','closing','MP','60s'],
],
},
3: {
tab: 'C · Three questions',
name: 'C — From questions to answers',
principle: 'Start from three research questions the clinic could not answer: ablation effectiveness in Brugada, gene–phenotype associations, predicting progression. Each AI role appears only as the enabler that unlocks one answer.',
fit: 'Best when the room wants results first · strongest opening hook (real numbers on slide 6)',
risk: 'The NLP/ontology depth shrinks — Sara’s block goes from 4 to 2–3 slides; engineering becomes invisible unless narrated',
meta: [['Slides', '13'], ['Talk time', '≈ 12.4 min'], ['Sara’s block', '2–3 slides'], ['Demo', 'a brand-new question, live']],
slides: [
[01,'Title','Title, speakers, event, venue & date (clean — no diagram)','title','MP','40s'],
[02,'The project at a glance','Project flow: 3 sources + future → staging → integration 🧠 → star schema → datamarts 🧠 → AritmoLab (DWH + portal)','architecture','MP','60s'],
[03,'Three questions we could not answer','Q1 ablation effectiveness in Brugada · Q2 gene–phenotype associations · Q3 predicting progression','concept','MP','75s'],
[04,'Where the answers hide','The answers exist — written in free text, scattered across systems','concept','MP','60s'],
[05,'Q1 — reading 58,438 letters','Clinical NLP: problems + ontology, compressed','concept','SP','90s'],
[06,'Q1 answered — and validated','1,244 Brugada patients with effective ablation; ≥95% gate on 100 manual reviews','stats','SP','60s'],
[07,'Q2 — reconciling genetics','Many sources, one patient','architecture','MP','60s','tbd'],
[08,'Q2 answered — genotype ↔ phenotype','Links between variants and clinical pictures','concept','MP','60s'],
[09,'Q3 — from datamarts to prediction','ThothII builds the cohorts; statistics and ML do the rest','concept','MP','75s'],
[10,'Live demo — a new question','ThothII answers a question from the audience','divider','MP','~2 min live'],
[11,'What we learned building it','Lessons learned','concept','MP','60s'],
[12,'Trust by design — recap','Deterministic, versioned, human-validated','concept','MP','45s'],
[13,'Thank you + questions','Substack deep-dive videos pointer','closing','MP','25s'],
],
},
};
const c = ['0','1','2','3'].includes(new URLSearchParams(location.search).get('c')) ? new URLSearchParams(location.search).get('c') : '1';
document.getElementById('tabs').innerHTML =
`<a href="?c=0" class="${c === '0' ? 'on' : ''}">⚡ Compare</a>` +
Object.entries(proposals).map(([k, v]) => `<a href="?c=${k}" class="${k === c ? 'on' : ''}">${v.tab}</a>`).join('');
if (c === '0') {
document.getElementById('lede').innerHTML = `
<div class="kicker">Compare · three story organizations, one consolidated layout</div>
<h1>Same skeleton, different story</h1>
<p>All three proposals share the same skeleton: clean opening → <b>project flow as slide 02</b> →
the clinical text-analysis block → engineering → live demo → thanks. What changes is
<b>what comes right after the flow</b> and the angle of the narration.
Pink rows = that proposal tells something different in that position; grey rows are identical.</p>
<div class="meta">
<span><b>A</b> · by AI role · 14 slides</span>
<span><b>B</b> · by one patient · 13 slides</span>
<span><b>C</b> · by research questions · 13 slides</span>
</div>`;
const names = {
1: 'A · Three roles<span>classic technical arc</span>',
2: 'B · One patient<span>narrative arc, clinician-friendly</span>',
3: 'C · Three questions<span>results-first, strongest hook</span>',
};
const maxLen = Math.max(...Object.values(proposals).map(p => p.slides.length));
let rows = `<div class="h"></div>` + [1, 2, 3].map(k => `<div class="h">${names[k]}</div>`).join('');
for (let i = 0; i < maxLen; i++) {
const cells = [1, 2, 3].map(k => proposals[k].slides[i] || null);
const titles = cells.map(s => (s ? s[1] : null));
const same = titles[0] !== null && titles.every(t => t === titles[0]);
rows += `<div class="no">${String(i + 1).padStart(2, '0')}</div>` + cells.map(s =>
s ? `<div class="cell ${same ? 'same' : 'diff'}"><b>${s[1]}</b><span class="s">${s[4]} · ${s[5]}</span></div>`
: `<div class="cell"><span class="s">—</span></div>`).join('');
}
document.getElementById('story').innerHTML = `<div class="cmp">${rows}</div>`;
} else {
const p = proposals[c];
document.getElementById('lede').innerHTML = `
<div class="kicker">Proposal ${c} of 3 · same consolidated light layout</div>
<h1>${p.name}</h1>
<p>${p.principle}</p>
<div class="meta">
${p.meta.map(([k, v]) => `<span>${k}: <b>${v}</b></span>`).join('')}
</div>
<p><strong>Fit:</strong> ${p.fit}. <strong>Watch out:</strong> ${p.risk}.</p>`;
document.getElementById('story').innerHTML = p.slides.map(([no, title, desc, master, sp, t, flag]) => `
<div class="sl ${sp === 'SP' ? 'sp' : ''} ${master === 'divider' ? 'demo' : ''}">
<span class="no">${String(no).padStart(2, '0')}</span>
<span class="tx"><b>${title}</b><span>${desc}</span></span>
<span class="side">
${flag === 'tbd' ? '<span class="chip tbd">wording TBD</span>' : ''}
<span class="chip">${master}</span>
<span class="chip speaker">${sp}</span>
<span class="chip">${t}</span>
</span>
</div>`).join('');
}
</script>
</body>
</html>