Cardiac cycle with exam friendly notes

Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Loading Image
Reading File
Reading File
Loading Image
Finding Sources
I have all the content I need. Let me now compile the comprehensive, exam-friendly notes:

The Cardiac Cycle - Exam-Friendly Notes

Definition: The sequence of electrical and mechanical events from the beginning of one heartbeat to the beginning of the next. At a heart rate of 72 bpm, one cardiac cycle lasts ~0.83 seconds.
The Wiggers diagram - the single most important figure to master for exams:
Wiggers Diagram - Cardiac Cycle

Initiation

  • Each cycle begins with spontaneous depolarization at the SA node (superior lateral wall of right atrium, near SVC)
  • Impulse travels through atria → AV node (0.1 sec delay) → Bundle of His → Purkinje fibers → ventricles
  • The AV delay allows atria to contract first, priming the ventricles ("atria = primer pumps")

The 7 Phases (Costanzo A-G Framework)

The Costanzo framework divides the cycle into 7 labeled phases. Study them vertically - for each phase, correlate: ECG event, valve status, pressure change, volume change, heart sound.

DIASTOLE

Phase A - Atrial Systole

ParameterWhat happens
ECGP wave
ValvesMitral & tricuspid OPEN; aortic & pulmonary CLOSED
MechanismAtria contract → pushes ~30% remaining blood into ventricles
PressuresSmall rise in LV pressure ("blip")
VolumeLV volume reaches maximum = End-Diastolic Volume (EDV) ~130 mL
Venous pulse"a" wave (atrial contraction)
Heart soundS4 (not normal; heard in stiff/hypertrophied ventricle)
Key fact: ~70% of ventricular filling is passive during diastole; atrial systole contributes only the final 30%. (Ganong's)

Late Diastole (before atrial systole)

  • AV valves open, semilunar valves closed
  • Blood flows passively from atria → ventricles
  • Rate of filling declines as ventricles distend

SYSTOLE

Phase B - Isovolumetric Ventricular Contraction (IVC)

ParameterWhat happens
ECGQRS complex
ValvesALL VALVES CLOSED
MechanismVentricles start contracting; pressure rises sharply but volume stays constant
Duration~0.05 seconds
Trigger to endLV pressure exceeds aortic pressure (~80 mmHg diastolic) → aortic valve opens
Heart soundS1 ("lub") - due to closure of mitral valve
Exam trap: During IVC, the AV valves bulge into the atria → causes a small rise in atrial pressure seen as the "c" wave on the venous pulse.

Phase C - Rapid Ventricular Ejection

ParameterWhat happens
ECGST segment
ValvesAortic valve OPEN; mitral CLOSED
MechanismLV pressure peaks (~120 mmHg); blood rapidly ejected into aorta
VolumeLV volume rapidly decreases
Aortic pressureRises to systolic peak (~120 mmHg)

Phase D - Reduced Ventricular Ejection

ParameterWhat happens
ECGT wave (ventricular repolarization)
ValvesAortic valve still OPEN
MechanismSlower ejection; LV pressure begins to fall
VolumeLV volume reaches minimum = End-Systolic Volume (ESV) ~50 mL
Venous pulse"v" wave (venous filling of atria while AV valves closed)
Note: Late in systole, aortic pressure actually exceeds LV pressure, but momentum briefly keeps blood flowing forward.

BACK TO DIASTOLE

Phase E - Isovolumetric Ventricular Relaxation (IVR)

ParameterWhat happens
ECGAfter T wave (isoelectric)
ValvesALL VALVES CLOSED
MechanismLV pressure drops rapidly; volume stays constant
Duration~0.04-0.08 seconds
Trigger to endLV pressure falls below left atrial pressure → mitral valve opens
Heart soundS2 ("dub") - due to closure of aortic valve
Protodiastole = brief period at very end of systole before aortic valve closes (~0.04 s). Represents the lag from momentum of ejected blood.

Phase F - Rapid Ventricular Filling

ParameterWhat happens
ECGAfter T wave
ValvesMitral OPEN; aortic CLOSED
MechanismVentricles relaxed; blood rushes in passively from atria
Venous pulse"y" descent (atrial emptying after AV valve opens)
Heart soundS3 (not normal in adults >40 yrs; heard in volume overload / heart failure)

Phase G - Reduced Ventricular Filling (Diastasis)

ParameterWhat happens
ValvesMitral still OPEN
MechanismFilling rate slows as pressure gradient equalizes
DurationLongest phase; shortened first when heart rate increases

Key Numbers to Memorize

ParameterValue
Duration of full cycle (72 bpm)0.83 sec
Systole duration~0.3 sec
Diastole duration~0.53 sec
End-diastolic volume (EDV)~130 mL
End-systolic volume (ESV)~50 mL
Stroke volume (SV = EDV - ESV)~70-90 mL
Ejection fraction (EF = SV/EDV)~65%
Peak LV pressure~120 mmHg
Peak RV pressure~25 mmHg
Aortic diastolic pressure (when aortic valve opens)~80 mmHg
Pulmonary artery pressure (when pulmonic valve opens)~10 mmHg
Duration of IVC~0.05 sec
Duration of IVR~0.04-0.08 sec

Heart Sounds Summary

SoundTimingCauseClinical significance
S1Beginning of systoleMitral (+ tricuspid) valve closureHeard best at apex
S2Beginning of diastoleAortic (+ pulmonic) valve closureHeard best at base
S3Early diastoleRapid ventricular filling (turbulence)Normal in children/young adults; abnormal in >40 yrs → HF, volume overload
S4Late diastole (pre-systole)Atrial contraction into stiff ventricleAlways abnormal; LV hypertrophy, hypertension, HCM
Mnemonic for S3/S4: S3 = "Ken-tuck-y" (lub-dub-ta); S4 = "Ten-nes-see" (ta-lub-dub)

Jugular Venous Pulse (JVP) Waves

WaveCausePhase
a waveAtrial contractionLate diastole
c waveAV valve bulging into atria during IVCEarly systole
x descentAtrial relaxation + AV valve moving down during systoleSystole
v waveVenous filling of atria while AV valves are closedLate systole
y descentAV valve opens; atria emptyEarly diastole
Absent "a" wave → atrial fibrillation (no organized atrial contraction) Cannon "a" wave → complete heart block or junctional rhythm (atria contracting against closed AV valve)

Pressure-Volume (P-V) Loop - Left Ventricle

Pressure-Volume Loop
PointEvent
a → b (bottom right, moving left)Isovolumetric contraction (volume constant, pressure rises)
b → c (up, moving left)Ventricular ejection (volume decreases, pressure peaks then drops)
c → d (down, moving right)Isovolumetric relaxation (volume constant, pressure falls)
d → a (bottom, moving right)Diastolic filling (volume increases, pressure rises slowly)
The area inside the loop = Stroke work (work done per beat) Width of loop = Stroke volume (EDV - ESV)

ECG Correlation with Mechanical Events

ECG EventMechanical Event
P waveAtrial depolarization → atrial systole
PR intervalAV nodal conduction delay
QRS complexVentricular depolarization → ventricular systole begins (IVC starts)
ST segmentPlateau phase of action potential → rapid ejection
T waveVentricular repolarization → reduced ejection / beginning of relaxation
After T waveIVR → ventricular filling

Effect of Increased Heart Rate

At high heart rates, diastole is disproportionately shortened (more than systole). At normal HR (72 bpm), systole = ~40% of cycle. At 3x normal HR, systole = ~65% of cycle. This reduces ventricular filling time and can compromise cardiac output. - Guyton & Hall, Textbook of Medical Physiology

Comparison: Left vs Right Ventricle

ParameterLeft VentricleRight Ventricle
Peak systolic pressure~120 mmHg~25 mmHg
Afterload pressure to overcome~80 mmHg (aortic diastolic)~10 mmHg (PA diastolic)
Wall thicknessThick (high pressure)Thin (low pressure)
TimingSlightly earlier depolarizationSlight differences due to RV conduction

Valve Opening/Closing - Quick Reference

ValveOpens when...Closes when...
MitralLV pressure < LA pressure (end of IVR)LV pressure > LA pressure (start of IVC) → S1
AorticLV pressure > aortic diastolic pressure (~80 mmHg)LV pressure < aortic pressure (protodiastole) → S2
TricuspidRV pressure < RA pressureRV pressure > RA pressure → S1
PulmonicRV pressure > PA diastolic pressure (~10 mmHg)RV pressure < PA pressure → S2

High-Yield Exam Traps

  1. All valves closed during IVC and IVR (two phases with no valve movement)
  2. S1 = mitral closure (NOT mitral opening - "opening snap" is abnormal)
  3. The "a" wave on JVP precedes S1 (atrial contraction happens before ventricular systole)
  4. Ejection fraction ~65% is normal - NOT 100%
  5. S3 in adults = pathological (heart failure, dilated cardiomyopathy)
  6. During rapid ejection, LV pressure peaks THEN falls - aortic pressure actually exceeds LV pressure briefly at end of systole yet flow continues due to momentum
  7. Diastole is longer than systole at rest - shortened first with tachycardia
  8. The heart's own coronary perfusion occurs mainly during diastole (systolic compression of coronary vessels)

Sources: Guyton and Hall Textbook of Medical Physiology; Costanzo Physiology 7th Edition; Ganong's Review of Medical Physiology 26th Edition; Miller's Anesthesia 10th Edition

Give notes according to the 1st year student

Here are the cardiac cycle notes tailored for a 1st year medical/health sciences student - clear language, logical flow, no unnecessary complexity:

🫀 The Cardiac Cycle - 1st Year Notes

What is the Cardiac Cycle?

The cardiac cycle is the complete sequence of events that happens in the heart during one heartbeat - from the beginning of one beat to the beginning of the next.
  • At a normal heart rate of 72 beats/min, one cycle lasts about 0.8 seconds
  • The cycle has two main parts: Systole (contraction) and Diastole (relaxation)

First - Understand the Basic Terms

TermSimple Meaning
SystoleHeart muscle contracting, squeezing blood out
DiastoleHeart muscle relaxing, filling with blood
Atrial systoleOnly the atria contract
Ventricular systoleOnly the ventricles contract
IsovolumetricVolume stays the same (no blood entering or leaving)
EDVEnd-Diastolic Volume - how full the ventricle is before contracting
ESVEnd-Systolic Volume - how much blood is left after contracting
Stroke VolumeBlood pumped per beat = EDV - ESV

The "Trigger" - How Each Cycle Starts

  1. The SA node (in the right atrium) fires an electrical signal automatically
  2. Signal spreads across both atria → atria contract
  3. Signal reaches the AV node → slows down (0.1 second delay)
  4. This delay lets atria finish emptying into ventricles before ventricles contract
  5. Signal then goes down the Bundle of His → Purkinje fibers → ventricles contract
Think of the SA node as the "pacemaker" - it's the boss that starts every heartbeat.

The Wiggers Diagram - The Most Important Figure

Wiggers Diagram
This diagram shows all events happening at the same time:
  • Top: Pressure in aorta, left ventricle, and left atrium
  • Middle: Left ventricular volume
  • Bottom: ECG + heart sounds + venous pulse

The 7 Phases - One by One

Think of it as a story that repeats with every heartbeat.

📌 Phase 1 - Late Diastole (The "Resting/Filling" Phase)

What's happening: Heart is relaxed. Blood is flowing passively from veins → atria → ventricles through the open AV valves (mitral and tricuspid).
  • AV valves (mitral, tricuspid) = OPEN
  • Semilunar valves (aortic, pulmonary) = CLOSED
  • Ventricles filling with blood quietly
  • ~70% of ventricular filling happens here, without any pumping - just passive flow

📌 Phase 2 - Atrial Systole ("The Atrial Top-Up")

What's happening: The atria contract and push the last bit of blood into the ventricles.
  • Triggered by: P wave on ECG
  • AV valves still OPEN
  • Adds the final ~30% of ventricular filling
  • Ventricle reaches its maximum volume = EDV (~130 mL)
  • Heart sound: S4 (not normally audible - only heard in a stiff, diseased ventricle)
  • Venous pulse shows: "a" wave
Analogy: Imagine filling a water balloon - 70% flows in on its own, then you give it one last squeeze to fill it completely.

📌 Phase 3 - Isovolumetric Contraction (IVC) ⭐ HIGH YIELD

What's happening: Ventricles start contracting but ALL valves are closed - so pressure shoots up but NO blood moves.
  • Triggered by: QRS complex on ECG
  • ALL VALVES CLOSED ← memorize this
  • Ventricular pressure rises rapidly
  • Volume stays exactly the same (that's why it's called "isovolumetric")
  • Heart sound: S1 ("lub") - mitral valve snaps shut
  • Ends when: LV pressure exceeds aortic pressure (~80 mmHg) → aortic valve is forced open
  • Venous pulse: small "c" wave (AV valve bulges into atrium)
Analogy: Like squeezing a sealed water balloon - pressure builds but nothing comes out yet.

📌 Phase 4 - Rapid Ventricular Ejection ("The Big Squeeze")

What's happening: Aortic valve opens - blood rushes out into the aorta rapidly.
  • ECG: ST segment
  • Aortic valve = OPEN; Mitral = CLOSED
  • Ventricular pressure peaks at ~120 mmHg (LV) / ~25 mmHg (RV)
  • Aortic pressure rises to its systolic peak
  • Ventricular volume falls rapidly
  • ~2/3 of stroke volume is ejected here

📌 Phase 5 - Reduced Ventricular Ejection ("Slowing Down")

What's happening: Ventricle still ejecting but slowing. Pressure starts to fall.
  • ECG: T wave
  • Aortic valve still OPEN
  • Ventricular volume reaches minimum = ESV (~50 mL)
  • Venous pulse: "v" wave (blood is piling up in atria - can't go anywhere while AV valves are closed)
  • Late in this phase: aortic pressure briefly exceeds LV pressure - but blood keeps moving forward due to momentum

📌 Phase 6 - Isovolumetric Relaxation (IVR) ⭐ HIGH YIELD

What's happening: Ventricles relax - pressure drops fast - but again ALL valves are closed so no blood moves.
  • ECG: after T wave (flat line)
  • ALL VALVES CLOSED ← memorize this
  • Ventricular pressure falls rapidly
  • Volume stays constant again
  • Heart sound: S2 ("dub") - aortic valve snaps shut
  • Ends when: LV pressure falls below left atrial pressure → mitral valve opens

📌 Phase 7 - Rapid Ventricular Filling ("Refilling")

What's happening: Mitral valve opens - blood rushes from atria into ventricles again. New cycle begins filling.
  • Mitral valve = OPEN
  • Passive filling - no pumping needed
  • Heart sound: S3 (not normal in adults - suggests heart failure or volume overload)
  • Venous pulse: "y" descent (atria emptying out)

Summary Table - All 7 Phases at a Glance

PhaseValvesVolumePressureECGHeart Sound
Late DiastoleAV open, SL closedFillingLow--
Atrial SystoleAV open, SL closed↑ to EDVSmall ↑P waveS4 (abnormal)
IVCAll closedFixed↑↑ rapidlyQRSS1
Rapid EjectionAV closed, SL open↓↓Peaks at 120ST segment-
Reduced EjectionAV closed, SL open↓ to ESVStarts fallingT wave-
IVRAll closedFixed↓↓ rapidlyAfter TS2
Rapid FillingAV open, SL closedLow-S3 (abnormal)
SL = Semilunar valves (aortic + pulmonary) AV = Atrioventricular valves (mitral + tricuspid)

Key Numbers (Must Know for Exams)

WhatValue
Heart rate (normal)72 beats/min
Duration of 1 cycle~0.8 sec
Systole duration~0.3 sec
Diastole duration~0.5 sec
EDV (end-diastolic volume)~130 mL
ESV (end-systolic volume)~50 mL
Stroke Volume (SV)~80 mL
Ejection Fraction (EF)~65%
Peak LV pressure~120 mmHg
Peak RV pressure~25 mmHg
Ejection Fraction = (Stroke Volume / EDV) × 100 = (80/130) × 100 ≈ 65% Normal EF > 55%. EF < 40% = heart failure with reduced ejection fraction (HFrEF)

The 4 Heart Sounds - Simple Version

SoundWhenCauseNormal?
S1 "lub"Start of systoleMitral + tricuspid valves closing✅ Yes
S2 "dub"Start of diastoleAortic + pulmonary valves closing✅ Yes
S3Early diastoleBlood hitting a floppy/overloaded ventricle✅ Normal in kids/young adults only; ❌ abnormal in adults = heart failure
S4Late diastoleBlood hitting a stiff/rigid ventricle❌ Always abnormal = hypertrophy, hypertension
Memory trick: "S1 = shut (mitral/tricuspid shut), S2 = shut (aortic/pulmonic shut)"

Atrial vs Ventricular Systole - Don't Confuse These!

  • Atrial systole = happens at the END of diastole (the heart cycle is still in diastole!)
  • Ventricular systole = THE systole we mean when we say "systole"
  • So when someone says "systole" they almost always mean ventricular systole

Two "All Valves Closed" Phases - Most Common Exam Question

Which phases have all valves closed?
  1. Isovolumetric Contraction (IVC) - beginning of systole
  2. Isovolumetric Relaxation (IVR) - beginning of diastole
During both, volume stays constant because there is literally nowhere for blood to go - every valve is shut.

Simple Flow Chart of the Whole Cycle

SA node fires
      ↓
P wave → Atrial contraction → Blood tops up ventricles
      ↓
QRS → Ventricles contract → ALL VALVES CLOSE → Pressure builds (IVC) → S1
      ↓
Aortic valve opens → Blood ejected into aorta
      ↓
T wave → Ventricles relax → Aortic valve closes → ALL VALVES CLOSE → Pressure falls (IVR) → S2
      ↓
Mitral valve opens → Ventricles refill passively
      ↓
Cycle repeats...

Common Exam Questions - Quick Answers

Q: What happens during isovolumetric contraction? A: All valves are closed, ventricular pressure rises, volume stays constant. Ends when LV pressure > aortic pressure.
Q: What causes S1? A: Closure of the mitral (and tricuspid) valve at the start of ventricular systole.
Q: What causes S2? A: Closure of the aortic (and pulmonary) valve at the start of diastole.
Q: What is ejection fraction and normal value? A: EF = SV/EDV × 100. Normal = ~65%. Below 40% = heart failure.
Q: Why does diastole get shorter with exercise? A: When heart rate increases, both systole and diastole shorten, but diastole shortens more. This limits ventricular filling time.
Q: What is the "a" wave on JVP? A: Caused by atrial contraction during atrial systole.

Sources: Guyton & Hall Medical Physiology | Costanzo Physiology 7th Ed | Ganong's Review of Medical Physiology 26th Ed

Create a study guide PDF on the cardiac cycle I can download

Reading File
Running Command

mkdir -p /home/daytona/workspace/cardiac-cycle && ls /home/daytona/workspace/car…

Writing File

~/cardiac-cycle/generate_pdf.py

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, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import Flowable
import urllib.request
import io

# ── Colour Palette ──────────────────────────────────────────────────────────
DEEP_RED   = colors.HexColor("#C0392B")
MED_RED    = colors.HexColor("#E74C3C")
LIGHT_RED  = colors.HexColor("#FADBD8")
DARK_BLUE  = colors.HexColor("#1A3A5C")
MED_BLUE   = colors.HexColor("#2980B9")
LIGHT_BLUE = colors.HexColor("#D6EAF8")
DARK_GREY  = colors.HexColor("#2C3E50")
MED_GREY   = colors.HexColor("#7F8C8D")
LIGHT_GREY = colors.HexColor("#F2F3F4")
YELLOW_BG  = colors.HexColor("#FEF9E7")
GREEN_BG   = colors.HexColor("#EAFAF1")
GREEN_DARK = colors.HexColor("#1E8449")
ORANGE     = colors.HexColor("#E67E22")
ORANGE_BG  = colors.HexColor("#FEF5E7")
WHITE      = colors.white

PAGE_W, PAGE_H = A4
MARGIN = 1.8 * cm

# ── Styles ───────────────────────────────────────────────────────────────────
base_styles = getSampleStyleSheet()

def make_style(name, parent="Normal", **kwargs):
    return ParagraphStyle(name, parent=base_styles[parent], **kwargs)

styles = {
    "cover_title": make_style("cover_title", fontSize=34, leading=42,
                              textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold"),
    "cover_sub":   make_style("cover_sub",   fontSize=16, leading=22,
                              textColor=colors.HexColor("#FADBD8"), alignment=TA_CENTER),
    "cover_tag":   make_style("cover_tag",   fontSize=11, leading=16,
                              textColor=colors.HexColor("#AED6F1"), alignment=TA_CENTER),
    "h1": make_style("h1", fontSize=18, leading=24, textColor=WHITE,
                     fontName="Helvetica-Bold", alignment=TA_LEFT,
                     spaceAfter=4, spaceBefore=8),
    "h2": make_style("h2", fontSize=13, leading=18, textColor=DARK_BLUE,
                     fontName="Helvetica-Bold", spaceBefore=10, spaceAfter=3),
    "h3": make_style("h3", fontSize=11, leading=15, textColor=MED_RED,
                     fontName="Helvetica-Bold", spaceBefore=6, spaceAfter=2),
    "body": make_style("body", fontSize=9.5, leading=14, textColor=DARK_GREY,
                       spaceAfter=4, alignment=TA_JUSTIFY),
    "bullet": make_style("bullet", fontSize=9.5, leading=14, textColor=DARK_GREY,
                         leftIndent=14, bulletIndent=4, spaceAfter=2),
    "note": make_style("note", fontSize=9, leading=13,
                       textColor=colors.HexColor("#6E2F00"),
                       leftIndent=8, spaceAfter=3),
    "keynum": make_style("keynum", fontSize=10, leading=14, textColor=DARK_GREY,
                         fontName="Helvetica-Bold"),
    "footer": make_style("footer", fontSize=7.5, leading=10,
                         textColor=MED_GREY, alignment=TA_CENTER),
    "toc": make_style("toc", fontSize=10, leading=16, textColor=DARK_BLUE),
    "phase_title": make_style("phase_title", fontSize=11, leading=15,
                              textColor=WHITE, fontName="Helvetica-Bold"),
    "phase_body": make_style("phase_body", fontSize=9, leading=13,
                             textColor=DARK_GREY),
    "qbox": make_style("qbox", fontSize=9.5, leading=14,
                       textColor=DARK_BLUE, fontName="Helvetica-Bold"),
    "abox": make_style("abox", fontSize=9.5, leading=14, textColor=DARK_GREY),
    "trap_title": make_style("trap_title", fontSize=10, leading=14,
                             textColor=colors.HexColor("#7B241C"),
                             fontName="Helvetica-Bold"),
    "trap_body":  make_style("trap_body",  fontSize=9.5, leading=14,
                             textColor=DARK_GREY),
}

# ── Helpers ──────────────────────────────────────────────────────────────────
def section_header(title, color=DARK_BLUE):
    """Coloured full-width section header bar."""
    data = [[Paragraph(title, styles["h1"])]]
    tbl = Table(data, colWidths=[PAGE_W - 2*MARGIN])
    tbl.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
        ("RIGHTPADDING",  (0,0), (-1,-1), 10),
        ("ROUNDEDCORNERS", [4]),
    ]))
    return tbl

