Make a full notes about ECG to a 3rd year medical student
Finish it
"Electrocardiography allows simultaneous recording of myocardial activation from multiple vantage points on the body's surface, thereby permitting analysis of electrical activation in different myocardial regions." - Goldman-Cecil Medicine


| Parameter | Value |
|---|---|
| Paper speed | 25 mm/sec |
| Small box (1 mm) | 0.04 sec horizontally; 0.1 mV vertically |
| Large box (5 mm) | 0.2 sec horizontally; 0.5 mV vertically |
| Standard calibration | 10 mm = 1 mV |
| Recording duration | 10 seconds (standard ECG) |
| Lead | Positive Electrode | Views |
|---|---|---|
| Lead I | Left arm | Lateral wall |
| Lead II | Left leg | Inferior wall |
| Lead III | Left leg | Inferior wall |
| aVR | Right arm | Cavity (inverted) |
| aVL | Left arm | Lateral wall |
| aVF | Left leg | Inferior wall |
| Lead | Position | Views |
|---|---|---|
| V1 | 4th ICS, right sternal border | Septal / RV |
| V2 | 4th ICS, left sternal border | Septal |
| V3 | Between V2 and V4 | Anterior |
| V4 | 5th ICS, midclavicular line | Anterior |
| V5 | Anterior axillary line (same level as V4) | Lateral |
| V6 | Midaxillary line (same level as V4) | Lateral |

