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.
659 lines
32 KiB
HTML
659 lines
32 KiB
HTML
<!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 & 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 & 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>
|