deck slide 05: JS-drawn edge-to-edge cycle arrows (precise start/land on measured component boxes); note as two wide bullets, 14px, clinician wording

This commit is contained in:
Codex
2026-09-07 21:47:12 +02:00
parent 7177c346ab
commit 5a18c546ce
2 changed files with 60 additions and 13 deletions
+6 -2
View File
@@ -192,7 +192,7 @@
.arit.stats .sstat { display: flex; align-items: baseline; gap: 9px; }
.arit.stats .sstat .n { font: 700 25px/1 var(--sans); color: var(--bordeaux); letter-spacing: -.02em; }
.arit.stats .sstat .l { font: 600 11.5px/1.2 var(--sans); color: var(--ink); }
.arit.stats .cycle { position: relative; width: 1184px; height: 280px; margin: 6px auto 0; }
.arit.stats .cycle { position: relative; width: 1184px; height: 270px; margin: 4px auto 0; }
.arit.stats .cycle-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.arit.stats .vcomp { position: absolute; transform: translate(-50%, -50%); width: 250px;
text-align: center; background: var(--surface); padding: 4px 6px; }
@@ -200,7 +200,11 @@
.arit.stats .vcomp b { display: block; font: 700 14.5px/1.25 var(--sans); text-transform: uppercase;
letter-spacing: .04em; color: var(--ink); }
.arit.stats .vcomp span.d { display: block; margin-top: 4px; font: 400 11.5px/1.4 var(--sans); color: var(--ink-muted); }
.arit.stats .stat-note { margin-top: 8px; font-size: 12.5px; line-height: 1.55; max-width: 1100px; }
.arit.stats .note-row { display: flex; gap: 30px; margin-top: 6px; }
.arit.stats .nitem { flex: 1; position: relative; padding-left: 16px; font: 400 14px/1.45 var(--sans); color: var(--ink); }
.arit.stats .nitem::before { content: ''; position: absolute; left: 0; top: 7px; width: 8px; height: 8px;
border-radius: 9999px; background: var(--bordeaux); }
.arit.stats .nitem b { color: var(--bordeaux-deep); }
/* divider */
.arit.divider .sbody { justify-content: center; }
+54 -11
View File
@@ -233,22 +233,25 @@
<div class="sstat"><span class="n">2,307</span><span class="l">Brugada patients</span></div>
<div class="sstat"><span class="n">≈ 240</span><span class="l">letters / month</span></div>
</div>
<div class="cycle">
<svg class="cycle-svg" viewBox="0 0 1184 280">
<div class="cycle" id="cycle">
<svg class="cycle-svg" viewBox="0 0 1184 270">
<defs><marker id="arrw" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#cb333b"/></marker></defs>
<g fill="none" stroke="#cb333b" stroke-width="2">
<path d="M627 52 A400 88 0 0 1 898 83" marker-end="url(#arrw)"/>
<path d="M898 197 A400 88 0 0 1 627 228" marker-end="url(#arrw)"/>
<path d="M557 228 A400 88 0 0 1 286 197" marker-end="url(#arrw)"/>
<path d="M194 132 A400 88 0 0 1 286 83" marker-end="url(#arrw)"/>
<g id="cycle-arcs" fill="none" stroke="#cb333b" stroke-width="2">
<path marker-end="url(#arrw)"/>
<path marker-end="url(#arrw)"/>
<path marker-end="url(#arrw)"/>
<path marker-end="url(#arrw)"/>
</g>
</svg>
<div class="vcomp" style="left:592px; top:50px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="4" width="20" height="26" rx="2.5"/><path d="M12 11h10"/><path d="M12 16h10"/><path d="M12 21h6"/></svg><b>Letter reader</b><span class="d">every discharge letter, Italian &amp; English</span></div>
<div class="vcomp" style="left:992px; top:140px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="14.5" cy="14.5" r="8"/><path d="M20.5 20.5 L29 29"/><path d="M11 14.5 h7"/><path d="M14.5 11 v7"/></svg><b>Clinical matcher</b><span class="d">recognises diagnoses, procedures, test outcomes</span></div>
<div class="vcomp" style="left:592px; top:230px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3.5 L28 8 v8.5 c0 7-4.7 11.6-11 14 C10.7 28.1 6 23.5 6 16.5 V8 Z"/><path d="M12.5 16.5 l3.2 3.2 L23.5 12"/></svg><b>Context guard</b><span class="d">negations &amp; family history kept apart</span></div>
<div class="vcomp" style="left:192px; top:140px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17" cy="7" r="3"/><circle cx="7" cy="26.5" r="3"/><circle cx="27" cy="26.5" r="3"/><path d="M17 10 v7"/><path d="M17 17 L8 23.5"/><path d="M17 17 L26 23.5"/></svg><b>Ontology sorter</b><span class="d">every finding into its clinical category</span></div>
<div class="vcomp" style="left:992px; top:135px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="14.5" cy="14.5" r="8"/><path d="M20.5 20.5 L29 29"/><path d="M11 14.5 h7"/><path d="M14.5 11 v7"/></svg><b>Clinical matcher</b><span class="d">recognises diagnoses, procedures, test outcomes</span></div>
<div class="vcomp" style="left:592px; top:220px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3.5 L28 8 v8.5 c0 7-4.7 11.6-11 14 C10.7 28.1 6 23.5 6 16.5 V8 Z"/><path d="M12.5 16.5 l3.2 3.2 L23.5 12"/></svg><b>Context guard</b><span class="d">negations &amp; family history kept apart</span></div>
<div class="vcomp" style="left:192px; top:135px"><svg class="cicon" viewBox="0 0 34 34" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17" cy="7" r="3"/><circle cx="7" cy="26.5" r="3"/><circle cx="27" cy="26.5" r="3"/><path d="M17 10 v7"/><path d="M17 17 L8 23.5"/><path d="M17 17 L26 23.5"/></svg><b>Ontology sorter</b><span class="d">every finding into its clinical category</span></div>
</div>
<div class="note-row">
<div class="nitem"><b>Traceable.</b> Every finding carries the version of the rules that produced it (<code>pattern_version</code>): any number can be rebuilt years later.</div>
<div class="nitem"><b>Audited.</b> A new rule set goes live only after it proves ≥95% accuracy on 100 records reviewed by hand.</div>
</div>
<p class="stat-note">No black box: every extracted finding carries the version of the rules that produced it (<code>pattern_version</code>) — any number in this talk can be rebuilt years from now. And no rule set reaches production until it proves ≥95% accuracy on 100 records reviewed by hand.</p>
</div>
<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 class="g num">05 / 14</span></footer>
<aside class="notes">
@@ -606,6 +609,46 @@
Reveal.on('ready', alignProblem);
if (document.fonts && document.fonts.ready) document.fonts.ready.then(alignProblem);
// slide "What the text miner reads": curved arrows between the four components,
// drawn from the measured edges of each block so every arrow starts and lands precisely.
function alignCycle() {
const cyc = document.getElementById('cycle');
if (!cyc || !cyc.closest('section').classList.contains('present')) return;
const s = Reveal.getScale() || 1;
const cr = cyc.getBoundingClientRect();
const boxes = [...cyc.querySelectorAll('.vcomp')].map(c => {
const r = c.getBoundingClientRect();
const l = (r.left - cr.left) / s, t = (r.top - cr.top) / s, w = r.width / s, h = r.height / s;
return { l, t, w, h, cx: l + w / 2, cy: t + h / 2 };
});
function edgePoint(from, to, box) {
const dx = to.cx - from.cx, dy = to.cy - from.cy;
let t = 1;
if (dx > 0) t = Math.min(t, ((box.l + box.w) - from.cx) / dx);
if (dx < 0) t = Math.min(t, (box.l - from.cx) / dx);
if (dy > 0) t = Math.min(t, ((box.t + box.h) - from.cy) / dy);
if (dy < 0) t = Math.min(t, (box.t - from.cy) / dy);
return { x: from.cx + dx * t, y: from.cy + dy * t };
}
const paths = cyc.querySelectorAll('#cycle-arcs path');
const seq = [0, 1, 2, 3];
for (let k = 0; k < 4; k++) {
const a = boxes[seq[k]], b = boxes[seq[(k + 1) % 4]];
const start = edgePoint(a, b, a), end = edgePoint(b, a, b);
const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
let ox = mid.x - 592, oy = mid.y - 135;
const len = Math.hypot(ox, oy) || 1;
const cpx = mid.x + (ox / len) * 46, cpy = mid.y + (oy / len) * 46;
paths[k].setAttribute('d',
`M ${start.x.toFixed(1)} ${start.y.toFixed(1)} Q ${cpx.toFixed(1)} ${cpy.toFixed(1)} ${end.x.toFixed(1)} ${end.y.toFixed(1)}`);
}
}
addEventListener('load', alignCycle);
addEventListener('resize', alignCycle);
Reveal.on('slidechanged', alignCycle);
Reveal.on('ready', alignCycle);
if (document.fonts && document.fonts.ready) document.fonts.ready.then(alignCycle);
const AI_CONTRIBS = {
ingestion: {
title: 'The mappings',