I am a ST-2 trainee - MRCOG in Max Hospital Delhi. I need you help with OBGYN

Welcome! Happy to help you with OBGYN for your MRCOG ST-2 preparation at Max Hospital Delhi.
What topic or question would you like to work on? You can ask about:
  • Clinical scenarios - a case you've seen or a tricky management decision
  • MRCOG exam prep - SBAs, EMQs, or structured revision on any topic
  • Core OBGYN topics - obstetrics, gynaecology, maternal-fetal medicine, reproductive medicine
  • Guidelines - RCOG Green-top, NICE, FOGSI, or WHO guidance
  • Procedures - operative obstetrics, laparoscopy, colposcopy, etc.
What would you like to cover today?

So, my name is Dr Aayushi Rao and today I’d like to learn in the easiest way possible - CTG and terms like - baseline, variability, accelerations, cycling, quickly, tardy and offshoots and the rest

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Loading Image
Searching the Web

CTG cardiotocography RCOG NICE 2023 guidelines baseline variability accelerations decelerations cycling

I now have all the content I need. Let me compile a thorough, easy-to-read CTG guide for Dr. Aayushi.

CTG Made Easy - A Guide for Dr. Aayushi Rao (MRCOG ST-2)

Think of the CTG as a conversation the baby is having with you. Your job is to listen carefully and understand what it's saying. Let's break it down piece by piece.

The CTG Paper - What Are You Looking At?

The CTG trace has two channels running simultaneously:
  • Top channel (FHR): Fetal heart rate in beats per minute (bpm) - runs on a scale of 50-210 bpm
  • Bottom channel (UC/TOCO): Uterine contractions - shows frequency and shape
Paper speed is standardised at 1 cm/min in the UK.

The DR C BRAVADO Framework (How to Read Any CTG Systematically)

Use this mnemonic every single time - it stops you missing things:
Define Risk → Contractions → Baseline Rate → Acelerations → Variability → Abnormal Decelerations → Overall impression

1. BASELINE

What it is: The average FHR rounded to the nearest 5 bpm, assessed over a 10-minute window, excluding accelerations, decelerations, and periods of marked variability.
CategoryRate
Normal110-160 bpm
Tachycardia>160 bpm
Bradycardia<110 bpm
Think of it this way: Imagine the baby's resting heart rate when nothing special is happening. That flat-ish "middle line" on the trace.
Why does tachycardia happen?
  • Maternal fever/chorioamnionitis (most common!)
  • Fetal hypoxia (early response)
  • Maternal drugs: ephedrine, atropine, salbutamol
  • Fetal anaemia, fetal infection
Why does bradycardia happen?
  • Fetal heart block (congenital)
  • Fetal hypoxia + acidosis (late, ominous sign)
  • Post-epidural (maternal hypotension)
  • Prolonged cord compression
Barash Clinical Anesthesia 9e - "Persistently elevated rates may be associated with chronic fetal distress, maternal fever, or administration of drugs such as ephedrine and atropine."

2. VARIABILITY

