Files
ThothII/presentation/prototype/layout-variants.html
T
Codex 0f1391e1b7 feat: AritmoLab AI presentation deck (work in progress)
Reveal.js deck + prototypes + speaker scripts for the talk
'Role of AI in the Analysis of Unstructured Clinical Databases'
(AritmoLab — Policlinico San Donato, 2-3 October 2026).

Lives on its own branch while in progress; not for main until ready.
2026-09-06 23:06:28 +02:00

659 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PROTOTYPE · 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>