Write me prescription for itching and rashes all over the body
| Factor | Why it matters |
|---|---|
| Rash distribution & morphology | Distinguishes eczema vs. urticaria vs. viral exanthem, etc. |
| Duration & onset | Acute vs. chronic changes the diagnosis |
| Associated symptoms | Fever, joint pain, GI symptoms point to systemic causes |
| Medications & allergen exposure | Drug rashes are common and serious |
| Personal/family history | Atopy, psoriasis, autoimmune disease |
Ok leave it
Teach me basic ecg
basic ECG waveform P QRS T wave diagram


| Wave/Complex | What it represents | Normal duration |
|---|---|---|
| P wave | Atrial depolarization (atria contract) | < 120 ms (3 small boxes) |
| QRS complex | Ventricular depolarization (ventricles contract) | < 100–110 ms |
| T wave | Ventricular repolarization (ventricles reset) | — |
| U wave | Repolarization of Purkinje fibers (small, not always seen) | — |
Note: Atrial repolarization is NOT visible — it is buried inside the QRS complex.
Interval = includes the waves within it | Segment = flat line between waves only
| Interval/Segment | What it measures | Normal value |
|---|---|---|
| PR interval | From start of P wave → start of QRS. Represents conduction through AV node | 120–200 ms (3–5 small boxes) |
| QRS interval | Duration of ventricular depolarization | < 100–110 ms |
| ST segment | From end of QRS (J point) → start of T wave. Should be flat (isoelectric) | Flat/isoelectric |
| QT interval | From start of QRS → end of T wave. Total ventricular depolarization + repolarization | < 460 ms (women), < 450 ms (men) |
| RR interval | From one R wave to the next. Used to calculate heart rate | Depends on rate |
Heart Rate = 60 ÷ RR interval (in seconds)
| Large boxes between R waves | Heart rate |
|---|---|
| 1 | 300 bpm |
| 2 | 150 bpm |
| 3 | 100 bpm |
| 4 | 75 bpm ← normal |
| 5 | 60 bpm |
| 6 | 50 bpm |
| Lead | View |
|---|---|
| I, II, III | Standard bipolar limb leads |
| aVR, aVL, aVF | Augmented unipolar limb leads |
| Lead | Position |
|---|---|
| V1 | 4th intercostal space, right sternal border |
| V2 | 4th intercostal space, left sternal border |
| V3 | Between V2 and V4 |
| V4 | 5th intercostal space, midclavicular line |
| V5 | Anterior axillary line |
| V6 | Midaxillary line |
| Finding | Think of |
|---|---|
| PR interval > 200 ms | Heart block (AV block) |
| QRS > 120 ms | Bundle branch block |
| ST elevation | Myocardial infarction (STEMI), pericarditis |
| ST depression | Ischemia, NSTEMI |
| Prolonged QT | Drug toxicity, electrolyte abnormalities, channelopathies |
| Absent P waves + irregular rhythm | Atrial fibrillation |
| Tall peaked T waves | Hyperkalemia |
| Delta wave (slurred QRS upstroke) | WPW syndrome |
Make it as pdf
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /home/daytona/workspace/ecg-guide && ls /home/daytona/workspace/
~/ecg-guide/create_ecg_pdf.py
import json, subprocess, base64, os
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from io import BytesIO
OUTPUT = "/home/daytona/workspace/ecg-guide/Basic_ECG_Guide.pdf"
# ── Fetch images ──────────────────────────────────────────────────────────────
IMAGE_URLS = [
"https://cdn.orris.care/cdss_images/d10fea83a9eb1cedfe9f47e0134dbd583444784d9cb2fbb3a373acfe18d879c7.png",
"https://cdn.orris.care/cdss_images/042124084c74430362fb12de1758340e2dd3b129abd476a16698a7fda88e0376.png",
]
img_paths = []
for i, url in enumerate(IMAGE_URLS):
dest = f"/home/daytona/workspace/ecg-guide/ecg_img_{i}.png"
result = subprocess.run(["curl", "-sL", "-o", dest, url], capture_output=True)
if os.path.exists(dest) and os.path.getsize(dest) > 1000:
img_paths.append(dest)
print(f"Downloaded image {i}: {os.path.getsize(dest)} bytes")
else:
img_paths.append(None)
print(f"Failed to download image {i}")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
BLUE_DARK = colors.HexColor("#1a3a5c")
BLUE_MID = colors.HexColor("#2563a8")
BLUE_LIGHT = colors.HexColor("#dbeafe")
TEAL = colors.HexColor("#0f766e")
RED_SOFT = colors.HexColor("#dc2626")
GREY_LIGHT = colors.HexColor("#f1f5f9")
GREY_MID = colors.HexColor("#94a3b8")
WHITE = colors.white
title_style = ParagraphStyle(
"MyTitle", parent=styles["Title"],
fontSize=28, textColor=WHITE, spaceAfter=4,
fontName="Helvetica-Bold", alignment=TA_CENTER,
)
subtitle_style = ParagraphStyle(
"MySubtitle", parent=styles["Normal"],
fontSize=13, textColor=colors.HexColor("#bfdbfe"),
alignment=TA_CENTER, spaceAfter=0,
)
h1_style = ParagraphStyle(
"H1", parent=styles["Heading1"],
fontSize=15, textColor=WHITE, fontName="Helvetica-Bold",
spaceBefore=4, spaceAfter=4,
)
h2_style = ParagraphStyle(
"H2", parent=styles["Heading2"],
fontSize=12, textColor=BLUE_DARK, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=4,
)
body_style = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=10, textColor=colors.HexColor("#1e293b"),
leading=15, spaceAfter=4, alignment=TA_JUSTIFY,
)
bullet_style = ParagraphStyle(
"Bullet", parent=body_style,
leftIndent=14, bulletIndent=4, spaceAfter=3,
)
caption_style = ParagraphStyle(
"Caption", parent=styles["Normal"],
fontSize=8.5, textColor=GREY_MID,
alignment=TA_CENTER, spaceAfter=6,
)
note_style = ParagraphStyle(
"Note", parent=body_style,
fontSize=9.5, textColor=TEAL, fontName="Helvetica-Oblique",
)
footer_style = ParagraphStyle(
"Footer", parent=styles["Normal"],
fontSize=7.5, textColor=GREY_MID, alignment=TA_CENTER,
)
# ── Helper: section header banner ────────────────────────────────────────────
def section_header(num, title):
tbl = Table(
[[Paragraph(f"{num}. {title}", h1_style)]],
colWidths=[17*cm]
)
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BLUE_MID),
("ROUNDEDCORNERS", [6]),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 14),
]))
return tbl
def info_box(text, color=BLUE_LIGHT, text_color=BLUE_DARK):
p = Paragraph(text, ParagraphStyle("ib", parent=body_style, textColor=text_color, fontSize=9.5))
tbl = Table([[p]], colWidths=[17*cm])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("BOX", (0,0), (-1,-1), 0.5, BLUE_MID),
]))
return tbl
# ── Build story ───────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Basic ECG Guide",
author="Orris Medical AI"
)
story = []
# ══ COVER BANNER ══════════════════════════════════════════════════════════════
cover = Table(
[[Paragraph("Basic ECG Guide", title_style)],
[Paragraph("A Complete Beginner's Reference", subtitle_style)]],
colWidths=[17*cm]
)
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BLUE_DARK),
("ROUNDEDCORNERS",[8]),
("TOPPADDING", (0,0), (-1,-1), 22),
("BOTTOMPADDING", (0,0), (-1,-1), 22),
("ALIGN", (0,0), (-1,-1), "CENTER"),
]))
story.append(cover)
story.append(Spacer(1, 10))
src_p = Paragraph(
"<i>Based on: Harrison's Principles of Internal Medicine 22nd Ed. | "
"Costanzo Physiology 7th Ed. | Medical Physiology (Boron & Boulpaep)</i>",
ParagraphStyle("src", parent=caption_style, textColor=GREY_MID, fontSize=8)
)
story.append(src_p)
story.append(Spacer(1, 16))
# ══ SECTION 1 — What is an ECG ════════════════════════════════════════════════
story.append(section_header(1, "What is an ECG?"))
story.append(Spacer(1, 6))
story.append(Paragraph(
"An <b>Electrocardiogram (ECG or EKG)</b> records the electrical activity of the heart "
"from electrodes placed on the body surface. Because different parts of the heart activate "
"in sequence — atria first, then ventricles — we can attribute each part of the ECG tracing "
"to a specific cardiac event. The ECG provides direct measurement of rate, rhythm, and the "
"time-dependent electrical vector of the heart.",
body_style
))
story.append(Spacer(1, 8))
# ══ SECTION 2 — ECG Paper ═════════════════════════════════════════════════════
story.append(section_header(2, "The ECG Paper"))
story.append(Spacer(1, 6))
paper_data = [
[Paragraph("<b>Feature</b>", body_style), Paragraph("<b>Measurement</b>", body_style)],
["Small box (1 mm wide)", "0.04 seconds (40 ms)"],
["Large box (5 mm wide)", "0.20 seconds (200 ms)"],
["5 large boxes", "1.0 second"],
["Vertical axis", "1 mV = 10 mm (standard calibration)"],
["Standard paper speed", "25 mm/second"],
]
paper_tbl = Table(paper_data, colWidths=[9*cm, 8*cm])
paper_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(paper_tbl)
story.append(Spacer(1, 12))
# ══ SECTION 3 — Basic Waveform ════════════════════════════════════════════════
story.append(section_header(3, "The Basic Waveform"))
story.append(Spacer(1, 6))
story.append(Paragraph(
"Every heartbeat produces this pattern: <b>P → QRS → T (→ U)</b>",
body_style
))
story.append(Spacer(1, 8))
# Images side by side
img_row = []
for i, path in enumerate(img_paths):
if path:
img = Image(path, width=7.8*cm, height=5.5*cm, kind="proportional")
img_row.append(img)
else:
img_row.append(Paragraph("[Image not available]", caption_style))
if img_row:
img_tbl = Table([img_row], colWidths=[8.5*cm, 8.5*cm])
img_tbl.setStyle(TableStyle([
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
story.append(img_tbl)
story.append(Paragraph(
"Fig 1: ECG waveform with labeled intervals (Harrison's, 22nd Ed.) | "
"Fig 2: ECG segments including PR, ST, QT (Costanzo Physiology, 7th Ed.)",
caption_style
))
story.append(Spacer(1, 12))
# ══ SECTION 4 — Waves Explained ═══════════════════════════════════════════════
story.append(section_header(4, "Each Wave Explained"))
story.append(Spacer(1, 6))
waves_data = [
[Paragraph("<b>Wave / Complex</b>", body_style),
Paragraph("<b>Represents</b>", body_style),
Paragraph("<b>Normal Duration</b>", body_style)],
["P wave", "Atrial depolarization (atria contract)", "< 120 ms (3 small boxes)"],
["QRS complex", "Ventricular depolarization (ventricles contract)", "< 100-110 ms"],
["T wave", "Ventricular repolarization (ventricles reset)", "—"],
["U wave", "Repolarization of Purkinje fibers (small, not always seen)", "—"],
]
waves_tbl = Table(waves_data, colWidths=[3.5*cm, 9*cm, 4.5*cm])
waves_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(waves_tbl)
story.append(Spacer(1, 6))
story.append(info_box(
"<b>Note:</b> Atrial repolarization is NOT visible on a normal ECG — it is buried inside the QRS complex."
))
story.append(Spacer(1, 12))
# ══ SECTION 5 — Key Intervals ═════════════════════════════════════════════════
story.append(section_header(5, "Key Intervals and Segments"))
story.append(Spacer(1, 6))
story.append(Paragraph(
"<b>Interval</b> = includes waves within it | "
"<b>Segment</b> = flat line between waves only",
note_style
))
story.append(Spacer(1, 6))
intervals_data = [
[Paragraph("<b>Interval / Segment</b>", body_style),
Paragraph("<b>What It Measures</b>", body_style),
Paragraph("<b>Normal Value</b>", body_style)],
["PR interval", "Start of P wave to start of QRS. AV node conduction time.", "120-200 ms"],
["QRS interval", "Duration of ventricular depolarization.", "< 100-110 ms"],
["ST segment", "J point to start of T wave. Should be isoelectric (flat).", "Flat / isoelectric"],
["QT interval", "Start of QRS to end of T wave. Total ventricular activity.", "< 460 ms (F), < 450 ms (M)"],
["RR interval", "Peak of one R wave to next. Used to calculate heart rate.", "Depends on rate"],
]
int_tbl = Table(intervals_data, colWidths=[3.5*cm, 9*cm, 4.5*cm])
int_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(int_tbl)
story.append(Spacer(1, 14))
# ══ SECTION 6 — Heart Rate ════════════════════════════════════════════════════
story.append(section_header(6, "How to Calculate Heart Rate"))
story.append(Spacer(1, 6))
story.append(Paragraph("<b>Method 1 — Precise:</b>", h2_style))
story.append(info_box("Heart Rate (bpm) = 60 ÷ RR interval (in seconds)", color=BLUE_LIGHT))
story.append(Spacer(1, 8))
story.append(Paragraph("<b>Method 2 — Quick (Most Popular):</b>", h2_style))
story.append(Paragraph(
"Count the large boxes between two consecutive R waves and use this sequence:",
body_style
))
story.append(Spacer(1, 4))
hr_data = [
[Paragraph("<b>Large Boxes Between R Waves</b>", body_style),
Paragraph("<b>Heart Rate (bpm)</b>", body_style)],
["1", "300"],
["2", "150"],
["3", "100"],
["4", "75 (Normal)"],
["5", "60"],
["6", "50"],
]
hr_tbl = Table(hr_data, colWidths=[9*cm, 8*cm])
hr_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#dcfce7")), # highlight normal
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (-1,-1), "CENTER"),
]))
story.append(hr_tbl)
story.append(Spacer(1, 6))
story.append(info_box(
"<b>Memory trick:</b> 300 → 150 → 100 → 75 → 60 → 50",
color=colors.HexColor("#fef9c3"), text_color=colors.HexColor("#713f12")
))
story.append(Spacer(1, 14))
# ══ SECTION 7 — Normal Sinus Rhythm ══════════════════════════════════════════
story.append(section_header(7, "Normal Sinus Rhythm — Checklist"))
story.append(Spacer(1, 6))
nsr_items = [
("Rate", "60-100 bpm"),
("Rhythm", "Regular (R-R intervals equal)"),
("P wave", "Present before every QRS"),
("PR interval", "120-200 ms (normal AV conduction)"),
("QRS", "Narrow < 110 ms"),
("T wave", "Upright in most leads"),
]
nsr_data = [[Paragraph("<b>Parameter</b>", body_style), Paragraph("<b>Normal Finding</b>", body_style)]]
for param, val in nsr_items:
nsr_data.append([
Paragraph(f"<b>{param}</b>", body_style),
Paragraph(f"✓ {val}", ParagraphStyle("ok", parent=body_style, textColor=TEAL)),
])
nsr_tbl = Table(nsr_data, colWidths=[5*cm, 12*cm])
nsr_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(nsr_tbl)
story.append(Spacer(1, 14))
# ══ PAGE BREAK ════════════════════════════════════════════════════════════════
story.append(PageBreak())
# ══ SECTION 8 — 12-Lead Placement ════════════════════════════════════════════
story.append(section_header(8, "The 12-Lead ECG — Electrode Positions"))
story.append(Spacer(1, 6))
story.append(Paragraph("Limb Leads (Frontal Plane):", h2_style))
limb_data = [
[Paragraph("<b>Lead</b>", body_style), Paragraph("<b>Description</b>", body_style)],
["I", "Right arm (–) to left arm (+)"],
["II", "Right arm (–) to left leg (+) — most commonly used"],
["III", "Left arm (–) to left leg (+)"],
["aVR", "Augmented — right arm (+)"],
["aVL", "Augmented — left arm (+)"],
["aVF", "Augmented — left foot (+) — inferior view"],
]
limb_tbl = Table(limb_data, colWidths=[3*cm, 14*cm])
limb_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(limb_tbl)
story.append(Spacer(1, 10))
story.append(Paragraph("Precordial Leads (Horizontal Plane):", h2_style))
prec_data = [
[Paragraph("<b>Lead</b>", body_style), Paragraph("<b>Position</b>", body_style)],
["V1", "4th intercostal space, right sternal border"],
["V2", "4th intercostal space, left sternal border"],
["V3", "Between V2 and V4"],
["V4", "5th intercostal space, midclavicular line"],
["V5", "Anterior axillary line (same level as V4)"],
["V6", "Midaxillary line (same level as V4 and V5)"],
]
prec_tbl = Table(prec_data, colWidths=[3*cm, 14*cm])
prec_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(prec_tbl)
story.append(Spacer(1, 14))
# ══ SECTION 9 — Systematic Approach ══════════════════════════════════════════
story.append(section_header(9, "Systematic Approach to Reading Any ECG"))
story.append(Spacer(1, 6))
story.append(Paragraph(
"Harrison's recommends analyzing <b>14 parameters</b> every time:",
body_style
))
story.append(Spacer(1, 4))
steps = [
"Calibration and technical quality (lead placement, artifacts)",
"Rhythm (regular? irregular?)",
"Heart rate",
"PR interval / AV conduction",
"QRS interval (narrow vs. wide)",
"QT / QTc interval",
"Mean QRS electrical axis",
"P wave morphology",
"QRS voltages",
"Precordial R-wave progression",
"Abnormal Q waves",
"ST segments (elevated or depressed?)",
"T waves (upright or inverted?)",
"U waves",
]
steps_data = []
for i in range(0, len(steps), 2):
left = Paragraph(f"<b>{i+1}.</b> {steps[i]}", body_style)
right = Paragraph(f"<b>{i+2}.</b> {steps[i+1]}", body_style) if i+1 < len(steps) else Paragraph("", body_style)
steps_data.append([left, right])
steps_tbl = Table(steps_data, colWidths=[8.5*cm, 8.5*cm])
steps_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.3, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(steps_tbl)
story.append(Spacer(1, 14))
# ══ SECTION 10 — Common Abnormalities ════════════════════════════════════════
story.append(section_header(10, "Quick Clinical Clues — Common Abnormalities"))
story.append(Spacer(1, 6))
abn_data = [
[Paragraph("<b>ECG Finding</b>", body_style), Paragraph("<b>Think Of</b>", body_style)],
["PR interval > 200 ms", "Heart block (AV block)"],
["QRS > 120 ms", "Bundle branch block"],
["ST elevation", "STEMI, pericarditis, Brugada syndrome"],
["ST depression", "Ischemia, NSTEMI, digoxin effect"],
["Prolonged QT", "Drug toxicity, electrolyte abnormalities, channelopathies"],
["Absent P waves + irregular rhythm", "Atrial fibrillation (AF)"],
["Saw-tooth P waves at 300 bpm", "Atrial flutter"],
["Peaked tall T waves", "Hyperkalemia"],
["Delta wave (slurred QRS upstroke)", "Wolff-Parkinson-White (WPW) syndrome"],
["Pathological Q waves (wide + deep)", "Old myocardial infarction"],
["Low QRS voltages", "Pericardial effusion, obesity, hypothyroidism"],
]
abn_tbl = Table(abn_data, colWidths=[7*cm, 10*cm])
abn_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), BLUE_DARK),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(abn_tbl)
story.append(Spacer(1, 16))
# ══ FOOTER / SOURCES ══════════════════════════════════════════════════════════
story.append(HRFlowable(width="100%", thickness=0.5, color=GREY_MID))
story.append(Spacer(1, 6))
story.append(Paragraph(
"<b>Sources:</b> Harrison's Principles of Internal Medicine, 22nd Ed. (McGraw-Hill, 2025) | "
"Costanzo Medical Physiology, 7th Ed. | Boron & Boulpaep Medical Physiology | "
"Generated by Orris Medical AI · May 2026",
footer_style
))
# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
print(f"File size: {os.path.getsize(OUTPUT):,} bytes")
cd /home/daytona/workspace/ecg-guide && python create_ecg_pdf.py
Basic ECG Guide
PDF Document · PDF