What it is: The up-and-down fluctuations of the FHR baseline from beat to beat - measured as the amplitude (peak to trough) over any 1-minute window.
CategoryAmplitudeMeaning
Normal (moderate)5-25 bpmHealthy autonomic nervous system
Reduced/minimal<5 bpm for >40-50 minWorrying - possible hypoxia
Absent (silent/flat)UndetectableOminous
Marked/saltatory>25 bpmAcute stress response (sudden cord compression)
The easy analogy: Variability is like the "wobbliness" of the baseline. A healthy baby with a well-oxygenated brain constantly fine-tunes its heart rate through the autonomic nervous system. This constant micro-adjustment is what you SEE as variability. A flat/smooth trace means the brain isn't doing this - usually because it's compromised.
Normal physiological reasons for reduced variability (don't panic immediately!):
  • Fetal sleep cycle (quiet sleep, lasts 20-40 min max)
  • Drugs: opioids, benzodiazepines, MgSO4, general anaesthetics
  • Prematurity (<28 weeks)
Pathological reasons:
  • Fetal hypoxia and acidosis (CNS depression)
Barash: "Presence of normal variability is a reassuring sign of normal fetal acid-base status. Asphyxia leading to fetal CNS depression may cause decreased variability; therefore, a smooth FHR tracing without variability may be an ominous finding."

3. ACCELERATIONS

What it is: An abrupt rise in FHR of ≥15 bpm above baseline, lasting ≥15 seconds (in fetuses >32 weeks). Before 32 weeks, ≥10 bpm for ≥10 seconds counts.
What it tells you: The fetus is NOT acidotic. Accelerations are the most reliable reassuring sign. An acceleration means the cerebral cortex is active and responding to movement/stimuli.
Types:
  • Spontaneous - occur with fetal movement (the best kind!)
  • Provoked - in response to uterine contractions, scalp stimulation, or vibroacoustic stimulation
Clinical pearl: Even if variability is reduced, the presence of accelerations is very reassuring. Conversely, absence of accelerations on an otherwise normal trace does NOT by itself indicate compromise - it just means you need to keep watching.

4. CYCLING (Sleep-Wake Cycling)

This is often underemphasised but important for MRCOG.
What it is: The normal alternation between active (awake) fetal state and quiet (sleep) fetal state on the CTG. This appears as episodes of:
  • Higher baseline + more variability + accelerations (awake)
  • Lower baseline + reduced variability + fewer accelerations (quiet sleep)
Why it matters: Cycling tells you the fetal brain is functioning normally and switching between sleep states - a neurologically intact brain sign.
  • A healthy term fetus sleeps for 20-40 minutes at a time (maximum 90 min in a preterm fetus)
  • If variability is reduced but then increases and cycling is seen, it's very likely a fetal sleep cycle not compromise
  • Loss of cycling (persistently flat with no alternation) is worrying
Practical tip: Before calling a CTG abnormal for reduced variability, wait and watch for a full 40-minute cycle. If the baby "wakes up" with accelerations and variability returns, you've witnessed normal cycling.

5. DECELERATIONS - The Big One

A deceleration = a decrease in FHR below baseline by ≥15 bpm, lasting ≥15 seconds.
There are 3 main types. The key is their timing relative to contractions.

5a. Early Decelerations

Shape: Mirror image of the contraction (uniform, smooth, symmetrical) Timing: Starts with contraction, nadir at peak of contraction, returns to baseline as uterus relaxes Cause: Head compression → vagal stimulation Significance: Benign/physiological - no fetal compromise
CTG deceleration patterns showing early, late and variable decelerations
Figure: Classification and mechanism of fetal heart rate decelerations - Barash Clinical Anesthesia 9e

5b. Late Decelerations

Shape: Uniform, smooth, symmetrical - looks like early but shifted to the right Timing: Starts AFTER contraction begins, nadir AFTER contraction peak, recovery AFTER contraction ends (≥30 seconds lag) Cause: Uteroplacental insufficiency → fetal hypoxia → CNS/myocardial ischaemia Significance: OMINOUS - marker of uteroplacental compromise
Memory trick: "LATE = PLACENTA'S FATE" - late decels are the placenta failing to deliver oxygen.
Common causes: Pre-eclampsia, IUGR, placenta praevia, abruption, maternal hypotension (especially post-epidural).

5c. Variable Decelerations

Shape: Variable - abrupt, irregular in shape, "W" pattern often seen (shoulders before and after) Timing: Variable in onset relative to contractions - can occur with or without contractions Cause: Umbilical cord compression → baroreceptor reflex Significance: Most common type in labour. Generally benign if typical, but can indicate fetal compromise if atypical
Typical vs Atypical variable decelerations:
FeatureTypical (reassuring)Atypical (concerning)
ShouldersPresent (accelerations before + after)Absent
Duration<60 seconds>60 seconds
Depth<60 bpm below baseline>60 bpm below baseline
RecoverySharp return to baselineSlow return
Variability within decelNormalReduced/absent
Post-decelReturns to original baselineBaseline rise (tachycardia)
Management of variable decels:
  • Change maternal position (left lateral)
  • Check for cord prolapse
  • If persistent: fetal blood sampling (FBS)

6. QUICKLY and TARDY - The UK NICE/RCOG Terms for Deceleration Timing

These two terms are specifically used in the NICE CTG guidelines (2022/2023) for categorising decelerations:

QUICKLY (Rapid return)

  • Refers to decelerations where the FHR returns to baseline rapidly after the nadir
  • Usually seen with typical variable decelerations
  • Generally a reassuring feature
  • The return to baseline is sharp and fast

TARDY (Delayed/slow return)

  • Refers to decelerations where the FHR returns to baseline slowly after the nadir
  • Suggests the myocardium took longer to recover - implies greater stress
  • This is an atypical/concerning feature in variable decelerations
  • A tardy return is one of the features that upgrades a variable decel from "typical" to "atypical"
Memory trick:
  • Quickly = Good. Baby bounces back fast. Happy baby.
  • Tardy = Trouble. Baby takes its time recovering. Struggling baby.

7. SHOULDERS and OFFSHOOTS

Shoulders (Accelerations flanking variable decelerations)

Small accelerations that appear just before and after a variable deceleration. They look like "shoulders" on either side of the dip.
  • Pre-decel shoulder: Rise in FHR just before the drop (umbilical vein compressed first → slight tachycardia before arteries close off)
  • Post-decel shoulder: Rise in FHR just after recovery (rebound)
  • Significance: Presence of shoulders = typical variable deceleration = reassuring
  • Loss of shoulders = atypical variable = concerning

Offshoots (Biphasic/W-pattern variable decelerations)

Sometimes variable decelerations have a more complex shape with a secondary dip or "offshoot" - a notch within the deceleration. This can indicate:
  • Prolonged cord compression with more severe autonomic response
  • Is classified as an atypical feature

8. SINUSOIDAL PATTERN

What it is: A smooth, regular, sine-wave-like oscillation of the FHR:
  • Amplitude: 5-15 bpm above and below baseline
  • Frequency: 2-5 cycles/min
  • Resembles a gentle wave with NO true accelerations
True sinusoidal pattern = EMERGENCY
  • Caused by severe fetal anaemia (Rh isoimmunisation, fetomaternal haemorrhage, vasa praevia)
  • Category III trace - requires immediate delivery
Pseudo-sinusoidal: Can be caused by maternal opioid administration (pethidine) - differentiate by history; usually resolves spontaneously

9. SALTATORY PATTERN

What it is: Marked variability >25 bpm (excessively "spiky" trace)
Cause: Often seen with acute hypoxia (sudden cord compression, uterine hyperstimulation)
  • The fetal ANS is reacting vigorously to an acute stressor
  • Can precede a period of reduced variability if the hypoxia continues
Action: Investigate cause, stop oxytocin if running, check for cord issues

10. CLASSIFICATION (NICE 2022 Framework)

FeatureReassuringNon-reassuringAbnormal
Baseline110-160 bpm100-109 or 161-180 bpm<100 or >180 bpm
Variability5-25 bpm<5 bpm for 40-90 min OR >25 bpm for <30 min<5 bpm for >90 min or sinusoidal
AccelerationsPresentAbsent (after 45 min)N/A
DecelerationsNone / earlyVariable with no concerning features <90 minLate / prolonged / atypical variable
CTG category:
  • Normal: All 4 features reassuring
  • Suspicious: 1 non-reassuring feature
  • Pathological: 2+ non-reassuring OR any 1 abnormal feature

Quick Summary Table - The "Cheat Sheet"

TermNormal/AbnormalMemory Hook
Baseline 110-160Normal"110 → 160, all is fine"
Variability 5-25 bpmNormal"5 to 25 - brain is alive"
Accelerations presentReassuring"Accels = not acidotic"
Cycling (sleep-wake)Normal"Cycling = brain is driving"
Early decelsBenign"Early = head squished"
Late decelsOminous"Late = placenta failing"
Variable decelsCommon, investigate"Variable = cord compressed"
Shoulders presentTypical, reassuring"Shoulders up = baby ok"
Quickly (fast return)Reassuring"Quick return = good reserve"
Tardy (slow return)Concerning"Tardy return = tired baby"
Saltatory (>25 bpm)Acute stress"Too spiky = acute panic"
SinusoidalEmergency"Sine wave = needs delivery"

The Golden Rule in Practice

Normal variability + accelerations = almost certainly NOT acidotic, no matter what else is on the trace.
Absent variability + late decelerations = acidosis until proven otherwise. Act.

What would you like to go deeper on, Dr. Aayushi? We can cover:
  1. FBS (Fetal Blood Sampling) - when to do it and how to interpret pH/lactate
  2. CTG in specific scenarios - IUGR, post-dates, eclampsia, prolonged labour
  3. NICE 2022 management flowcharts - exactly what to do for each category
  4. Practice CTG interpretation - I can describe traces and you interpret them (like MRCOG OSCEs!)

Create a visual flowchart PDF for CTG interpretation I can print

Reading File
Running Command

ls -la /tmp/workspace/ 2>/dev/null || echo "workspace empty or missing"

Running Command

mkdir -p /tmp/workspace/ctg-flowchart && python3 - << 'PYEOF' from reportlab.lib.pagesizes import A4, landscape from reportlab.lib import colors from reportlab.lib.units import mm, cm from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT from reportlab.graphics.shapes import Drawing, Rect, String, Line, Polygon, Circle, Arrow from reportlab.graphics import renderPDF from reportlab.platypus.flowables import Flowable import reportlab.graphics.shapes as shapes print("reportlab version:", __import__('reportlab').Version) print("All imports OK") PYEOF

Writing File

~/ctg-flowchart/build_ctg_pdf.py

"""
CTG Interpretation Flowchart PDF - Dr Aayushi Rao, MRCOG ST-2
Max Hospital Delhi
Built with ReportLab - A4 landscape, print-ready
"""

from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
                                 TableStyle, HRFlowable, KeepTogether)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.graphics.shapes import (Drawing, Rect, String, Line, Polygon,
                                        Group, Circle)
