labor
What aspect of 'labor' are you interested in?
| Feature | True Labor | False Labor (Braxton Hicks) |
|---|---|---|
| Contraction rhythm | Regular | Irregular |
| Intervals | Gradually shorten | Unchanged |
| Intensity | Gradually increases | Unchanged |
| Discomfort location | Back AND abdomen | Lower abdomen only |
| Effect of sedation | No relief | Usually relieved |
| Cervical dilation | Yes | No |

Explain in Hinglish language like I never forget of this concept
"Ek baby 9 mahine se ghar ke andar band hai. Ab usse bahar aana hai. Uska journey - woh hai LABOR!"
| TRUE Labor (Asli Visitor) | FALSE Labor (Prank Call) | |
|---|---|---|
| Contractions | Regular - jaise train schedule | Irregular - kabhi aaye kabhi na aaye |
| Interval | Gradually kam hota jaata hai | Same rehta hai |
| Dard | Badhta jaata hai | Same rehta hai |
| Dard kahan | Peeth + Pet dono mein | Sirf neeche pet mein |
| Neend/Sedation se? | Koi asar nahi - asli hai | Relief milti hai - nakli tha |
| Cervix khulti hai? | Haan! ✅ | Nahi ❌ |
"Asli Visitor Regular aata hai, Dono taraf se darwaza bajata hai, aur neend se nahi rukta!"
"Pehli baar SLOW (1.2), Second time FAST (1.5) - Experienced driver tezi chalata hai!"
"Baby aata hai, Placenta uske baad "bye bye" kehke jaata hai - max 60 min ka notice period!"
1. 🎯 ENGAGEMENT - Baby ka head pelvis mein ghusa
2. 🙇 FLEXION - Chin chest pe - chhota dikhne ke liye
3. ⬇️ DESCENT - Neeche utarna
4. 🔄 INTERNAL ROTATION - Andar rotate karna (fit hone ke liye)
5. 🔝 EXTENSION - Head pubic bone ke neeche se nikalta hai
6. 🔃 EXTERNAL ROTATION - Bahar aake shoulders ke saath align karna
"Every Fetus Does Its Excellent Exit" Engagement → Flexion → Descent → Internal rotation → Extension → External rotation
| Pattern | Matlab | Status |
|---|---|---|
| Good variability | Baby active aur healthy | ✅ Reassuring |
| Accelerations | Baby happy | ✅ Reassuring |
| Flat/Poor variability | Baby thaka hua / hypoxia? | ⚠️ Concerning |
| Variable decelerations | Cord compression - umbilical cord dab rahi | ⚠️ Watch |
| Late decelerations | Uteroplacental insufficiency - baby ko oxygen nahi mil raha | 🚨 EMERGENCY |
"Late = Wait mat karo! Late deceleration = placenta fail, baby ko oxygen nahi = TURANT action lo!"
"Station zero = Starting line. Plus mein = Race jeet raha hai baby!"
"Blue matlab 'Baahar aa' - membrane toot gayi!"
LABOR = 3 Acts
ACT 1️⃣ → Darwaza Kholo (Contractions → 10 cm dilation)
Latent + Active phase
1.2 cm/hr (Nulli) | 1.5 cm/hr (Multi)
ACT 2️⃣ → Baby Aao (10 cm → Delivery)
54 min (Nulli) | 20 min (Multi)
6 Cardinal Moves: Every Fetus Does Its Excellent Exit
ACT 3️⃣ → Placenta Jao (Delivery → Placenta out)
15 min usually | 60 min = Retained!
🎯 Final Yaad-Rakhne Wali Line: "Labor ek drama hai - pehle darwaza khula, phir hero aaya, phir set saaf hua. Har act ka time hota hai, aur baby ka dance (cardinal movements) miss mat karna!" 😄
Create a cheat sheet PDF for labor stages & fetal monitoring
ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty"
~/labor-cheatsheet/create_pdf.py
from reportlab.lib.pagesizes import A4
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, TA_RIGHT
from reportlab.platypus import Flowable
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon
from reportlab.graphics import renderPDF
import os
OUTPUT_PATH = "/home/daytona/workspace/labor-cheatsheet/Labor_Cheatsheet.pdf"
os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True)
# ── Colour palette ──────────────────────────────────────────────────────────
C_DARK_BLUE = colors.HexColor("#1A237E")
C_MED_BLUE = colors.HexColor("#3949AB")
C_LIGHT_BLUE = colors.HexColor("#E8EAF6")
C_TEAL = colors.HexColor("#00695C")
C_LIGHT_TEAL = colors.HexColor("#E0F2F1")
C_RED = colors.HexColor("#C62828")
C_LIGHT_RED = colors.HexColor("#FFEBEE")
C_ORANGE = colors.HexColor("#E65100")
C_LIGHT_ORANGE= colors.HexColor("#FFF3E0")
C_YELLOW_BG = colors.HexColor("#FFFDE7")
C_GREEN = colors.HexColor("#2E7D32")
C_LIGHT_GREEN = colors.HexColor("#E8F5E9")
C_PURPLE = colors.HexColor("#6A1B9A")
C_LIGHT_PURPLE= colors.HexColor("#F3E5F5")
C_GREY_BG = colors.HexColor("#F5F5F5")
C_WHITE = colors.white
C_BLACK = colors.black
C_HEADER_BG = colors.HexColor("#0D47A1")
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
TITLE = S("TITLE", fontSize=22, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=2)
SUBTITLE = S("SUBTITLE", fontSize=11, fontName="Helvetica",
textColor=colors.HexColor("#BBDEFB"), alignment=TA_CENTER)
SECTION_HEAD = S("SEC", fontSize=12, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=1)
BODY = S("BODY", fontSize=8.5, fontName="Helvetica",
textColor=C_BLACK, leading=13)
BODY_BOLD = S("BB", fontSize=8.5, fontName="Helvetica-Bold", textColor=C_BLACK)
SMALL = S("SM", fontSize=7.5, fontName="Helvetica", textColor=C_BLACK, leading=11)
SMALL_BOLD = S("SMB", fontSize=7.5, fontName="Helvetica-Bold", textColor=C_BLACK)
CELL = S("CELL", fontSize=8, fontName="Helvetica", textColor=C_BLACK,
alignment=TA_CENTER, leading=11)
CELL_BOLD = S("CELLB", fontSize=8, fontName="Helvetica-Bold", textColor=C_BLACK,
alignment=TA_CENTER)
TRICK = S("TRICK", fontSize=8, fontName="Helvetica-Oblique",
textColor=C_PURPLE, leading=12)
EMOJI_S = S("EMJI", fontSize=9, fontName="Helvetica-Bold", textColor=C_TEAL)
# ── Custom Flowables ─────────────────────────────────────────────────────────
class ColorBox(Flowable):
"""A coloured rounded-corner header box."""
def __init__(self, text, bg=C_DARK_BLUE, fg=C_WHITE,
width=None, height=22, radius=4, fontsize=11):
Flowable.__init__(self)
self.text = text
self.bg = bg
self.fg = fg
self._w = width
self.height = height
self.radius = radius
self.fontsize = fontsize
def wrap(self, avW, avH):
self.width = self._w or avW
return self.width, self.height
def draw(self):
c = self.canv
c.setFillColor(self.bg)
c.roundRect(0, 0, self.width, self.height, self.radius, fill=1, stroke=0)
c.setFillColor(self.fg)
c.setFont("Helvetica-Bold", self.fontsize)
c.drawCentredString(self.width/2, (self.height - self.fontsize)/2 + 1, self.text)
class TrickBox(Flowable):
"""A yellow sticky-note style memory trick box."""
def __init__(self, text, width=None, height=None):
Flowable.__init__(self)
self.text = text
self._w = width
self._h = height
def wrap(self, avW, avH):
self.width = self._w or avW
self.height = self._h or 30
return self.width, self.height
def draw(self):
c = self.canv
c.setFillColor(colors.HexColor("#FFF9C4"))
c.setStrokeColor(colors.HexColor("#F9A825"))
c.setLineWidth(1.2)
c.roundRect(0, 0, self.width, self.height, 5, fill=1, stroke=1)
c.setFillColor(C_PURPLE)
c.setFont("Helvetica-BoldOblique", 7.5)
lines = self.text.split("\n")
y = self.height - 11
for line in lines:
c.drawString(6, y, line)
y -= 11
class StageBar(Flowable):
"""Horizontal 3-stage progress bar."""
def __init__(self, width=None, height=38):
Flowable.__init__(self)
self._w = width
self.height = height
def wrap(self, avW, avH):
self.width = self._w or avW
return self.width, self.height
def draw(self):
c = self.canv
w = self.width
seg = w / 3
colors_list = [C_MED_BLUE, C_TEAL, C_ORANGE]
labels = ["STAGE 1\nContractions → 10cm", "STAGE 2\n10cm → Baby Out", "STAGE 3\nBaby → Placenta Out"]
for i in range(3):
x = i * seg
c.setFillColor(colors_list[i])
c.rect(x + 1, 12, seg - 2, 22, fill=1, stroke=0)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 7)
lines = labels[i].split("\n")
c.drawCentredString(x + seg/2, 26, lines[0])
c.setFont("Helvetica", 6.5)
c.drawCentredString(x + seg/2, 16, lines[1])
# Arrow connectors
for i in range(1, 3):
x = i * seg
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 14)
c.drawCentredString(x, 17, "→")
# Duration line
durations = [
("Primi: ~12h | Multi: ~7h", C_MED_BLUE),
("Primi: 54min | Multi: 20min", C_TEAL),
("~15min (max 60min)", C_ORANGE),
]
c.setFont("Helvetica", 6)
for i, (txt, col) in enumerate(durations):
c.setFillColor(col)
c.drawCentredString(i * seg + seg/2, 5, txt)
class FHRBox(Flowable):
"""FHR pattern visual legend."""
def __init__(self, width=None, height=85):
Flowable.__init__(self)
self._w = width
self.height = height
def wrap(self, avW, avH):
self.width = self._w or avW
return self.width, self.height
def draw(self):
c = self.canv
w = self.width
patterns = [
("Good Variability", "120-160 bpm, wavy line", "REASSURING", C_GREEN, C_LIGHT_GREEN),
("Accelerations", "HR rises >15bpm for >15sec", "REASSURING", C_GREEN, C_LIGHT_GREEN),
("Poor/Flat Variability", "<5 bpm variation - possible hypoxia", "CONCERNING", C_ORANGE, C_LIGHT_ORANGE),
("Variable Decelerations", "Abrupt drop - cord compression", "WATCH", C_ORANGE, C_LIGHT_ORANGE),
("Late Decelerations", "Drop after peak contraction - UTI", "EMERGENCY", C_RED, C_LIGHT_RED),
]
row_h = self.height / len(patterns)
for i, (name, desc, status, col, bg) in enumerate(patterns):
y = self.height - (i + 1) * row_h
# background
c.setFillColor(bg)
c.rect(0, y + 1, w, row_h - 2, fill=1, stroke=0)
# status badge
badge_w = 62
c.setFillColor(col)
c.roundRect(w - badge_w - 2, y + 3, badge_w, row_h - 6, 3, fill=1, stroke=0)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 6.5)
c.drawCentredString(w - badge_w/2 - 2, y + (row_h - 6)/2 + 1, status)
# text
c.setFillColor(col)
c.setFont("Helvetica-Bold", 7.5)
c.drawString(4, y + row_h/2 + 1, name)
c.setFillColor(C_BLACK)
c.setFont("Helvetica", 6.5)
c.drawString(4, y + row_h/2 - 8, desc)
# ── Build document ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=12*mm, rightMargin=12*mm,
topMargin=10*mm, bottomMargin=10*mm,
)
PW = A4[0] - 24*mm # printable width
story = []
# ════════════════════════════════════════════════════════════════════════════
# HEADER BANNER
# ════════════════════════════════════════════════════════════════════════════
class HeaderBanner(Flowable):
def __init__(self, width, height=58):
Flowable.__init__(self)
self.width = width
self.height = height
def wrap(self, avW, avH):
return self.width, self.height
def draw(self):
c = self.canv
# gradient-like background using two rects
c.setFillColor(C_HEADER_BG)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
# decorative circles
c.setFillColor(colors.HexColor("#1565C0"))
c.circle(self.width - 30, self.height - 15, 40, fill=1, stroke=0)
c.circle(20, 8, 28, fill=1, stroke=0)
# Title
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 20)
c.drawCentredString(self.width/2, self.height - 26, "LABOR: Stages & Fetal Monitoring")
c.setFont("Helvetica", 9)
c.setFillColor(colors.HexColor("#BBDEFB"))
c.drawCentredString(self.width/2, self.height - 40,
"Quick Reference Cheat Sheet | Obstetrics | Tintinalli · Creasy & Resnik · Developing Human")
# Tags
tags = ["3 Stages", "Cardinal Movements", "FHR Patterns", "True vs False Labor", "Cervical Assessment"]
tag_w = 74
total = len(tags) * tag_w + (len(tags)-1)*4
start_x = (self.width - total)/2
for j, tag in enumerate(tags):
tx = start_x + j*(tag_w+4)
c.setFillColor(colors.HexColor("#1565C0"))
c.roundRect(tx, 4, tag_w, 13, 3, fill=1, stroke=0)
c.setFillColor(colors.HexColor("#90CAF9"))
c.setFont("Helvetica", 6.5)
c.drawCentredString(tx + tag_w/2, 8, tag)
story.append(HeaderBanner(PW))
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════════════════
# ROW 1 — Stage Bar + True vs False
# ════════════════════════════════════════════════════════════════════════════
story.append(ColorBox("STAGES OF LABOR AT A GLANCE", bg=C_MED_BLUE, height=20))
story.append(Spacer(1, 2*mm))
story.append(StageBar(PW, height=42))
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════════════════
# ROW 2 — Stage details (3 columns)
# ════════════════════════════════════════════════════════════════════════════
def stage_cell(num, color, title, details, duration):
inner = [
[Paragraph(f"<b>STAGE {num}</b>", ParagraphStyle("sh", fontSize=10,
fontName="Helvetica-Bold", textColor=C_WHITE, alignment=TA_CENTER))],
[Paragraph(title, ParagraphStyle("st", fontSize=8, fontName="Helvetica",
textColor=C_WHITE, alignment=TA_CENTER))],
]
for d in details:
inner.append([Paragraph(f"• {d}", ParagraphStyle("sd", fontSize=7.5,
fontName="Helvetica", textColor=C_BLACK, leading=11))])
inner.append([Paragraph(f"<b>Duration:</b> {duration}",
ParagraphStyle("dur", fontSize=7.5, fontName="Helvetica-Bold", textColor=color, leading=11))])
t = Table(inner, colWidths=[(PW/3 - 4*mm)])
hdr_rows = 2
ts = TableStyle([
("BACKGROUND", (0,0), (-1,hdr_rows-1), color),
("BACKGROUND", (0,hdr_rows), (-1,-1), C_GREY_BG),
("BOX", (0,0), (-1,-1), 0.8, color),
("ROWBACKGROUNDS", (0,hdr_rows), (-1,-2), [C_WHITE, C_GREY_BG]),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
])
t.setStyle(ts)
return t
s1 = stage_cell(1, C_MED_BLUE, "Onset contractions → 10 cm dilation",
["Latent phase: irregular contractions, cervix softens + effaces",
"Active phase: starts at 3-4 cm dilation",
"Nullipara rate: 1.2 cm/hr",
"Multipara rate: 1.5 cm/hr",
"Avg total: Primi 12h | Multi 7h"],
"Primi ~12h | Multi ~7h")
s2 = stage_cell(2, C_TEAL, "Full dilation (10 cm) → Baby delivered",
["Push ONLY after full 10 cm (avoids lacerations)",
"6 Cardinal Movements (see below)",
"Station at +3 = scalp visible, delivery imminent",
"Mean: Nullipara 54 min | Multipara 20 min",
"Fetal descent follows cardinal movements"],
"Nulli 54 min | Multi 20 min")
s3 = stage_cell(3, C_ORANGE, "Baby out → Placenta delivered",
["Uterus retracts → hematoma forms → placenta separates",
"Myometrial contractions clamp spiral arteries",
"90% complete within 15 minutes",
"Intervene if not out by 30 min",
"Retained placenta = not out by 60 min"],
"~15 min (max 60 min)")
row2 = Table([[s1, s2, s3]],
colWidths=[PW/3 - 2*mm, PW/3 - 2*mm, PW/3 - 2*mm])
row2.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
]))
story.append(row2)
story.append(Spacer(1, 2*mm))
story.append(TrickBox(
'Memory Trick: "Darwaza Kholo (S1) → Hero Aao (S2) → Set Saaf Karo (S3)"\n'
'Rates: "Experienced driver tezi chalata hai" — Multi (1.5) > Nulli (1.2) cm/hr',
height=26
))
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════════════════
# ROW 3 — True vs False Labor + Cervical Assessment
# ════════════════════════════════════════════════════════════════════════════
left_col_w = PW * 0.48
right_col_w = PW * 0.48
gap = PW - left_col_w - right_col_w
# TRUE vs FALSE
tvf_head = [
[Paragraph("<b>FEATURE</b>", CELL_BOLD),
Paragraph("<b>TRUE LABOR ✓</b>", ParagraphStyle("th", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph("<b>FALSE LABOR ✗</b>", ParagraphStyle("fh", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER))],
]
tvf_rows = [
["Rhythm", "Regular", "Irregular"],
["Interval", "Gradually shortens", "Unchanged"],
["Intensity", "Gradually increases", "Unchanged"],
["Pain location", "Back + Abdomen", "Lower abdomen only"],
["Sedation effect", "No relief", "Usually relieved"],
["Cervical change", "YES ✓", "NO ✗"],
]
tvf_data = tvf_head + [[Paragraph(r[0], SMALL_BOLD),
Paragraph(r[1], SMALL),
Paragraph(r[2], SMALL)] for r in tvf_rows]
tvf_w = [left_col_w*0.36, left_col_w*0.34, left_col_w*0.30]
tvf_t = Table(tvf_data, colWidths=tvf_w)
tvf_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("BACKGROUND", (1,1), (1,-1), C_LIGHT_GREEN),
("BACKGROUND", (2,1), (2,-1), C_LIGHT_RED),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("ROWBACKGROUNDS", (0,1), (0,-1), [C_WHITE, C_GREY_BG]),
("BOX", (0,0), (-1,-1), 0.5, C_DARK_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("ALIGN", (1,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
# CERVICAL ASSESSMENT
cerv_data = [
[Paragraph("<b>CERVICAL ASSESSMENT</b>", ParagraphStyle("ch", fontSize=8.5,
fontName="Helvetica-Bold", textColor=C_WHITE, alignment=TA_CENTER))],
[Paragraph("<b>Dilation:</b> Diameter of internal os (0 → 10 cm = full)\n"
"<b>Effacement:</b> Cervical thinning expressed as %\n"
"<b>Station:</b> Presenting part relative to ischial spines", SMALL)],
]
station_rows = [
["-3 / -2 / -1", "Above spines", "NOT YET"],
["0", "At spines", "ENGAGED"],
["+1 / +2", "Below spines", "DESCENDING"],
["+3", "Scalp visible","DELIVERY IMMINENT!"],
]
station_data = [
[Paragraph("<b>Station</b>", SMALL_BOLD),
Paragraph("<b>Position</b>", SMALL_BOLD),
Paragraph("<b>Meaning</b>", SMALL_BOLD)]
] + [[Paragraph(r[0], SMALL), Paragraph(r[1], SMALL),
Paragraph(r[2], ParagraphStyle("sm2", fontSize=7.5, fontName="Helvetica-Bold",
textColor=C_RED if r[2].startswith("DELIVERY") else C_TEAL))]
for r in station_rows]
sw = [right_col_w*0.25, right_col_w*0.40, right_col_w*0.35]
station_t = Table(station_data, colWidths=sw)
station_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_TEAL),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_WHITE, C_LIGHT_TEAL]),
("BOX", (0,0), (-1,-1), 0.5, C_TEAL),
("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
# Membrane rupture mini box
mem_data = [
[Paragraph("<b>MEMBRANE RUPTURE TEST</b>", ParagraphStyle("mh", fontSize=8,
fontName="Helvetica-Bold", textColor=C_WHITE, alignment=TA_CENTER))],
[Paragraph(
"<b>Nitrazine paper:</b><br/>"
"• Amniotic fluid pH 7.0-7.4 → <b>BLUE</b> (ruptured ✓)<br/>"
"• Vaginal fluid pH 4.5-5.5 → <b>Yellow</b> (intact)<br/>"
"False + : blood, lubricant, Trichomonas, semen<br/>"
"<b>Ferning:</b> NaCl crystals on dried fluid under microscope", SMALL)],
]
mem_t = Table(mem_data, colWidths=[right_col_w])
mem_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_TEAL),
("BACKGROUND", (0,1), (-1,-1), C_LIGHT_TEAL),
("BOX", (0,0), (-1,-1), 0.5, C_TEAL),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
right_inner = Table([
[station_t],
[Spacer(1, 2*mm)],
[mem_t],
], colWidths=[right_col_w])
right_inner.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
# Left: TRUE vs FALSE with header
tvf_title = Table(
[[Paragraph("<b>TRUE vs FALSE LABOR</b>", ParagraphStyle("tvfh", fontSize=8.5,
fontName="Helvetica-Bold", textColor=C_WHITE, alignment=TA_CENTER))]],
colWidths=[left_col_w]
)
tvf_title.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
left_inner = Table([[tvf_title], [tvf_t]], colWidths=[left_col_w])
left_inner.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
row3 = Table([[left_inner, Spacer(gap, 1), right_inner]],
colWidths=[left_col_w, gap, right_col_w])
row3.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(row3)
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════════════════
# ROW 4 — Cardinal Movements + FHR Monitoring
# ════════════════════════════════════════════════════════════════════════════
card_w = PW * 0.42
fhr_w = PW * 0.54
gap2 = PW - card_w - fhr_w
# Cardinal Movements
card_data = [
[Paragraph("<b>6 CARDINAL MOVEMENTS OF LABOR</b>",
ParagraphStyle("cmh", fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER))],
]
moves = [
("1", "ENGAGEMENT", "Widest head diameter below pelvic inlet", "E"),
("2", "FLEXION", "Chin on chest → smallest head diameter presented", "F"),
("3", "DESCENT", "Downward progress through birth canal", "D"),
("4", "INTERNAL ROTATION", "Head rotates to align with AP pelvic outlet", "I"),
("5", "EXTENSION", "Head extends under pubic symphysis", "E"),
("6", "EXTERNAL ROTATION", "Head rotates to align with shoulders post-delivery","E"),
]
for num, name, desc, _ in moves:
card_data.append([
Table([[
Paragraph(f"<b>{num}</b>",
ParagraphStyle("mn", fontSize=9, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph(f"<b>{name}</b><br/><font size=7>{desc}</font>",
ParagraphStyle("md", fontSize=7.5, fontName="Helvetica",
textColor=C_BLACK, leading=10))
]], colWidths=[14*mm, card_w - 14*mm - 10*mm])
])
trick_row = [Paragraph(
'<b>Mnemonic: "Every Fetus Does Its Excellent Exit"</b><br/>'
'E-ngagement F-lexion D-escent I-nternal E-xtension E-xternal',
ParagraphStyle("mn2", fontSize=7, fontName="Helvetica-BoldOblique",
textColor=C_PURPLE, leading=10)
)]
card_data.append([trick_row])
card_t = Table(card_data, colWidths=[card_w])
num_rows = len(card_data)
bg_colors = [C_MED_BLUE, C_TEAL, C_MED_BLUE, C_TEAL, C_MED_BLUE, C_TEAL, C_MED_BLUE]
ts2 = [
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("BACKGROUND", (0,-1), (-1,-1), colors.HexColor("#FFF9C4")),
("BOX", (0,0), (-1,-1), 0.5, C_DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
]
for i, col in enumerate(bg_colors[:len(moves)]):
ts2.append(("BACKGROUND", (0, i+1), (-1, i+1),
C_LIGHT_BLUE if i % 2 == 0 else C_LIGHT_TEAL))
card_t.setStyle(TableStyle(ts2))
# FHR Monitoring
fhr_title = Table(
[[Paragraph("<b>FETAL HEART RATE MONITORING</b>",
ParagraphStyle("fhrh", fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER))]],
colWidths=[fhr_w]
)
fhr_title.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_RED),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
normal_row = Table([[
Paragraph("<b>Normal FHR: 120-160 bpm</b>",
ParagraphStyle("nr", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph("<b>Bradycardia: <110 bpm</b>",
ParagraphStyle("br", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph("<b>Tachycardia: >160 bpm</b>",
ParagraphStyle("tr", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
]], colWidths=[fhr_w/3]*3)
normal_row.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), C_GREEN),
("BACKGROUND", (1,0), (1,0), C_ORANGE),
("BACKGROUND", (2,0), (2,0), C_RED),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("INNERGRID", (0,0), (-1,-1), 0.5, C_WHITE),
]))
patterns = [
("Good Variability", "120-160 bpm, wavy line, beat-to-beat variation", "REASSURING", C_GREEN, C_LIGHT_GREEN),
("Accelerations", "HR rises >15 bpm for >15 sec above baseline", "REASSURING", C_GREEN, C_LIGHT_GREEN),
("Poor/Flat Variability", "Variation <5 bpm — possible fetal hypoxia or sleep cycle", "CONCERNING", C_ORANGE, C_LIGHT_ORANGE),
("Variable Decelerations", "Abrupt drops unrelated to contractions — cord compression", "WATCH CLOSELY", C_ORANGE, C_LIGHT_ORANGE),
("Late Decelerations", "Drop AFTER contraction peak — uteroplacental insufficiency", "EMERGENCY!", C_RED, C_LIGHT_RED),
]
pat_data = [
[Paragraph("<b>Pattern</b>", SMALL_BOLD),
Paragraph("<b>Description</b>", SMALL_BOLD),
Paragraph("<b>Status</b>", SMALL_BOLD)]
]
for name, desc, status, col, bg in patterns:
pat_data.append([
Paragraph(f"<b>{name}</b>", ParagraphStyle("pn", fontSize=7.5,
fontName="Helvetica-Bold", textColor=col)),
Paragraph(desc, SMALL),
Paragraph(f"<b>{status}</b>", ParagraphStyle("ps", fontSize=7,
fontName="Helvetica-Bold", textColor=col, alignment=TA_CENTER)),
])
pat_t = Table(pat_data, colWidths=[fhr_w*0.30, fhr_w*0.48, fhr_w*0.22])
pat_ts = [
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#880E4F")),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BOX", (0,0), (-1,-1), 0.5, C_RED),
("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
for i, (_, _, _, col, bg) in enumerate(patterns):
pat_ts.append(("BACKGROUND", (0, i+1), (-1, i+1), bg))
pat_t.setStyle(TableStyle(pat_ts))
# Distress action box
action_data = [[
Paragraph("<b>FETAL DISTRESS ACTION (LEFT OIC):</b> "
"<b>L</b>eft lateral position | <b>O</b>xygen | <b>I</b>V fluids | <b>C</b>onsult OB",
ParagraphStyle("act", fontSize=8, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER))
]]
action_t = Table(action_data, colWidths=[fhr_w])
action_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
trick_fhr = Table([[
Paragraph('<b>Trick: "Late = Deri mat karo! Uteroplacental failure = TURANT action!"</b>',
ParagraphStyle("tf", fontSize=7.5, fontName="Helvetica-BoldOblique",
textColor=C_PURPLE))
]], colWidths=[fhr_w])
trick_fhr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FFF9C4")),
("BOX", (0,0), (-1,-1), 0.8, colors.HexColor("#F9A825")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
fhr_block = Table([
[fhr_title],
[normal_row],
[pat_t],
[action_t],
[Spacer(1,2)],
[trick_fhr],
], colWidths=[fhr_w])
fhr_block.setStyle(TableStyle([
("TOPPADDING", (0,0), (-1,-1), 1),
("BOTTOMPADDING", (0,0), (-1,-1), 1),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
row4 = Table([[card_t, Spacer(gap2, 1), fhr_block]],
colWidths=[card_w, gap2, fhr_w])
row4.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(row4)
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════════════════
# ROW 5 — Post-delivery + Apgar + Footer
# ════════════════════════════════════════════════════════════════════════════
post_w = PW * 0.48
apgar_w = PW * 0.48
gap3 = PW - post_w - apgar_w
post_items = [
("Cord Clamping", "Delay AT LEAST 1-3 min after birth (increases neonatal iron)"),
("Suction", "Do NOT routinely suction nose/mouth (causes bradycardia)"),
("Warmth", "Keep infant warm, provide gentle stimulation"),
("Episiotomy", "NOT routine — only for fetal distress, shoulder dystocia, forceps"),
("Pushing", "Only push after FULL 10 cm dilation"),
("Apgar", "Score at 1 min AND 5 min post-delivery"),
]
post_data = [[Paragraph("<b>POST-DELIVERY ESSENTIALS</b>",
ParagraphStyle("pdh", fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER))]]
for item, desc in post_items:
post_data.append([Paragraph(f"<b>{item}:</b> {desc}", SMALL)])
post_t = Table(post_data, colWidths=[post_w])
post_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_PURPLE),
("BACKGROUND", (0,1), (-1,-1), C_LIGHT_PURPLE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_PURPLE, C_WHITE]),
("BOX", (0,0), (-1,-1), 0.5, C_PURPLE),
("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
apgar_rows = [
["Color", "Pink all over", "Pink body, blue extremities", "Blue/pale all over"],
["Tone", "Active motion", "Some flexion", "Limp"],
["Heart Rate", ">100 bpm", "<100 bpm", "Absent"],
["Respiratory Effort","Strong cry", "Weak/irregular", "Absent"],
["Reflex/Grimace", "Cries/coughs", "Grimace only", "No response"],
]
apgar_data = [
[Paragraph("<b>APGAR SCORE</b>",
ParagraphStyle("ah", fontSize=8.5, fontName="Helvetica-Bold",
textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph(""), Paragraph(""), Paragraph("")],
[Paragraph("<b>Sign</b>", CELL_BOLD),
Paragraph("<b>2</b>", ParagraphStyle("s2", fontSize=8, fontName="Helvetica-Bold",
textColor=C_GREEN, alignment=TA_CENTER)),
Paragraph("<b>1</b>", ParagraphStyle("s1", fontSize=8, fontName="Helvetica-Bold",
textColor=C_ORANGE, alignment=TA_CENTER)),
Paragraph("<b>0</b>", ParagraphStyle("s0", fontSize=8, fontName="Helvetica-Bold",
textColor=C_RED, alignment=TA_CENTER))],
]
for row in apgar_rows:
apgar_data.append([Paragraph(row[0], SMALL_BOLD)] +
[Paragraph(v, SMALL) for v in row[1:]])
apgar_data.append([
Paragraph("<b>Score 7-10</b> = Healthy ✓ | "
"<b>Score <7</b> = Needs intervention | "
"<b>HR <100</b> = Positive pressure ventilation",
ParagraphStyle("as", fontSize=7, fontName="Helvetica-Bold",
textColor=C_BLACK, alignment=TA_CENTER)),
Paragraph(""), Paragraph(""), Paragraph(""),
])
aw = [apgar_w*0.30, apgar_w*0.24, apgar_w*0.24, apgar_w*0.22]
apgar_t = Table(apgar_data, colWidths=aw)
apgar_t.setStyle(TableStyle([
("SPAN", (0,0), (-1,0)),
("SPAN", (0,-1), (-1,-1)),
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#4A148C")),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#7B1FA2")),
("TEXTCOLOR", (0,0), (-1,1), C_WHITE),
("BACKGROUND", (1,2), (1,-2), C_LIGHT_GREEN),
("BACKGROUND", (2,2), (2,-2), C_LIGHT_ORANGE),
("BACKGROUND", (3,2), (3,-2), C_LIGHT_RED),
("BACKGROUND", (0,-1), (-1,-1), colors.HexColor("#EDE7F6")),
("ROWBACKGROUNDS", (0,2), (0,-2), [C_WHITE, C_GREY_BG]),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#4A148C")),
("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("ALIGN", (1,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
row5 = Table([[post_t, Spacer(gap3, 1), apgar_t]],
colWidths=[post_w, gap3, apgar_w])
row5.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(row5)
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════════════════════════════════
footer_data = [[
Paragraph("Sources: Tintinalli's Emergency Medicine | Creasy & Resnik's Maternal-Fetal Medicine | The Developing Human | Textbook of Family Medicine 9e",
ParagraphStyle("ft", fontSize=6.5, fontName="Helvetica", textColor=C_WHITE, alignment=TA_CENTER)),
Paragraph("Orris Medical Education | For Study Purposes Only",
ParagraphStyle("ft2", fontSize=6.5, fontName="Helvetica-Bold", textColor=colors.HexColor("#BBDEFB"), alignment=TA_RIGHT)),
]]
footer_t = Table(footer_data, colWidths=[PW*0.72, PW*0.28])
footer_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK_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),
("ROUNDEDCORNERS", [4]),
]))
story.append(footer_t)
# ── Build ─────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT_PATH}")
cd /home/daytona/workspace/labor-cheatsheet && python create_pdf.py 2>&1
ls -lh /home/daytona/workspace/labor-cheatsheet/Labor_Cheatsheet.pdf
cd /home/daytona/workspace/labor-cheatsheet && python -c " from pypdf import PdfReader r = PdfReader('Labor_Cheatsheet.pdf') print(f'Pages: {len(r.pages)}') print(f'Page size: {r.pages[0].mediabox}') text = r.pages[0].extract_text() print(f'Text chars: {len(text)}') print('First 300 chars:', text[:300]) "
~/labor-cheatsheet/create_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/labor-cheatsheet/create_pdf.py'
~/labor-cheatsheet/create_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/labor-cheatsheet/create_pdf.py'
~/labor-cheatsheet/create_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/labor-cheatsheet/create_pdf.py'
cd /home/daytona/workspace/labor-cheatsheet && python -c " content = open('create_pdf.py').read() # Reduce spacers content = content.replace('story.append(Spacer(1, 4*mm))', 'story.append(Spacer(1, 2*mm))') content = content.replace('story.append(Spacer(1, 3*mm))', 'story.append(Spacer(1, 2*mm))') content = content.replace('story.append(Spacer(1, 2*mm))', 'story.append(Spacer(1, 1*mm))') # Reduce stage bar height content = content.replace('story.append(StageBar(PW, height=42))', 'story.append(StageBar(PW, height=36))') # Reduce trick box height content = content.replace('TrickBox(\n \'Memory Trick:', 'TrickBox(\n \'Memory Trick:') open('create_pdf.py', 'w').write(content) print('done') "
cd /home/daytona/workspace/labor-cheatsheet && python create_pdf.py 2>&1
cd /home/daytona/workspace/labor-cheatsheet && python -c " from pypdf import PdfReader r = PdfReader('Labor_Cheatsheet.pdf') print(f'Pages: {len(r.pages)}') import os; print(f'Size: {os.path.getsize(\"Labor_Cheatsheet.pdf\")/1024:.1f} KB') "
cd /home/daytona/workspace/labor-cheatsheet && python -c " from pypdf import PdfReader r = PdfReader('Labor_Cheatsheet.pdf') print('PAGE 2 TEXT:') print(r.pages[1].extract_text()[:800]) "
cd /home/daytona/workspace/labor-cheatsheet && python -c " content = open('create_pdf.py').read() # Reduce all TOPPADDING/BOTTOMPADDING from 3 to 2 content = content.replace('(\"TOPPADDING\", (0,0), (-1,-1), 3)', '(\"TOPPADDING\", (0,0), (-1,-1), 2)') content = content.replace('(\"BOTTOMPADDING\", (0,0), (-1,-1), 3)', '(\"BOTTOMPADDING\", (0,0), (-1,-1), 2)') # reduce 5-padding to 3 content = content.replace('(\"TOPPADDING\", (0,0), (-1,-1), 5)', '(\"TOPPADDING\", (0,0), (-1,-1), 3)') content = content.replace('(\"BOTTOMPADDING\", (0,0), (-1,-1), 5)', '(\"BOTTOMPADDING\", (0,0), (-1,-1), 3)') # reduce 4-padding to 2 content = content.replace('(\"TOPPADDING\", (0,0), (-1,-1), 4)', '(\"TOPPADDING\", (0,0), (-1,-1), 2)') content = content.replace('(\"BOTTOMPADDING\", (0,0), (-1,-1), 4)', '(\"BOTTOMPADDING\", (0,0), (-1,-1), 2)') open('create_pdf.py', 'w').write(content) print('done') "
cd /home/daytona/workspace/labor-cheatsheet && python create_pdf.py 2>&1 && python -c " from pypdf import PdfReader; import os r = PdfReader('Labor_Cheatsheet.pdf') print(f'Pages: {len(r.pages)}, Size: {os.path.getsize(\"Labor_Cheatsheet.pdf\")/1024:.1f} KB') "
cd /home/daytona/workspace/labor-cheatsheet && python -c " from pypdf import PdfReader; import os r = PdfReader('Labor_Cheatsheet.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): t = p.extract_text() print(f'--- Page {i+1} ({len(t)} chars) ---') print(t[:200]) print('...') print(t[-100:]) print() "
Labor Cheatsheet
PDF Document · PDF