def coloured_table(data, col_widths, header_bg=DARK_BLUE, row_bg=LIGHT_BLUE,
                   alt_bg=WHITE, font_size=9):
    tbl = Table(data, colWidths=col_widths, repeatRows=1)
    n = len(data)
    style_cmds = [
        ("BACKGROUND",    (0, 0), (-1,  0), header_bg),
        ("TEXTCOLOR",     (0, 0), (-1,  0), WHITE),
        ("FONTNAME",      (0, 0), (-1,  0), "Helvetica-Bold"),
        ("FONTSIZE",      (0, 0), (-1, -1), font_size),
        ("LEADING",       (0, 0), (-1, -1), font_size + 4),
        ("TOPPADDING",    (0, 0), (-1, -1), 5),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
        ("LEFTPADDING",   (0, 0), (-1, -1), 7),
        ("RIGHTPADDING",  (0, 0), (-1, -1), 7),
        ("GRID",          (0, 0), (-1, -1), 0.4, colors.HexColor("#BDC3C7")),
        ("ROWBACKGROUNDS",(0, 1), (-1, -1), [row_bg, alt_bg]),
        ("VALIGN",        (0, 0), (-1, -1), "MIDDLE"),
    ]
    tbl.setStyle(TableStyle(style_cmds))
    return tbl