from reportlab.graphics import renderPDF
from reportlab.platypus.flowables import Flowable
import os

OUTPUT = "/tmp/workspace/ctg-flowchart/CTG_Interpretation_Flowchart.pdf"

# ─── Colour palette ───────────────────────────────────────────────────────────
GREEN      = colors.HexColor("#1B7F4E")   # normal / reassuring
DARK_GREEN = colors.HexColor("#145C38")
AMBER      = colors.HexColor("#D97706")   # non-reassuring
RED        = colors.HexColor("#B91C1C")   # abnormal / pathological
DARK_RED   = colors.HexColor("#7F1D1D")
BLUE       = colors.HexColor("#1D4ED8")   # info boxes
LIGHT_BLUE = colors.HexColor("#DBEAFE")
LIGHT_GREEN= colors.HexColor("#D1FAE5")
LIGHT_AMBER= colors.HexColor("#FEF3C7")
LIGHT_RED  = colors.HexColor("#FEE2E2")
LIGHT_GREY = colors.HexColor("#F3F4F6")
MID_GREY   = colors.HexColor("#6B7280")
DARK_GREY  = colors.HexColor("#1F2937")
WHITE      = colors.white
PURPLE     = colors.HexColor("#7C3AED")
LIGHT_PURP = colors.HexColor("#EDE9FE")

PAGE_W, PAGE_H = landscape(A4)   # 297 x 210 mm
MARGIN = 12 * mm

# ─── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

def S(name, **kw):
    base = styles.get(name, styles["Normal"])
    return ParagraphStyle(name + str(id(kw)), parent=base, **kw)

title_style   = S("Normal", fontSize=18, textColor=WHITE, alignment=TA_CENTER,
                  fontName="Helvetica-Bold", spaceAfter=2)
sub_style     = S("Normal", fontSize=9,  textColor=WHITE, alignment=TA_CENTER,
                  fontName="Helvetica")
h2_style      = S("Normal", fontSize=11, textColor=WHITE, alignment=TA_CENTER,
                  fontName="Helvetica-Bold")
h3_style      = S("Normal", fontSize=9,  textColor=DARK_GREY, alignment=TA_CENTER,
                  fontName="Helvetica-Bold")
body_style    = S("Normal", fontSize=8,  textColor=DARK_GREY, alignment=TA_LEFT,
                  fontName="Helvetica", leading=11)
body_c_style  = S("Normal", fontSize=8,  textColor=DARK_GREY, alignment=TA_CENTER,
                  fontName="Helvetica", leading=11)
bold_c_style  = S("Normal", fontSize=8,  textColor=DARK_GREY, alignment=TA_CENTER,
                  fontName="Helvetica-Bold", leading=11)