HR = 60 / R-R interval (in seconds) = 60,000 / R-R interval (in msec)
300 - 150 - 100 - 75 - 60 - 50 (for 1, 2, 3, 4, 5, 6 large boxes) Formula: HR = 300 / (number of large boxes)
| Rate | Interpretation |
|---|---|
| < 50 bpm | Bradycardia |
| 50-100 bpm | Normal |
| > 100 bpm | Tachycardia |
| Interval/Wave | Normal Value | Clinical Significance if Abnormal |
|---|---|---|
| HR | 50-100 bpm | Brady/tachycardia |
| P wave duration | < 120 msec | Atrial enlargement if broad |
| PR interval | 90-200 msec | AV block if long; WPW if short |
| QRS duration | 75-110 msec | BBB if > 120 msec |
| QTc (male) | 390-450 msec | TdP risk if prolonged |
| QTc (female) | 390-460 msec | TdP risk if prolonged |
| QRS axis | -30° to +90° | LAD / RAD if deviated |
| Lead I | Lead aVF | Axis |
|---|---|---|
| Positive | Positive | Normal (-30° to +90°) |
| Positive | Negative | Left axis deviation (LAD) |
| Negative | Positive | Right axis deviation (RAD) |
| Negative | Negative | Extreme RAD ("Northwest axis") |
| Time | ECG Change |
|---|---|
| Minutes | Hyperacute (tall, peaked) T waves |
| Hours | ST elevation |
| Hours-days | T wave inversion |
| Days-weeks | Pathological Q waves form |
| Weeks-months | ST returns to baseline |
| Permanent | Q waves may persist |
| Territory | Leads with changes | Coronary Artery |
|---|---|---|
| Inferior | II, III, aVF | RCA (80%), LCx (20%) |
| Lateral | I, aVL, V5, V6 | LCx or diagonal |
| Anterior | V1-V4 | LAD |
| Septal | V1-V2 | Septal perforators of LAD |
| Posterior | Tall R and ST depression in V1-V2 (reciprocal) | RCA or LCx |
| Right ventricular | V1, ST elevation in V4R | RCA |
| Feature | STEMI | Pericarditis |
|---|---|---|
| Distribution | Regional (one territory) | Diffuse (all leads except aVR/V1) |
| Morphology | Convex ("tombstone") | Concave (saddle-shaped) |
| PR depression | Absent | Present (pathognomonic) |
| Reciprocal changes | Present | Absent |
| Q waves | Develop | Do not develop |
| Drug | ECG Effect |
|---|---|
| Digoxin | "Reverse tick" ST depression, shortened QT, T wave inversion, PR prolongation, various arrhythmias |
| Beta-blockers | Bradycardia, PR prolongation |
| Class Ia antiarrhythmics (quinidine) | QT prolongation, QRS widening |
| Class Ic (flecainide) | QRS widening, PR prolongation |
| Class III (amiodarone, sotalol) | QT prolongation |
| TCAs (tricyclics) | Sinus tachycardia, QRS widening, QT prolongation, right axis deviation |
| Antipsychotics | QT prolongation |
| Fluoroquinolones / macrolides | QT prolongation |
| ECG Finding | Think Of |
|---|---|
| Delta wave + short PR | WPW |
| Sawtooth waves at 300 bpm | Atrial flutter |
| Irregularly irregular, no P waves | Atrial fibrillation |
| Progressive PR prolongation + dropped beat | Mobitz I (Wenckebach) |
| Fixed PR + sudden dropped beat | Mobitz II |
| P-QRS complete dissociation | Complete heart block |
| RSR' in V1 + wide S in V6 | RBBB |
| Broad notched R in V5-V6, no Q in V6 | LBBB |
| S1Q3T3 + sinus tachycardia | Pulmonary embolism |
| Diffuse ST elevation + PR depression | Pericarditis |
| Tall peaked T waves | Hyperkalemia |
| Prominent U waves | Hypokalemia |
| Short QT | Hypercalcemia |
| Long QT + low K/Mg | Torsades risk |
| Osborn (J) waves | Hypothermia |
| Reverse tick ST depression | Digoxin effect |
| Formula | Use |
|---|---|
| HR = 300 / (# large boxes) | Quick rate estimation |
| HR = 60,000 / RR interval (msec) | Precise rate |
| QTc = QT / √RR | Bazett's correction for QT |
| Sokolow-Lyon: SV1 + RV5/V6 > 35 mm | LVH criterion |
| Pathological Q: width ≥ 0.04 sec, depth ≥ 25% R | Q wave = infarction |
Create a presentation summarizing ECG interpretation steps
ls /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace/
mkdir -p /home/daytona/workspace/ecg-pptx && ls /home/daytona/workspace/ecg-pptx
~/ecg-pptx/build.js
const pptxgen = require("pptxgenjs");
const { execSync } = require("child_process");
// ── Palette ────────────────────────────────────────────────────────────────
// Deep navy dominant, crimson accent, white / light-steel secondary
const C = {
navy: "0D1B2A", // dominant bg
navyMid: "1A2E45", // card bg
navyLight: "1E3A5F", // lighter card / stripe
crimson: "C0392B", // accent red (ECG trace colour)
crimsonSoft:"E74C3C", // lighter accent
teal: "1ABC9C", // positive highlight
amber: "F39C12", // warning / caution
white: "FFFFFF",
offWhite: "EDF2F7",
steelGray: "8FA8C8", // muted text
lightGray: "BDC3C7",
};
// ── Helper utilities ───────────────────────────────────────────────────────
function fetchImages(...urls) {
const result = execSync(
`node /home/daytona/skills/shared/scripts/fetch_images.js ${urls.map(u => `"${u}"`).join(" ")}`
).toString();
return JSON.parse(result);
}
// Download key ECG images used throughout the deck
const imgUrls = [
"https://cdn.orris.care/cdss_images/ef815eeb06c673b405e3c7f9112d1e9ff6fbea96a00a2723b233d384156386bd.png", // ECG waveform diagram
"https://cdn.orris.care/cdss_images/ed3e36266afd563652d7495682c022c3508452257126640a6ab3375b0b0a7e46.png", // Conduction system
"https://cdn.orris.care/cdss_images/a768a511d3107d6bff69c3e8cf7b50c4485d458be34be6234f7fc6d4ca4fc3ec.png", // Cardiac cycle
];
console.log("Downloading images...");
const imgs = fetchImages(...imgUrls);
const ecgWaveImg = imgs[0].base64; // waveform
const condSysImg = imgs[1].base64; // conduction system
const cardCycImg = imgs[2].base64; // cardiac cycle
// ── Presentation setup ─────────────────────────────────────────────────────
const pres = new pptxgen();
pres.layout = "LAYOUT_16x9";
pres.title = "ECG Interpretation – A Systematic Approach";
pres.author = "Medical Education";
pres.subject = "Electrocardiography";
// ── Slide helpers ──────────────────────────────────────────────────────────
/** Dark header bar at top of slide */
function addHeaderBar(slide, text, subtext = "") {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 10, h: 1.1,
fill: { color: C.crimson }, line: { color: C.crimson }
});
slide.addText(text, {
x: 0.35, y: 0, w: 9.3, h: subtext ? 0.65 : 1.1,
fontSize: subtext ? 22 : 26, bold: true, color: C.white,
valign: subtext ? "bottom" : "middle", margin: 0
});
if (subtext) {
slide.addText(subtext, {
x: 0.35, y: 0.62, w: 9.3, h: 0.48,
fontSize: 13, color: "FADADD", italic: true, valign: "top", margin: 0
});
}
}
/** Numbered step badge */
function addStepBadge(slide, num, x, y, color = C.teal) {
slide.addShape(pres.shapes.ELLIPSE, {
x, y, w: 0.52, h: 0.52,
fill: { color }, line: { color }
});
slide.addText(String(num), {
x, y, w: 0.52, h: 0.52,
fontSize: 18, bold: true, color: C.white,
align: "center", valign: "middle", margin: 0
});
}
/** Coloured card rectangle */
function addCard(slide, x, y, w, h, fillColor = C.navyMid) {
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
x, y, w, h,
fill: { color: fillColor },
line: { color: fillColor },
rectRadius: 0.08,
shadow: { type: "outer", color: "000000", blur: 8, offset: 3, angle: 135, opacity: 0.25 }
});
}
/** Left accent stripe on a card */
function addAccentStripe(slide, x, y, h, color = C.crimson) {
slide.addShape(pres.shapes.RECTANGLE, {
x, y, w: 0.07, h,
fill: { color }, line: { color }
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 1 – Title
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
// Red horizontal stripe top
s.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 10, h: 0.18, fill: { color: C.crimson }, line: { color: C.crimson }
});
// Red horizontal stripe bottom
s.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 5.44, w: 10, h: 0.18, fill: { color: C.crimson }, line: { color: C.crimson }
});
// ECG waveform image – right half, tinted
s.addImage({ data: ecgWaveImg, x: 4.6, y: 0.9, w: 5.1, h: 3.8, transparency: 35 });
// Dark overlay panel left
addCard(s, 0.3, 0.55, 5.2, 4.5, C.navyMid);
addAccentStripe(s, 0.3, 0.55, 4.5, C.crimson);
s.addText("ECG Interpretation", {
x: 0.6, y: 0.75, w: 4.7, h: 1.0,
fontSize: 32, bold: true, color: C.white, margin: 0
});
s.addText("A Systematic Approach", {
x: 0.6, y: 1.75, w: 4.7, h: 0.6,
fontSize: 20, color: C.crimsonSoft, bold: true, margin: 0
});
s.addShape(pres.shapes.RECTANGLE, {
x: 0.6, y: 2.42, w: 1.5, h: 0.05, fill: { color: C.steelGray }, line: { color: C.steelGray }
});
s.addText([
{ text: "For 3rd Year Medical Students\n", options: { breakLine: true } },
{ text: "Based on Goldman-Cecil Medicine & Medical Physiology" }
], {
x: 0.6, y: 2.6, w: 4.7, h: 1.2,
fontSize: 12, color: C.steelGray, italic: true, margin: 0
});
s.addText("Rate • Rhythm • Axis • Intervals • Hypertrophy • Ischemia", {
x: 0.3, y: 4.7, w: 9.4, h: 0.5,
fontSize: 11, color: C.steelGray, align: "center",
italic: true, charSpacing: 1, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 2 – Why ECG & What It Measures
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "What Does the ECG Measure?", "Understanding the fundamentals before systematic interpretation");
// Conduction system image
s.addImage({ data: condSysImg, x: 5.6, y: 1.2, w: 4.1, h: 3.5 });
const items = [
["Heart Rate", "Beats per minute from R-R interval"],
["Rhythm", "Origin & regularity of the pacemaker"],
["Axis", "Net direction of ventricular depolarisation"],
["Intervals", "Conduction time through each structure"],
["Morphology", "Hypertrophy, ischaemia, infarction patterns"],
];
items.forEach(([title, desc], i) => {
const y = 1.28 + i * 0.76;
addCard(s, 0.3, y, 5.1, 0.64, C.navyMid);
addAccentStripe(s, 0.3, y, 0.64, C.teal);
addStepBadge(s, i + 1, 0.5, y + 0.06, C.teal);
s.addText(title, {
x: 1.15, y: y + 0.02, w: 2.0, h: 0.3,
fontSize: 13, bold: true, color: C.white, margin: 0
});
s.addText(desc, {
x: 1.15, y: y + 0.3, w: 4.0, h: 0.28,
fontSize: 11, color: C.steelGray, margin: 0
});
});
s.addText("Conduction System", {
x: 5.6, y: 4.75, w: 4.1, h: 0.3,
fontSize: 10, color: C.steelGray, align: "center", italic: true, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 3 – ECG Paper & Calibration
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "ECG Paper & Calibration", "Know your grid before measuring anything");
// Waveform image right
s.addImage({ data: ecgWaveImg, x: 5.2, y: 1.2, w: 4.5, h: 3.9 });
// Two info cards
const gridData = [
["Small Box (1 mm)", "0.04 sec | 0.1 mV"],
["Large Box (5 mm)", "0.20 sec | 0.5 mV"],
["Standard Speed", "25 mm / second"],
["Calibration", "10 mm = 1 mV"],
["Recording Time", "10 seconds (standard)"],
];
gridData.forEach(([label, val], i) => {
const y = 1.3 + i * 0.72;
addCard(s, 0.3, y, 4.75, 0.6, i % 2 === 0 ? C.navyMid : C.navyLight);
s.addText(label, {
x: 0.5, y: y + 0.06, w: 2.3, h: 0.25,
fontSize: 12, bold: true, color: C.offWhite, margin: 0
});
s.addText(val, {
x: 0.5, y: y + 0.3, w: 4.4, h: 0.24,
fontSize: 12, color: C.teal, bold: true, margin: 0
});
});
s.addText("Normal ECG Waveform with Intervals\n(Goldman-Cecil Medicine)", {
x: 5.2, y: 5.1, w: 4.5, h: 0.4,
fontSize: 9.5, color: C.steelGray, align: "center", italic: true, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 4 – The 12 Leads
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "The 12 Leads – What Each Views", "Each lead is a different vantage point on the same electrical activity");
const limb = [
["I", "Left arm (+)", "Lateral"],
["II", "Left leg (+)", "Inferior"],
["III", "Left leg (+)", "Inferior"],
["aVR", "Right arm (+)", "Cavity (inverted)"],
["aVL", "Left arm (+)", "Lateral"],
["aVF", "Left leg (+)", "Inferior"],
];
const prec = [
["V1", "4th ICS R sternal border", "Septal / RV"],
["V2", "4th ICS L sternal border", "Septal"],
["V3", "Between V2 & V4", "Anterior"],
["V4", "5th ICS midclavicular", "Anterior"],
["V5", "Anterior axillary line", "Lateral"],
["V6", "Midaxillary line", "Lateral"],
];
// Limb leads table
s.addText("LIMB LEADS", {
x: 0.25, y: 1.2, w: 4.6, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const limbRows = [
[{ text: "Lead", options: { bold: true, color: C.white } },
{ text: "Positive Electrode", options: { bold: true, color: C.white } },
{ text: "Territory", options: { bold: true, color: C.white } }],
...limb.map(([l, e, t]) => [
{ text: l, options: { bold: true, color: C.teal } },
{ text: e, options: { color: C.offWhite } },
{ text: t, options: { color: C.steelGray } }
])
];
s.addTable(limbRows, {
x: 0.25, y: 1.52, w: 4.6, h: 2.3,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [0.8, 2.1, 1.7],
fontSize: 11,
rowH: 0.3,
});
// Precordial leads table
s.addText("PRECORDIAL LEADS", {
x: 5.2, y: 1.2, w: 4.6, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const precRows = [
[{ text: "Lead", options: { bold: true, color: C.white } },
{ text: "Position", options: { bold: true, color: C.white } },
{ text: "Territory", options: { bold: true, color: C.white } }],
...prec.map(([l, p, t]) => [
{ text: l, options: { bold: true, color: C.amber } },
{ text: p, options: { color: C.offWhite } },
{ text: t, options: { color: C.steelGray } }
])
];
s.addTable(precRows, {
x: 5.2, y: 1.52, w: 4.6, h: 2.3,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [0.8, 2.2, 1.6],
fontSize: 11,
rowH: 0.3,
});
// Key rule
addCard(s, 0.25, 4.05, 9.5, 0.72, C.navyLight);
addAccentStripe(s, 0.25, 4.05, 0.72, C.amber);
s.addText("Key Rule: ", {
x: 0.5, y: 4.12, w: 1.1, h: 0.3,
fontSize: 12, bold: true, color: C.amber, margin: 0
});
s.addText("A positive deflection = wavefront travelling TOWARD the positive electrode of that lead.", {
x: 1.55, y: 4.12, w: 7.9, h: 0.3,
fontSize: 12, color: C.offWhite, margin: 0
});
s.addText("Reciprocal changes in opposite leads confirm the finding.", {
x: 1.55, y: 4.44, w: 7.9, h: 0.25,
fontSize: 11, color: C.steelGray, italic: true, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 5 – The Normal Waveforms
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "The Normal ECG Waveforms", "What each deflection represents physiologically");
const waves = [
{ name: "P Wave", color: C.teal, text: "Atrial depolarisation | < 120 msec | < 2.5 mm limb leads" },
{ name: "PR Interval", color: C.steelGray, text: "Atrial dep. → ventricular dep. (AV node delay) | 90–200 msec" },
{ name: "QRS Complex", color: C.crimsonSoft,text: "Ventricular depolarisation | 75–110 msec" },
{ name: "ST Segment", color: C.amber, text: "Early ventricular repolarisation (plateau) | Isoelectric = normal" },
{ name: "T Wave", color: C.teal, text: "Ventricular repolarisation | Upright I, II, V3–V6" },
{ name: "QT Interval", color: C.crimsonSoft,text: "Total ventricular activity | QTc = QT / √RR | ♂ ≤450 ms ♀ ≤460 ms" },
{ name: "U Wave", color: C.steelGray, text: "Purkinje repolarisation (debated) | Prominent → hypokalemia" },
];
waves.forEach((w, i) => {
const col = i < 4 ? 0 : 1;
const row = i < 4 ? i : i - 4;
const x = col === 0 ? 0.25 : 5.1;
const y = 1.22 + row * 0.88;
addCard(s, x, y, 4.6, 0.74, C.navyMid);
addAccentStripe(s, x, y, 0.74, w.color);
s.addText(w.name, {
x: x + 0.2, y: y + 0.04, w: 4.1, h: 0.28,
fontSize: 13, bold: true, color: w.color, margin: 0
});
s.addText(w.text, {
x: x + 0.2, y: y + 0.34, w: 4.25, h: 0.34,
fontSize: 10.5, color: C.offWhite, margin: 0
});
});
// ECG image at bottom right of col 2 (fills remaining space)
s.addImage({ data: ecgWaveImg, x: 5.1, y: 4.46, w: 4.6, h: 1.05, transparency: 20 });
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 6 – Normal Intervals Table
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Normal Interval Reference Table", "Memorise these — they define normal vs. abnormal");
const rows = [
[{ text: "Parameter", options: { bold: true, color: C.white, fill: { color: C.crimson } } },
{ text: "Normal Range", options: { bold: true, color: C.white, fill: { color: C.crimson } } },
{ text: "If Abnormal…", options: { bold: true, color: C.white, fill: { color: C.crimson } } }],
[{ text: "Heart Rate", options: { color: C.teal, bold: true } },
{ text: "50–100 bpm", options: { color: C.offWhite } },
{ text: "Brady / Tachycardia", options: { color: C.steelGray } }],
[{ text: "P Wave Duration", options: { color: C.teal, bold: true } },
{ text: "< 120 msec", options: { color: C.offWhite } },
{ text: "Atrial enlargement", options: { color: C.steelGray } }],
[{ text: "PR Interval", options: { color: C.teal, bold: true } },
{ text: "90–200 msec", options: { color: C.offWhite } },
{ text: "AV block (↑) / WPW (↓)", options: { color: C.steelGray } }],
[{ text: "QRS Duration", options: { color: C.teal, bold: true } },
{ text: "75–110 msec", options: { color: C.offWhite } },
{ text: "Bundle branch block > 120 msec", options: { color: C.steelGray } }],
[{ text: "QTc (Male)", options: { color: C.amber, bold: true } },
{ text: "390–450 msec", options: { color: C.offWhite } },
{ text: "TdP risk if prolonged", options: { color: C.steelGray } }],
[{ text: "QTc (Female)", options: { color: C.amber, bold: true } },
{ text: "390–460 msec", options: { color: C.offWhite } },
{ text: "TdP risk if prolonged", options: { color: C.steelGray } }],
[{ text: "QRS Axis", options: { color: C.teal, bold: true } },
{ text: "−30° to +90°", options: { color: C.offWhite } },
{ text: "LAD / RAD", options: { color: C.steelGray } }],
];
s.addTable(rows, {
x: 0.4, y: 1.25, w: 9.2, h: 3.95,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [2.6, 2.8, 3.8],
fontSize: 12.5,
rowH: 0.46,
});
// Bazett formula box
addCard(s, 0.4, 5.28, 9.2, 0.28, C.navyLight);
s.addText("Bazett's Formula: QTc = QT (sec) ÷ √RR (sec) | Measure QT in leads II, V5, V6 – use the longest", {
x: 0.6, y: 5.3, w: 8.8, h: 0.24,
fontSize: 11, color: C.amber, italic: true, bold: true, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 7 – Step-by-Step Interpretation Framework
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "10-Step Systematic Approach", "Use this sequence every single time — never skip a step");
const steps = [
["Rate", "Count large boxes between R waves → 300/boxes", C.teal],
["Rhythm", "Regular or irregular? P before every QRS?", C.teal],
["P Wave", "Present, upright I & II, normal morphology?", C.teal],
["PR Interval", "Normal 90–200 msec? Prolonged = AV block", C.amber],
["QRS Duration", "< 110 msec? Widened = BBB / preexcitation", C.amber],
["QRS Axis", "Leads I & aVF: both positive = normal axis", C.amber],
["ST Segment", "Isoelectric? Elevation or depression?", C.crimsonSoft],
["T Wave", "Upright I, II, V3–V6? Inverted = ischaemia?", C.crimsonSoft],
["QT Interval", "Calculate QTc — prolonged = TdP risk", C.crimsonSoft],
["Interpretation","Synthesise all findings → clinical diagnosis", C.steelGray],
];
steps.forEach(([ title, desc, col ], i) => {
const column = i < 5 ? 0 : 1;
const row = i < 5 ? i : i - 5;
const x = column === 0 ? 0.25 : 5.1;
const y = 1.2 + row * 0.82;
addCard(s, x, y, 4.6, 0.7, C.navyMid);
addStepBadge(s, i + 1, x + 0.1, y + 0.09, col);
s.addText(title, {
x: x + 0.75, y: y + 0.04, w: 3.7, h: 0.28,
fontSize: 13, bold: true, color: col, margin: 0
});
s.addText(desc, {
x: x + 0.75, y: y + 0.35, w: 3.75, h: 0.3,
fontSize: 10.5, color: C.offWhite, margin: 0
});
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 8 – Heart Rate & Rhythm
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Steps 1 & 2: Rate and Rhythm", "Foundation of every ECG read");
// Rate methods
s.addText("HEART RATE METHODS", {
x: 0.3, y: 1.25, w: 4.5, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const rateMethods = [
["Quick Method", "300 ÷ number of large boxes between R waves\n300 → 150 → 100 → 75 → 60 → 50"],
["Precise Method", "60,000 ÷ R-R interval in milliseconds"],
["Irregular Rhythm", "Count QRS complexes in 10 sec strip × 6"],
];
rateMethods.forEach(([m, d], i) => {
const y = 1.62 + i * 0.88;
addCard(s, 0.3, y, 4.5, 0.76, C.navyMid);
addAccentStripe(s, 0.3, y, 0.76, C.teal);
s.addText(m, { x: 0.55, y: y + 0.06, w: 4.0, h: 0.26, fontSize: 12.5, bold: true, color: C.teal, margin: 0 });
s.addText(d, { x: 0.55, y: y + 0.35, w: 4.1, h: 0.36, fontSize: 11, color: C.offWhite, margin: 0 });
});
// Rhythm criteria
s.addText("NORMAL SINUS RHYTHM CRITERIA", {
x: 5.1, y: 1.25, w: 4.7, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const nsrItems = [
"Regular rhythm at 60–100 bpm",
"P wave upright in leads I, II, aVF",
"P wave inverted in aVR",
"Every P wave followed by a QRS",
"Every QRS preceded by a P wave",
"PR interval 120–200 msec",
"QRS duration < 120 msec",
"Normal axis (−30° to +90°)",
];
addCard(s, 5.1, 1.62, 4.7, 3.6, C.navyMid);
addAccentStripe(s, 5.1, 1.62, 3.6, C.amber);
s.addText(nsrItems.map(t => ({ text: t, options: { bullet: { indent: 15 }, breakLine: true } }))
.map((o, i) => i === nsrItems.length - 1 ? { ...o, options: { ...o.options, breakLine: false } } : o),
{ x: 5.35, y: 1.72, w: 4.3, h: 3.3, fontSize: 12, color: C.offWhite, margin: 0, lineSpacingMultiple: 1.4 }
);
// Rate quick table
addCard(s, 0.3, 5.12, 4.5, 0.44, C.navyLight);
s.addText("< 60 bpm = Bradycardia | 60–100 = Normal | > 100 bpm = Tachycardia", {
x: 0.35, y: 5.15, w: 4.4, h: 0.38,
fontSize: 11, color: C.amber, bold: true, align: "center", margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 9 – QRS Axis
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Step 6: QRS Axis", "Use leads I and aVF for rapid axis determination");
// Axis quadrant diagram using shapes
const cx = 5.0, cy = 3.2, r = 1.6;
// Circle background
s.addShape(pres.shapes.ELLIPSE, {
x: cx - r, y: cy - r, w: r * 2, h: r * 2,
fill: { color: C.navyMid }, line: { color: C.steelGray, pt: 1.5 }
});
// Horizontal axis
s.addShape(pres.shapes.LINE, { x: cx - r, y: cy, w: r * 2, h: 0, line: { color: C.steelGray, pt: 1.5 } });
// Vertical axis
s.addShape(pres.shapes.LINE, { x: cx, y: cy - r, w: 0, h: r * 2, line: { color: C.steelGray, pt: 1.5 } });
// Quadrant labels
s.addText("NORMAL\n(−30° to +90°)\nI ✓ aVF ✓", { x: cx + 0.1, y: cy + 0.08, w: 1.5, h: 0.7, fontSize: 10, color: C.teal, bold: true, align: "center", margin: 0 });
s.addText("LAD\n(< −30°)\nI ✓ aVF ✗", { x: cx - 1.55, y: cy + 0.08, w: 1.45, h: 0.7, fontSize: 10, color: C.amber, bold: true, align: "center", margin: 0 });
s.addText("RAD\n(> +90°)\nI ✗ aVF ✓", { x: cx + 0.1, y: cy - 0.78, w: 1.45, h: 0.7, fontSize: 10, color: C.crimsonSoft, bold: true, align: "center", margin: 0 });
s.addText("EXTREME\nNW axis\nI ✗ aVF ✗", { x: cx - 1.55, y: cy - 0.78, w: 1.45, h: 0.7, fontSize: 10, color: C.steelGray, bold: true, align: "center", margin: 0 });
// Lead labels
s.addText("aVF (+)", { x: cx + 0.1, y: cy + r + 0.05, w: 0.9, h: 0.28, fontSize: 10, color: C.steelGray, margin: 0 });
s.addText("aVF (−)", { x: cx + 0.1, y: cy - r - 0.3, w: 0.9, h: 0.28, fontSize: 10, color: C.steelGray, margin: 0 });
s.addText("I (+)", { x: cx + r + 0.05, y: cy, w: 0.7, h: 0.28, fontSize: 10, color: C.steelGray, margin: 0 });
s.addText("I (−)", { x: cx - r - 0.75, y: cy, w: 0.7, h: 0.28, fontSize: 10, color: C.steelGray, margin: 0 });
// LAD causes
s.addText("LEFT AXIS DEVIATION (LAD)", {
x: 0.25, y: 1.2, w: 4.3, h: 0.32,
fontSize: 11, bold: true, color: C.amber, charSpacing: 1.5, margin: 0
});
const ladCauses = ["Left anterior fascicular block (most common)", "Left ventricular hypertrophy", "Inferior STEMI", "WPW (right-sided accessory pathway)"];
addCard(s, 0.25, 1.55, 4.3, 1.6, C.navyMid);
addAccentStripe(s, 0.25, 1.55, 1.6, C.amber);
s.addText(ladCauses.map((t, i) => ({ text: t, options: { bullet: { indent: 15 }, breakLine: i < ladCauses.length - 1 } })), {
x: 0.5, y: 1.65, w: 3.9, h: 1.4, fontSize: 11.5, color: C.offWhite, lineSpacingMultiple: 1.45, margin: 0
});
// RAD causes
s.addText("RIGHT AXIS DEVIATION (RAD)", {
x: 0.25, y: 3.28, w: 4.3, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 1.5, margin: 0
});
const radCauses = ["Right ventricular hypertrophy", "Left posterior fascicular block", "Pulmonary embolism / cor pulmonale", "Normal in children & tall thin adults"];
addCard(s, 0.25, 3.62, 4.3, 1.6, C.navyMid);
addAccentStripe(s, 0.25, 3.62, 1.6, C.crimsonSoft);
s.addText(radCauses.map((t, i) => ({ text: t, options: { bullet: { indent: 15 }, breakLine: i < radCauses.length - 1 } })), {
x: 0.5, y: 3.72, w: 3.9, h: 1.4, fontSize: 11.5, color: C.offWhite, lineSpacingMultiple: 1.45, margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 10 – Bundle Branch Blocks
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Bundle Branch Blocks", "QRS ≥ 120 msec + specific morphology");
// RBBB left column
s.addText("RIGHT BUNDLE BRANCH BLOCK", {
x: 0.25, y: 1.2, w: 4.6, h: 0.32,
fontSize: 11, bold: true, color: C.amber, charSpacing: 1.5, margin: 0
});
addCard(s, 0.25, 1.55, 4.6, 3.75, C.navyMid);
addAccentStripe(s, 0.25, 1.55, 3.75, C.amber);
const rbbItems = [
["QRS Duration", "≥ 120 msec (3 small boxes)"],
["V1 Pattern", "RSR' – 'rabbit ears' (M-shape)"],
["I, V5, V6", "Wide, slurred S wave"],
["T waves", "Inversion V1–V3 (secondary change)"],
["Causes", "PE, cor pulmonale, congenital, ischaemia, normal variant"],
["Mnemonic", "R in the Right: RSR' in V1"],
];
rbbItems.forEach(([k, v], i) => {
const y = 1.7 + i * 0.55;
s.addText(k + ": ", { x: 0.5, y, w: 1.5, h: 0.27, fontSize: 11.5, bold: true, color: C.amber, margin: 0 });
s.addText(v, { x: 1.9, y, w: 2.8, h: 0.27, fontSize: 11.5, color: C.offWhite, margin: 0 });
});
// LBBB right column
s.addText("LEFT BUNDLE BRANCH BLOCK", {
x: 5.15, y: 1.2, w: 4.6, h: 0.32,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 1.5, margin: 0
});
addCard(s, 5.15, 1.55, 4.6, 3.75, C.navyMid);
addAccentStripe(s, 5.15, 1.55, 3.75, C.crimsonSoft);
const lbbItems = [
["QRS Duration", "≥ 120 msec"],
["V1 Pattern", "Broad QS or rS (W-shape)"],
["I, V5, V6", "Broad, notched R; no Q waves in V5-V6"],
["T waves", "Discordant (opposite to QRS direction)"],
["Significance", "Almost always pathological (CAD, cardiomyopathy, HTN)"],
["Mnemonic", "WiLLiaM MoRRoW: W in V1, M in V6 for LBBB"],
];
lbbItems.forEach(([k, v], i) => {
const y = 1.7 + i * 0.55;
s.addText(k + ": ", { x: 5.4, y, w: 1.5, h: 0.27, fontSize: 11.5, bold: true, color: C.crimsonSoft, margin: 0 });
s.addText(v, { x: 6.8, y, w: 2.8, h: 0.27, fontSize: 11.5, color: C.offWhite, margin: 0 });
});
// Bottom note
addCard(s, 0.25, 5.38, 9.5, 0.2, C.navyLight);
s.addText("New LBBB + chest pain = treat as STEMI equivalent | Apply Sgarbossa criteria for ischaemia diagnosis", {
x: 0.35, y: 5.39, w: 9.3, h: 0.18,
fontSize: 10.5, color: C.amber, bold: true, align: "center", margin: 0
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 11 – Ischaemia & Infarction
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Ischaemia & Infarction Patterns", "ST changes and Q waves localise the territory");
// Timeline of STEMI changes
s.addText("STEMI EVOLUTION", {
x: 0.25, y: 1.2, w: 9.5, h: 0.3,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const stemiStages = [
["Minutes", "Hyperacute T waves\n(tall, peaked)", C.amber],
["Hours", "ST elevation\n(convex / tombstone)", C.crimson],
["Hours–Days", "T wave inversion", C.crimsonSoft],
["Days–Weeks", "Pathological Q waves\nform", C.steelGray],
["Persistent", "Q waves remain\n(scar)", C.navyLight],
];
stemiStages.forEach(([t, d, col], i) => {
const x = 0.25 + i * 1.9;
addCard(s, x, 1.55, 1.75, 1.3, col === C.navyLight ? C.navyMid : C.navyMid);
addAccentStripe(s, x, 1.55, 1.3, col);
s.addText(t, { x: x + 0.15, y: 1.6, w: 1.45, h: 0.3, fontSize: 11, bold: true, color: col, margin: 0 });
s.addText(d, { x: x + 0.15, y: 1.92, w: 1.5, h: 0.55, fontSize: 10.5, color: C.offWhite, margin: 0 });
});
// Territory table
s.addText("INFARCT LOCALISATION", {
x: 0.25, y: 3.0, w: 9.5, h: 0.3,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const terrRows = [
[{ text: "Territory", options: { bold: true, color: C.white, fill: { color: C.crimson } } },
{ text: "Leads", options: { bold: true, color: C.white, fill: { color: C.crimson } } },
{ text: "Artery", options: { bold: true, color: C.white, fill: { color: C.crimson } } }],
["Inferior", "II, III, aVF", "RCA (80%) / LCx (20%)"],
["Lateral", "I, aVL, V5-V6", "LCx or diagonal"],
["Anterior", "V1–V4", "LAD"],
["Septal", "V1–V2", "Septal perforators (LAD)"],
["Posterior", "Tall R + ST↓ in V1-V2", "RCA or LCx"],
["Right Ventricle", "V4R (RV lead)", "RCA (proximal)"],
];
const formattedRows = terrRows.map((row, i) => {
if (i === 0) return row;
return (row as string[]).map((cell, j) => ({
text: cell,
options: { color: j === 0 ? C.teal : j === 1 ? C.offWhite : C.steelGray }
}));
});
s.addTable(formattedRows, {
x: 0.25, y: 3.33, w: 9.5, h: 2.2,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [2.5, 3.5, 3.5],
fontSize: 12,
rowH: 0.34,
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 12 – Common Arrhythmias Quick Reference
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Common Arrhythmias – Quick Reference", "Recognise the pattern, identify the mechanism");
const arrhythmias = [
{ name: "Sinus Tachycardia", features: "Rate > 100, normal P waves, regular, narrow QRS", color: C.teal },
{ name: "Sinus Bradycardia", features: "Rate < 60, normal P waves, otherwise normal", color: C.teal },
{ name: "Atrial Fibrillation", features: "Irregularly irregular, no P waves, variable rate, narrow QRS", color: C.amber },
{ name: "Atrial Flutter", features: "Sawtooth at 300 bpm, usually 2:1 block → ventricular rate ~150", color: C.amber },
{ name: "SVT (AVNRT/AVRT)", features: "Regular narrow-complex tachycardia 150–250 bpm, P buried in QRS", color: C.amber },
{ name: "VT", features: "≥3 wide QRS > 100 bpm, AV dissociation, fusion/capture beats", color: C.crimsonSoft },
{ name: "VF", features: "Chaotic irregular wide complexes, no output – cardiac arrest", color: C.crimson },
{ name: "1° AV Block", features: "PR > 200 msec, all P waves conduct, usually benign", color: C.steelGray },
{ name: "Mobitz I (Wenckebach)", features: "Progressive PR prolongation → dropped QRS → repeats", color: C.amber },
{ name: "Mobitz II", features: "Fixed PR, sudden dropped QRS – below AV node, serious", color: C.crimsonSoft },
{ name: "Complete Heart Block", features: "P–QRS complete dissociation, escape rhythm – emergency", color: C.crimson },
];
// Two columns
const col1 = arrhythmias.slice(0, 6);
const col2 = arrhythmias.slice(6);
[[col1, 0.25], [col2, 5.1]].forEach(([items, xBase]) => {
(items as typeof arrhythmias).forEach((arr, i) => {
const y = 1.2 + i * 0.7;
addCard(s, xBase as number, y, 4.6, 0.6, C.navyMid);
addAccentStripe(s, xBase as number, y, 0.6, arr.color);
s.addText(arr.name, {
x: (xBase as number) + 0.2, y: y + 0.04, w: 4.2, h: 0.25,
fontSize: 12, bold: true, color: arr.color, margin: 0
});
s.addText(arr.features, {
x: (xBase as number) + 0.2, y: y + 0.31, w: 4.2, h: 0.25,
fontSize: 10.5, color: C.offWhite, margin: 0
});
});
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 13 – Electrolyte & Drug Effects
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "Electrolyte & Drug Effects on ECG", "High-yield for exams and clinical practice");
// Electrolytes table
s.addText("ELECTROLYTES", {
x: 0.25, y: 1.22, w: 5.5, h: 0.3,
fontSize: 11, bold: true, color: C.teal, charSpacing: 2, margin: 0
});
const elecRows = [
[{ text: "Condition", options: { bold: true, color: C.white, fill: { color: C.navyLight } } },
{ text: "Key ECG Finding", options: { bold: true, color: C.white, fill: { color: C.navyLight } } }],
[{ text: "Hyperkalemia (early)", options: { color: C.amber } }, { text: "Tall peaked narrow T waves", options: { color: C.offWhite } }],
[{ text: "Hyperkalemia (severe)", options: { color: C.crimson } }, { text: "Widened QRS → sine wave → VF", options: { color: C.offWhite } }],
[{ text: "Hypokalemia", options: { color: C.teal } }, { text: "Flat T waves, prominent U waves, long QU", options: { color: C.offWhite } }],
[{ text: "Hypercalcemia", options: { color: C.amber } }, { text: "Short QT interval (short ST segment)", options: { color: C.offWhite } }],
[{ text: "Hypocalcemia", options: { color: C.steelGray } }, { text: "Long QT interval (long ST segment)", options: { color: C.offWhite } }],
[{ text: "Hypomagnesemia", options: { color: C.teal } }, { text: "Long QT, TdP risk (often + hypoK)", options: { color: C.offWhite } }],
];
s.addTable(elecRows, {
x: 0.25, y: 1.55, w: 4.6, h: 2.8,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [2.2, 2.4],
fontSize: 11.5, rowH: 0.39,
});
// Drugs table
s.addText("DRUGS", {
x: 5.15, y: 1.22, w: 4.6, h: 0.3,
fontSize: 11, bold: true, color: C.amber, charSpacing: 2, margin: 0
});
const drugRows = [
[{ text: "Drug", options: { bold: true, color: C.white, fill: { color: C.navyLight } } },
{ text: "ECG Effect", options: { bold: true, color: C.white, fill: { color: C.navyLight } } }],
[{ text: "Digoxin", options: { color: C.amber } }, { text: "Reverse tick ST↓, short QT, ↑PR", options: { color: C.offWhite } }],
[{ text: "Beta-blockers", options: { color: C.steelGray } }, { text: "Bradycardia, ↑PR", options: { color: C.offWhite } }],
[{ text: "Class Ia (quinidine)", options: { color: C.crimsonSoft } }, { text: "↑QT, wide QRS", options: { color: C.offWhite } }],
[{ text: "Class III (amiodarone)", options: { color: C.crimsonSoft } }, { text: "↑QT, bradycardia", options: { color: C.offWhite } }],
[{ text: "TCAs", options: { color: C.crimson } }, { text: "Sinus tach, wide QRS, ↑QT, RAD", options: { color: C.offWhite } }],
[{ text: "Fluoroquinolones / antipsychotics", options: { color: C.amber } }, { text: "↑QT → TdP risk", options: { color: C.offWhite } }],
];
s.addTable(drugRows, {
x: 5.15, y: 1.55, w: 4.6, h: 2.8,
border: { pt: 0.5, color: C.navyLight },
fill: { color: C.navyMid },
colW: [2.3, 2.3],
fontSize: 11.5, rowH: 0.39,
});
// Special patterns box
s.addText("SPECIAL PATTERNS", {
x: 0.25, y: 4.5, w: 9.5, h: 0.3,
fontSize: 11, bold: true, color: C.crimsonSoft, charSpacing: 2, margin: 0
});
const specials = [
["WPW", "Short PR + delta wave + wide QRS", C.amber],
["Brugada", "Coved ST elevation V1-V3 + RBBB-like", C.crimsonSoft],
["PE", "Sinus tach; S1Q3T3; RBBB; T inv V1-V4", C.teal],
["Hypothermia","Sinus brady + Osborn (J) waves", C.steelGray],
];
specials.forEach(([name, desc, col], i) => {
const x = 0.25 + i * 2.45;
addCard(s, x, 4.84, 2.3, 0.72, C.navyMid);
addAccentStripe(s, x, 4.84, 0.72, col as string);
s.addText(name as string, { x: x + 0.18, y: 4.89, w: 1.95, h: 0.26, fontSize: 12, bold: true, color: col as string, margin: 0 });
s.addText(desc as string, { x: x + 0.18, y: 5.16, w: 1.95, h: 0.36, fontSize: 10, color: C.offWhite, margin: 0 });
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 14 – Quick Clues Cheat Sheet
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
addHeaderBar(s, "ECG Quick Clues – Cheat Sheet", "One finding → think of …");
const clues = [
["Delta wave + short PR", "WPW syndrome", C.amber],
["Sawtooth waves ~300 bpm", "Atrial flutter", C.teal],
["Irregularly irregular, no P", "Atrial fibrillation", C.teal],
["Progressive PR → dropped beat", "Mobitz I (Wenckebach)", C.steelGray],
["Fixed PR + sudden dropped QRS", "Mobitz II", C.crimsonSoft],
["P-QRS complete dissociation", "Complete heart block", C.crimson],
["RSR' in V1 + wide S in V6", "RBBB", C.amber],
["Broad notched R V5-V6, no Q", "LBBB", C.crimsonSoft],
["S1Q3T3 + sinus tachycardia", "Pulmonary embolism", C.teal],
["Diffuse ST↑ + PR depression", "Pericarditis", C.steelGray],
["Tall peaked T waves", "Hyperkalemia (early)", C.amber],
["Prominent U waves", "Hypokalemia", C.teal],
["Short QT interval", "Hypercalcemia", C.steelGray],
["Long QT + low K/Mg", "TdP risk", C.crimsonSoft],
["Osborn (J) waves", "Hypothermia", C.steelGray],
["Reverse tick ST depression", "Digoxin effect", C.amber],
];
const half = 8;
[[clues.slice(0, half), 0.25], [clues.slice(half), 5.1]].forEach(([items, xBase]) => {
(items as typeof clues).forEach(([ecg, dx, col], i) => {
const y = 1.2 + i * 0.52;
addCard(s, xBase as number, y, 4.6, 0.44, C.navyMid);
addAccentStripe(s, xBase as number, y, 0.44, col as string);
s.addText(ecg as string, {
x: (xBase as number) + 0.2, y: y + 0.04, w: 2.1, h: 0.36,
fontSize: 11, color: C.offWhite, margin: 0
});
s.addText("→ " + (dx as string), {
x: (xBase as number) + 2.3, y: y + 0.04, w: 2.2, h: 0.36,
fontSize: 11, bold: true, color: col as string, margin: 0
});
});
});
}
// ══════════════════════════════════════════════════════════════════════════
// SLIDE 15 – Summary / Closing
// ══════════════════════════════════════════════════════════════════════════
{
const s = pres.addSlide();
s.background = { color: C.navy };
// Full crimson top bar
s.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 10, h: 0.18, fill: { color: C.crimson }, line: { color: C.crimson }
});
s.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 5.44, w: 10, h: 0.18, fill: { color: C.crimson }, line: { color: C.crimson }
});
s.addText("Always Approach the ECG Systematically", {
x: 0.5, y: 0.35, w: 9, h: 0.7,
fontSize: 28, bold: true, color: C.white, align: "center", margin: 0
});
const pillars = [
["1 Rate", "300 ÷ large boxes"],
["2 Rhythm", "Regular? P→QRS?"],
["3 P Wave", "Upright I & II?"],
["4 PR Interval","90–200 msec"],
["5 QRS", "< 110 msec"],
["6 Axis", "I & aVF positive?"],
["7 ST Segment", "Isoelectric?"],
["8 T Wave", "Upright V3–V6?"],
["9 QTc", "≤ 450 / 460 msec"],
["10 Synthesise", "Clinical context"],
];
pillars.forEach(([step, hint], i) => {
const col = i % 5;
const row = Math.floor(i / 5);
const x = 0.25 + col * 1.92;
const y = 1.25 + row * 1.6;
const accent = i < 3 ? C.teal : i < 6 ? C.amber : i < 9 ? C.crimsonSoft : C.steelGray;
addCard(s, x, y, 1.78, 1.38, C.navyMid);
s.addShape(pres.shapes.RECTANGLE, {
x, y, w: 1.78, h: 0.4, fill: { color: accent }, line: { color: accent }
});
s.addText(step, {
x: x + 0.08, y: y + 0.06, w: 1.62, h: 0.28,
fontSize: 12, bold: true, color: C.white, margin: 0
});
s.addText(hint, {
x: x + 0.08, y: y + 0.52, w: 1.62, h: 0.72,
fontSize: 11, color: C.steelGray, align: "center", valign: "middle", margin: 0
});
});
s.addText("Based on Goldman-Cecil Medicine | Medical Physiology (Boron & Boulpaep) | Miller's Anesthesia", {
x: 0.3, y: 5.05, w: 9.4, h: 0.28,
fontSize: 9.5, color: C.steelGray, align: "center", italic: true, margin: 0
});
}
// ── Write file ─────────────────────────────────────────────────────────────
const outPath = "/home/daytona/workspace/ecg-pptx/ECG_Interpretation.pptx";
pres.writeFile({ fileName: outPath }).then(() => {
console.log("✅ Saved: " + outPath);
}).catch(err => {
console.error("❌ Error:", err);
process.exit(1);
});
cd /home/daytona/workspace/ecg-pptx && node build.js 2>&1
grep -n " as " /home/daytona/workspace/ecg-pptx/build.js
cd /home/daytona/workspace/ecg-pptx && \ sed -i 's/(row as string\[\])/row/g' build.js && \ sed -i 's/(items as typeof arrhythmias)/items/g' build.js && \ sed -i 's/xBase as number/xBase/g' build.js && \ sed -i 's/col as string/col/g' build.js && \ sed -i 's/name as string/name/g' build.js && \ sed -i 's/desc as string/desc/g' build.js && \ sed -i 's/ecg as string/ecg/g' build.js && \ sed -i 's/dx as string/dx/g' build.js && \ sed -i 's/(items as typeof clues)/items/g' build.js && \ echo "Done"
cd /home/daytona/workspace/ecg-pptx && python3 -c " content = open('build.js').read() replacements = [ ('(row as string[]).map', 'row.map'), ('(items as typeof arrhythmias).forEach', 'items.forEach'), ('xBase as number', 'xBase'), ('col as string', 'col'), ('name as string', 'name'), ('desc as string', 'desc'), ('ecg as string', 'ecg'), ('dx as string', 'dx'), ('(items as typeof clues).forEach', 'items.forEach'), ] for old, new in replacements: content = content.replace(old, new) open('build.js', 'w').write(content) print('Done') "
cd /home/daytona/workspace/ecg-pptx && node build.js 2>&1
cd /home/daytona/workspace/ecg-pptx && python3 -c " content = open('build.js').read() # Replace the fetchImages function and usage with one-at-a-time fetching old = '''function fetchImages(...urls) { const result = execSync( \`node /home/daytona/skills/shared/scripts/fetch_images.js \${urls.map(u => \`\"\${u}\"\`).join(\" \")}\` ).toString(); return JSON.parse(result); } // Download key ECG images used throughout the deck const imgUrls = [ \"https://cdn.orris.care/cdss_images/ef815eeb06c673b405e3c7f9112d1e9ff6fbea96a00a2723b233d384156386bd.png\", // ECG waveform diagram \"https://cdn.orris.care/cdss_images/ed3e36266afd563652d7495682c022c3508452257126640a6ab3375b0b0a7e46.png\", // Conduction system \"https://cdn.orris.care/cdss_images/a768a511d3107d6bff69c3e8cf7b50c4485d458be34be6234f7fc6d4ca4fc3ec.png\", // Cardiac cycle ]; console.log(\"Downloading images...\"); const imgs = fetchImages(...imgUrls); const ecgWaveImg = imgs[0].base64; // waveform const condSysImg = imgs[1].base64; // conduction system const cardCycImg = imgs[2].base64; // cardiac cycle''' new_code = '''function fetchOneImage(url) { const result = execSync( \`node /home/daytona/skills/shared/scripts/fetch_images.js \"\${url}\"\`, { maxBuffer: 20 * 1024 * 1024 } ).toString(); return JSON.parse(result)[0].base64; } // Download key ECG images used throughout the deck console.log(\"Downloading images...\"); const ecgWaveImg = fetchOneImage(\"https://cdn.orris.care/cdss_images/ef815eeb06c673b405e3c7f9112d1e9ff6fbea96a00a2723b223d384156386bd.png\"); const condSysImg = fetchOneImage(\"https://cdn.orris.care/cdss_images/ed3e36266afd563652d7495682c022c3508452257126640a6ab3375b0b0a7e46.png\"); const cardCycImg = fetchOneImage(\"https://cdn.orris.care/cdss_images/a768a511d3107d6bff69c3e8cf7b50c4485d458be34be6234f7fc6d4ca4fc3ec.png\");''' content = content.replace(old, new_code) open('build.js', 'w').write(content) print('Done, checking...') " && node -e "require('./build.js')" 2>&1 | head -5 || true
~/ecg-pptx/build.js
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-pptx/build.js'
cd /home/daytona/workspace/ecg-pptx && node build.js 2>&1
node -e "const p = require('pptxgenjs'); const pr = new p(); console.log(JSON.stringify(Object.keys(pr.ShapeType || pr.shapes || {})).slice(0,400))"
node -e "const p = require('pptxgenjs'); const pr = new p(); const keys = Object.keys(pr.ShapeType || {}); console.log(keys.filter(k => /ellipse|oval|circle|rect|round/i.test(k)).join(', '))"
cd /home/daytona/workspace/ecg-pptx && python3 -c " content = open('build.js').read() # Fix shape type references content = content.replace('pres.shapes.ELLIPSE', 'pres.ShapeType.ellipse') content = content.replace('pres.shapes.RECTANGLE', 'pres.ShapeType.rect') content = content.replace('pres.shapes.ROUNDED_RECTANGLE', 'pres.ShapeType.roundRect') content = content.replace('pres.shapes.LINE', 'pres.ShapeType.line') open('build.js', 'w').write(content) print('Fixed shape types') "
cd /home/daytona/workspace/ecg-pptx && node build.js 2>&1