def info_box(text, bg=YELLOW_BG, border=ORANGE, label="📌 Key Point"):
    content = f"<b>{label}</b><br/>{text}"
    data = [[Paragraph(content, styles["note"])]]
    tbl = Table(data, colWidths=[PAGE_W - 2*MARGIN - 4])
    tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("LEFTBORDERPADDING", (0,0), (-1,-1), 6),
        ("BOX",           (0,0), (-1,-1), 1.5, border),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 10),
        ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ]))
    return tbl

def phase_block(number, name, ecg, valves, mechanism, heart_sound, color):
    """Coloured phase card."""
    header_data = [[
        Paragraph(f"Phase {number}", styles["phase_title"]),
        Paragraph(name, styles["phase_title"]),
    ]]
    header_tbl = Table(header_data, colWidths=[2.5*cm, PAGE_W - 2*MARGIN - 2.5*cm - 4])
    header_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), color),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ]))

    rows = [
        ["ECG Event", ecg],
        ["Valves", valves],
        ["Mechanism", mechanism],
        ["Heart Sound", heart_sound],
    ]
    body_data = [[Paragraph(f"<b>{r[0]}</b>", styles["phase_body"]),
                  Paragraph(r[1], styles["phase_body"])] for r in rows]
    body_tbl = Table(body_data, colWidths=[3.2*cm, PAGE_W - 2*MARGIN - 3.2*cm - 4])
    body_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), colors.HexColor("#FDFEFE")),
        ("ROWBACKGROUNDS",(0,0), (-1,-1), [LIGHT_GREY, WHITE]),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("GRID",          (0,0), (-1,-1), 0.3, colors.HexColor("#D5D8DC")),
        ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ]))

    wrapper = Table([[header_tbl], [body_tbl]],
                    colWidths=[PAGE_W - 2*MARGIN])
    wrapper.setStyle(TableStyle([
        ("BOX",           (0,0), (-1,-1), 1, color),
        ("TOPPADDING",    (0,0), (-1,-1), 0),
        ("BOTTOMPADDING", (0,0), (-1,-1), 0),
        ("LEFTPADDING",   (0,0), (-1,-1), 0),
        ("RIGHTPADDING",  (0,0), (-1,-1), 0),
    ]))
    return wrapper