white_b_style = S("Normal", fontSize=8,  textColor=WHITE,     alignment=TA_CENTER,
                  fontName="Helvetica-Bold", leading=11)
small_style   = S("Normal", fontSize=7,  textColor=MID_GREY,  alignment=TA_CENTER,
                  fontName="Helvetica", leading=9)
small_l_style = S("Normal", fontSize=7,  textColor=DARK_GREY, alignment=TA_LEFT,
                  fontName="Helvetica", leading=9)
green_b_style = S("Normal", fontSize=8,  textColor=GREEN,     alignment=TA_CENTER,
                  fontName="Helvetica-Bold")
red_b_style   = S("Normal", fontSize=8,  textColor=RED,       alignment=TA_CENTER,
                  fontName="Helvetica-Bold")
amber_b_style = S("Normal", fontSize=8,  textColor=AMBER,     alignment=TA_CENTER,
                  fontName="Helvetica-Bold")


# ─── Helper: section header ───────────────────────────────────────────────────
def section_header(text, bg=BLUE, fg=WHITE, size=11):
    style = S("Normal", fontSize=size, textColor=fg, alignment=TA_CENTER,
              fontName="Helvetica-Bold", spaceAfter=0, spaceBefore=0)
    t = Table([[Paragraph(text, style)]], colWidths=[PAGE_W - 2*MARGIN])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), bg),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("ROUNDEDCORNERS", [4]),
    ]))
    return t


# ─── Arrow flowable ───────────────────────────────────────────────────────────
class DownArrow(Flowable):
    def __init__(self, width=20, height=14, color=MID_GREY):
        super().__init__()
        self.width  = width
        self.height = height
        self._color = color

    def draw(self):
        c = self.canv
        w, h = self.width, self.height
        c.setFillColor(self._color)
        c.setStrokeColor(self._color)
        # shaft
        shaft_w = w * 0.35
        shaft_x = (w - shaft_w) / 2
        shaft_h = h * 0.55
        c.rect(shaft_x, h * 0.45, shaft_w, shaft_h, fill=1, stroke=0)
        # arrow head
        pts = [w/2, 0,  0, h*0.5,  w, h*0.5]
        p = c.beginPath()
        p.moveTo(pts[0], pts[1])
        p.lineTo(pts[2], pts[3])
        p.lineTo(pts[4], pts[5])
        p.close()
        c.drawPath(p, fill=1, stroke=0)

    def wrap(self, aw, ah):
        return self.width, self.height


def arrow_row(color=MID_GREY):
    return Table([[DownArrow(24, 14, color)]], colWidths=[PAGE_W - 2*MARGIN],
                 rowHeights=[18])


# ─── Build PDF ────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=landscape(A4),
    leftMargin=MARGIN, rightMargin=MARGIN,
    topMargin=MARGIN,  bottomMargin=MARGIN,
    title="CTG Interpretation Flowchart",
    author="Dr Aayushi Rao – MRCOG ST-2, Max Hospital Delhi",
)

story = []

# ══════════════════════════════════════════════════════════════════════════════
#  PAGE 1 – Title banner + DR C BRAVADO + Feature Classification Table
# ══════════════════════════════════════════════════════════════════════════════

# Title banner
banner = Table(
    [[Paragraph("CTG Interpretation Flowchart", title_style)],
     [Paragraph("NICE 2022 Framework  •  MRCOG ST-2 Quick Reference  •  Dr Aayushi Rao, Max Hospital Delhi", sub_style)]],
    colWidths=[PAGE_W - 2*MARGIN]
)
banner.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), DARK_GREEN),
    ("TOPPADDING",    (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ("ROUNDEDCORNERS", [6]),
]))
story.append(banner)
story.append(Spacer(1, 4*mm))

# ── Step 1: DR C BRAVADO mnemonic ─────────────────────────────────────────────
story.append(section_header("STEP 1  –  DR C BRAVADO  (Systematic Approach)", bg=BLUE))
story.append(Spacer(1, 2*mm))

bravado_cols = [34*mm]*8
bravado_items = [
    ("D\nDefine\nRisk", LIGHT_BLUE, BLUE),
    ("R\nContractions\n(Rate/Strength)", LIGHT_BLUE, BLUE),
    ("C\nBaseline\nRate", LIGHT_GREEN, GREEN),
    ("B\nVariability", LIGHT_GREEN, GREEN),
    ("R\nAccelerations", LIGHT_GREEN, GREEN),
    ("A\nDecelerations", LIGHT_RED, RED),
    ("V\nOverall\nImpression", LIGHT_PURP, PURPLE),
    ("O\nAction", LIGHT_AMBER, AMBER),
]

bravado_row1 = []
bravado_row2 = []
bravado_style_data = []

for i, (label, bg, fg) in enumerate(bravado_items):
    parts = label.split("\n")
    letter = parts[0]
    words  = "\n".join(parts[1:])
    cell_txt = Paragraph(f'<font size="14" color="{fg}"><b>{letter}</b></font><br/>'
                         f'<font size="7" color="#374151">{words}</font>', body_c_style)
    bravado_row1.append(cell_txt)

br_table = Table([bravado_row1], colWidths=bravado_cols, rowHeights=[36])
ts = [("ALIGN",       (0,0),(-1,-1), "CENTER"),
      ("VALIGN",      (0,0),(-1,-1), "MIDDLE"),
      ("TOPPADDING",  (0,0),(-1,-1), 4),
      ("BOTTOMPADDING",(0,0),(-1,-1), 4),
      ("GRID",        (0,0),(-1,-1), 0.5, colors.HexColor("#CBD5E1")),
      ("ROUNDEDCORNERS", [3])]
