Files
ThothII/presentation/prototype/content-proposals.html
T
Codex 0f1391e1b7 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.
2026-09-06 23:06:28 +02:00

229 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>