# ── Page callbacks ────────────────────────────────────────────────────────────
def on_first_page(canvas, doc):
    canvas.saveState()
    # Full page background gradient simulation
    canvas.setFillColor(DARK_BLUE)
    canvas.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
    # Decorative circles
    canvas.setFillColor(colors.HexColor("#1A5276"))
    canvas.circle(PAGE_W * 0.85, PAGE_H * 0.85, 6*cm, fill=1, stroke=0)
    canvas.setFillColor(colors.HexColor("#154360"))
    canvas.circle(PAGE_W * 0.1, PAGE_H * 0.15, 4*cm, fill=1, stroke=0)
    canvas.setFillColor(MED_RED)
    canvas.circle(PAGE_W * 0.5, PAGE_H * 0.5, 0.4*cm, fill=1, stroke=0)
    canvas.restoreState()

def on_later_pages(canvas, doc):
    canvas.saveState()
    # Top bar
    canvas.setFillColor(DARK_BLUE)
    canvas.rect(0, PAGE_H - 1.1*cm, PAGE_W, 1.1*cm, fill=1, stroke=0)
    canvas.setFillColor(MED_RED)
    canvas.rect(0, PAGE_H - 1.15*cm, PAGE_W, 0.12*cm, fill=1, stroke=0)
    canvas.setFont("Helvetica-Bold", 8)
    canvas.setFillColor(WHITE)
    canvas.drawString(MARGIN, PAGE_H - 0.75*cm, "THE CARDIAC CYCLE - 1st Year Study Guide")
    canvas.drawRightString(PAGE_W - MARGIN, PAGE_H - 0.75*cm, "Physiology")
    # Bottom bar
    canvas.setFillColor(LIGHT_GREY)
    canvas.rect(0, 0, PAGE_W, 1*cm, fill=1, stroke=0)
    canvas.setFillColor(MED_GREY)
    canvas.setFont("Helvetica", 7.5)
    canvas.drawCentredString(PAGE_W/2, 0.35*cm,
        f"Page {doc.page}  |  Sources: Guyton & Hall | Costanzo | Ganong's Review of Medical Physiology")
    canvas.restoreState()

# ── Document ──────────────────────────────────────────────────────────────────
OUTPUT = "/home/daytona/workspace/cardiac-cycle/Cardiac_Cycle_Study_Guide.pdf"

doc = SimpleDocTemplate(
    OUTPUT, pagesize=A4,
    leftMargin=MARGIN, rightMargin=MARGIN,
    topMargin=1.4*cm, bottomMargin=1.4*cm,
    title="The Cardiac Cycle - 1st Year Study Guide",
    author="Orris Medical AI",
)

story = []

# ════════════════════════════════════════════════════════════════════════════════
# COVER PAGE
# ════════════════════════════════════════════════════════════════════════════════
story.append(Spacer(1, 4.5*cm))
story.append(Paragraph("THE CARDIAC CYCLE", styles["cover_title"]))
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph("First Year Medical Student Study Guide", styles["cover_sub"]))
story.append(Spacer(1, 1.2*cm))

# Heart icon in text form
heart_text = "♥  Physiology  |  1st Year MBBS  |  Exam-Ready Notes  ♥"
story.append(Paragraph(heart_text, styles["cover_tag"]))
story.append(Spacer(1, 2.5*cm))