for i, (_, bg, _) in enumerate(bravado_items):
    ts.append(("BACKGROUND", (i,0),(i,0), bg))
br_table.setStyle(TableStyle(ts))
story.append(br_table)
story.append(Spacer(1, 4*mm))

# ── Step 2: Feature Classification Table ──────────────────────────────────────
story.append(section_header("STEP 2  –  CLASSIFY EACH FEATURE", bg=DARK_GREY))
story.append(Spacer(1, 2*mm))

col_w = [(PAGE_W - 2*MARGIN) * p for p in [0.18, 0.22, 0.22, 0.22, 0.16]]

def hdr(t):  return Paragraph(t, white_b_style)
def grn(t):  return Paragraph(t, S("Normal", fontSize=7.5, textColor=GREEN, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10))
def amb(t):  return Paragraph(t, S("Normal", fontSize=7.5, textColor=AMBER, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10))
def red_(t): return Paragraph(t, S("Normal", fontSize=7.5, textColor=RED,   alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10))
def bl(t):   return Paragraph(t, S("Normal", fontSize=7.5, textColor=DARK_GREY, alignment=TA_LEFT,   fontName="Helvetica", leading=10))
def blc(t):  return Paragraph(t, S("Normal", fontSize=7.5, textColor=DARK_GREY, alignment=TA_CENTER, fontName="Helvetica", leading=10))

feat_data = [
    [hdr("FEATURE"), hdr("✅  REASSURING"), hdr("⚠️  NON-REASSURING"), hdr("❌  ABNORMAL"), hdr("Contributes to")],
    [bl("Baseline FHR"),
     grn("110 – 160 bpm"),
     amb("100–109  OR  161–180 bpm"),
     red_("< 100  OR  > 180 bpm\n(or sudden bradycardia)"),
     blc("Tachycardia: fever,\nhypoxia, drugs\nBradycardia: cord,\nheart block, acidosis")],
    [bl("Variability\n(peak-to-trough)"),
     grn("5 – 25 bpm"),
     amb("< 5 bpm for 40–90 min\nOR  > 25 bpm < 30 min"),
     red_("< 5 bpm for > 90 min\nOR Sinusoidal pattern"),
     blc("Reduced: sleep, opioids,\nhypoxia, acidosis\nSaltatory: acute stress")],
    [bl("Accelerations\n(≥15 bpm, ≥15 sec)"),
     grn("Present\n(or absent < 45 min)"),
     amb("Absent ≥ 45 min\n(no other concerns)"),
     red_("Absent with other\nabnormal features"),
     blc("Presence = NOT acidotic\nBest reassuring sign")],
    [bl("Decelerations"),
     grn("None  OR\nEarly decelerations"),
     amb("Variable decels\nno concerning features\n< 90 min"),
     red_("Late decelerations\nAtypical variable\nProlonged > 3 min"),
     blc("Late = UPI\nVariable = cord\nEarly = head compression")],
]

feat_table = Table(feat_data, colWidths=col_w, repeatRows=1)
feat_ts = [
    ("BACKGROUND",    (0,0), (-1,0),  DARK_GREY),
    ("BACKGROUND",    (0,1), (-1,1),  LIGHT_GREEN),
    ("BACKGROUND",    (0,2), (-1,2),  colors.HexColor("#F0FDF4")),
    ("BACKGROUND",    (0,3), (-1,3),  LIGHT_GREEN),
    ("BACKGROUND",    (0,4), (-1,4),  colors.HexColor("#F0FDF4")),
    ("GRID",          (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_GREEN, colors.HexColor("#F0FDF4")]),
]
feat_table.setStyle(TableStyle(feat_ts))
story.append(feat_table)
story.append(Spacer(1, 4*mm))

# ── Step 3: CTG Category Decision boxes ───────────────────────────────────────
story.append(section_header("STEP 3  –  CLASSIFY THE OVERALL CTG", bg=DARK_GREY))
story.append(Spacer(1, 2*mm))

cat_col = (PAGE_W - 2*MARGIN - 8*mm) / 3

def cat_box(label, criteria, bg, fg, badge_bg):
    badge = Paragraph(label, S("Normal", fontSize=13, textColor=fg,
                               alignment=TA_CENTER, fontName="Helvetica-Bold"))
    crit  = Paragraph(criteria, S("Normal", fontSize=7.5, textColor=DARK_GREY,
                                  alignment=TA_CENTER, fontName="Helvetica", leading=11))
    inner = Table([[badge], [crit]], colWidths=[cat_col - 8])
    inner.setStyle(TableStyle([
        ("TOPPADDING",    (0,0),(-1,-1), 2),
        ("BOTTOMPADDING", (0,0),(-1,-1), 2),
    ]))
    outer = Table([[inner]], colWidths=[cat_col])
    outer.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(-1,-1), bg),
        ("TOPPADDING",    (0,0),(-1,-1), 6),
        ("BOTTOMPADDING", (0,0),(-1,-1), 6),
        ("LEFTPADDING",   (0,0),(-1,-1), 4),
        ("RIGHTPADDING",  (0,0),(-1,-1), 4),
        ("BOX",           (0,0),(-1,-1), 1.5, badge_bg),
        ("ROUNDEDCORNERS", [5]),
    ]))
    return outer

cat_normal = cat_box(
    "✅  NORMAL",
    "ALL 4 features reassuring\n\nBaseline: 110–160\nVariability: 5–25 bpm\nAccelerations: present\nDecelerations: none / early only",
    LIGHT_GREEN, GREEN, GREEN)

cat_suspicious = cat_box(
    "⚠️  SUSPICIOUS",
    "1 non-reassuring feature\n(all others reassuring)\n\nContinue CTG\nAddress reversible causes\nConsider fetal scalp stimulation",
    LIGHT_AMBER, AMBER, AMBER)

