I am a ST-2 trainee - MRCOG in Max Hospital Delhi. I need you help with OBGYN
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
CTG cardiotocography RCOG NICE 2023 guidelines baseline variability accelerations decelerations cycling
Define Risk → Contractions → Baseline Rate → Acelerations → Variability → Abnormal Decelerations → Overall impression
| Category | Rate |
|---|---|
| Normal | 110-160 bpm |
| Tachycardia | >160 bpm |
| Bradycardia | <110 bpm |
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."
| Category | Amplitude | Meaning |
|---|---|---|
| Normal (moderate) | 5-25 bpm | Healthy autonomic nervous system |
| Reduced/minimal | <5 bpm for >40-50 min | Worrying - possible hypoxia |
| Absent (silent/flat) | Undetectable | Ominous |
| Marked/saltatory | >25 bpm | Acute stress response (sudden cord compression) |
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."

| Feature | Typical (reassuring) | Atypical (concerning) |
|---|---|---|
| Shoulders | Present (accelerations before + after) | Absent |
| Duration | <60 seconds | >60 seconds |
| Depth | <60 bpm below baseline | >60 bpm below baseline |
| Recovery | Sharp return to baseline | Slow return |
| Variability within decel | Normal | Reduced/absent |
| Post-decel | Returns to original baseline | Baseline rise (tachycardia) |
| Feature | Reassuring | Non-reassuring | Abnormal |
|---|---|---|---|
| Baseline | 110-160 bpm | 100-109 or 161-180 bpm | <100 or >180 bpm |
| Variability | 5-25 bpm | <5 bpm for 40-90 min OR >25 bpm for <30 min | <5 bpm for >90 min or sinusoidal |
| Accelerations | Present | Absent (after 45 min) | N/A |
| Decelerations | None / early | Variable with no concerning features <90 min | Late / prolonged / atypical variable |
| Term | Normal/Abnormal | Memory Hook |
|---|---|---|
| Baseline 110-160 | Normal | "110 → 160, all is fine" |
| Variability 5-25 bpm | Normal | "5 to 25 - brain is alive" |
| Accelerations present | Reassuring | "Accels = not acidotic" |
| Cycling (sleep-wake) | Normal | "Cycling = brain is driving" |
| Early decels | Benign | "Early = head squished" |
| Late decels | Ominous | "Late = placenta failing" |
| Variable decels | Common, investigate | "Variable = cord compressed" |
| Shoulders present | Typical, 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" |
| Sinusoidal | Emergency | "Sine wave = needs delivery" |
Normal variability + accelerations = almost certainly NOT acidotic, no matter what else is on the trace.
Absent variability + late decelerations = acidosis until proven otherwise. Act.
Create a visual flowchart PDF for CTG interpretation I can print
ls -la /tmp/workspace/ 2>/dev/null || echo "workspace empty or missing"
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
~/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")
cd /tmp/workspace/ctg-flowchart && python3 build_ctg_pdf.py 2>&1
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}') "
CTG Interpretation Flowchart
PDF Document · PDF