cover_info = [
    ["Topics Covered"],
    ["✓  Definition & Initiation"],
    ["✓  The 7 Phases of the Cardiac Cycle"],
    ["✓  Heart Sounds S1 S2 S3 S4"],
    ["✓  Key Numbers & Values"],
    ["✓  Pressure-Volume Loop"],
    ["✓  Jugular Venous Pulse Waves"],
    ["✓  Valve Opening & Closing"],
    ["✓  Exam Traps & Quick MCQ Answers"],
]
cover_tbl = Table(cover_info, colWidths=[10*cm])
cover_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0),  colors.HexColor("#1A5276")),
    ("BACKGROUND",    (0,1), (-1,-1), colors.HexColor("#154360")),
    ("TEXTCOLOR",     (0,0), (-1,-1), WHITE),
    ("FONTNAME",      (0,0), (-1,0),  "Helvetica-Bold"),
    ("FONTNAME",      (0,1), (-1,-1), "Helvetica"),
    ("FONTSIZE",      (0,0), (-1,-1), 11),
    ("LEADING",       (0,0), (-1,-1), 18),
    ("TOPPADDING",    (0,0), (-1,-1), 7),
    ("BOTTOMPADDING", (0,0), (-1,-1), 7),
    ("LEFTPADDING",   (0,0), (-1,-1), 18),
    ("BOX",           (0,0), (-1,-1), 1.5, MED_RED),
    ("LINEAFTER",     (0,0), (0,-1),  0.5, colors.HexColor("#2E86C1")),
]))
# Centre the table
cover_wrapper = Table([[cover_tbl]], colWidths=[PAGE_W - 2*MARGIN])
cover_wrapper.setStyle(TableStyle([
    ("ALIGN",  (0,0), (-1,-1), "CENTER"),
    ("TOPPADDING", (0,0), (-1,-1), 0),
    ("BOTTOMPADDING", (0,0), (-1,-1), 0),
    ("LEFTPADDING", (0,0), (-1,-1), 0),
    ("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(cover_wrapper)
story.append(Spacer(1, 2*cm))
story.append(Paragraph(
    "Sources: Guyton &amp; Hall Medical Physiology | Costanzo Physiology 7th Ed | Ganong's Review 26th Ed",
    styles["cover_tag"]
))
story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 1 - INTRODUCTION
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("1.  Introduction to the Cardiac Cycle", DARK_BLUE))
story.append(Spacer(1, 0.3*cm))

story.append(Paragraph(
    "The <b>cardiac cycle</b> is the complete sequence of electrical and mechanical events that occurs "
    "during a single heartbeat — from the beginning of one beat to the beginning of the next. "
    "At a normal heart rate of <b>72 beats/min</b>, one complete cycle lasts approximately <b>0.83 seconds</b>.",
    styles["body"]
))
story.append(Spacer(1, 0.2*cm))

story.append(Paragraph("The cycle has two main phases:", styles["body"]))
main_phases = [
    ["Phase", "Description", "Duration"],
    ["SYSTOLE", "Heart muscle contracting — blood is pumped OUT", "~0.3 sec"],
    ["DIASTOLE", "Heart muscle relaxing — chambers fill with blood", "~0.5 sec"],
]
story.append(coloured_table(main_phases,
    col_widths=[3.5*cm, 10*cm, 3*cm],
    header_bg=DEEP_RED))
story.append(Spacer(1, 0.4*cm))

story.append(Paragraph("How Each Cycle is Triggered", styles["h2"]))
trigger_steps = [
    ("1", "SA Node fires", "Located in right atrium near SVC. Acts as the natural pacemaker."),
    ("2", "Atrial conduction", "Electrical impulse spreads across both atria → atria contract (P wave on ECG)."),
    ("3", "AV Node delay", "Signal slows for ~0.1 second at AV node → atria finish emptying into ventricles."),
    ("4", "Bundle of His", "Signal travels down interventricular septum via Bundle of His."),
    ("5", "Purkinje fibers", "Signal spreads to ventricular walls → ventricles contract (QRS on ECG)."),
]
for num, title, desc in trigger_steps:
    row_data = [[
        Paragraph(num, ParagraphStyle("n", fontSize=13, fontName="Helvetica-Bold",
                                      textColor=WHITE, alignment=TA_CENTER)),
        Paragraph(f"<b>{title}</b><br/>{desc}", styles["phase_body"])
    ]]
    step_tbl = Table(row_data, colWidths=[1.0*cm, PAGE_W - 2*MARGIN - 1.0*cm - 4])
    step_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,-1), MED_BLUE),
        ("BACKGROUND",    (1,0), (1,-1), LIGHT_BLUE),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 6),
        ("RIGHTPADDING",  (0,0), (-1,-1), 6),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
        ("BOX",           (0,0), (-1,-1), 0.5, colors.HexColor("#AED6F1")),
    ]))
    story.append(step_tbl)
    story.append(Spacer(1, 0.15*cm))

story.append(Spacer(1, 0.2*cm))
story.append(info_box(
    "The <b>AV node delay (0.1 sec)</b> is essential — it lets the atria finish contracting "
    "and emptying blood into the ventricles BEFORE the ventricles contract. "
    "Without this delay, the atria and ventricles would contract simultaneously and ventricular filling would be incomplete.",
    label="Why the AV Delay Matters"
))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 2 - THE 7 PHASES
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("2.  The 7 Phases of the Cardiac Cycle", DEEP_RED))
story.append(Spacer(1, 0.3*cm))

story.append(Paragraph(
    "The cardiac cycle is divided into 7 phases. Study each phase by correlating: "
    "<b>ECG event → valve status → pressure change → volume change → heart sound.</b>",
    styles["body"]
))
story.append(Spacer(1, 0.3*cm))

phase_colors = [
    colors.HexColor("#1A5276"),  # 1 - late diastole
    colors.HexColor("#1E8449"),  # 2 - atrial systole
    colors.HexColor("#C0392B"),  # 3 - IVC
    colors.HexColor("#E74C3C"),  # 4 - rapid ejection
    colors.HexColor("#CA6F1E"),  # 5 - reduced ejection
    colors.HexColor("#6C3483"),  # 6 - IVR
    colors.HexColor("#1A6D9A"),  # 7 - rapid filling
]

phases = [
    (1, "Late Diastole — Passive Filling",
     "Before P wave",
     "AV valves OPEN  |  Semilunar valves CLOSED",
     "Heart fully relaxed. Blood flows passively from veins → atria → ventricles. ~70% of ventricular filling happens here without any pumping.",
     "None"),
    (2, "Atrial Systole — The 'Top-Up'",
     "P wave",
     "AV valves OPEN  |  Semilunar valves CLOSED",
     "Atria contract → push final 30% of blood into ventricles. Ventricle reaches maximum volume = EDV (~130 mL). Venous pulse shows 'a' wave.",
     "S4 — abnormal only (stiff ventricle)"),
    (3, "Isovolumetric Contraction (IVC) ★",
     "QRS complex",
     "ALL VALVES CLOSED ← memorise",
     "Ventricles contract. Pressure rises sharply. Volume stays fixed. Ends when LV pressure exceeds aortic diastolic pressure (~80 mmHg) → aortic valve forced open. AV valve bulging → 'c' wave on venous pulse.",
     "S1 'lub' — mitral valve closes"),
    (4, "Rapid Ventricular Ejection",
     "ST segment",
     "Aortic valve OPEN  |  Mitral valve CLOSED",
     "Blood rushes into aorta. LV pressure peaks at ~120 mmHg. Aortic pressure rises to systolic peak. LV volume falls rapidly. ~2/3 of stroke volume ejected here.",
     "None"),
    (5, "Reduced Ventricular Ejection",
     "T wave",
     "Aortic valve OPEN  |  Mitral valve CLOSED",
     "Ejection continues but slows. Pressure begins to fall. LV volume reaches minimum = ESV (~50 mL). Late: aortic pressure exceeds LV pressure but momentum keeps blood moving. Venous pulse 'v' wave appears.",
     "None"),
    (6, "Isovolumetric Relaxation (IVR) ★",
     "After T wave (flat)",
     "ALL VALVES CLOSED ← memorise",
     "Ventricles relax. Pressure falls rapidly. Volume stays fixed. Ends when LV pressure falls below LA pressure → mitral valve opens.",
     "S2 'dub' — aortic valve closes"),
    (7, "Rapid Ventricular Filling",
     "After T wave",
     "Mitral valve OPEN  |  Aortic valve CLOSED",
     "Mitral valve opens. Blood rushes from atria into ventricles passively. Venous pulse 'y' descent. Cycle now repeats.",
     "S3 — abnormal in adults (heart failure)"),
]