cat_patho = cat_box(
    "❌  PATHOLOGICAL",
    "≥2 non-reassuring  OR  ≥1 abnormal\n\nIMMEDIATE senior review\nIn-utero resuscitation\nConsider FBS / delivery",
    LIGHT_RED, RED, RED)

spacer_col = [Paragraph("", body_style)]
cat_row = Table(
    [[cat_normal, Spacer(4,1), cat_suspicious, Spacer(4,1), cat_patho]],
    colWidths=[cat_col, 4*mm, cat_col, 4*mm, cat_col]
)
story.append(cat_row)

# page break instruction note (soft)
story.append(Spacer(1, 3*mm))
story.append(Paragraph(
    "Based on NICE Intrapartum Care Guideline 2022  |  RCOG Green-top Guideline  |  Prepared for MRCOG ST-2, Max Hospital Delhi",
    small_style))


# ══════════════════════════════════════════════════════════════════════════════
#  PAGE 2 – Deceleration flowchart + Management + Quick-ref terms
# ══════════════════════════════════════════════════════════════════════════════
from reportlab.platypus import PageBreak
story.append(PageBreak())

# Title
banner2 = Table(
    [[Paragraph("CTG – Deceleration Guide & Management Flowchart", title_style)],
     [Paragraph("DR C BRAVADO  •  Decelerations in Detail  •  In-Utero Resuscitation  •  Quick-Reference Terms", sub_style)]],
    colWidths=[PAGE_W - 2*MARGIN]
)
banner2.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), colors.HexColor("#7C2D12")),
    ("TOPPADDING",    (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ("ROUNDEDCORNERS", [6]),
]))
story.append(banner2)
story.append(Spacer(1, 4*mm))

# ── Deceleration comparison table ─────────────────────────────────────────────
story.append(section_header("DECELERATION TYPES AT A GLANCE", bg=colors.HexColor("#7C2D12")))
story.append(Spacer(1, 2*mm))

dcol = [(PAGE_W - 2*MARGIN) * p for p in [0.13, 0.20, 0.20, 0.24, 0.23]]

def dh(t): return Paragraph(t, white_b_style)
def dn(t): return Paragraph(t, S("Normal", fontSize=7.5, textColor=DARK_GREY,
                                 alignment=TA_CENTER, fontName="Helvetica", leading=10))
def db(t): return Paragraph(t, S("Normal", fontSize=7.5, textColor=DARK_GREY,
                                 alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10))

decel_data = [
    [dh("TYPE"), dh("SHAPE / TIMING"), dh("CAUSE"), dh("SIGNIFICANCE"), dh("ACTION")],
    [db("EARLY\nDecelerations"),
     dn("Mirror image of contraction\nSymmetric, uniform\nNadir = peak of contraction\n(onset < 30 sec lag)"),
     dn("Head compression\n→ Vagal stimulation"),
     Paragraph("✅ BENIGN\nPhysiological\nNormal in active labour",
               S("Normal", fontSize=7.5, textColor=GREEN, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10)),
     dn("Reassure\nContinue monitoring\nNo action needed")],
    [db("LATE\nDecelerations"),
     dn("Looks like early BUT shifted RIGHT\nOnset > 30 sec after contraction\nNadir after contraction peak\n(\"TARDY\" return)"),
     dn("Uteroplacental insufficiency\n→ Fetal hypoxia\n→ CNS / myocardial ischaemia"),
     Paragraph("❌ OMINOUS\nUteroplacental failure\nAcidosis risk HIGH",
               S("Normal", fontSize=7.5, textColor=RED, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10)),
     dn("Senior review STAT\nLeft lateral position\nStop oxytocin\nIV fluids\nO₂ mask\nFBS / Deliver")],
    [db("VARIABLE\nDecelerations"),
     dn("VARIABLE shape (often W-pattern)\nAbrupt onset (< 30 sec to nadir)\nVariable timing vs contractions"),
     dn("Umbilical cord compression\n→ Baroreceptor reflex"),
     Paragraph("⚠️ VARIABLE\nTypical = reassuring\nAtypical = concerning",
               S("Normal", fontSize=7.5, textColor=AMBER, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10)),
     dn("Change position\nCheck for cord prolapse\nIf persistent / atypical → FBS")],
    [db("PROLONGED\nDeceleration"),
     dn("FHR drop ≥ 15 bpm\nDuration: 2–10 minutes"),
     dn("Cord prolapse, abruption,\nmaternal hypotension,\nrapid descent, uterine rupture"),
     Paragraph("❌ URGENT\nImminent threat",
               S("Normal", fontSize=7.5, textColor=RED, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=10)),
     dn("CALL FOR HELP\nLeft lateral / all fours\nStop oxytocin\nExclude cord prolapse\nPrepare for emergency C/S")],
]

decel_table = Table(decel_data, colWidths=dcol, repeatRows=1)
decel_table.setStyle(TableStyle([
    ("BACKGROUND",    (0,0),(-1,0),  colors.HexColor("#7C2D12")),
    ("ROWBACKGROUNDS",(0,1),(-1,-1), [LIGHT_GREEN, LIGHT_RED, LIGHT_AMBER, LIGHT_RED]),
    ("GRID",          (0,0),(-1,-1), 0.5, colors.HexColor("#CBD5E1")),
    ("VALIGN",        (0,0),(-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0),(-1,-1), 4),
    ("BOTTOMPADDING", (0,0),(-1,-1), 4),
    ("LEFTPADDING",   (0,0),(-1,-1), 5),
    ("RIGHTPADDING",  (0,0),(-1,-1), 5),
]))
story.append(decel_table)
story.append(Spacer(1, 4*mm))

