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>
+161
View File
@@ -0,0 +1,161 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · Font variants — AritmoLab deck</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=Manrope:wght@400;600;700;800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;600;700&family=IBM+Plex+Serif:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400&display=swap" rel="stylesheet">
<style>
:root {
--bordeaux: #cb333b; --bordeaux-deep: #8f1204; --bordeaux-tint: #f4dfe1;
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
--ink: #2a2426; --ink-muted: #6b6568;
--line: #e3e0e1; --line-strong: #c9c5c6;
--mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
}
body.f1 { --sans: 'Manrope', system-ui, sans-serif; --display: 'Manrope', system-ui, sans-serif; }
body.f2 { --sans: 'IBM Plex Sans', system-ui, sans-serif; --display: 'IBM Plex Serif', Georgia, serif; }
body.f3 { --sans: 'Source Sans 3', system-ui, sans-serif; --display: 'Source Serif 4', Georgia, serif; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--surface-sunken); font-family: var(--sans); padding: 36px 0 90px; }
.note { width: 1280px; margin: 0 auto 18px; padding: 12px 18px; background: #fff8e6; border: 1px solid #e8d9a8;
border-radius: 8px; font: 400 13px/1.5 var(--sans); color: #6b5d2a; }
.note b { font-weight: 700; }
.switcher { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
display: flex; align-items: center; gap: 6px; padding: 8px 10px; background: #2a2426;
border-radius: 9999px; box-shadow: 0 6px 24px rgba(0,0,0,.25); z-index: 10; white-space: nowrap; }
.switcher .tag { font: 700 10px/1 var(--mono); letter-spacing: .1em; color: #9c9598; padding: 0 10px; }
.switcher a { display: inline-flex; height: 30px; align-items: center; padding: 0 14px; border-radius: 9999px;
font: 700 12px/1 var(--sans); color: #fdfcfc; text-decoration: none; opacity: .75; }
.switcher a.active { background: var(--bordeaux); opacity: 1; }
.switcher .who { font: 400 11px/1 var(--sans); color: #9c9598; padding: 0 12px 0 8px; max-width: 420px; white-space: normal; }
.slide { position: relative; width: 1280px; height: 720px; margin: 0 auto 34px;
display: flex; flex-direction: column; overflow: hidden;
background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; }
.head { flex: 0 0 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 { flex: 0 0 46px; display: flex; align-items: center; justify-content: space-between;
padding: 0 40px; background: var(--bordeaux); color: var(--surface); }
.band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
.jump { background: transparent; color: #fff; border: none; padding: 6px 12px;
font: 700 11.5px/1.2 var(--sans); letter-spacing: .04em; }
.foot { flex: 0 0 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
padding: 0 40px; border-top: 1px solid var(--line);
font: 600 10px/1.4 var(--sans); letter-spacing: .03em; color: var(--ink-muted); }
.foot span { white-space: nowrap; }
.foot span:first-child { overflow: hidden; text-overflow: ellipsis; }
.sbody { flex: 1; display: flex; flex-direction: column; padding: 32px 48px 22px; }
.kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 14px; }
h1 { font: 600 46px/1.15 var(--display); letter-spacing: -.01em; color: var(--ink); }
h2 { font: 700 30px/1.15 var(--sans); color: var(--ink); margin: 0 0 10px; }
.lead { font: 400 16px/1.5 var(--sans); color: var(--ink-muted); margin-top: 10px; }
.hgap { height: 16px; }
.title-slide .sbody { justify-content: center; }
.title-meta { display: flex; align-items: center; gap: 14px; margin-top: 26px; }
.pill { display: inline-flex; align-items: center; height: 34px; padding: 0 18px; border-radius: 9999px;
background: var(--bordeaux); color: #fdfcfc; font: 700 12.5px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; }
.what { font: 400 14.5px/1.5 var(--sans); color: var(--ink-muted); }
.speakers { margin-top: 20px; display: flex; flex-direction: column; gap: 7px; }
.speakers span { font: 600 15px/1.4 var(--sans); }
.event { margin-top: 14px; font: italic 400 15px/1.5 var(--display); color: var(--ink-muted); }
.event-where { margin-top: 7px; font: 600 13.5px/1.4 var(--sans); color: var(--ink-muted); }
.points { list-style: none; }
.points li { position: relative; padding: 10px 0 10px 26px; border-bottom: 1px solid var(--line);
font: 400 15px/1.45 var(--sans); }
.points li::before { content: ''; position: absolute; left: 0; top: 17px; width: 10px; height: 10px;
border-radius: 9999px; background: var(--bordeaux); }
.points b { font-weight: 700; }
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; margin: 8px 0 6px; }
.stat { border: 1px solid var(--line); border-radius: 8px; background: var(--surface-alt);
padding: 22px 20px; display: flex; flex-direction: column; gap: 8px; }
.stat .num { font: 600 44px/1 var(--display); color: var(--bordeaux); letter-spacing: -.02em; }
.stat .lbl { font: 600 13px/1.35 var(--sans); }
.stat .sub { font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); }
.quote { padding: 14px 20px; border-left: 3px solid var(--bordeaux); background: var(--surface-alt);
border-radius: 0 8px 8px 0; font: italic 400 15.5px/1.5 var(--display); margin: 4px 0 14px; }
.code-line { font: 400 13px/1.6 var(--mono); color: var(--ink); background: var(--surface-sunken);
border: 1px solid var(--line-strong); border-radius: 8px; padding: 10px 16px; }
.code-line b { color: var(--bordeaux-deep); }
</style>
</head>
<body class="f1">
<div class="note"><b>EVALUATION ONLY</b> — in questo prototipo i font candidati vengono caricati da CDN solo per essere valutati.
Nel file finale il vincitore verrà <b>incorporato (woff2 locale)</b>: zero dipendenze da Google a runtime, connection pure assente.
Oppure: resta Manrope+Fraunces incorporati, nessun cambiamento visivo.</div>
<main id="deck"></main>
<nav class="switcher">
<span class="tag">PROTOTYPE</span>
<a href="?f=1" data-v="1">1 · Manrope only</a>
<a href="?f=2" data-v="2">2 · IBM Plex</a>
<a href="?f=3" data-v="3">3 · Source</a>
<span class="who" id="who"></span>
</nav>
<script>
const VARIANTS = {
1: { desc: 'Manrope per tutto (titoli in 800), come il sito Gruppo San Donato — coerenza di brand massima, nessun serif. È il font che il portale GSD usa davvero.' },
2: { desc: 'IBM Plex Sans (testi) + IBM Plex Serif (titoli): carattere tecnico-istituzionale, nasce per documentazione e presentazioni; Il corsivo del serif è molto leggibile.' },
3: { desc: 'Source Sans 3 + Source Serif 4 (Adobe, open source): umanista-clinico, pesi e proporzioni molto vicini a Manrope+Fraunces — la via di mezzo se vuoi cambiare senza stravolgere.' },
};
const q = new URLSearchParams(location.search);
const v = ['1','2','3'].includes(q.get('f')) ? q.get('f') : '1';
document.body.className = 'f' + v;
document.querySelectorAll('.switcher a').forEach(a => a.classList.toggle('active', a.dataset.v === v));
document.getElementById('who').textContent = VARIANTS[v].desc;
const foot = `<footer class="foot"><span>Role of AI in the Analysis of Unstructured Clinical Databases · Dr. Marco Pancotti - MultiPhysixLab · Dr. Sara Paratico - I.R.C.C.S. Policlinico San Donato · San Donato Milanese, Milan, Italy · 2–3 October 2026</span><span>0X / 14</span></footer>`;
const head = `<header class="head"><img src="logo.png" alt=""><span class="head-org">AritmoLab · Policlinico San Donato</span></header>
<nav class="band"><span class="band-section">Sample</span><span class="jump">02 · The project at a glance</span></nav>`;
document.getElementById('deck').innerHTML = `
<section class="slide title-slide">
${head}
<div class="sbody">
<h1>Role of AI in the Analysis of Unstructured Clinical Databases</h1>
<p class="lead">How we turned a mix of structured and free-text clinical records into a research data platform — the AritmoLab platform experience.</p>
<div class="title-meta"><span class="pill">AritmoLab</span><span class="what">A clinical data platform hosting cardiology data and analysis tools</span></div>
<div class="speakers"><span>Dr. Marco Pancotti - MultiPhysixLab</span><span>Dr. Sara Paratico - I.R.C.C.S. Policlinico San Donato</span></div>
<p class="event">“Multidimensional Characterization of Cardiac Arrhythmias: Role of Electrocardiology in the Artificial Intelligence Era”</p>
<p class="event-where">San Donato Milanese, Milan, Italy · 2–3 October 2026</p>
</div>
${foot}
</section>
<section class="slide">
${head}
<div class="sbody">
<div class="kicker">Sample content — headings, numbers, quote, code</div>
<h2>Deterministic AI, audited numbers</h2>
<div class="hgap"></div>
<div class="stat-row">
<div class="stat"><span class="num">58,438</span><span class="lbl">discharge letters read</span><span class="sub">bilingual IT/EN patterns</span></div>
<div class="stat"><span class="num">73,389</span><span class="lbl">pathologies extracted</span><span class="sub">two-tier clinical ontology</span></div>
<div class="stat"><span class="num">2,307</span><span class="lbl">Brugada patients identified</span><span class="sub">clinical review on dashboards</span></div>
</div>
<div class="quote">“Il paziente riferisce sincope ricorrente; ECG basale con sopraslivellamento ST in V1–V3; test provocativo con flecainide positivo per pattern Brugada.”</div>
<ul class="points">
<li><b>Negation</b> — “fibrillazione atriale esclusa” is not a diagnosis: rules scan a window around every match</li>
<li><b>Versioned like software</b> — every extracted row records its pattern-library semver</li>
</ul>
<div class="hgap"></div>
<div class="code-line"><b>analysis_function</b>: get_letter_pathologies_cascade · <b>integration_table</b>: cr_lettera_dimissione_diagnosi</div>
</div>
${foot}
</section>`;
</script>
</body>
</html>
+405
View File
@@ -0,0 +1,405 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · Icon variants — legacy systems (slides 02 & 03)</title>
<link rel="stylesheet" href="../deck/fonts.css">
<style>
:root {
--bordeaux: #cb333b; --bordeaux-deep: #8f1204; --bordeaux-tint: #f4dfe1;
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
--ink: #2a2426; --ink-muted: #6b6568;
--line: #e3e0e1; --line-strong: #c9c5c6;
--sans: 'Source Sans 3', system-ui, sans-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: 36px 0 90px; color: var(--ink); }
.note { width: 1280px; margin: 0 auto 18px; padding: 12px 18px; background: #fff8e6; border: 1px solid #e8d9a8;
border-radius: 8px; font: 400 13px/1.5 var(--sans); color: #6b5d2a; }
.note b { font-weight: 700; }
.switcher { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
display: flex; align-items: center; gap: 6px; padding: 8px 10px; background: #2a2426;
border-radius: 9999px; box-shadow: 0 6px 24px rgba(0,0,0,.25); z-index: 10; white-space: nowrap; }
.switcher .tag { font: 700 10px/1 var(--mono); letter-spacing: .1em; color: #9c9598; padding: 0 10px; }
.switcher a { display: inline-flex; height: 30px; align-items: center; padding: 0 14px; border-radius: 9999px;
font: 700 12px/1 var(--sans); color: #fdfcfc; text-decoration: none; opacity: .75; }
.switcher a.active { background: var(--bordeaux); opacity: 1; }
.switcher .who { font: 400 11px/1 var(--sans); color: #9c9598; padding: 0 12px 0 8px; max-width: 430px; white-space: normal; }
.slide { position: relative; width: 1280px; margin: 0 auto 34px; display: flex; flex-direction: column;
overflow: hidden; background: var(--surface); color: var(--ink); border: 1px solid var(--line);
border-radius: 8px; height: 720px; }
.head { flex: 0 0 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 { flex: 0 0 46px; display: flex; align-items: center; justify-content: space-between;
padding: 0 40px; background: var(--bordeaux); color: var(--surface); }
.band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
.jump { background: transparent; color: #fff; border: none; padding: 6px 12px;
font: 700 11.5px/1.2 var(--sans); letter-spacing: .04em; }
.foot { flex: 0 0 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
padding: 0 40px; border-top: 1px solid var(--line); font: 600 10px/1.4 var(--sans);
letter-spacing: .03em; color: var(--ink-muted); }
.foot span { white-space: nowrap; }
.foot span:first-child { overflow: hidden; text-overflow: ellipsis; }
.sbody { flex: 1; display: flex; flex-direction: column; padding: 30px 48px 22px; }
.kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
color: var(--bordeaux); margin-bottom: 14px; }
h2 { font: 700 30px/1.15 var(--sans); color: var(--ink); margin: 0 0 8px; }
.hint { margin-top: 12px; font: italic 400 12px/1.4 var(--sans); color: var(--ink-muted); }
.lead { font: 400 15px/1.5 var(--sans); color: var(--ink-muted); margin: 4px 0 14px; }
/* ---------- SLIDE 02 · system map ---------- */
.sysmap { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; flex: 1; align-content: center; }
.syscard { position: relative; display: flex; flex-direction: column; align-items: center; width: 100%;
border: 1.5px solid var(--line-strong); border-radius: 10px; background: var(--surface-alt);
padding: 18px 16px 14px; text-align: center; cursor: pointer; font-family: var(--sans);
transition: transform .15s ease, box-shadow .15s ease; }
.syscard:hover, .syscard:focus-visible { transform: translateY(-3px); box-shadow: 0 6px 18px rgba(42,36,38,.14); outline: none; }
.syscard b { display: block; font: 700 17px/1.3 var(--sans); color: var(--ink); margin: 10px 0 5px; }
.syscard .d { display: block; font: 400 12.5px/1.45 var(--sans); color: var(--ink-muted); }
.syscard .tag { position: absolute; top: -10px; right: 12px; background: var(--bordeaux); color: #fff;
border-radius: 9999px; padding: 3px 10px; font: 700 9.5px/1 var(--sans); letter-spacing: .08em; }
.syscard.ghost { border: 1.5px dashed var(--bordeaux); background: #fff; cursor: default; }
.syscard.ghost b { color: var(--bordeaux-deep); }
.syscard.ghost .tag { background: var(--bordeaux-deep); }
/* ---------- SLIDE 03 · flow with iconized sources ---------- */
.flow { display: flex; align-items: stretch; gap: 14px; flex: 1; min-height: 0; margin-top: 6px; }
.zone { border-radius: 8px; padding: 28px 14px 12px; position: relative; }
.zone > .zlabel { position: absolute; top: 8px; left: 14px; font: 700 10px/1 var(--mono);
letter-spacing: .14em; text-transform: uppercase; }
.zone.existed { flex: 0 0 210px; background: var(--surface-alt); border: 1px dashed var(--line-strong); }
.zone.existed > .zlabel { color: var(--ink-muted); }
.zone.built { flex: 1; background: #faf4f5; border: 1px solid #e7c3c6; }
.zone.built > .zlabel { color: var(--bordeaux); }
.zone.built .row { display: flex; align-items: center; gap: 10px; height: 100%; }
.fcol { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.fbox { background: #fff; border: 1px solid var(--line-strong); border-radius: 6px; padding: 9px 8px; text-align: center; }
.fbox b { display: block; font: 700 12.5px/1.25 var(--sans); }
.fbox span { display: block; margin-top: 2px; font: 400 10.5px/1.3 var(--sans); color: var(--ink-muted); }
.fbox.end { background: var(--bordeaux); border-color: var(--bordeaux); color: #fff; }
.fbox.end span { color: rgba(255,255,255,.85); }
.farrow { align-self: center; color: var(--bordeaux); font: 700 14px/1 var(--sans); }
.fcol.src-icons { flex: 0 0 190px; justify-content: space-evenly; }
.src-item { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.src-item b { font: 700 12px/1.2 var(--sans); }
</style>
</head>
<body class="i1">
<div class="note"><b>EVALUATION</b> — tre ipotesi di iconografia disegnata per i 4 sistemi di partenza (+ 2 "missing"),
applicate a slide 02 e 03. Nel file finale il set vincitore verrà <b>incorporato come SVG inline</b> (nessuna dipendenza, scalabile, colorabile col brand).</div>
<main id="deck"></main>
<nav class="switcher">
<span class="tag">PROTOTYPE</span>
<a href="?i=1" data-v="1">1 · App badges</a>
<a href="?i=2" data-v="2">2 · Era artifacts</a>
<a href="?i=3" data-v="3">3 · Emblems</a>
<span class="who" id="who"></span>
</nav>
<script>
/* ================================================================
ICONS — hand-drawn SVG, three styles
================================================================ */
const S = (inner, vb = '0 0 48 48', cls = '') =>
`<svg class="ico ${cls}" viewBox="${vb}" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">${inner}</svg>`;
const MONO = {
cardioref: S(`<rect x="8" y="6" width="32" height="36" rx="3"/>
<path d="M8 14 H40"/><path d="M31 10 h6"/><path d="M34 7 v6"/>
<path d="M13 22 H28"/><path d="M13 28 H34"/><path d="M13 34 H22"/>
<path d="M29 36 c0-2.2 3-3.2 4.2-1.2 1.2-2 4.2-1 4.2 1.2 0 2.4-4.2 4.4-4.2 4.4 s-4.2-2-4.2-4.4z"/>`),
genetic: S(`<path d="M18 4 C30 11 30 19 18 26 C10 31 10 37 18 44"/>
<path d="M30 4 C18 11 18 19 30 26 C38 31 38 37 30 44"/>
<path d="M17 9 H31"/><path d="M15 16 H33"/><path d="M17 23 H31"/><path d="M14 32 H26"/>
<rect x="30" y="31" width="13" height="13" rx="1.5" stroke-width="2"/>
<path d="M30 36 H43"/><path d="M36 31 V44"/>`),
omics: S(`<rect x="7" y="11" width="34" height="29" rx="3"/>
<path d="M7 17 H41"/><circle cx="11" cy="14" r="0.8" fill="currentColor"/><circle cx="15" cy="14" r="0.8" fill="currentColor"/>
<path d="M24 30 c0-2.4 3.2-3.4 4.4-1.3 1.2-2.1 4.4-1.1 4.4 1.3 0 2.6-4.4 4.8-4.4 4.8 s-4.4-2.2-4.4-4.8z"/>
<path d="M12 26 h5"/><path d="M36 26 h-5"/><path d="M17 26 h2"/><path d="M34 26 h-2"/>`),
ecg: S(`<rect x="8" y="10" width="32" height="28" rx="2"/>
<path d="M12 15 h4"/><path d="M20 15 h4"/><path d="M28 15 h4"/><path d="M36 15 h-1"/>
<path d="M11 30 h6 l3-9 4 15 3-7 h10"/>
<rect x="28" y="33" width="10" height="4" rx="1" stroke-width="1.8"/>`),
};
const FLAT = {
cardioref: S(`<rect x="5" y="7" width="38" height="27" rx="2" fill="#efece7" stroke-width="2.2"/>
<rect x="8" y="10" width="32" height="21" rx="1" fill="#dfe9f4" stroke-width="1.8"/>
<rect x="8" y="10" width="32" height="4" fill="#c9d7e8" stroke="none"/>
<path d="M11 20 H26" stroke-width="1.8"/><path d="M11 24 H30" stroke-width="1.8"/>
<path d="M11 28 H20" stroke-width="1.8"/>
<path d="M30 26 c0-2 2.8-3 4-1.2 1.2-1.8 4-0.8 4 1.2 0 2.2-4 4-4 4 s-4-1.8-4-4z" fill="#cb333b" stroke="none"/>
<rect x="19" y="34" width="10" height="3" fill="#efece7" stroke-width="1.8"/>
<rect x="13" y="37" width="22" height="3" rx="1" fill="#efece7" stroke-width="1.8"/>`, '0 0 48 44'),
genetic: S(`<rect x="5" y="8" width="15" height="32" rx="2" fill="#f0f0f0" stroke-width="2.2"/>
<rect x="8" y="12" width="9" height="3" rx="1" fill="#fff" stroke-width="1.6"/>
<circle cx="10" cy="22" r="1.6" fill="#cb333b" stroke="none"/><circle cx="15" cy="22" r="1.6" fill="#cb333b" stroke="none"/>
<rect x="8" y="27" width="9" height="6" rx="1" fill="#fff" stroke-width="1.6"/>
<rect x="26" y="5" width="17" height="36" rx="1" fill="#fff" stroke-width="2.2"/>
<path d="M26 11 H43" stroke-width="1.6"/><path d="M31.5 5 V41" stroke-width="1.2"/><path d="M37.5 5 V41" stroke-width="1.2"/>
<path d="M26 17 H43" stroke-width="1.2"/><path d="M26 23 H43" stroke-width="1.2"/><path d="M26 29 H43" stroke-width="1.2"/>
<path d="M35 30 l8 8 -2.5 1 -1.5 2.5 -4-9z" fill="#cb333b" stroke="none"/>
<path d="M24 24 C24 18 24 16 24 14" stroke-width="1.6" stroke-dasharray="3 3"/>`),
omics: S(`<rect x="8" y="14" width="32" height="26" rx="2" fill="#fff" stroke-width="2.2"/>
<path d="M8 20 H40" stroke-width="1.8"/><circle cx="12" cy="17" r="1" fill="#cb333b" stroke="none"/>
<circle cx="16" cy="17" r="1" fill="#cb333b" stroke="none"/>
<path d="M24 34 c0-2.4 3.2-3.4 4.4-1.3 1.2-2.1 4.4-1.1 4.4 1.3 0 2.6-4.4 4.8-4.4 4.8 s-4.4-2.2-4.4-4.8z" fill="#cb333b" stroke="none"/>
<path d="M24 14 C24 10 20 9 18 6" stroke-width="1.8" stroke-dasharray="3 3"/>
<path d="M24 14 C24 10 28 9 30 6" stroke-width="1.8" stroke-dasharray="3 3"/>
<path d="M18 6 c0 0 3-1 4 2" stroke-width="1.8"/><path d="M30 6 c0 0 -3-1 -4 2" stroke-width="1.8"/>
<path d="M13 27 h5 l2-3 2 5" stroke-width="1.6"/><path d="M33 27 h5" stroke-width="1.6"/>`),
ecg: S(`<rect x="6" y="16" width="22" height="22" rx="2" fill="#f0f0f0" stroke-width="2.2"/>
<rect x="9" y="20" width="16" height="9" rx="1" fill="#fff" stroke-width="1.6"/>
<path d="M10.5 24.5 h3 l1.5-3 2 5 1.5-2 h5" stroke-width="1.4"/>
<circle cx="11" cy="33" r="1.8" fill="#cb333b" stroke="none"/><circle cx="16" cy="33" r="1.8" fill="#cb333b" stroke="none"/>
<rect x="31" y="6" width="11" height="32" rx="1" fill="#fff" stroke-width="2.2"/>
<path d="M34 10 h5 M34 13 h5 M34 38 h5" stroke-width="1.2"/>
<path d="M33 16 l6 2 -4 4 6 2" stroke-width="1.6"/>
<rect x="30" y="40" width="13" height="5" rx="1" fill="#cb333b" stroke="none"/>
<text x="36.5" y="44" font-family="monospace" font-size="3.6" fill="#fff" stroke="none" text-anchor="middle">CSV</text>`),
};
const EMB = {
cardioref: S(`<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M24 33 c-6-3.5-9-7-9-10.5 0-3 4-5.5 7-3 1 .8 1.6 1.8 2 3 .4-1.2 1-2.2 2-3 3-2.5 7 0 7 3 0 3.5-3 7-9 10.5z" fill="currentColor" stroke="none"/>
<path d="M22.5 21 h3 v3 h3 v3 h-3 v3 h-3 v-3 h-3 v-3 h3 z" fill="#fff" stroke="none"/>`),
genetic: S(`<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M18 8 C28 14 28 20 18 26 C12 30 12 35 18 40"/>
<path d="M30 8 C20 14 20 20 30 26 C36 30 36 35 30 40"/>
<path d="M17 12 H31"/><path d="M15.5 18 H32.5"/><path d="M17 24 H31"/><path d="M14.5 31 H22"/>
<rect x="25" y="29" width="12" height="12" rx="1.5" stroke-width="2"/>
<path d="M25 34 H37"/><path d="M30.5 29 V41"/>`),
omics: S(`<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<circle cx="24" cy="24" r="4" fill="currentColor" stroke="none"/>
<circle cx="24" cy="10" r="3.4"/><circle cx="11.5" cy="31" r="3.4"/><circle cx="36.5" cy="31" r="3.4"/>
<path d="M24 20.5 V13.5"/><path d="M21 26.5 L14 29.5"/><path d="M27 26.5 L34 29.5"/>`),
ecg: S(`<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M9 24 h6 l3-9 5 16 3-7 h14" stroke-width="2.4"/>
<circle cx="24" cy="24" r="1.8" fill="currentColor" stroke="none"/>`),
};
const MISSING = {
1: S(`<rect x="8" y="8" width="32" height="32" rx="4" stroke-dasharray="5 4" stroke-width="2.2"/>
<path d="M18 18 L30 30" stroke-width="2.4"/><path d="M30 18 L18 30" stroke-width="2.4"/>`),
2: S(`<ellipse cx="24" cy="38" rx="14" ry="4" stroke-dasharray="4 4" stroke-width="2"/>
<path d="M24 34 V16" stroke-dasharray="4 4" stroke-width="2.2"/>
<path d="M13 16 h22" stroke-width="2.2"/><path d="M24 8 v4" stroke-width="2"/>`),
3: S(`<circle cx="24" cy="24" r="20" stroke-dasharray="5 4" stroke-width="2.4"/>
<path d="M15 33 L33 15" stroke-width="2.4"/><circle cx="17" cy="17" r="2.4" fill="currentColor" stroke="none"/>`),
};
const STYLE_ICON = (style, sys) => {
const dict = { 1: MONO, 2: FLAT, 3: EMB }[style];
return dict[sys];
};
const ICON_CLS = { 1: 'i-mono', 2: 'i-flat', 3: 'i-emb' };
/* ================================================================
SLIDES
================================================================ */
const SYS = [
{ id: 'cardioref', name: 'Cardioref', scope: 'hospital procedures: visits, operations, reports — on relational forms' },
{ id: 'genetic', name: 'Genetic data', scope: 'DNA instruments — but the data is typed into Excel by hand' },
{ id: 'omics', name: 'Omics Portal', scope: 'meant to host Cardioref + genetics — still immature' },
{ id: 'ecg', name: 'ECG', scope: 'classic devices: paper strip + a CSV you can view on request' },
];
const GHOSTS = [
{ name: 'No Health Intelligence', scope: 'nothing usable to analyse historical data' },
{ name: 'No ML-ready datamarts', scope: 'nothing for predictive statistics or machine learning' },
];
const CONTRIBS = {
cardioref: { kicker: 'The starting point · 20 years ago', title: 'Cardioref — the daily workhorse',
body: `<p class="lead">A traditional relational application: classic forms for the typical hospital procedures.</p>
<ul class="pop-list">
<li><b>Used for</b> — visits, operations, reports: the daily management of the Arrhythmology Unit</li>
<li><b>Good at it</b> — twenty years of patient management on structured records</li>
<li><b>The catch</b> — the clinical story is told in free-text fields, not in the boxes designed for it</li>
</ul>
<div class="roi"><b class="k">Problem</b>Almost unusable for research: the knowledge is there — locked in prose.</div>` },
genetic: { kicker: 'The starting point · 20 years ago', title: 'Genetic data — sophisticated in, Excel out',
body: `<p class="lead">The DNA instruments are state-of-the-art. What happens after them is not.</p>
<ul class="pop-list">
<li><b>Used for</b> — running the genetic analyses that the Unit prescribes</li>
<li><b>The catch</b> — results are transcribed by hand into Excel files</li>
<li><b>Never connected</b> — Cardioref has no idea genetics exists</li>
</ul>
<div class="roi"><b class="k">Problem</b>Two parallel worlds: genotypes on one side, clinical records on the other.</div>` },
omics: { kicker: 'The starting point · 20 years ago', title: 'Omics Portal — the right idea, too soon',
body: `<p class="lead">A portal meant to host both Cardioref and the genetic data — the seed of AritmoLab.</p>
<ul class="pop-list">
<li><b>Used for</b> — the first 360° patient-management features</li>
<li><b>The catch</b> — functionality still immature</li>
<li><b>Never connected</b> — it could not see the data it was meant to host</li>
</ul>
<div class="roi"><b class="k">Problem</b>A promising idea without a bridge: too young to carry the data.</div>` },
ecg: { kicker: 'The starting point · 20 years ago', title: 'ECG — paper, and a CSV if you ask',
body: `<p class="lead">Classic ECG devices: they print the strip, and they can export a file.</p>
<ul class="pop-list">
<li><b>Used for</b> — acquiring and viewing ECG traces</li>
<li><b>The catch</b> — no machine-to-machine communication</li>
<li><b>The only door out</b> — a CSV file, produced on request</li>
</ul>
<div class="roi"><b class="k">Problem</b>Every ECG meant a manual download — automation was impossible by design.</div>` },
};
const chrome = (band) => `
<header class="head"><img src="logo.png" alt=""><span class="head-org">AritmoLab · Policlinico San Donato</span></header>
<nav class="band"><span class="band-section">${band}</span><span class="jump">0X · …</span></nav>`;
const foot = `<footer class="foot"><span>Role of AI in the Analysis of Unstructured Clinical Databases · Dr. Marco Pancotti - MultiPhysixLab · Dr. Sara Paratico - Gruppo San Donato · San Donato Milanese, Milan, Italy · 2–3 October 2026</span><span>0X / 15</span></footer>`;
function iconBox(sys, style, size) {
return `<span class="icon-wrap ${ICON_CLS[style]}" style="--icon:${size}px">${STYLE_ICON(style, sys.id)}</span>`;
}
function slide02(style) {
const cards = SYS.map(s => `
<button class="syscard" data-start="${s.id}">
${iconBox(s, style, style === '3' ? 104 : 96)}
<b>${s.name}</b><span class="d">${s.scope}</span>
</button>`).join('');
const ghosts = GHOSTS.map(g => `
<div class="syscard ghost">
<span class="icon-wrap">${S(MISSING[String(style)], '0 0 48 48')}</span>
<b>${g.name}</b><span class="d">${g.scope}</span>
</div>`).join('');
return `
<section class="slide">
${chrome('Where we started')}
<div class="sbody">
<div class="kicker">Twenty years ago</div>
<h2>Four islands and two missing pieces</h2>
<div class="sysmap">${cards}${ghosts}</div>
<p class="hint">▸ click a system: how it was used — and what held it back</p>
</div>
${foot}
<aside class="notes">[icon evaluation — content as in the deck]</aside>
</section>`;
}
function slide03(style) {
const srcIcons = SYS.map(s => `
<div class="src-item">${iconBox(s, style, style === '3' ? 64 : 58)}<b>${s.name}</b></div>`).join('');
const ghostIcon = (style) => S(MISSING[String(style)], '0 0 48 48');
return `
<section class="slide">
${chrome('The plan')}
<div class="sbody">
<div class="kicker">The plan · what we wanted to build</div>
<h2>One platform, two destinations</h2>
<div class="flow">
<div class="zone existed">
<span class="zlabel">What existed — to connect</span>
<div class="fcol src-icons">
${srcIcons}
<div class="src-item"><span class="icon-wrap" style="width:56px;height:56px;color:var(--bordeaux);opacity:.45">${ghostIcon(style)}</span><b style="color:var(--bordeaux-deep)">missing</b></div>
</div>
</div>
<div class="farrow">→</div>
<div class="zone built">
<span class="zlabel">What we wanted to build</span>
<div class="row">
<div class="fcol"><div class="fbox"><b>Staging</b><span>raw replica</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Integration</b><span>clean · text mining</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Star schema</b><span>warehouse</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Datamarts</b><span>for research</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox end"><b>AritmoLab</b><span>portal + warehouse</span></div></div>
</div>
</div>
</div>
</div>
${foot}
<aside class="notes">[icon evaluation — content as in the deck]</aside>
</section>`;
}
/* ================================================================ */
const VARIANTS = {
1: { desc: 'App badges in monoline: pictogrammi a tratto disegnati ad hoc in badge arrotondati (come icone di app). Puliti, moderni, professionali.' },
2: { desc: 'Era artifacts: mini-illustrazioni flat che raccontano l\u2019epoca — il vecchio monitor con le form, lo strumento genetico col foglio Excel e la matita, il portale germoglio, l\u2019ECG con lo strip e il tag CSV.' },
3: { desc: 'Emblems: medaglioni circolari istituzionali (stile stemma di reparto) — cuore+croce, elica, rete del portale, tracciato ECG. Sobri, molto brand GSD.' },
};
const q = new URLSearchParams(location.search);
const v = ['1','2','3'].includes(q.get('i')) ? q.get('i') : '1';
document.body.className = 'i' + v;
document.querySelectorAll('.switcher a').forEach(a => a.classList.toggle('active', a.dataset.v === v));
document.getElementById('who').textContent = VARIANTS[v].desc;
document.getElementById('deck').innerHTML = slide02(v) + slide03(v);
const pop = document.createElement('div');
pop.className = 'ai-pop';
pop.innerHTML = `<div class="ai-pop-card" role="dialog"><button class="ai-pop-x">✕</button>
<div class="ai-pop-kicker" id="kick"></div><div class="ai-pop-title" id="ttl"></div><div id="bdy"></div></div>`;
document.body.appendChild(pop);
const closePop = () => pop.classList.remove('open');
document.addEventListener('click', (e) => {
const s = e.target.closest('[data-start]');
if (s) {
const c = CONTRIBS[s.dataset.start];
document.getElementById('kick').textContent = c.kicker;
document.getElementById('ttl').textContent = c.title;
document.getElementById('bdy').innerHTML = c.body;
pop.classList.add('open');
return;
}
if (e.target.closest('.ai-pop-x') || e.target === pop) closePop();
});
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closePop(); });
</script>
<style>
/* popup styles (same language as deck) */
.ai-pop { position: fixed; inset: 0; z-index: 100; background: rgba(42,36,38,.45);
display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity .2s; }
.ai-pop.open { opacity: 1; pointer-events: auto; }
.ai-pop-card { position: relative; width: min(560px, 92%); background: #fff; border: 1px solid var(--line);
border-radius: 12px; box-shadow: 0 18px 50px rgba(42,36,38,.28); padding: 24px 28px 22px;
transform: translateY(14px) scale(.95); transition: transform .24s cubic-bezier(.2,.9,.3,1.15); }
.ai-pop.open .ai-pop-card { transform: none; }
.ai-pop-kicker { font: 700 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 10px; }
.ai-pop-title { font: 700 18px/1.3 var(--sans); color: var(--ink); margin-bottom: 12px; }
.ai-pop-card p { font: 400 14px/1.65 var(--sans); color: var(--ink); margin: 0 0 12px; }
.pop-list { list-style: none; display: flex; flex-direction: column; gap: 9px; }
.pop-list li { position: relative; padding-left: 20px; font: 400 13.5px/1.5 var(--sans); }
.pop-list li::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px; border-radius: 9999px; background: var(--bordeaux); }
.pop-list b { font-weight: 700; }
.roi { margin-top: 14px; border: 1.5px solid var(--bordeaux); background: #faf4f5; border-radius: 8px; padding: 10px 14px; font: 400 13.5px/1.55 var(--sans); }
.roi .k { display: block; margin-bottom: 4px; font: 800 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux-deep); }
.ai-pop-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; border: none; border-radius: 9999px;
background: var(--surface-alt); font: 700 15px/1 var(--sans); color: var(--ink-muted); cursor: pointer; }
/* icon sizing */
.icon-wrap { display: flex; align-items: center; justify-content: center; width: var(--icon, 96px); height: var(--icon, 96px); }
.icon-wrap .ico { width: 100%; height: 100%; }
/* style 1 — monoline app badges */
.i1 .syscard .icon-wrap { background: #fff; border: 2.4px solid var(--bordeaux); border-radius: 22px;
color: var(--bordeaux-deep); }
.i1 .syscard.ghost .icon-wrap { border: 2px dashed var(--line-strong); border-radius: 22px; color: var(--ink-muted); }
.i1 .src-icons .icon-wrap { color: var(--bordeaux-deep); }
.i1 .flow .fcol.src-icons b { color: var(--ink); }
/* style 2 — flat era artifacts (no frame, illustration speaks) */
.i2 .syscard .icon-wrap { color: var(--ink); }
.i2 .syscard.ghost .icon-wrap { opacity: .55; }
.i2 .src-icons .icon-wrap { color: var(--ink); }
/* style 3 — institutional emblems */
.i3 .syscard .icon-wrap { color: var(--bordeaux); }
.i3 .syscard.ghost .icon-wrap { color: var(--ink-muted); }
.i3 .src-icons .icon-wrap { color: var(--bordeaux); }
</style>
</body>
</html>
+658
View File
@@ -0,0 +1,658 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · Layout variants — 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-hover: #b7252d;
--bordeaux-deep: #8f1204;
--bordeaux-tint: #f4dfe1;
--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: 40px 0 120px; }
body.v2 { background: var(--surface-alt); }
body.v3 { background: #0f0f0f; }
.caption {
width: 1280px; margin: 36px auto 10px; padding-left: 4px;
font: 600 11px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
color: #8a8487;
}
/* ---------- slide frame ---------- */
.slide {
position: relative; width: 1280px; height: 720px; margin: 0 auto;
display: flex; flex-direction: column; overflow: hidden;
background: var(--surface); color: var(--ink);
border: 1px solid var(--line); border-radius: 8px;
box-shadow: 0 1px 2px rgba(0,0,0,.03);
}
body.v2 .slide { border-radius: 16px; }
body.v3 .slide { background: #1a1a1a; color: #e8e8e8; border-color: #404040; box-shadow: none; }
.head {
flex: 0 0 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 {
flex: 0 0 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; }
.sbody { flex: 1; display: flex; flex-direction: column; padding: 52px 64px 36px; }
.foot {
flex: 0 0 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
padding: 0 40px; border-top: 1px solid var(--line);
font: 600 10px/1.4 var(--sans); letter-spacing: .03em; color: var(--ink-muted);
}
.foot span { white-space: nowrap; flex-shrink: 0; }
.foot .g { white-space: nowrap; }
.foot span:first-child { overflow: hidden; text-overflow: ellipsis; flex-shrink: 1; }
body.v2 .foot { border-top: none; justify-content: flex-end; }
body.v3 .foot { border-top-color: #404040; color: #b8b8b8; }
/* ---------- typography ---------- */
.kicker {
font: 700 13px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
color: var(--bordeaux); margin-bottom: 22px;
}
body.v3 .kicker { color: #d94450; }
h1 { font: 600 50px/1.12 var(--display); letter-spacing: -.01em; max-width: 24ch; }
h2 { font: 700 34px/1.15 var(--sans); letter-spacing: -.01em; }
body.v2 h2 { font: 600 40px/1.12 var(--display); }
.lead { font: 400 17.5px/1.5 var(--sans); color: var(--ink-muted); }
body.v3 .lead { color: #b8b8b8; }
.hgap { height: 18px; }
/* v2 accents: serif headings + bordeaux rule + numerals */
body.v2 .sbody { padding: 72px 80px 40px; }
body.v2 .kicker { margin-bottom: 26px; }
body.v2 h1 { font-size: 62px; }
body.v2 .rule { width: 64px; height: 4px; background: var(--bordeaux); border-radius: 2px; margin: 26px 0; }
/* ---------- master: title ---------- */
.title-slide .sbody { justify-content: center; }
/* ---------- master: project flow ---------- */
.flow-slide .sbody { padding: 34px 48px 24px; }
.flow-slide h2 { font-size: 30px; margin-bottom: 20px; }
.flow { margin-top: 20px; display: flex; align-items: stretch; gap: 14px; }
.zone { border-radius: 8px; padding: 30px 14px 14px; position: relative; }
.zone > .zlabel {
position: absolute; top: 8px; left: 14px;
font: 700 10px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
}
.zone.existed { flex: 0 0 168px; background: var(--surface-alt); border: 1px dashed var(--line-strong); }
.zone.existed > .zlabel { color: var(--ink-muted); }
.zone.built { flex: 1; background: #faf4f5; border: 1px solid #e7c3c6; }
.zone.built > .zlabel { color: var(--bordeaux); }
.fcol { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.fbox {
background: #fff; border: 1px solid var(--line-strong); border-radius: 6px;
padding: 10px 10px; text-align: center;
}
.fbox b { display: block; font: 700 13.5px/1.25 var(--sans); }
.fbox span { display: block; margin-top: 3px; font: 400 11px/1.3 var(--sans); color: var(--ink-muted); }
.fbox.src { background: var(--surface-alt); border-color: var(--line); }
.fbox.src.future {
background: transparent; border: 1.5px dashed var(--line-strong);
}
.fbox.src.future b { color: var(--ink-muted); font-weight: 700; }
.fbox.src.future .plus {
display: inline-flex; width: 16px; height: 16px; margin-right: 5px; vertical-align: -3px;
align-items: center; justify-content: center; border-radius: 9999px;
border: 1.5px solid var(--bordeaux); color: var(--bordeaux);
font: 800 11px/1 var(--sans);
}
.fbox.end { background: var(--bordeaux); border-color: var(--bordeaux); color: #fff; padding: 14px 10px; }
.fbox.end span { color: rgba(255,255,255,.85); }
.farrow { align-self: center; color: var(--bordeaux); font: 700 15px/1 var(--sans); }
.fcol.ai { position: relative; padding-top: 12px; }
.brain {
position: absolute; top: -6px; right: 6px; z-index: 1;
width: 30px; height: 30px; border-radius: 9999px;
background: #fff; border: 1.5px solid var(--bordeaux);
display: flex; align-items: center; justify-content: center; font-size: 15px;
}
.fcap { font: italic 400 11px/1.4 var(--sans); color: var(--ink-muted); text-align: center; }
.flownote { margin-top: 14px; font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); }
/* clickable AI brains + popup */
.fcol.ai-hit { flex: 0 0 44px; align-items: center; justify-content: center; gap: 8px; }
.fcol.ai-hit .brain-btn { position: static; }
button.brain-btn {
cursor: pointer; padding: 0;
transition: transform .15s ease, box-shadow .15s ease;
}
button.brain-btn:hover, button.brain-btn:focus-visible {
transform: scale(1.18); box-shadow: 0 0 0 5px rgba(203,51,59,.14); outline: none;
}
.ai-pop {
position: fixed; inset: 0; z-index: 100;
background: rgba(42,36,38,.45); backdrop-filter: blur(2px);
display: flex; align-items: center; justify-content: center;
opacity: 0; pointer-events: none; transition: opacity .2s ease;
}
.ai-pop.open { opacity: 1; pointer-events: auto; }
.ai-pop-card {
position: relative; width: min(580px, 92%);
background: #fff; border: 1px solid var(--line); border-radius: 12px;
box-shadow: 0 18px 50px rgba(42,36,38,.28); padding: 24px 28px 22px;
transform: translateY(14px) scale(.95); transition: transform .24s cubic-bezier(.2,.9,.3,1.15);
}
.ai-pop.open .ai-pop-card { transform: none; }
.ai-pop-kicker { font: 700 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
color: var(--bordeaux); margin-bottom: 10px; }
.ai-pop-title { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.ai-pop-title .brain { position: static; }
.ai-pop-title .brain-btn { cursor: default; pointer-events: none; }
.ai-pop-title b { font: 700 18px/1.3 var(--sans); color: var(--ink); }
.ai-pop-card p { font: 400 14px/1.65 var(--sans); color: var(--ink); }
.ai-pop-card p.lead { margin-bottom: 14px; }
.ai-pop-list { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.ai-pop-list li { position: relative; padding-left: 20px; font: 400 13.5px/1.5 var(--sans); color: var(--ink); }
.ai-pop-list li::before { content: ''; position: absolute; left: 0; top: 7px;
width: 8px; height: 8px; border-radius: 9999px; background: var(--bordeaux); }
.ai-pop-list b { font-weight: 800; }
.ai-pop-list .n { color: var(--bordeaux-deep); font-weight: 800; }
.roi {
margin-top: 14px; border: 1.5px solid var(--bordeaux); background: #faf4f5;
border-radius: 8px; padding: 10px 14px;
font: 400 13.5px/1.55 var(--sans); color: var(--ink);
}
.roi .k {
display: block; margin-bottom: 4px;
font: 800 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
color: var(--bordeaux-deep);
}
.ai-pop-x {
position: absolute; top: 12px; right: 14px; width: 30px; height: 30px;
border: none; border-radius: 9999px; background: var(--surface-alt);
font: 700 15px/1 var(--sans); color: var(--ink-muted); cursor: pointer;
}
.ai-pop-x:hover { background: var(--bordeaux-tint); color: var(--bordeaux-deep); }
.title-meta { margin-top: 40px; display: flex; align-items: center; gap: 14px; }
.title-meta .pill {
display: inline-flex; align-items: center; height: 34px; padding: 0 18px;
border-radius: 9999px; background: var(--bordeaux); color: #fdfcfc;
font: 700 12.5px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
}
.title-meta .what { font: 400 14.5px/1.5 var(--sans); color: var(--ink-muted); }
.speakers { margin-top: 24px; display: flex; flex-direction: column; gap: 8px; }
.speakers span { font: 600 15.5px/1.4 var(--sans); color: var(--ink); }
.event {
margin-top: 16px;
font: italic 400 15px/1.5 var(--sans); color: var(--ink-muted);
}
.event-where {
margin-top: 8px;
font: 600 14px/1.4 var(--sans); color: var(--ink-muted);
}
body.v3 .title-meta .pill { background: #d94450; }
body.v3 .title-meta .what { color: #b8b8b8; }
body.v3 .speakers span { color: #e8e8e8; }
body.v3 .event { color: #b8b8b8; }
body.v3 .event-where { color: #b8b8b8; }
/* ---------- master: diagram ---------- */
.quote {
margin: 22px 0 30px; padding: 18px 24px; border-left: 3px solid var(--bordeaux);
background: var(--surface-alt); border-radius: 0 8px 8px 0;
font: italic 400 16.5px/1.5 var(--display); color: var(--ink);
}
body.v3 .quote { background: #2d2d2d; color: #e8e8e8; }
.quote small { display: block; margin-top: 6px; font: 600 11.5px/1.3 var(--sans); font-style: normal;
letter-spacing: .06em; text-transform: uppercase; color: var(--ink-muted); }
.pipeline { display: flex; align-items: stretch; gap: 10px; }
.stage {
flex: 1; padding: 14px 12px; text-align: center;
border: 1px solid var(--line); border-radius: 8px; background: var(--surface-alt);
}
body.v3 .stage { background: #2d2d2d; border-color: #404040; }
.stage b { display: block; font: 700 14.5px/1.2 var(--sans); margin-bottom: 5px; }
.stage span { display: block; font: 400 12px/1.35 var(--sans); color: var(--ink-muted); }
body.v3 .stage span { color: #b8b8b8; }
.stage .tag {
display: inline-block; margin-top: 7px; padding: 3px 10px; border-radius: 9999px;
background: var(--bordeaux); color: #fdfcfc; font: 700 10px/1 var(--sans);
letter-spacing: .1em; text-transform: uppercase;
}
body.v3 .stage .tag { background: #d94450; }
.stage.ai { background: var(--bordeaux-tint); border-color: var(--bordeaux); }
body.v3 .stage.ai { background: rgba(217,68,80,.14); }
.arrow { align-self: center; color: var(--line-strong); font: 700 20px/1 var(--sans); }
body.v3 .arrow { color: #5a5a5a; }
.thoth-wrap { display: flex; justify-content: flex-end; }
.thoth-col { width: 470px; text-align: center; }
.thoth-arrow { color: var(--line-strong); font: 700 18px/1 var(--sans); margin: 4px 0; }
.thoth-box { border: 1px solid var(--bordeaux); border-radius: 8px; padding: 12px 14px;
background: var(--surface-alt); font: 600 13px/1.4 var(--sans); }
.thoth-box .tag { margin-right: 8px; }
body.v3 .thoth-box { background: #2d2d2d; border-color: #d94450; color: #e8e8e8; }
/* ---------- master: artifact ---------- */
.split { display: flex; gap: 48px; flex: 1; min-height: 0; }
.split .left { flex: 0 0 420px; display: flex; flex-direction: column; }
.split .right { flex: 1; min-width: 0; }
.points { list-style: none; margin-top: 4px; }
.points li {
position: relative; padding: 12px 0 12px 26px; border-bottom: 1px solid var(--line);
font: 400 15.5px/1.45 var(--sans); color: var(--ink);
}
body.v3 .points li { border-bottom-color: #404040; color: #e8e8e8; }
.points li::before { content: ''; position: absolute; left: 0; top: 20px;
width: 10px; height: 10px; border-radius: 9999px; background: var(--bordeaux); }
body.v3 .points li::before { background: #d94450; }
.code {
height: 100%; display: flex; flex-direction: column; overflow: hidden;
border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface-sunken);
}
body.v3 .code { background: #161616; border-color: #404040; }
.code-head {
flex: 0 0 40px; display: flex; align-items: center; justify-content: space-between;
padding: 0 16px; border-bottom: 1px solid var(--line);
font: 600 11.5px/1 var(--mono); color: var(--ink-muted);
}
body.v3 .code-head { border-bottom-color: #404040; color: #b8b8b8; }
.code pre { flex: 1; padding: 18px 20px; overflow: hidden;
font: 400 13.5px/1.62 var(--mono); color: var(--ink); }
body.v3 .code pre { color: #d6d6d6; }
.code b { font-weight: 700; color: var(--bordeaux-deep); }
.code i { font-style: normal; color: #2f6f4f; }
body.v3 .code b { color: #d94450; }
body.v3 .code i { color: #7fb685; }
/* ---------- master: stats ---------- */
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; flex: 1; align-content: center; }
.stat {
border: 1px solid var(--line); border-radius: 8px; background: var(--surface-alt);
padding: 30px 26px; display: flex; flex-direction: column; gap: 12px;
}
body.v3 .stat { background: #2d2d2d; border-color: #404040; }
.stat .num { font: 600 54px/1 var(--display); color: var(--bordeaux); letter-spacing: -.02em; }
body.v3 .stat .num { color: #d94450; }
.stat .lbl { font: 600 14px/1.35 var(--sans); color: var(--ink); }
.stat .sub { font: 400 12px/1.4 var(--sans); color: var(--ink-muted); }
body.v3 .stat .lbl { color: #e8e8e8; }
body.v3 .stat .sub { color: #b8b8b8; }
.stat-note { margin-top: 26px; font: 400 13px/1.5 var(--sans); color: var(--ink-muted); }
body.v3 .stat-note { color: #b8b8b8; }
/* ---------- master: divider ---------- */
.divider .sbody { justify-content: center; }
.divider .numeral { font: 400 130px/1 var(--display); color: var(--bordeaux); }
body.v3 .divider .numeral { color: #d94450; }
.divider h2 { font-size: 44px; max-width: 22ch; margin-top: 12px; }
body.v2 .divider h2 { font-size: 52px; }
.divider .cue { margin-top: 26px; font: 600 13px/1 var(--sans); letter-spacing: .12em;
text-transform: uppercase; color: var(--ink-muted); }
body.v3 .divider .cue { color: #b8b8b8; }
/* ---------- switcher ---------- */
.switcher {
position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
display: flex; align-items: center; gap: 6px; padding: 8px 10px;
background: #2a2426; border-radius: 9999px; box-shadow: 0 6px 24px rgba(0,0,0,.25);
z-index: 10; white-space: nowrap;
}
.switcher .proto-tag { font: 700 10px/1 var(--mono); letter-spacing: .1em; color: #9c9598; padding: 0 10px; }
.switcher a {
display: inline-flex; height: 30px; align-items: center; padding: 0 14px; border-radius: 9999px;
font: 700 12px/1 var(--sans); color: #fdfcfc; text-decoration: none; opacity: .75;
}
.switcher a.active { background: var(--bordeaux); opacity: 1; }
.switcher .hint { font: 600 11px/1 var(--sans); color: #9c9598; padding: 0 12px 0 8px; }
</style>
</head>
<body class="v1">
<nav class="switcher">
<span class="proto-tag">PROTOTYPE · THROWAWAY</span>
<a href="?v=1">1 · Clinical</a>
<a href="?v=2">2 · Editorial</a>
<a href="?v=3">3 · Dark</a>
<span class="hint">same draft content, three looks — pick a direction</span>
</nav>
<main id="deck"></main>
<script>
const q = new URLSearchParams(location.search);
const v = ['1', '2', '3'].includes(q.get('v')) ? q.get('v') : '1';
document.body.className = 'v' + v;
document.querySelectorAll('.switcher a').forEach(a => {
if (a.getAttribute('href') === '?v=' + v) a.classList.add('active');
});
const foot = (n) => `
<footer class="foot">
<span class="g">Role of AI in the Analysis of Unstructured Clinical Databases</span><span class="g">Dr. Marco Pancotti - MultiPhysixLab</span><span class="g">Dr. Sara Paratico - Gruppo San Donato</span><span class="g">San Donato Milanese, Milan, Italy · 2–3 October 2026</span>
<span>0${n} / 06</span>
</footer>`;
const masters = {
title: () => `
<section class="slide title-slide">
<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">AritmoLab · Clinical Data Platform</span>
</nav>
<div class="sbody">
<h1>Role of AI in the Analysis of Unstructured Clinical Databases</h1>
<div class="hgap"></div>
<p class="lead">How we turned a mix of structured and free-text clinical records into a research data platform — the AritmoLab platform experience.</p>
<div class="title-meta">
<span class="pill">AritmoLab</span>
<span class="what">A clinical data platform hosting cardiology data and analysis tools</span>
</div>
<div class="speakers">
<span>Dr. Marco Pancotti - MultiPhysixLab</span>
<span>Dr. Sara Paratico - I.R.C.C.S. Policlinico San Donato</span>
</div>
<p class="event">“Multidimensional Characterization of Cardiac Arrhythmias: Role of Electrocardiology in the Artificial Intelligence Era”</p>
<p class="event-where">San Donato Milanese, Milan, Italy · 2–3 October 2026</p>
</div>
${foot(1)}
</section>`,
diagram: () => `
<section class="slide">
<header class="head">
<img src="logo.png" alt="">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">The platform</span>
</nav>
<div class="sbody">
<div class="kicker">From free text to a research platform</div>
<h2>The clinical truth lives in unstructured columns</h2>
<div class="quote">
“Il paziente riferisce sincope ricorrente; ECG basale con sopraslivellamento ST in V1–V3;
test provocativo con flecainide positivo per pattern Brugada.”
<small>One sentence from a discharge letter (example) — diagnosis, history and test outcome, all in free text</small>
</div>
<div class="pipeline">
<div class="stage"><b>Sources</b><span>Cardioref · SQL Server<br>Kokoro · PostgreSQL</span></div>
<div class="arrow">→</div>
<div class="stage"><b>Staging</b><span>raw replica</span></div>
<div class="arrow">→</div>
<div class="stage ai"><b>Integration</b><span>3NF · text mining</span><span class="tag">AI</span></div>
<div class="arrow">→</div>
<div class="stage"><b>Data Warehouse</b><span>star schema</span></div>
<div class="arrow">→</div>
<div class="stage"><b>Marts</b><span>dbt · Superset</span></div>
</div>
<div class="thoth-wrap">
<div class="thoth-col">
<div class="thoth-arrow">↑</div>
<div class="thoth-box"><span class="tag">AI</span>ThothII — natural-language → SQL over the DWH, builds the final datamarts</div>
</div>
</div>
</div>
${foot(3)}
</section>`,
flow: () => `
<section class="slide flow-slide">
<header class="head">
<img src="logo.png" alt="">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">The project</span>
</nav>
<div class="sbody">
<div class="kicker">What existed → what we built</div>
<h2>The project at a glance</h2>
<div class="flow">
<div class="zone existed">
<span class="zlabel">What existed</span>
<div class="fcol">
<div class="fbox src"><b>Cardioref</b><span>cardiology records · procedures · letters</span></div>
<div class="fbox src"><b>Genetic data</b><span>labs · variants · nomenclature</span></div>
<div class="fbox src"><b>ECG</b><span>signals · device follow-up</span></div>
<div class="fbox src future"><b><span class="plus">+</span>Future sources</b><span>new subsystems can be connected as sources</span></div>
</div>
</div>
<div class="fcol ai-hit">
<button class="brain brain-btn" data-ai="ingestion" aria-label="AI contribution: the mappings">🧠</button>
<div class="farrow">→</div>
</div>
<div class="zone built">
<span class="zlabel">What we built</span>
<div style="display:flex; align-items:center; gap:10px; height:100%;">
<div class="fcol" style="flex:0.8; justify-content:center"><div class="fbox"><b>Staging</b><span>raw replica of the sources</span></div></div>
<div class="farrow">→</div>
<div class="fcol ai" style="flex:1.25">
<button class="brain brain-btn" data-ai="integration" aria-label="AI contribution: clinical text reading">🧠</button>
<div class="fbox"><b>Integration</b><span>cleaned, normalized, deduplicated</span></div>
<div class="fcap">AI reads the clinical text: pathologies, procedures, drug-challenge outcomes</div>
</div>
<div class="farrow">→</div>
<div class="fcol" style="flex:1.05; justify-content:center"><div class="fbox"><b>Star schema</b><span>dimensional transformation</span></div></div>
<div class="farrow">→</div>
<div class="fcol ai" style="flex:1.25">
<button class="brain brain-btn" data-ai="datamarts" aria-label="AI contribution: datamart generation">🧠</button>
<div class="fbox"><b>Datamarts</b><span>research-ready marts</span></div>
<div class="fcap">AI builds them on demand from plain-English questions (ThothII)</div>
</div>
<div class="farrow">→</div>
<div class="fcol" style="flex:1">
<div class="fbox end"><b>AritmoLab Data Warehouse</b><span>queried for research</span></div>
<div class="fbox end"><b>AritmoLab Portal</b><span>management &amp; exploration</span></div>
</div>
</div>
</div>
</div>
</div>
${foot(2)}
</section>`,
artifact: () => `
<section class="slide">
<header class="head">
<img src="logo.png" alt="">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">AI as co-engineer</span>
</nav>
<div class="sbody">
<div class="kicker">Mappings as code</div>
<h2>LLM agents draft it. Humans review it. Git versions it.</h2>
<div class="hgap"></div>
<div class="split">
<div class="left">
<ul class="points">
<li>Staging → integration mappings are declarative YAML: schema-validated, reviewed, versioned.</li>
<li>LLM agents authored them from prompts (<code>etl/prompts/fill_integration_yaml.md</code>).</li>
<li>The same file wires the runtime text mining: <code>analysis_function</code> + <code>version_function</code> per enrichment.</li>
<li>Every extracted row records its pattern-library version — reproducible by design.</li>
</ul>
</div>
<div class="right">
<div class="code">
<div class="code-head">
<span>etl/config/integration-map.yaml</span>
<span>19,764 lines · 42 mappings</span>
</div>
<pre><b>text_cascade_enrichments</b>:
- <b>source_columns</b>:
- <i>cr_diagnosi</i>
- <i>cr_anamnesi_cardio</i>
- <i>cr_anamnesi_extra_cardio</i>
<b>analysis_function</b>: <i>get_letter_pathologies_cascade</i>
<b>version_function</b>: <i>get_letter_pathology_version</i>
<b>integration_table</b>: <i>cr_lettera_dimissione_diagnosi</i>
<b>output_columns</b>:
- <b>column</b>: <i>patologia</i>
<b>postgres_type</b>: <i>TEXT NOT NULL</i>
- <b>column</b>: <i>tier</i>
<b>postgres_type</b>: <i>TEXT NOT NULL</i>
- <b>column</b>: <i>campo_sorgente</i>
<b>postgres_type</b>: <i>TEXT NOT NULL</i></pre>
</div>
</div>
</div>
</div>
${foot(4)}
</section>`,
stats: () => `
<section class="slide">
<header class="head">
<img src="logo.png" alt="">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">Results</span>
</nav>
<div class="sbody">
<div class="kicker">What the text mining produced</div>
<h2>Deterministic AI, audited numbers</h2>
<div class="stat-grid">
<div class="stat"><span class="num">58,438</span><span class="lbl">discharge letters read</span><span class="sub">free-text fields, bilingual IT/EN patterns</span></div>
<div class="stat"><span class="num">73,389</span><span class="lbl">pathologies extracted</span><span class="sub">16 categories, TIER1 arrhythmic / TIER2 structural</span></div>
<div class="stat"><span class="num">10,908</span><span class="lbl">drug-challenge tests parsed</span><span class="sub">flecainide / ajmaline · adrenaline / isoprenaline</span></div>
<div class="stat"><span class="num">2,307</span><span class="lbl">Brugada patients identified</span><span class="sub">confirmed by clinical review on dashboards</span></div>
</div>
<p class="stat-note">Every row carries its pattern-library semver (<code>pattern_version</code>) · quality gate: ≥95% accuracy on 100 manual reviews.</p>
</div>
${foot(5)}
</section>`,
divider: () => `
<section class="slide divider">
<header class="head">
<img src="logo.png" alt="">
<span class="head-org">AritmoLab · Policlinico San Donato</span>
</header>
<nav class="band">
<span class="band-section">Live demo</span>
</nav>
<div class="sbody">
<div class="numeral">04</div>
<h2>The result: star schema &amp; dashboards</h2>
<div class="cue">switching to the browser — Superset · dbt schema</div>
</div>
${foot(6)}
</section>`
};
const order = [
['title', 'MASTER 1/6 — Title (clean opening)'],
['flow', 'MASTER 2/6 — Project flow (second step)'],
['diagram', 'MASTER 3/6 — Concept / problem'],
['artifact', 'MASTER 4/6 — Artifact showcase (YAML)'],
['stats', 'MASTER 5/6 — Numbers'],
['divider', 'MASTER 6/6 — Section / demo cue'],
];
document.getElementById('deck').innerHTML = order
.map(([m, caption], i) => `<p class="caption">${caption}</p>${masters[m]()}`)
.join('');
// ---- AI contribution popups (click any brain) ----
const AI_CONTRIBS = {
ingestion: {
title: 'The mappings',
body: `
<p class="lead">The mappings that turn three raw sources into clean data are <b>crafted by AI</b> — <b>revised by humans</b>.</p>
<ul class="ai-pop-list">
<li><b>AI drafts</b> — LLM agents write the mappings from prompts and schema samples</li>
<li><b>What is a mapping?</b> — a plain instruction sheet that tells the system, field by field, where each piece of data comes from and where it has to land</li>
<li><b>At scale</b> — <span class="n">19,764</span> lines of instructions, all machine-validated</li>
<li><b>Humans revise</b> — every line is checked, then recorded in <b>git</b>, the archive that keeps every version of the instructions and can undo any change</li>
</ul>
<div class="roi"><b class="k">Positive effects</b>Weeks of hand-writing became days of reviewing.</div>`,
},
integration: {
title: 'Reading the clinical text',
body: `
<p class="lead">A deterministic, bilingual (IT/EN) pattern library reads the free text inside the nightly pipeline.</p>
<ul class="ai-pop-list">
<li><b>Pathologies</b> — <span class="n">73,389</span> extracted from <span class="n">58,438</span> discharge letters, into a two-tier clinical ontology</li>
<li><b>Procedures ↔ pathologies</b> — linked at clause level</li>
<li><b>Drug-challenge tests</b> — <span class="n">10,908</span> parsed (flecainide, ajmaline, adrenaline, isoprenaline)</li>
<li><b>No black box</b> — semver rules, <code>pattern_version</code> on every row, <span class="n">≥95%</span> accuracy gate on 100 manual reviews</li>
</ul>
<div class="roi"><b class="k">Positive effects</b>58,438 letters of free text became an analysable research asset — automatically, every night.</div>`,
},
datamarts: {
title: 'Datamarts from plain English',
body: `
<p class="lead">Researchers ask in plain English; the AI writes the SQL over the star schema.</p>
<ul class="ai-pop-list">
<li><b>Ask</b> — “how many Brugada patients had an effective ablation?”</li>
<li><b>Generate</b> — AI builds the SQL and assembles a curated datamart</li>
<li><b>Serve</b> — results flow to Superset for statistics and machine learning</li>
<li><b>In the loop</b> — the researcher reviews every proposed query before it runs</li>
</ul>
<div class="roi"><b class="k">Positive effects</b>A new research datamart in minutes instead of weeks of hand-written SQL — with the researcher approving every query.</div>`,
},
};
const pop = document.createElement('div');
pop.className = 'ai-pop';
pop.innerHTML = `
<div class="ai-pop-card" role="dialog" aria-modal="true">
<button class="ai-pop-x" aria-label="Close">✕</button>
<div class="ai-pop-kicker">AI contribution</div>
<div class="ai-pop-title"><span class="brain brain-btn" id="aiPopBrain">🧠</span><b id="aiPopTitle"></b></div>
<p id="aiPopBody"></p>
</div>`;
document.body.appendChild(pop);
const closePop = () => pop.classList.remove('open');
const openPop = (key) => {
const c = AI_CONTRIBS[key];
if (!c) return;
document.getElementById('aiPopTitle').textContent = c.title;
document.getElementById('aiPopBody').innerHTML = c.body;
pop.classList.add('open');
};
document.addEventListener('click', (e) => {
const b = e.target.closest('.brain-btn[data-ai]');
if (b) { openPop(b.dataset.ai); return; }
if (e.target.closest('.ai-pop-x') || e.target === pop) closePop();
});
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closePop(); });
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

@@ -0,0 +1,355 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · Scatter layouts — emblem icons (slides 02 & 03)</title>
<link rel="stylesheet" href="../deck/fonts.css">
<style>
:root {
--bordeaux: #cb333b; --bordeaux-deep: #8f1204; --bordeaux-tint: #f4dfe1;
--surface: #fdfcfc; --surface-alt: #f6f5f5; --surface-sunken: #ecebeb;
--ink: #2a2426; --ink-muted: #6b6568;
--line: #e3e0e1; --line-strong: #c9c5c6;
--sans: 'Source Sans 3', system-ui, sans-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: 36px 0 90px; color: var(--ink); }
.note { width: 1280px; margin: 0 auto 18px; padding: 12px 18px; background: #fff8e6; border: 1px solid #e8d9a8;
border-radius: 8px; font: 400 13px/1.5 var(--sans); color: #6b5d2a; }
.note b { font-weight: 700; }
.switcher { position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
display: flex; align-items: center; gap: 6px; padding: 8px 10px; background: #2a2426;
border-radius: 9999px; box-shadow: 0 6px 24px rgba(0,0,0,.25); z-index: 10; white-space: nowrap; }
.switcher .tag { font: 700 10px/1 var(--mono); letter-spacing: .1em; color: #9c9598; padding: 0 10px; }
.switcher a { display: inline-flex; height: 30px; align-items: center; padding: 0 14px; border-radius: 9999px;
font: 700 12px/1 var(--sans); color: #fdfcfc; text-decoration: none; opacity: .75; }
.switcher a.active { background: var(--bordeaux); opacity: 1; }
.switcher .who { font: 400 11px/1 var(--sans); color: #9c9598; padding: 0 12px 0 8px; max-width: 430px; white-space: normal; }
.slide { position: relative; width: 1280px; height: 720px; margin: 0 auto 34px;
overflow: hidden; background: var(--surface); color: var(--ink); border: 1px solid var(--line);
border-radius: 8px; display: flex; flex-direction: column; }
.head { flex: 0 0 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 { flex: 0 0 46px; display: flex; align-items: center; justify-content: space-between;
padding: 0 40px; background: var(--bordeaux); color: var(--surface); }
.band-section { font: 700 13px/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; }
.foot { flex: 0 0 34px; display: flex; align-items: center; justify-content: space-between; gap: 24px;
padding: 0 40px; border-top: 1px solid var(--line); font: 600 10px/1.4 var(--sans);
letter-spacing: .03em; color: var(--ink-muted); }
.foot span { white-space: nowrap; }
.foot span:first-child { overflow: hidden; text-overflow: ellipsis; }
.sbody { flex: 1; display: flex; flex-direction: column; padding: 26px 48px 16px; }
.kicker { font: 700 12px/1 var(--sans); letter-spacing: .16em; text-transform: uppercase;
color: var(--bordeaux); margin-bottom: 12px; }
h2 { font: 700 30px/1.15 var(--sans); color: var(--ink); margin: 0 0 6px; }
.lead { font: 400 15px/1.5 var(--sans); color: var(--ink-muted); margin: 4px 0 0; }
/* ===== scattered islands canvas ===== */
.scatter { position: relative; flex: 1; min-height: 0; }
.isle { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 2px;
cursor: pointer; transform: rotate(var(--rot, 0deg)); transition: transform .18s ease; border: none; background: none;
font-family: var(--sans); width: var(--w, 240px); }
.isle:hover { transform: rotate(var(--rot, 0deg)) scale(1.04); }
.isle:focus-visible { outline: none; transform: rotate(var(--rot, 0deg)) scale(1.04); }
.isle .ico { width: var(--size, 140px); height: var(--size, 140px); color: var(--bordeaux); display: block; }
.isle .nm { font: 700 16px/1.3 var(--sans); color: var(--ink); margin-top: 4px; }
.isle .sc { font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); max-width: 240px; }
.isle.ghost .ico { color: var(--ink-muted); opacity: .5; }
.isle.ghost .nm { color: var(--bordeaux-deep); font-size: 14px; }
.isle.ghost .sc { font-style: italic; }
/* slide 03 existed zone */
.flow { display: flex; align-items: stretch; gap: 14px; flex: 1; min-height: 0; margin-top: 6px; }
.zone { border-radius: 8px; position: relative; }
.zone > .zlabel { position: absolute; top: 8px; left: 14px; font: 700 10px/1 var(--mono);
letter-spacing: .14em; text-transform: uppercase; }
.zone.existed { flex: 0 0 250px; background: var(--surface-alt); border: 1px dashed var(--line-strong); position: relative; }
.zone.existed > .zlabel { color: var(--ink-muted); }
.zone.built { flex: 1; background: #faf4f5; border: 1px solid #e7c3c6; }
.zone.built > .zlabel { color: var(--bordeaux); }
.zone.built .row { display: flex; align-items: center; gap: 8px; height: 100%; padding: 0 10px; }
.fcol { flex: 1; display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.fbox { background: #fff; border: 1px solid var(--line-strong); border-radius: 6px; padding: 9px 8px; text-align: center; }
.fbox b { display: block; font: 700 12px/1.25 var(--sans); }
.fbox span { display: block; margin-top: 2px; font: 400 10px/1.3 var(--sans); color: var(--ink-muted); }
.fbox.end { background: var(--bordeaux); border-color: var(--bordeaux); color: #fff; }
.fbox.end span { color: rgba(255,255,255,.85); }
.farrow { align-self: center; color: var(--bordeaux); font: 700 14px/1 var(--sans); }
.src-scatter { position: relative; flex: 1; min-height: 0; }
.src-scatter .isle { --w: 120px; position: absolute; }
.src-scatter .isle .ico { width: var(--size, 64px); height: var(--size, 64px); }
.src-scatter .isle .nm { font-size: 11.5px; }
.todo { border: 2px dashed var(--bordeaux); border-radius: 8px; background: #fff; padding: 10px 14px;
margin-bottom: 10px; box-shadow: 0 2px 8px rgba(42,36,38,.06); }
.todo b { display: block; font: 700 15px/1.3 var(--sans); color: var(--bordeaux-deep); }
.todo span { display: block; font: 400 11px/1.4 var(--sans); color: var(--ink-muted); margin-top: 2px; }
.todo-stack { position: absolute; z-index: 2; }
.isle .ico { color: var(--bordeaux); }
.isle.ghost .ico { color: #9a938f; }
.isle .nm { font-size: 15px; }
.isle .sc { font-size: 11.5px; max-width: 230px; }
/* popup */
.ai-pop { position: fixed; inset: 0; z-index: 100; background: rgba(42,36,38,.45);
display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transition: opacity .2s; }
.ai-pop.open { opacity: 1; pointer-events: auto; }
.ai-pop-card { position: relative; width: min(560px, 92%); background: #fff; border: 1px solid var(--line);
border-radius: 12px; box-shadow: 0 18px 50px rgba(42,36,38,.28); padding: 24px 28px 22px;
transform: translateY(14px) scale(.95); transition: transform .24s cubic-bezier(.2,.9,.3,1.15); }
.ai-pop.open .ai-pop-card { transform: none; }
.ai-pop-kicker { font: 700 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux); margin-bottom: 10px; }
.ai-pop-title { font: 700 18px/1.3 var(--sans); color: var(--ink); margin-bottom: 12px; }
.ai-pop-card p { font: 400 14px/1.65 var(--sans); color: var(--ink); margin: 0 0 12px; }
.pop-list { list-style: none; display: flex; flex-direction: column; gap: 9px; }
.pop-list li { position: relative; padding-left: 20px; font: 400 13.5px/1.5 var(--sans); }
.pop-list li::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px; border-radius: 9999px; background: var(--bordeaux); }
.pop-list b { font-weight: 700; }
.roi { margin-top: 14px; border: 1.5px solid var(--bordeaux); background: #faf4f5; border-radius: 8px; padding: 10px 14px; font: 400 13.5px/1.55 var(--sans); }
.roi .k { display: block; margin-bottom: 4px; font: 800 10.5px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--bordeaux-deep); }
.ai-pop-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; border: none; border-radius: 9999px;
background: var(--surface-alt); font: 700 15px/1 var(--sans); color: var(--ink-muted); cursor: pointer; }
</style>
</head>
<body>
<div class="note"><b>EVALUATION</b> — icone Emblems (le tue preferite), <b>più grandi, senza bordi/card</b>, disposte in modo
<b>sparsamente organico</b> invece che a griglia. Tre composizioni per slide 02 e adattamento alla 03.</div>
<main id="deck"></main>
<nav class="switcher">
<span class="tag">PROTOTYPE</span>
<a href="?s=1" data-v="1">A · Constellation</a>
<a href="?s=2" data-v="2">B · Cascade</a>
<a href="?s=3" data-v="3">C · Orbit</a>
<span class="who" id="who"></span>
</nav>
<script>
/* ---- emblem icons (style 3, winner) ---- */
const EMB = {
cardioref: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M24 33 c-6-3.5-9-7-9-10.5 0-3 4-5.5 7-3 1 .8 1.6 1.8 2 3 .4-1.2 1-2.2 2-3 3-2.5 7 0 7 3 0 3.5-3 7-9 10.5z" fill="currentColor" stroke="none"/>
<path d="M22.5 21 h3 v3 h3 v3 h-3 v3 h-3 v-3 h-3 v-3 h3 z" fill="#fff" stroke="none"/>`,
genetic: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M18 8 C28 14 28 20 18 26 C12 30 12 35 18 40"/>
<path d="M30 8 C20 14 20 20 30 26 C36 30 36 35 30 40"/>
<path d="M17 12 H31"/><path d="M15.5 18 H32.5"/><path d="M17 24 H31"/><path d="M14.5 31 H22"/>
<rect x="25" y="29" width="12" height="12" rx="1.5" stroke-width="2"/>
<path d="M25 34 H37"/><path d="M30.5 29 V41"/>`,
omics: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<circle cx="24" cy="24" r="4" fill="currentColor" stroke="none"/>
<circle cx="24" cy="10" r="3.4"/><circle cx="11.5" cy="31" r="3.4"/><circle cx="36.5" cy="31" r="3.4"/>
<path d="M24 20.5 V13.5"/><path d="M21 26.5 L14 29.5"/><path d="M27 26.5 L34 29.5"/>`,
ecg: `<circle cx="24" cy="24" r="20" stroke-width="2.6"/>
<path d="M9 24 h6 l3-9 5 16 3-7 h14" stroke-width="2.4"/>
<circle cx="24" cy="24" r="1.8" fill="currentColor" stroke="none"/>`,
};
const GHOST = `<circle cx="24" cy="24" r="20" stroke-dasharray="5 4" stroke-width="2.4"/>
<path d="M15 33 L33 15" stroke-width="2.4"/><circle cx="17" cy="17" r="2.4" fill="currentColor" stroke="none"/>`;
const ico = (key, size) =>
`<svg class="ico" viewBox="0 0 48 48" style="width:${size}px;height:${size}px" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">${key === 'ghost' ? GHOST : EMB[key]}</svg>`;
const SYS = [
{ key: 'cardioref', name: 'Cardioref', scope: 'visits · operations · reports — twenty years of management on structured forms' },
{ key: 'genetic', name: 'Genetic data', scope: 'DNA instruments — results typed into Excel by hand' },
{ key: 'omics', name: 'Omics Portal', scope: 'meant to host Cardioref + genetics — still immature' },
{ key: 'ecg', name: 'ECG', scope: 'paper strip + a CSV on request' },
];
const GHOSTS = [
{ name: 'No Health Intelligence', scope: 'nothing for historical data' },
{ name: 'No ML-ready datamarts', scope: 'nothing for predictive statistics' },
];
const CONTRIBS = {
cardioref: { kicker: 'The starting point · 20 years ago', title: 'Cardioref — the daily workhorse',
body: `<p class="lead">A traditional relational application: classic forms for the typical hospital procedures.</p>
<ul class="pop-list"><li><b>Used for</b> — visits, operations, reports: the daily management of the Unit</li>
<li><b>Good at it</b> — twenty years of patient management on structured records</li>
<li><b>The catch</b> — the clinical story lives in free-text fields, not in the boxes designed for it</li></ul>
<div class="roi"><b class="k">Problem</b>Almost unusable for research: the knowledge is there — locked in prose.</div>` },
genetic: { kicker: 'The starting point · 20 years ago', title: 'Genetic data — sophisticated in, Excel out',
body: `<p class="lead">The DNA instruments are state-of-the-art. What happens after them is not.</p>
<ul class="pop-list"><li><b>Used for</b> — running the genetic analyses the Unit prescribes</li>
<li><b>The catch</b> — results are transcribed by hand into Excel files</li>
<li><b>Never connected</b> — Cardioref has no idea genetics exists</li></ul>
<div class="roi"><b class="k">Problem</b>Two parallel worlds: genotypes on one side, clinical records on the other.</div>` },
omics: { kicker: 'The starting point · 20 years ago', title: 'Omics Portal — the right idea, too soon',
body: `<p class="lead">A portal meant to host both Cardioref and the genetic data — the seed of AritmoLab.</p>
<ul class="pop-list"><li><b>Used for</b> — the first 360° patient-management features</li>
<li><b>The catch</b> — functionality still immature</li>
<li><b>Never connected</b> — it could not see the data it was meant to host</li></ul>
<div class="roi"><b class="k">Problem</b>A promising idea without a bridge: too young to carry the data.</div>` },
ecg: { kicker: 'The starting point · 20 years ago', title: 'ECG — paper, and a CSV if you ask',
body: `<p class="lead">Classic ECG devices: they print the strip, and they can export a file.</p>
<ul class="pop-list"><li><b>Used for</b> — acquiring and viewing ECG traces</li>
<li><b>The catch</b> — no machine-to-machine communication</li>
<li><b>The only door out</b> — a CSV file, produced on request</li></ul>
<div class="roi"><b class="k">Problem</b>Every ECG meant a manual download — automation was impossible by design.</div>` },
};
const chrome = (band) => `
<header class="head"><img src="logo.png" alt=""><span class="head-org">AritmoLab · Policlinico San Donato</span></header>
<nav class="band"><span class="band-section">${band}</span><span class="jump">0X · …</span></nav>`;
const foot = `<footer class="foot"><span>Role of AI in the Analysis of Unstructured Clinical Databases · Dr. Marco Pancotti - MultiPhysixLab · Dr. Sara Paratico - Gruppo San Donato · San Donato Milanese, Milan, Italy · 2–3 October 2026</span><span>0X / 15</span></footer>`;
function isle(s, size, rot, x, y) {
const pos = x ? `left:${x}; top:${y};` : '';
return `<button class="isle" data-start="${s.key}" style="--rot:${rot}deg; --size:${size}px; ${pos}">
${ico(s.key, size)}<span class="nm">${s.name}</span><span class="sc">${s.scope}</span></button>`;
}
function ghostIsle(g, size, rot, x, y) {
const pos = x ? `left:${x}; top:${y};` : '';
return `<div class="isle ghost" style="--rot:${rot}deg; --size:${size}px; ${pos}">
${ico('ghost', size)}<span class="nm">${g.name}</span><span class="sc">${g.scope}</span></div>`;
}
/* ---- A · Constellation: scattered islands (hand-placed, organic jitter) ---- */
const TODO_STACK = [
['Clinical Intelligence', 'dashboards on the clinical history of the Unit and its patients'],
['Datawarehouse', 'the single source of truth'],
['ML-ready data', 'cohort tables, ready for model training'],
['Professional Portal', 'AritmoLab — the grown-up Omics Portal'],
];
/* three placements of the central "to build" rectangles */
const ORBIT_CFG = {
1: { isles: [
{ key: 'cardioref', x: '0%', y: '2%', rot: -3, size: 124 },
{ key: 'genetic', x: '72%', y: '2%', rot: 3, size: 118 },
{ key: 'omics', x: '0%', y: '56%', rot: -2, size: 120 },
{ key: 'ecg', x: '72%', y: '56%', rot: 2, size: 118 } ],
stack: { left: '27%', top: '20%', width: '46%' }, offsets: ['0%', '8%', '4%', '10%'], icon: 116 },
2: { isles: [
{ key: 'cardioref', x: '36%', y: '4%', rot: -3, size: 118 },
{ key: 'genetic', x: '64%', y: '18%', rot: 3, size: 112 },
{ key: 'omics', x: '36%', y: '48%', rot: -2, size: 114 },
{ key: 'ecg', x: '63%', y: '60%', rot: 2, size: 112 } ],
stack: { left: '3%', top: '16%', width: '25%' }, offsets: ['0%', '10%', '4%', '12%'], icon: 106 },
3: { isles: [
{ key: 'cardioref', x: '1%', y: '6%', rot: -3, size: 120 },
{ key: 'genetic', x: '75%', y: '6%', rot: 3, size: 114 },
{ key: 'omics', x: '1%', y: '56%', rot: -2, size: 118 },
{ key: 'ecg', x: '75%', y: '56%', rot: 2, size: 114 } ],
stack: { left: '25%', top: '20%', width: '50%' }, offsets: ['0%', '7%', '4%', '9%'], icon: 114, grid: true },
};
function orbitSlide02(v) {
const cfg = ORBIT_CFG[v];
let h = `<div class="todo-stack" style="left:${cfg.stack.left}; top:${cfg.stack.top}; width:${cfg.stack.width}">` +
TODO_STACK.map(([n, c], i) => `<div class="todo" style="margin-left:${cfg.offsets[i]}"><b>${n}</b><span>${c}</span></div>`).join('') + `</div>`;
for (const s of cfg.isles) {
const sys = SYS.find(vv => vv.key === s.key);
h += `<button class="isle" data-start="${s.key}" style="--rot:${s.rot}deg; --size:${s.size}px; left:${s.x}; top:${s.y}; width:230px">
${ico(s.key, s.size)}<span class="nm">${sys.name}</span><span class="sc">${sys.scope}</span></button>`;
}
const ghosts = [
['No Health Intelligence', 'nothing for historical data', '8%', '72%'],
['No ML-ready datamarts', 'nothing for predictive research', '62%', '72%'],
];
for (const [name, scope, x, y] of ghosts) {
h += `<div class="isle ghost" style="--rot:0deg; --size:92px; left:${x}; top:${y}; width:220px">
${ico('ghost', 92)}<span class="nm" style="color:var(--bordeaux-deep)">${name}</span><span class="sc">${scope}</span></div>`;
}
return h;
}
function layoutZone(v) {
const items = [
['cardioref', 62, -3, '2%', '0%'],
['genetic', 62, 3, '52%', '16%'],
['omics', 62, -2, '4%', '38%'],
['ecg', 62, 3, '54%', '60%'],
];
let h = items.map(([k, size, rot, x, y]) => {
const s = SYS.find(vv => vv.key === k);
return `<div class="isle" style="--rot:${rot}deg; --size:${size}px; left:${x}; top:${y}; width:170px">
${ico(k, size)}<span class="nm">${s.name}</span></div>`;
}).join('');
h += `<div class="isle ghost" style="--rot:-2deg; --size:56px; left:32%; top:82%; width:170px">
${ico('ghost', 56)}<span class="nm" style="color:var(--bordeaux-deep)">missing</span></div>`;
return h;
}
const LAYOUTS = {
1: { desc: 'A · Center stack: i quattro obiettivi impilati al centro, i sistemi in orbita attorno.' },
2: { desc: 'B · Side column: la pila degli obiettivi a sinistra, i sistemi orbitano più larghi a destra.' },
3: { desc: 'C · Compact 2×2: griglia compatta e sfalsata al centro, orbita larga.' },
};
const q = new URLSearchParams(location.search);
const v = ['1','2','3'].includes(q.get('s')) ? q.get('s') : '1';
document.body.className = 's' + v;
document.querySelectorAll('.switcher a').forEach(a => a.classList.toggle('active', a.dataset.v === v));
document.getElementById('who').textContent = LAYOUTS[v].desc;
document.getElementById('deck').innerHTML = `
<section class="slide">
${chrome('Where we started')}
<div class="sbody">
<div class="kicker">Twenty years ago</div>
<h2>Four islands and two missing pieces</h2>
<div class="scatter">${orbitSlide02(v)}</div>
</div>
${foot}
</section>
<section class="slide">
${chrome('The plan')}
<div class="sbody">
<div class="kicker">The plan · what we wanted to build</div>
<h2>One platform, two destinations</h2>
<div class="flow">
<div class="zone existed">
<span class="zlabel">What existed — to connect</span>
<div class="src-scatter">${layoutZone(v)}</div>
</div>
<div class="farrow">→</div>
<div class="zone built">
<span class="zlabel">What we wanted to build</span>
<div class="row">
<div class="fcol"><div class="fbox"><b>Staging</b><span>raw replica</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Integration</b><span>clean · text mining</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Star schema</b><span>warehouse</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox"><b>Datamarts</b><span>for research</span></div></div>
<div class="farrow">→</div>
<div class="fcol"><div class="fbox end"><b>AritmoLab</b><span>portal + warehouse</span></div></div>
</div>
</div>
</div>
</div>
${foot}
</section>`;
const pop = document.createElement('div');
pop.className = 'ai-pop';
pop.innerHTML = `<div class="ai-pop-card" role="dialog"><button class="ai-pop-x">✕</button>
<div class="ai-pop-kicker" id="kick"></div><div class="ai-pop-title" id="ttl"></div><div id="bdy"></div></div>`;
document.body.appendChild(pop);
document.addEventListener('click', (e) => {
const s = e.target.closest('[data-start]');
if (s) {
const c = CONTRIBS[s.dataset.start];
document.getElementById('kick').textContent = c.kicker;
document.getElementById('ttl').textContent = c.title;
document.getElementById('bdy').innerHTML = c.body;
pop.classList.add('open');
return;
}
if (e.target.closest('.ai-pop-x') || e.target === pop) pop.classList.remove('open');
});
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') pop.classList.remove('open'); });
</script>
</body>
</html>