for ph in phases:
    story.append(KeepTogether([
        phase_block(ph[0], ph[1], ph[2], ph[3], ph[4], ph[5], phase_colors[ph[0]-1]),
        Spacer(1, 0.25*cm),
    ]))

story.append(info_box(
    "The only TWO phases where <b>ALL valves are closed</b> are:<br/>"
    "1. <b>Isovolumetric Contraction (IVC)</b> — beginning of systole<br/>"
    "2. <b>Isovolumetric Relaxation (IVR)</b> — beginning of diastole<br/>"
    "During both, volume is constant because blood has nowhere to go.",
    label="★ HIGH YIELD EXAM POINT", bg=LIGHT_RED, border=DEEP_RED
))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 3 - SUMMARY TABLE
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("3.  Summary Table — All 7 Phases", MED_BLUE))
story.append(Spacer(1, 0.3*cm))

summary_data = [
    ["Phase", "Valves", "Volume", "Pressure", "ECG", "Sound"],
    ["1. Late Diastole",     "AV open\nSL closed",   "Filling",    "Low",        "—",         "—"],
    ["2. Atrial Systole",    "AV open\nSL closed",   "↑ to EDV",   "Small ↑",    "P wave",    "S4*"],
    ["3. IVC ★",             "ALL CLOSED",           "Fixed",      "↑↑ rapidly", "QRS",       "S1"],
    ["4. Rapid Ejection",    "AV closed\nSL open",   "↓↓",         "Peak 120",   "ST seg.",   "—"],
    ["5. Reduced Ejection",  "AV closed\nSL open",   "↓ to ESV",   "Falling",    "T wave",    "—"],
    ["6. IVR ★",             "ALL CLOSED",           "Fixed",      "↓↓ rapidly", "After T",   "S2"],
    ["7. Rapid Filling",     "AV open\nSL closed",   "↑",          "Low",        "—",         "S3*"],
]
summary_tbl = Table(summary_data,
    colWidths=[3.7*cm, 2.8*cm, 2.0*cm, 2.2*cm, 2.0*cm, 1.7*cm],
    repeatRows=1)
summary_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0),  DARK_BLUE),
    ("TEXTCOLOR",     (0,0), (-1,0),  WHITE),
    ("FONTNAME",      (0,0), (-1,0),  "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 8.5),
    ("LEADING",       (0,0), (-1,-1), 12),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.4, colors.HexColor("#BDC3C7")),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
    ("BACKGROUND",    (0,3), (-1,3),  LIGHT_RED),
    ("BACKGROUND",    (0,6), (-1,6),  LIGHT_RED),
    ("FONTNAME",      (0,3), (-1,3),  "Helvetica-Bold"),
    ("FONTNAME",      (0,6), (-1,6),  "Helvetica-Bold"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("ALIGN",         (0,0), (0,-1),  "LEFT"),
]))
story.append(summary_tbl)
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
    "* S3 and S4 are shown in red rows — both are abnormal in adults. "
    "AV = Atrioventricular valves (mitral + tricuspid). SL = Semilunar valves (aortic + pulmonary).",
    styles["note"]
))

story.append(Spacer(1, 0.5*cm))

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 4 - KEY NUMBERS
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("4.  Key Numbers to Memorise", colors.HexColor("#1E8449")))
story.append(Spacer(1, 0.3*cm))

nums_data = [
    ["Parameter", "Value", "Why It Matters"],
    ["Duration of 1 cycle (72 bpm)", "0.83 sec", "Inverse of heart rate"],
    ["Systole duration", "~0.3 sec", "Contraction phase"],
    ["Diastole duration", "~0.5 sec", "Relaxation/filling phase"],
    ["End-Diastolic Volume (EDV)", "~130 mL", "Max volume before ejection"],
    ["End-Systolic Volume (ESV)", "~50 mL", "Residual volume after ejection"],
    ["Stroke Volume (SV = EDV − ESV)", "~80 mL", "Blood pumped per beat"],
    ["Ejection Fraction (EF)", "~65%", "SV/EDV × 100; Normal > 55%"],
    ["Peak LV systolic pressure", "~120 mmHg", "Drives aortic systolic BP"],
    ["Peak RV systolic pressure", "~25 mmHg", "Much lower — pulmonary circuit"],
    ["Aortic diastolic pressure", "~80 mmHg", "Pressure to open aortic valve"],
    ["Pulmonary artery diastolic pressure", "~10 mmHg", "Pressure to open pulmonic valve"],
    ["AV nodal delay", "~0.1 sec", "Allows atria to empty first"],
    ["Duration of IVC", "~0.05 sec", "Pressure build-up phase"],
    ["Duration of IVR", "~0.04–0.08 sec", "Pressure drop phase"],
]
story.append(coloured_table(nums_data,
    col_widths=[6.5*cm, 3.0*cm, 7.0*cm],
    header_bg=GREEN_DARK, row_bg=GREEN_BG))

story.append(Spacer(1, 0.3*cm))
story.append(info_box(
    "<b>EF Formula:</b>  EF = (Stroke Volume / EDV) × 100 = (80 / 130) × 100 ≈ <b>65%</b><br/>"
    "Normal EF ≥ 55%.  EF &lt; 40% = Heart Failure with Reduced EF (HFrEF)",
    label="Ejection Fraction — Formula to Remember", bg=GREEN_BG, border=GREEN_DARK
))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 5 - HEART SOUNDS
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("5.  Heart Sounds", colors.HexColor("#6C3483")))
story.append(Spacer(1, 0.3*cm))

sounds_data = [
    ["Sound", "Timing", "Cause", "Normal?", "Clinical Significance"],
    ["S1  'lub'", "Start of systole", "Mitral + tricuspid valve CLOSURE", "Yes", "Heard best at apex"],
    ["S2  'dub'", "Start of diastole", "Aortic + pulmonary valve CLOSURE", "Yes", "Heard best at base"],
    ["S3", "Early diastole", "Blood hitting a floppy/overloaded ventricle during rapid filling", "Only in young adults", "Adults: Heart failure, DCM, volume overload"],
    ["S4", "Late diastole (pre-S1)", "Blood hitting a stiff/rigid ventricle during atrial contraction", "Never", "LV hypertrophy, hypertension, HCM"],
]
sounds_tbl = Table(sounds_data,
    colWidths=[1.8*cm, 2.5*cm, 4.5*cm, 2.2*cm, 5.4*cm],
    repeatRows=1)