# ── Bottom row: Variable decel features + In-utero resuscitation + Key terms ──
story.append(section_header("TYPICAL vs ATYPICAL VARIABLE DECELS  |  IN-UTERO RESUSCITATION  |  KEY TERMS", bg=DARK_GREY))
story.append(Spacer(1, 2*mm))

# Left: Typical vs Atypical
tv_col = (PAGE_W - 2*MARGIN - 8*mm) * 0.34
iu_col = (PAGE_W - 2*MARGIN - 8*mm) * 0.30
kt_col = (PAGE_W - 2*MARGIN - 8*mm) * 0.36

def mini_hdr(t, bg, fg=WHITE):
    p = Paragraph(t, S("Normal", fontSize=8, textColor=fg,
                       alignment=TA_CENTER, fontName="Helvetica-Bold"))
    tbl = Table([[p]], colWidths=[tv_col - 4])
    tbl.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(-1,-1), bg),
        ("TOPPADDING",    (0,0),(-1,-1), 3),
        ("BOTTOMPADDING", (0,0),(-1,-1), 3),
        ("ROUNDEDCORNERS",[3]),
    ]))
    return tbl

def item(text, color=DARK_GREY, bold=False):
    fn = "Helvetica-Bold" if bold else "Helvetica"
    return Paragraph(f"• {text}", S("Normal", fontSize=7.5, textColor=color,
                                    alignment=TA_LEFT, fontName=fn, leading=11))

# Typical / Atypical table
tv_data = [
    [mini_hdr("✅  TYPICAL (Reassuring)", GREEN),
     mini_hdr("❌  ATYPICAL (Concerning)", RED)],
    [item("Shoulders present (pre/post)"),
     item("Shoulders absent", RED)],
    [item("Duration < 60 seconds"),
     item("Duration > 60 seconds", RED)],
    [item("Depth < 60 bpm drop"),
     item("Depth > 60 bpm drop", RED)],
    [item("QUICK return to baseline"),
     item("TARDY (slow) return", RED)],
    [item("Normal variability within decel"),
     item("Reduced variability within decel", RED)],
    [item("Returns to same baseline"),
     item("Post-decel tachycardia / rise", RED)],
    [item("< 90 minutes duration total"),
     item("> 90 minutes OR > 50% contractions", RED)],
]
tv_col_w = [(tv_col-4)/2, (tv_col-4)/2]
tv_table = Table(tv_data, colWidths=tv_col_w)
tv_table.setStyle(TableStyle([
    ("GRID",          (0,0),(-1,-1), 0.3, colors.HexColor("#E5E7EB")),
    ("TOPPADDING",    (0,0),(-1,-1), 2),
    ("BOTTOMPADDING", (0,0),(-1,-1), 2),
    ("LEFTPADDING",   (0,0),(-1,-1), 4),
    ("RIGHTPADDING",  (0,0),(-1,-1), 4),
    ("VALIGN",        (0,0),(-1,-1), "TOP"),
    ("BACKGROUND",    (0,0),(0,0),  LIGHT_GREEN),
    ("BACKGROUND",    (1,0),(1,0),  LIGHT_RED),
]))

# In-utero resuscitation
def iu_item(num, text, bg):
    n  = Paragraph(f"<b>{num}</b>", S("Normal", fontSize=9, textColor=WHITE,
                                       alignment=TA_CENTER, fontName="Helvetica-Bold"))
    tx = Paragraph(text, S("Normal", fontSize=7.5, textColor=DARK_GREY,
                            alignment=TA_LEFT, fontName="Helvetica", leading=11))
    t  = Table([[n, tx]], colWidths=[14, iu_col - 22])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0),(0,0), bg),
        ("VALIGN",        (0,0),(-1,-1), "MIDDLE"),
        ("TOPPADDING",    (0,0),(-1,-1), 3),
        ("BOTTOMPADDING", (0,0),(-1,-1), 3),
        ("LEFTPADDING",   (0,0),(-1,-1), 3),
        ("RIGHTPADDING",  (0,0),(-1,-1), 3),
        ("ROUNDEDCORNERS", [2]),
    ]))
    return t

iu_steps = [
    (mini_hdr("IN-UTERO RESUSCITATION (ITUR)", BLUE),),
    (iu_item("1", "CALL for senior / obstetric help immediately", BLUE),),
    (iu_item("2", "LEFT LATERAL position (relieve aortocaval compression)", GREEN),),
    (iu_item("3", "STOP OXYTOCIN – reduce uterine over-activity", RED),),
    (iu_item("4", "IV FLUIDS – correct hypotension (500 mL crystalloid bolus)", BLUE),),
    (iu_item("5", "OXYGEN – face mask 15 L/min (limited evidence, short-term)", MID_GREY),),
    (iu_item("6", "TOCOLYSIS – Terbutaline 0.25 mg SC if hyperstimulation", AMBER),),
    (iu_item("7", "ASSESS RESPONSE – if no improvement → FBS / delivery", RED),),
    (iu_item("8", "EXCLUDE cord prolapse – VE immediately if prolonged decel", DARK_GREY),),
]
iu_table = Table(iu_steps, colWidths=[iu_col])
iu_table.setStyle(TableStyle([
    ("TOPPADDING",    (0,0),(-1,-1), 2),
    ("BOTTOMPADDING", (0,0),(-1,-1), 2),
    ("LEFTPADDING",   (0,0),(-1,-1), 0),
    ("RIGHTPADDING",  (0,0),(-1,-1), 0),
]))

# Key terms
def kt_row(term, definition, tc=DARK_GREEN):
    t = Paragraph(f'<b><font color="{tc}">{term}</font></b>', 
                  S("Normal", fontSize=8, textColor=tc, fontName="Helvetica-Bold", leading=11))
    d = Paragraph(definition, S("Normal", fontSize=7.5, textColor=DARK_GREY,
                                fontName="Helvetica", leading=11))
    return [t, d]

kt_data = [
    [Paragraph("KEY CTG TERMS", S("Normal", fontSize=8, textColor=WHITE,
                                   alignment=TA_CENTER, fontName="Helvetica-Bold")),
     Paragraph("", body_style)],
    *[kt_row(*r) for r in [
        ("BASELINE",         "Average FHR over 10 min, excluding accels/decels. Normal 110–160 bpm."),
        ("VARIABILITY",      "Peak-to-trough fluctuation. Normal 5–25 bpm. Reflects ANS/CNS integrity."),
        ("ACCELERATION",     "≥15 bpm rise for ≥15 sec (>32 wks). Best sign fetus is NOT acidotic."),
        ("CYCLING",          "Alternation of awake (high variability + accels) and sleep (quiet) states. Normal lasts 20–40 min."),
        ("SALTATORY",        "Variability > 25 bpm. Acute fetal stress (cord compression, hyperstimulation)."),
        ("SINUSOIDAL",       "Smooth sine-wave pattern 2–5 cycles/min. EMERGENCY – severe fetal anaemia.", "#B91C1C"),
        ("QUICKLY",          "Fast return to baseline after deceleration nadir. Reassuring feature."),
        ("TARDY",            "Slow/delayed return to baseline. Atypical – suggests reduced fetal reserve.", "#B91C1C"),
        ("SHOULDERS",        "Accelerations flanking a variable deceleration (pre & post). Reassuring = typical variable."),
        ("OFFSHOOTS",        "Biphasic W-pattern or secondary notch in variable decel. Atypical feature.", "#D97706"),
        ("FBS",              "Fetal Blood Sampling: pH ≥7.25 = normal, 7.21–7.24 = borderline, ≤7.20 = deliver."),
        ("ITUR",             "In-Utero Resuscitation: SALO = Stop oxytocin, All-fours/Left lateral, IV fluids, O₂."),
    ]]
]
kt_cw = [kt_col * 0.28, kt_col * 0.72]
kt_table = Table(kt_data, colWidths=kt_cw)
kt_table.setStyle(TableStyle([
    ("BACKGROUND",    (0,0),(1,0), DARK_GREY),
    ("TOPPADDING",    (0,0),(-1,-1), 2),
    ("BOTTOMPADDING", (0,0),(-1,-1), 2),
    ("LEFTPADDING",   (0,0),(-1,-1), 5),
    ("RIGHTPADDING",  (0,0),(-1,-1), 5),
    ("GRID",          (0,0),(-1,-1), 0.3, colors.HexColor("#E5E7EB")),
    ("VALIGN",        (0,0),(-1,-1), "TOP"),
    ("ROWBACKGROUNDS",(0,1),(-1,-1), [LIGHT_GREY, WHITE]),
]))

# Assemble bottom row
bottom_row = Table(
    [[tv_table, Spacer(4,1), iu_table, Spacer(4,1), kt_table]],
    colWidths=[tv_col, 4*mm, iu_col, 4*mm, kt_col]
)
story.append(bottom_row)
story.append(Spacer(1, 3*mm))
story.append(Paragraph(
    "NICE Intrapartum Care Guideline 2022  |  Barash Clinical Anesthesia 9e  |  RCOG Green-top  |  "
    "Prepared by Dr Aayushi Rao, MRCOG ST-2, Max Hospital Delhi  |  July 2026",
    small_style))

# ─── Build ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")
print(f"File size: {os.path.getsize(OUTPUT):,} bytes")
Running Command

cd /tmp/workspace/ctg-flowchart && python3 build_ctg_pdf.py 2>&1

Running Command

python3 -c " from pypdf import PdfReader r = PdfReader('/tmp/workspace/ctg-flowchart/CTG_Interpretation_Flowchart.pdf') print(f'Pages: {len(r.pages)}') print(f'Page 1 size: {r.pages[0].mediabox}') print(f'Page 2 size: {r.pages[1].mediabox}') meta = r.metadata print(f'Title: {meta.title}') print(f'Author: {meta.author}') "

Your print-ready PDF is ready, Dr. Aayushi! 🎉

CTG Interpretation Flowchart

PDF Document · PDF


What's inside (3 pages, A4 landscape):
Page 1 - The Systematic Framework
  • DR C BRAVADO mnemonic banner with colour-coded blocks
  • Feature Classification Table - Baseline, Variability, Accelerations, Decelerations with Reassuring / Non-Reassuring / Abnormal columns
  • CTG Category Decision boxes - Normal (green), Suspicious (amber), Pathological (red) with criteria
Page 2 - Decelerations & Management
  • Deceleration comparison table - Early vs Late vs Variable vs Prolonged with cause, significance and action
  • Typical vs Atypical Variable Decel side-by-side (including QUICKLY vs TARDY)
  • In-Utero Resuscitation 8-step numbered protocol
  • Key Terms glossary - Baseline, Variability, Acceleration, Cycling, Saltatory, Sinusoidal, Quickly, Tardy, Shoulders, Offshoots, FBS, ITUR
Print tips:
  • Print A4 Landscape, borderless for best results
  • Works in colour or greyscale (colour-coded but text labels carry all meaning)
  • Laminate it and stick it in your labour ward pocket folder!
This is a shared conversation. Sign in to Orris to start your own chat.