sounds_tbl.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0),  colors.HexColor("#6C3483")),
    ("TEXTCOLOR",     (0,0), (-1,0),  WHITE),
    ("FONTNAME",      (0,0), (-1,0),  "Helvetica-Bold"),
    ("FONTSIZE",      (0,0), (-1,-1), 8.5),
    ("LEADING",       (0,0), (-1,-1), 12),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.4, colors.HexColor("#BDC3C7")),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.HexColor("#F5EEF8"), WHITE]),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sounds_tbl)
story.append(Spacer(1, 0.3*cm))

story.append(info_box(
    "<b>Memory trick for S3/S4:</b><br/>"
    "S3 = 'Ken-TUCK-y' rhythm (lub-dub-ta) — 3 syllables — 3rd sound<br/>"
    "S4 = 'TEN-nes-see' rhythm (ta-lub-dub) — 4th sound comes BEFORE S1<br/><br/>"
    "<b>Key rule:</b> S1 = valves CLOSING at START of systole. S2 = valves CLOSING at END of systole. "
    "Neither sound is from valves OPENING.",
    label="Memory Tricks — Heart Sounds",
    bg=colors.HexColor("#F5EEF8"), border=colors.HexColor("#6C3483")
))

story.append(Spacer(1, 0.5*cm))

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 6 - JVP WAVES
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("6.  Jugular Venous Pulse (JVP) Waves", colors.HexColor("#CA6F1E")))
story.append(Spacer(1, 0.3*cm))

story.append(Paragraph(
    "The JVP waveform reflects pressure changes in the right atrium, visible in the jugular vein. "
    "It consists of three upward waves (a, c, v) and two downward descents (x, y).",
    styles["body"]
))
story.append(Spacer(1, 0.2*cm))

jvp_data = [
    ["Wave/Descent", "Cause", "Phase of Cycle"],
    ["a wave  (upward)", "Atrial contraction", "Late diastole — atrial systole"],
    ["c wave  (upward)", "AV valve bulging into atrium during IVC", "Early systole (IVC)"],
    ["x descent  (down)", "Atrial relaxation + AV valve moving down", "Systole"],
    ["v wave  (upward)", "Venous filling of atria while AV valves are closed", "Late systole"],
    ["y descent  (down)", "AV valve opens — atria empty into ventricles", "Early diastole"],
]
story.append(coloured_table(jvp_data,
    col_widths=[4.0*cm, 8.0*cm, 4.4*cm],
    header_bg=ORANGE, row_bg=ORANGE_BG))
story.append(Spacer(1, 0.25*cm))

jvp_clinical = [
    ["Absent 'a' wave", "Atrial fibrillation (no organised atrial contraction)"],
    ["Cannon 'a' wave", "Complete heart block / junctional rhythm — atria contract against closed AV valve"],
    ["Raised JVP", "Right heart failure, cardiac tamponade, SVC obstruction"],
    ["Absent 'x' descent", "Cardiac tamponade"],
    ["Absent 'y' descent", "Cardiac tamponade (steep in constrictive pericarditis)"],
]
clin_data = [["Clinical Finding", "Cause"]] + jvp_clinical
story.append(coloured_table(clin_data,
    col_widths=[5.5*cm, 11.0*cm],
    header_bg=DARK_GREY, row_bg=LIGHT_GREY))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 7 - VALVE SUMMARY
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("7.  Valve Opening & Closing — Quick Reference", DARK_BLUE))
story.append(Spacer(1, 0.3*cm))

valve_data = [
    ["Valve", "Opens When...", "Closes When...", "Sound on Closing"],
    ["Mitral", "LV pressure < LA pressure\n(end of IVR)", "LV pressure > LA pressure\n(start of IVC)", "S1"],
    ["Tricuspid", "RV pressure < RA pressure\n(end of IVR)", "RV pressure > RA pressure\n(start of IVC)", "S1"],
    ["Aortic", "LV pressure > aortic diastolic\n(~80 mmHg)", "LV pressure < aortic pressure\n(protodiastole → S2)", "S2"],
    ["Pulmonary", "RV pressure > PA diastolic\n(~10 mmHg)", "RV pressure < PA pressure\n(protodiastole → S2)", "S2"],
]
story.append(coloured_table(valve_data,
    col_widths=[2.5*cm, 5.5*cm, 5.5*cm, 3.0*cm],
    header_bg=DARK_BLUE))

story.append(Spacer(1, 0.5*cm))

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 8 - ECG CORRELATION
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("8.  ECG Correlation with Mechanical Events", MED_BLUE))
story.append(Spacer(1, 0.3*cm))

ecg_data = [
    ["ECG Event", "Electrical Meaning", "Mechanical Event"],
    ["P wave", "Atrial depolarisation", "Atrial contraction (atrial systole begins)"],
    ["PR interval", "AV nodal conduction delay", "Blood filling ventricles"],
    ["QRS complex", "Ventricular depolarisation", "Ventricular systole begins — IVC starts — S1"],
    ["ST segment", "Ventricular plateau (sustained contraction)", "Rapid ejection phase"],
    ["T wave", "Ventricular repolarisation", "Reduced ejection — ventricle beginning to relax"],
    ["After T wave", "Electrical silence", "IVR → S2 → mitral valve opens → rapid filling"],
]
story.append(coloured_table(ecg_data,
    col_widths=[2.8*cm, 5.0*cm, 8.6*cm],
    header_bg=MED_BLUE))

story.append(Spacer(1, 0.5*cm))

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 9 - PRESSURE-VOLUME LOOP
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("9.  Pressure-Volume (P-V) Loop", colors.HexColor("#1E8449")))
story.append(Spacer(1, 0.3*cm))

story.append(Paragraph(
    "The P-V loop is a graphical representation of LV pressure vs. LV volume during one cardiac cycle. "
    "It is traced counter-clockwise. The <b>area inside the loop = stroke work</b> (work done per beat). "
    "The <b>width of the loop = stroke volume</b>.",
    styles["body"]
))
story.append(Spacer(1, 0.2*cm))

pv_data = [
    ["Segment", "Points", "Volume Change", "Pressure Change", "Phase"],
    ["Diastolic filling", "d → a", "↑ (filling)", "Small ↑", "Passive filling + atrial systole"],
    ["Isovolumetric contraction", "a → b", "None (fixed)", "↑↑ sharply", "IVC"],
    ["Ventricular ejection", "b → c", "↓ (ejecting)", "Peaks then falls", "Rapid + reduced ejection"],
    ["Isovolumetric relaxation", "c → d", "None (fixed)", "↓↓ sharply", "IVR"],
]
story.append(coloured_table(pv_data,
    col_widths=[4.0*cm, 1.8*cm, 2.8*cm, 3.0*cm, 4.8*cm],
    header_bg=GREEN_DARK, row_bg=GREEN_BG))

story.append(Spacer(1, 0.3*cm))
story.append(info_box(
    "• <b>Increased preload</b> (e.g. extra blood volume) → loop shifts RIGHT (larger EDV)<br/>"
    "• <b>Increased afterload</b> (e.g. hypertension) → loop is TALLER and NARROWER (less volume ejected)<br/>"
    "• <b>Increased contractility</b> (e.g. adrenaline) → loop shifts LEFT (smaller ESV, larger SV)",
    label="P-V Loop Changes in Disease — Exam Favourite",
    bg=GREEN_BG, border=GREEN_DARK
))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 10 - EXAM TRAPS
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("10.  Exam Traps & Common MCQ Mistakes", DEEP_RED))
story.append(Spacer(1, 0.3*cm))

traps = [
    ("Trap 1", "S1 ≠ mitral valve OPENING",
     "S1 is caused by mitral valve CLOSING. The opening snap of the mitral valve is a pathological sound in mitral stenosis."),
    ("Trap 2", "Atrial systole happens during diastole",
     "The overall cardiac cycle is still in 'diastole' during atrial systole. 'Systole' without a qualifier always refers to VENTRICULAR systole."),
    ("Trap 3", "All valves closed — TWO phases, not one",
     "Both IVC and IVR have all valves closed. Students often forget IVR. In both phases volume is constant."),
    ("Trap 4", "EF is not 100%",
     "~50 mL always remains in the ventricle after ejection (ESV). Normal EF is ~65%, not 100%."),
    ("Trap 5", "S3 in young adults is NORMAL",
     "S3 is physiological in children and athletes under ~40. Only becomes abnormal (suggesting HF/volume overload) in adults over 40."),
    ("Trap 6", "Aortic pressure briefly exceeds LV pressure at end of systole",
     "Yet blood continues to flow forward briefly due to momentum (kinetic energy). The aortic valve only closes when momentum is fully overcome."),
    ("Trap 7", "Diastole shortens MORE than systole with tachycardia",
     "At high heart rates, filling time (diastole) is disproportionately reduced. At 3x normal HR, systole = ~65% of the cycle instead of ~40%."),
    ("Trap 8", "Coronary perfusion is mainly in DIASTOLE",
     "During systole, contracting myocardium compresses coronary vessels. Most coronary blood flow (especially to LV) occurs during diastole."),
]

for title, heading, explanation in traps:
    trap_data = [[
        Paragraph(title, ParagraphStyle("tn", fontSize=9, fontName="Helvetica-Bold",
                                         textColor=WHITE, alignment=TA_CENTER)),
        Paragraph(f"<b>{heading}</b><br/>{explanation}", styles["trap_body"])
    ]]
    trap_tbl = Table(trap_data, colWidths=[1.6*cm, PAGE_W - 2*MARGIN - 1.6*cm - 4])
    trap_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (0,-1), DEEP_RED),
        ("BACKGROUND",    (1,0), (1,-1), colors.HexColor("#FEF9F9")),
        ("TOPPADDING",    (0,0), (-1,-1), 7),
        ("BOTTOMPADDING", (0,0), (-1,-1), 7),
        ("LEFTPADDING",   (0,0), (-1,-1), 7),
        ("RIGHTPADDING",  (0,0), (-1,-1), 7),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
        ("BOX",           (0,0), (-1,-1), 0.5, colors.HexColor("#F5B7B1")),
    ]))
    story.append(trap_tbl)
    story.append(Spacer(1, 0.2*cm))

story.append(PageBreak())

# ════════════════════════════════════════════════════════════════════════════════
# SECTION 11 - QUICK MCQ ANSWERS
# ════════════════════════════════════════════════════════════════════════════════
story.append(section_header("11.  Quick MCQ Answers", colors.HexColor("#1A6D9A")))
story.append(Spacer(1, 0.3*cm))

qas = [
    ("What is the duration of one cardiac cycle at 72 bpm?",
     "~0.83 seconds (reciprocal of heart rate)"),
    ("What event triggers the start of the cardiac cycle?",
     "Spontaneous depolarisation of the SA node (sinus node)"),
    ("What causes S1?",
     "Closure of the mitral and tricuspid (AV) valves at the start of ventricular systole"),
    ("What causes S2?",
     "Closure of the aortic and pulmonary (semilunar) valves at the start of diastole"),
    ("During which two phases are ALL heart valves closed?",
     "Isovolumetric Contraction (IVC) and Isovolumetric Relaxation (IVR)"),
    ("What is the normal ejection fraction?",
     "~65% (normal ≥ 55%; HFrEF defined as EF < 40%)"),
    ("What is EDV and ESV?",
     "EDV ~130 mL (before ejection); ESV ~50 mL (after ejection); SV = 80 mL"),
    ("What does the 'a' wave on JVP represent?",
     "Atrial contraction — rise in atrial pressure during atrial systole"),
    ("Why is diastole important at high heart rates?",
     "Diastole shortens more than systole, reducing ventricular filling time and cardiac output"),
    ("What is the significance of the AV nodal delay?",
     "Ensures atria fully contract and empty blood into ventricles BEFORE ventricular contraction begins"),
    ("When is S3 pathological?",
     "In adults over 40 — suggests heart failure, dilated cardiomyopathy, or volume overload"),
    ("What is a cannon 'a' wave on JVP?",
     "Atria contracting against closed AV valve — seen in complete heart block, junctional rhythm"),
    ("What does the area inside the P-V loop represent?",
     "Stroke work — the mechanical work done by the ventricle per beat"),
    ("What LV pressure must be exceeded for the aortic valve to open?",
     "Aortic diastolic pressure ~80 mmHg"),
]

for i, (q, a) in enumerate(qas):
    qa_data = [[
        Paragraph(f"Q{i+1}. {q}", styles["qbox"]),
    ], [
        Paragraph(f"A: {a}", styles["abox"]),
    ]]
    qa_tbl = Table(qa_data, colWidths=[PAGE_W - 2*MARGIN])
    qa_tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,0), LIGHT_BLUE),
        ("BACKGROUND",    (0,1), (-1,1), WHITE),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 9),
        ("RIGHTPADDING",  (0,0), (-1,-1), 9),
        ("BOX",           (0,0), (-1,-1), 0.8, MED_BLUE),
        ("LINEBELOW",     (0,0), (-1,0),  0.5, colors.HexColor("#AED6F1")),
        ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ]))
    story.append(qa_tbl)
    story.append(Spacer(1, 0.2*cm))

story.append(Spacer(1, 0.4*cm))
story.append(HRFlowable(width="100%", thickness=1, color=MED_GREY))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
    "<b>Sources:</b> Guyton &amp; Hall Textbook of Medical Physiology | "
    "Costanzo Physiology 7th Edition | Ganong's Review of Medical Physiology 26th Edition | "
    "Miller's Anesthesia 10th Edition",
    styles["footer"]
))
story.append(Paragraph(
    "Generated by Orris Medical AI  |  For educational use only",
    styles["footer"]
))

# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_later_pages)
print(f"PDF generated: {OUTPUT}")
Running Command

cd /home/daytona/workspace/cardiac-cycle && python generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/cardiac-cycle/Cardiac_Cycle_Study_Guide.pdf

Your study guide PDF is ready! Click the link below to download it:

Cardiac Cycle Study Guide

PDF Document · PDF


What's inside the PDF (11 sections, colour-coded throughout):
  1. Introduction - Definition, cycle duration, and how each beat is triggered (SA node → AV delay → ventricles)
  2. The 7 Phases - Each phase as a colour-coded card with ECG event, valve status, mechanism, and heart sound
  3. Summary Table - All 7 phases at a glance with highlighted IVC and IVR rows
  4. Key Numbers - Every value you need to memorise (EDV, ESV, EF, pressures, durations)
  5. Heart Sounds - S1-S4 table with causes, normality, and clinical significance
  6. JVP Waves - a, c, v waves + x, y descents with clinical correlations
  7. Valve Opening & Closing - All 4 valves, when they open/close, which sound they make
  8. ECG Correlation - Each ECG event mapped to its mechanical event
  9. Pressure-Volume Loop - Points a-b-c-d explained with disease changes
  10. Exam Traps - 8 common MCQ mistakes students make
  11. Quick MCQ Answers - 14 Q&A cards covering the most frequently tested facts
This is a shared conversation. Sign in to Orris to start your own chat.