Today the first topic for your teaching session is “Approach to the patient with postmenopausal uterine bleeding”. I have uploaded the guidelines pdf for your reference. Please be my gynobs professor and create a comprehensive clinical notes, in a pdf format. Please create it as how, you would have taught me this if you were in the real world ward. Before teaching each concept ask a question to make me think clinically. Also add pitfalls, mistakes which are more likely to be done by junior doctors. Also make a chart of never miss these things. I want to become a good clinician. Please help me with the knowledge part so that I’ll apply it into clinical practice and excel as a doctor.
cd "/tmp/workspace/attachments/5a257ba1-f94e-4264-a51b-146138f35a45/" && python3 -c " import pdfplumber files = [ 'Approach to the patient with postmenopausal uterine bleeding.pdf', 'Causes of abnormal genital tract bleeding in females.pdf', 'Medications and other substances that may increase the risk of bleeding or bruis.pdf' ] for f in files: print(f'\\n\\n========== FILE: {f} ==========') with pdfplumber.open(f) as pdf: print(f'Total pages: {len(pdf.pages)}') for i, page in enumerate(pdf.pages): text = page.extract_text() if text: print(f'--- Page {i+1} ---') print(text) "
mkdir -p /tmp/workspace/pmb-clinical-notes && ls /tmp/workspace/pmb-clinical-notes/
~/pmb-clinical-notes/build_notes.py
"""
Comprehensive Clinical Teaching Notes: Approach to Postmenopausal Uterine Bleeding
Built with ReportLab
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.colors import HexColor
import os
# ─── COLOUR PALETTE ────────────────────────────────────────────────────────────
DARK_NAVY = HexColor("#1A2744")
MED_BLUE = HexColor("#2563EB")
LIGHT_BLUE = HexColor("#DBEAFE")
ACCENT_TEAL = HexColor("#0D9488")
TEAL_LIGHT = HexColor("#CCFBF1")
DANGER_RED = HexColor("#DC2626")
DANGER_LIGHT = HexColor("#FEE2E2")
WARN_ORANGE = HexColor("#D97706")
WARN_LIGHT = HexColor("#FEF3C7")
GREEN = HexColor("#16A34A")
GREEN_LIGHT = HexColor("#DCFCE7")
PURPLE = HexColor("#7C3AED")
PURPLE_LIGHT = HexColor("#EDE9FE")
GREY_BG = HexColor("#F1F5F9")
GREY_DARK = HexColor("#374151")
GREY_MID = HexColor("#6B7280")
WHITE = colors.white
BLACK = colors.black
PINK_LIGHT = HexColor("#FCE7F3")
PINK_DARK = HexColor("#BE185D")
OUTPUT_PATH = "/tmp/workspace/pmb-clinical-notes/PMB_Clinical_Notes.pdf"
# ─── STYLES ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kwargs):
s = ParagraphStyle(name, parent=styles[parent], **kwargs)
return s
# Main styles
H1 = make_style("H1", fontSize=22, textColor=WHITE, spaceAfter=6, spaceBefore=6,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=28)
H2 = make_style("H2", fontSize=16, textColor=DARK_NAVY, spaceAfter=4, spaceBefore=10,
fontName="Helvetica-Bold", leading=20)
H3 = make_style("H3", fontSize=13, textColor=MED_BLUE, spaceAfter=3, spaceBefore=8,
fontName="Helvetica-Bold", leading=17)
H4 = make_style("H4", fontSize=11, textColor=DARK_NAVY, spaceAfter=2, spaceBefore=5,
fontName="Helvetica-Bold", leading=14)
BODY = make_style("Body", fontSize=10, textColor=GREY_DARK, spaceAfter=4, spaceBefore=2,
fontName="Helvetica", leading=14, alignment=TA_JUSTIFY)
BODY_BOLD = make_style("BodyBold", fontSize=10, textColor=GREY_DARK, spaceAfter=4,
fontName="Helvetica-Bold", leading=14)
SMALL = make_style("Small", fontSize=8.5, textColor=GREY_MID, spaceAfter=2,
fontName="Helvetica", leading=11)
BULLET = make_style("Bullet", fontSize=10, textColor=GREY_DARK, spaceAfter=3,
fontName="Helvetica", leading=13, leftIndent=14, bulletIndent=4)
BULLET_BOLD = make_style("BulletBold", fontSize=10, textColor=DARK_NAVY, spaceAfter=3,
fontName="Helvetica-Bold", leading=13, leftIndent=14, bulletIndent=4)
Q_STYLE = make_style("Question", fontSize=11, textColor=DARK_NAVY, spaceAfter=4,
fontName="Helvetica-BoldOblique", leading=15, leftIndent=10)
ITALIC = make_style("Italic", fontSize=10, textColor=GREY_MID, spaceAfter=3,
fontName="Helvetica-Oblique", leading=13)
ALERT_STYLE = make_style("Alert", fontSize=10, textColor=DANGER_RED, spaceAfter=3,
fontName="Helvetica-Bold", leading=13, leftIndent=10)
PEARL_STYLE = make_style("Pearl", fontSize=10, textColor=GREEN, spaceAfter=3,
fontName="Helvetica-Bold", leading=13, leftIndent=10)
CAPTION = make_style("Caption", fontSize=9, textColor=GREY_MID, spaceAfter=2,
fontName="Helvetica-Oblique", leading=11, alignment=TA_CENTER)
TOCENT = make_style("TocEntry", fontSize=11, textColor=DARK_NAVY, spaceAfter=6,
fontName="Helvetica", leading=15, leftIndent=10)
TOCENT_BOLD = make_style("TocEntryBold", fontSize=12, textColor=MED_BLUE, spaceAfter=4,
fontName="Helvetica-Bold", leading=16)
# ─── HELPER FLOWABLES ──────────────────────────────────────────────────────────
def hr(color=LIGHT_BLUE, thickness=1.2):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=4, spaceBefore=4)
def sp(h=6):
return Spacer(1, h)
def colored_box(content_items, bg=LIGHT_BLUE, border=MED_BLUE, padding=8):
"""Wrap flowables in a coloured rounded table cell."""
inner = [item for item in content_items]
tbl = Table([[inner]], colWidths=["100%"])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1.2, border),
("LEFTPADDING", (0,0), (-1,-1), padding),
("RIGHTPADDING", (0,0), (-1,-1), padding),
("TOPPADDING", (0,0), (-1,-1), padding),
("BOTTOMPADDING", (0,0), (-1,-1), padding),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
]))
return tbl
def section_header(title, subtitle=None, color=DARK_NAVY, bg=MED_BLUE):
data = [[Paragraph(title, H1)]]
if subtitle:
data.append([Paragraph(subtitle, make_style("SH_sub", fontSize=11,
textColor=LIGHT_BLUE, fontName="Helvetica-Oblique",
alignment=TA_CENTER, leading=14))])
tbl = Table(data, colWidths=["100%"])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
]))
return tbl
def question_box(text):
"""Clinical question box."""
q_inner = [
Paragraph("CLINICAL QUESTION — Think Before Reading On!", make_style(
"QH", fontSize=9, textColor=PURPLE, fontName="Helvetica-Bold", leading=12)),
sp(3),
Paragraph(text, Q_STYLE),
]
return colored_box(q_inner, bg=PURPLE_LIGHT, border=PURPLE, padding=10)
def pitfall_box(items):
"""Red pitfall box for junior doctor mistakes."""
inner = [
Paragraph("JUNIOR DOCTOR PITFALLS", make_style(
"PFH", fontSize=9, textColor=DANGER_RED, fontName="Helvetica-Bold", leading=12)),
sp(3),
]
for item in items:
inner.append(Paragraph(f"\u26a0 {item}", ALERT_STYLE))
return colored_box(inner, bg=DANGER_LIGHT, border=DANGER_RED, padding=10)
def pearl_box(items):
"""Green clinical pearl box."""
inner = [
Paragraph("CLINICAL PEARLS", make_style(
"PPH", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold", leading=12)),
sp(3),
]
for item in items:
inner.append(Paragraph(f"\u2713 {item}", PEARL_STYLE))
return colored_box(inner, bg=GREEN_LIGHT, border=GREEN, padding=10)
def key_point_box(items):
"""Orange key points box."""
inner = [
Paragraph("KEY POINTS", make_style(
"KPH", fontSize=9, textColor=WARN_ORANGE, fontName="Helvetica-Bold", leading=12)),
sp(3),
]
for item in items:
inner.append(Paragraph(f"\u2022 {item}", make_style(
"KPB", fontSize=10, textColor=GREY_DARK, fontName="Helvetica",
leading=13, leftIndent=10, spaceAfter=2)))
return colored_box(inner, bg=WARN_LIGHT, border=WARN_ORANGE, padding=10)
def definition_box(term, defn):
inner = [
Paragraph(f"<b>DEFINITION:</b> {term}", make_style(
"DefT", fontSize=10, textColor=ACCENT_TEAL, fontName="Helvetica-Bold", leading=13)),
sp(3),
Paragraph(defn, make_style("DefB", fontSize=10, textColor=GREY_DARK,
fontName="Helvetica", leading=13)),
]
return colored_box(inner, bg=TEAL_LIGHT, border=ACCENT_TEAL, padding=10)
# ─── HEADER / FOOTER ───────────────────────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
W, H = A4
# Header bar
canvas.setFillColor(DARK_NAVY)
canvas.rect(0, H - 28, W, 28, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 9)
canvas.drawString(15, H - 18, "CLINICAL TEACHING NOTES | GynObs Ward")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(W - 15, H - 18, "Postmenopausal Uterine Bleeding")
# Footer
canvas.setFillColor(GREY_BG)
canvas.rect(0, 0, W, 22, fill=1, stroke=0)
canvas.setFillColor(GREY_MID)
canvas.setFont("Helvetica", 7.5)
canvas.drawString(15, 7, "Based on UpToDate 2026 Guidelines | Berek & Novak's Gynecology | Robbins Pathology")
canvas.drawRightString(W - 15, 7, f"Page {doc.page}")
canvas.restoreState()
# ─── MAIN BUILD ────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT_PATH, pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=1.8*cm, bottomMargin=1.8*cm,
onFirstPage=on_page, onLaterPages=on_page
)
story = []
# ══════════════════════════════════════════════════════════════════════════════
# COVER PAGE
# ══════════════════════════════════════════════════════════════════════════════
# Title block
cover_data = [[
Paragraph("CLINICAL TEACHING NOTES", make_style("CVT", fontSize=13,
textColor=LIGHT_BLUE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=16)),
Paragraph("GynObs Ward Round Series", make_style("CVS", fontSize=10,
textColor=HexColor("#93C5FD"), fontName="Helvetica-Oblique",
alignment=TA_CENTER, leading=13)),
]]
# flatten
cover_tbl = Table([[Paragraph("CLINICAL TEACHING NOTES", make_style(
"CVT2", fontSize=14, textColor=LIGHT_BLUE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=18))]],colWidths=["100%"])
cover_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), DARK_NAVY),
("TOPPADDING", (0,0),(-1,-1), 16),
("BOTTOMPADDING", (0,0),(-1,-1), 16),
]))
story.append(cover_tbl)
story.append(sp(8))
# Big title
title_data = [[
Paragraph("Approach to the Patient with", make_style("TT1", fontSize=20,
textColor=DARK_NAVY, fontName="Helvetica", alignment=TA_CENTER, leading=25)),
Paragraph("Postmenopausal Uterine Bleeding", make_style("TT2", fontSize=26,
textColor=MED_BLUE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=32)),
Paragraph("PMB", make_style("TT3", fontSize=48, textColor=LIGHT_BLUE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=56)),
]]
title_tbl = Table([[
Paragraph("Approach to the Patient with<br/><font size=26 color='#2563EB'><b>Postmenopausal Uterine Bleeding</b></font>",
make_style("TTM", fontSize=20, textColor=DARK_NAVY, fontName="Helvetica",
alignment=TA_CENTER, leading=36))
]], colWidths=["100%"])
title_tbl.setStyle(TableStyle([
("TOPPADDING",(0,0),(-1,-1),20),
("BOTTOMPADDING",(0,0),(-1,-1),20),
]))
story.append(title_tbl)
story.append(sp(6))
# Subtitle info table
info_rows = [
["Subject", "Obstetrics & Gynaecology"],
["Level", "Junior Doctor / Postgraduate"],
["Based on", "UpToDate 2026 | Berek & Novak's Gynecology | Robbins Pathology"],
["Date", "July 28, 2026"],
["Format", "Ward-based Clinical Teaching with Questions & Pitfalls"],
]
info_tbl = Table(info_rows, colWidths=[5*cm, 12.7*cm])
info_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), DARK_NAVY),
("BACKGROUND", (1,0), (1,-1), GREY_BG),
("TEXTCOLOR", (0,0), (0,-1), WHITE),
("TEXTCOLOR", (1,0), (1,-1), GREY_DARK),
("FONTNAME", (0,0), (0,-1), "Helvetica-Bold"),
("FONTNAME", (1,0), (1,-1), "Helvetica"),
("FONTSIZE", (0,0), (-1,-1), 9.5),
("ROWBACKGROUNDS", (1,0), (1,-1), [WHITE, GREY_BG]),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING",(0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 7),
("BOTTOMPADDING",(0,0),(-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
]))
story.append(info_tbl)
story.append(sp(16))
# Motto
motto = colored_box([
Paragraph('"Every postmenopausal bleed is endometrial carcinoma until proven otherwise."',
make_style("Motto", fontSize=12, textColor=DARK_NAVY, fontName="Helvetica-BoldOblique",
alignment=TA_CENTER, leading=17)),
sp(4),
Paragraph("— The cardinal rule of postmenopausal gynaecology", CAPTION),
], bg=WARN_LIGHT, border=WARN_ORANGE, padding=16)
story.append(motto)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# TABLE OF CONTENTS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("TABLE OF CONTENTS"))
story.append(sp(10))
toc_items = [
("1.", "DEFINITION & EPIDEMIOLOGY", "Setting the stage — who gets it & how common?"),
("2.", "AETIOLOGY — THE DIFFERENTIAL DIAGNOSIS", "From benign atrophy to life-threatening carcinoma"),
("3.", "RISK FACTORS FOR ENDOMETRIAL CARCINOMA", "Know your risk stratification"),
("4.", "SOURCES OF BLEEDING — ANATOMICAL FRAMEWORK", "Not all blood comes from the uterus!"),
("5.", "DIAGNOSTIC APPROACH", "History, Examination, Investigations — step by step"),
("6.", "ENDOMETRIAL EVALUATION", "TVUS vs. Biopsy vs. Hysteroscopy — when to use what"),
("7.", "SPECIAL SITUATIONS", "Hormone therapy, recurrent bleeding, proliferative endometrium"),
("8.", "NEVER-MISS CHART", "Red flags and must-not-forget items"),
("9.", "JUNIOR DOCTOR PITFALLS SUMMARY", "Common errors and how to avoid them"),
("10.", "CLINICAL PEARLS SUMMARY", "Quick revision before the ward round"),
]
for num, title, sub in toc_items:
toc_row = Table([[
Paragraph(f"<b>{num}</b>", make_style("TN", fontSize=11, textColor=MED_BLUE,
fontName="Helvetica-Bold", leading=14)),
[Paragraph(f"<b>{title}</b>", TOCENT_BOLD),
Paragraph(sub, TOCENT)]
]], colWidths=[1.2*cm, 16.5*cm])
toc_row.setStyle(TableStyle([
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING",(0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
]))
story.append(toc_row)
story.append(hr(LIGHT_BLUE, 0.5))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1: DEFINITION & EPIDEMIOLOGY
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 1", "Definition & Epidemiology", bg=DARK_NAVY))
story.append(sp(8))
story.append(question_box(
"Before we begin — A 62-year-old woman walks into your clinic. She says she noticed some blood-stained discharge 3 weeks ago. "
"She had her last period 11 years ago. She is not on any hormones. What is your single most important concern, and what is your "
"first investigation?"
))
story.append(sp(8))
story.append(definition_box(
"Postmenopausal Bleeding (PMB)",
"Any uterine bleeding occurring in a menopausal patient — defined as a woman who has had 12 consecutive months of amenorrhoea "
"— other than the expected cyclic bleeding that occurs in patients taking combined (estrogen-progestin) cyclic postmenopausal "
"hormone therapy. Even a single episode of spotting counts."
))
story.append(sp(8))
story.append(Paragraph("Epidemiology", H2))
story.append(hr())
epi_data = [
["Parameter", "Data", "Clinical Significance"],
["Prevalence", "4–11% of postmenopausal women", "Very common — expect to see it frequently"],
["Office visits", "~5% of all gynaecology visits", "High-burden complaint"],
["Risk of endometrial cancer in PMB", "Overall ~9% (meta-analysis, 92 studies, 31,000+ patients)", "1 in 11 women with PMB has cancer"],
["Risk without HRT use", "~12%", "Higher if not on hormones"],
["Risk if ET ≥4–5 mm + PMB", "~19%", "Thickened endometrium = major red flag"],
["Timing pattern", "Inversely related to time since menopause", "More common in first 12 months post-menopause"],
["Early post-menopause (<1 yr)", "409 bleeds per 1000 person-years", "Very high — mostly atrophy/polyps"],
[">3 yrs post-menopause", "42 bleeds per 1000 person-years", "Rarer — but carcinoma risk is proportionally higher"],
]
epi_tbl = Table(epi_data, colWidths=[5.5*cm, 5.5*cm, 6.7*cm])
epi_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 7),
("RIGHTPADDING",(0,0),(-1,-1), 7),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(epi_tbl)
story.append(sp(8))
story.append(pitfall_box([
"Never dismiss 'just a little spotting' in a postmenopausal woman. Even a single episode counts as PMB and must be investigated.",
"Do NOT assume it is 'just atrophy' without ruling out cancer first — atrophy is a diagnosis of exclusion.",
"Do NOT be falsely reassured because the patient is young (early 50s post-menopause) — endometrial cancer can occur at any postmenopausal age.",
"Women with cervical stenosis may present with PAIN and no visible bleeding (hematometra) — do not miss this presentation."
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2: AETIOLOGY
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 2", "Aetiology — The Differential Diagnosis", bg=MED_BLUE))
story.append(sp(8))
story.append(question_box(
"A pathology report comes back from 454 postmenopausal women with uterine bleeding. Which is the MOST common finding? "
"And which finding, though less common, can be FATAL if missed? Can you rank all the causes in order of frequency?"
))
story.append(sp(8))
story.append(Paragraph("Frequency Distribution of Endometrial Pathology in PMB (Prospective Study, n=454)", H3))
freq_data = [
["Rank", "Cause", "Frequency", "Malignant Potential", "Clinical Note"],
["1", "Endometrial Polyp", "37.7%", "Low (higher in PM than pre-PM)", "Most common — can miss on blind biopsy"],
["2", "Atrophy (Hypotrophy)", "30.8%", "None", "Diagnosis of exclusion after malignancy ruled out"],
["3", "Proliferative/Secretory\nEndometrium", "14.5%", "Indirect — suggests excess\nestrogen exposure", "Must investigate oestrogen source"],
["4", "Endometrial Carcinoma", "6.6%", "MALIGNANT", "The diagnosis you MUST NOT MISS"],
["5", "Leiomyoma (Fibroid)", "6.2%", "Very low (sarcoma rare)", "Never accept fibroid as sole cause of PMB"],
["6", "Hyperplasia without atypia", "2.0%", "Low but present", "Requires management"],
["7", "Hyperplasia with atypia", "0.2%", "HIGH (premalignant)", "Treat as malignancy until proven otherwise"],
]
freq_tbl = Table(freq_data, colWidths=[1*cm, 4*cm, 2.2*cm, 3.5*cm, 6.6*cm])
freq_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("BACKGROUND", (0,4), (-1,4), DANGER_LIGHT),
("TEXTCOLOR", (0,4), (2,4), DANGER_RED),
("FONTNAME", (0,4), (-1,4), "Helvetica-Bold"),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("BACKGROUND", (0,4), (-1,4), DANGER_LIGHT),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(freq_tbl)
story.append(sp(8))
# Aetiology detail
story.append(Paragraph("Detailed Aetiology", H2))
story.append(hr())
aet_items = [
("A. STRUCTURAL CAUSES", DARK_NAVY, [
("Endometrial Polyps", [
"Localised hyperplastic overgrowths of endometrial glands and stroma",
"Stimulated by oestrogen therapy and tamoxifen",
"IMPORTANT: Incidence of malignant or hyperplastic polyps is HIGHER in postmenopausal vs premenopausal women",
"Often MISSED on blind endometrial biopsy — hysteroscopy or SIS needed",
]),
("Endometrial / Vaginal Atrophy", [
"Hypoestrogenic changes cause thinning and dryness of endometrium and vagina",
"Collapsed atrophic surfaces rub together, causing micro-erosions and chronic inflammation",
"Results in light spotting, especially after intercourse",
"Vaginal atrophy accounts for up to 15% of all PMB causes",
"REMEMBER: Atrophy is the most common overall cause — but you CANNOT diagnose it without ruling out cancer",
]),
("Leiomyomata (Fibroids)", [
"Prevalence is one-tenth that of premenopausal women after menopause",
"Most fibroids shrink post-menopause (EXCEPT in women on HRT)",
"Mechanism of bleeding: distortion of endometrial cavity OR aberrant vascular architecture",
"CRITICAL RULE: NEVER accept fibroids as the cause of PMB — assume endometrial pathology/atrophy first",
"A rapidly growing fibroid post-menopause = think UTERINE SARCOMA (rare, but must exclude)",
]),
("Adenomyosis", [
"Endometrial glands infiltrating the myometrial wall",
"Symptomatic adenomyosis does NOT occur after menopause WITHOUT hormone therapy",
"If adenomyosis is causing PMB, ask: Is the patient on HRT?",
]),
]),
("B. HYPERPLASIA AND CARCINOMA", DANGER_RED, [
("Endometrial Hyperplasia (EH)", [
"May progress to, or coexist with, endometrial carcinoma",
"Most common in perimenopausal or EARLY postmenopausal patients",
"Without atypia: LOW risk of progression",
"WITH atypia: HIGH risk — treat aggressively",
"Risk factors are the SAME as for endometrial carcinoma",
]),
("Endometrial Carcinoma", [
"The diagnosis that MUST NOT be missed",
"Occurs in ~9% of all PMB patients overall (meta-analysis: 92 studies, 31,000+ patients)",
"~12% in women NOT on hormone therapy",
"90% of women with endometrial carcinoma have vaginal bleeding as their ONLY symptom",
"Most women present within 3 months of onset — do NOT delay workup",
"Average age of diagnosis: 60 years; 75% occur in women >50 years",
"Less than 5% are asymptomatic at diagnosis",
"Cervical stenosis may prevent blood egress — presents as PAIN not bleeding (hematometra/pyometra)",
"Purulent discharge with cervical stenosis = POOR prognostic sign",
]),
("Upper Genital Tract Carcinomas", [
"Fallopian tube carcinoma can present with PMB — 6/8 (75%) cases in one UK study presented with PMB",
"Ovarian cancer rarely but can cause PMB",
"Do not forget the adnexa when evaluating PMB",
]),
("Choriocarcinoma", [
"Rare — but PMB from choriocarcinoma has been described",
"Check serum beta-hCG if no obvious diagnosis found",
]),
]),
("C. MEDICATION-RELATED CAUSES", WARN_ORANGE, [
("Postmenopausal Hormone Therapy (HRT)", [
"Cyclic combined (oestrogen-progestogen) HRT: bleeding EXPECTED — occurs in almost all patients",
"Continuous combined HRT: breakthrough bleeding common in first 6 months — ACCEPTABLE",
"IMPORTANT: Continued bleeding BEYOND 6 months on continuous HRT = endometrial evaluation REQUIRED",
"Unopposed oestrogen therapy: 4–8x increased risk of endometrial cancer",
]),
("Medications Causing Bleeding (Table 2)", [
"Anticoagulants (warfarin, DOACs) — interfere with secondary haemostasis",
"Antiplatelet agents + NSAIDs — interfere with primary haemostasis",
"SSRIs — interfere with platelet function",
"Glucocorticoids — impair vascular integrity",
"Antibiotics — vitamin K deficiency especially with prolonged use",
"Alcohol — liver disease effects on clotting + direct marrow toxicity",
"Vitamin E — interferes with vitamin K metabolism",
"Garlic supplements — interfere with platelet function",
"Ginkgo biloba — mechanism unclear",
"Tamoxifen — stimulates endometrial polyp and leiomyoma growth; increases hyperplasia risk",
"Soy/phytoestrogens in large doses — stimulate endometrial lining",
]),
]),
("D. OTHER / UNCOMMON CAUSES", ACCENT_TEAL, [
("Adjacent Organ Disease", [
"Diverticulitis can cause corresponding inflammation of the upper genital tract",
"Ruptured sigmoid diverticulum can fistulize INTO the uterus — presents as uterine bleeding + endometritis",
"Always consider bowel pathology in PMB with associated bowel symptoms",
]),
("Post-Radiation Therapy", [
"Obliterative endarteritis leads to tissue necrosis and bleeding",
"Can also cause: haemorrhagic cystitis, proctitis, vaginal vault necrosis",
"History of pelvic radiotherapy (cervical/rectal/bladder cancer) is KEY",
]),
("Infection", [
"Endometritis: uncommon cause of PMB",
"Genital tuberculosis: rare in resource-abundant countries — but consider in appropriate epidemiological context",
]),
]),
]
for section_title, color, topics in aet_items:
sec_hdr = Table([[Paragraph(section_title, make_style("SH", fontSize=11,
textColor=WHITE, fontName="Helvetica-Bold", leading=14,
alignment=TA_LEFT))]], colWidths=["100%"])
sec_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), color),
("LEFTPADDING",(0,0),(-1,-1), 10),
("TOPPADDING",(0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
]))
story.append(sec_hdr)
story.append(sp(4))
for topic, bullets in topics:
story.append(Paragraph(f"<b>{topic}</b>", H4))
for b in bullets:
story.append(Paragraph(f"\u2022 {b}", BULLET))
story.append(sp(4))
story.append(sp(4))
story.append(pitfall_box([
"Assuming PMB is 'just from fibroids' — fibroids rarely cause PMB and endometrial pathology must be excluded first.",
"Stopping the workup when you find atrophy — atrophy is a diagnosis of EXCLUSION, not a first finding.",
"Missing the drug history — anticoagulants, SSRIs, tamoxifen, phytoestrogens all cause PMB and must be asked about.",
"Forgetting non-uterine sources — bladder cancer, urethral prolapse, and rectal pathology can mimic PMB.",
"Diagnosing 'breakthrough bleeding' on HRT beyond 6 months of continuous therapy WITHOUT investigation.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3: RISK FACTORS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 3", "Risk Factors for Endometrial Carcinoma", bg=DANGER_RED))
story.append(sp(8))
story.append(question_box(
"You see two patients with PMB: Patient A is a 55-year-old lean woman, no comorbidities, not on any medications. "
"Patient B is a 64-year-old obese (BMI 38), diabetic, nulliparous woman who takes tamoxifen for breast cancer. "
"Both have a thin endometrium on TVUS. Which patient needs more aggressive follow-up and why? "
"What is the mnemonic you would use to remember risk factors for endometrial carcinoma?"
))
story.append(sp(8))
story.append(Paragraph("Risk Factor Framework — Remember: CLOSED", H3))
rf_mnemonic = [
["Letter", "Risk Factor", "Mechanism", "Clinical Importance"],
["C", "Cancer history (personal/family) + COUCH potato (obesity)", "Adipose tissue aromatises androgens to oestrogen — unopposed oestrogen stimulates endometrium", "BMI >30: risk doubles; BMI >40: risk triples"],
["L", "Late menopause (>52 yrs) + Lynch syndrome (MMR gene mutations)", "Longer oestrogenic exposure; Lynch = MLH1, MSH2, MSH6, PMS2 mutations", "Lynch syndrome has 40–60% lifetime risk of endometrial cancer"],
["O", "Obesity + Oestrogen (exogenous unopposed)", "See above — adipose aromatase is the key mechanism", "4–8x increased risk with unopposed oestrogen"],
["S", "Sterility (nulliparity) + SERM (tamoxifen)", "No protective effect of progesterone from pregnancy; tamoxifen acts as oestrogen agonist in endometrium", "Tamoxifen: 2–3x increased risk of endometrial cancer"],
["E", "Early menarche + Elderly (increasing age)", "Longer oestrogenic exposure overall", "Peak incidence 6th–7th decade"],
["D", "Diabetes mellitus + Diet (high fat)", "Insulin resistance promotes oestrogen excess + cell proliferation", "Diabetes = independent risk factor even after controlling for obesity"],
]
rf_tbl = Table(rf_mnemonic, colWidths=[1*cm, 4.5*cm, 5.5*cm, 6.7*cm])
rf_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), DANGER_RED),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(rf_tbl)
story.append(sp(8))
story.append(key_point_box([
"Endometrial carcinoma is essentially a disease of OESTROGEN EXCESS — anything that increases unopposed oestrogen raises risk.",
"Lynch syndrome (hereditary non-polyposis colorectal cancer / HNPCC) gives 40–60% lifetime risk of endometrial carcinoma — ALWAYS ask family history of colon + endometrial cancer.",
"Protective factors include: multiparity, combined OCP use, smoking (paradoxically, through anti-oestrogenic effects), low body weight, physical activity.",
"A postmenopausal woman with PMB + diabetes + obesity = VERY HIGH risk — do not just reassure based on a thin endometrium.",
]))
story.append(sp(6))
story.append(pitfall_box([
"Forgetting to ask about family history of colorectal AND endometrial cancer (Lynch syndrome).",
"Not asking about tamoxifen use in a patient with a history of breast cancer — tamoxifen is oestrogenic in the endometrium.",
"Assuming a thin endometrium (<4mm) = safe in a high-risk patient — persistent bleeding in a high-risk patient needs biopsy regardless of ET thickness.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4: ANATOMICAL SOURCES OF BLEEDING
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 4", "Sources of Bleeding — Anatomical Framework", bg=ACCENT_TEAL))
story.append(sp(8))
story.append(question_box(
"A 68-year-old woman presents with 'vaginal bleeding'. She has not had a period for 15 years. "
"You do a careful examination and find no cervical lesion, no vaginal lesion, but a small urethral swelling. "
"Where is the bleeding actually coming from? What is your diagnosis? What does this teach you about the approach to 'vaginal bleeding'?"
))
story.append(sp(8))
story.append(Paragraph(
"Not all 'vaginal bleeding' comes from the uterus. Your examination must systematically exclude every possible source.",
make_style("ItalBold", fontSize=11, textColor=DARK_NAVY, fontName="Helvetica-BoldOblique", leading=15)
))
story.append(sp(6))
sources_data = [
["Anatomical Site", "Causes to Consider", "Key Examination Finding"],
["Vulva", "Skin tags, sebaceous cysts, condylomata, angiokeratoma, invasive carcinoma, Crohn disease, Behcet syndrome, pemphigoid/pemphigus, erosive lichen planus", "Visible lesion on inspection"],
["Vagina", "Atrophic vaginitis (up to 15% of all PMB), polyps, Gartner duct cysts, adenosis, carcinoma, bacterial vaginosis, STIs, foreign body (pessary)", "Thin friable vaginal wall; visible mass or discharge"],
["Cervix", "Cervical polyps, ectropion, cervicitis, invasive carcinoma, metastatic disease", "Visible cervical lesion; abnormal Pap smear"],
["Uterus (endometrium)", "MOST COMMON UTERINE SOURCES — atrophy, polyps, hyperplasia, carcinoma, fibroids, adenomyosis, sarcoma", "Uterus size/contour on bimanual; TVUS/biopsy needed"],
["Fallopian tubes", "Primary fallopian tube carcinoma (75% present with PMB), PID (rare post-PM)", "Adnexal mass on examination or ultrasound"],
["Ovaries", "Ovarian carcinoma (rare cause of PMB), hormone-secreting tumours (granulosa cell)", "Adnexal mass; elevated CA-125; elevated oestrogen"],
["Urethra / Bladder", "Urethral prolapse, atrophic urethral changes, urethritis, bladder cancer, UTI", "Urethral mass; haematuria; urine dipstick positive"],
["Rectum / Bowel", "Haemorrhoids, colorectal cancer, IBD, diverticular fistula to uterus", "Rectal examination; stool occult blood"],
["Non-local / Systemic", "von Willebrand disease, thrombocytopenia, acute leukemia, coagulation factor deficiencies, advanced liver disease, thyroid disease, PCOS, Cushing syndrome, renal disease", "Bleeding from other sites; coagulation screen; FBC"],
]
src_tbl = Table(sources_data, colWidths=[3.5*cm, 8.5*cm, 5.7*cm])
src_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), ACCENT_TEAL),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(src_tbl)
story.append(sp(8))
story.append(pearl_box([
"Atrophic changes post-menopause increase risk of lower genital tract lacerations and cervical lacerations — a thin, friable vaginal wall can bleed easily after intercourse.",
"A pessary left in situ for too long can cause vaginal bleeding — ALWAYS ask about pessary use.",
"Trauma, consensual intercourse, and intimate partner violence can cause genital bleeding — ask sensitively about these.",
"In a patient with a history of pelvic radiation, bleeding may be from radiation-induced changes to the vagina, bladder, or rectum — not necessarily from the uterus.",
"A granulosa cell tumour of the ovary secretes oestrogen — it can cause endometrial proliferation and hyperplasia even in a postmenopausal woman.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5: DIAGNOSTIC APPROACH
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 5", "Diagnostic Approach — History & Examination", bg=DARK_NAVY))
story.append(sp(8))
story.append(question_box(
"You are about to clerk a 70-year-old woman with PMB. Your registrar tells you to 'take a focused history'. "
"Write down the 10 most important questions you would ask — in the order you would ask them — and explain WHY each one matters clinically."
))
story.append(sp(8))
story.append(Paragraph("A. THE HISTORY — 10 Essential Questions", H2))
story.append(hr())
hx_data = [
["#", "Question to Ask", "What It Tells You", "Clinical Implication"],
["1", "When did the bleeding start and how many episodes?", "Duration and recurrence pattern", "Recurrent bleeding = higher malignancy concern"],
["2", "What is the nature of bleeding? (amount, colour, associated discharge)", "Quantity and character", "Heavy bleeding + tissue = malignancy until proven otherwise"],
["3", "Were there any precipitating factors? (intercourse, trauma, stress)", "Triggers help identify source", "Post-coital bleeding = cervical or vaginal cause"],
["4", "Any associated symptoms? (pelvic pain, abdominal bloating, weight loss, bowel/bladder changes)", "Systemic involvement, extrauterine spread", "Weight loss + bloating = advanced disease; pain = hematometra/cervical stenosis"],
["5", "Any fever or vaginal discharge?", "Infectious cause (endometritis, TB)", "Purulent discharge = poor prognosis in context of cervical stenosis"],
["6", "Complete medication history: HRT, anticoagulants, SSRIs, tamoxifen, herbal supplements, soy products", "Drug-induced bleeding", "Tamoxifen, anticoagulants are major causes; herbals often missed"],
["7", "History of obesity, diabetes, hypertension?", "Metabolic risk factors for endometrial cancer", "Metabolic syndrome = highest risk group"],
["8", "Family history of endometrial, colorectal, breast, ovarian cancer?", "Lynch syndrome, hereditary risk", "HNPCC family history mandates genetic counselling"],
["9", "Date and result of last cervical smear and any previous gynaecological procedures?", "Previous abnormal cytology or uterine surgery", "Cervical stenosis from previous LLETZ/ablation"],
["10", "Obstetric history (parity, last delivery), prior pelvic radiation or surgery", "Nulliparity = risk factor; radiation history = radiation cause", "Also screens for prior malignancy treatment"],
]
hx_tbl = Table(hx_data, colWidths=[0.7*cm, 4.5*cm, 4*cm, 8.5*cm])
hx_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), MED_BLUE),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(hx_tbl)
story.append(sp(8))
story.append(Paragraph("B. PHYSICAL EXAMINATION — Systematic Approach", H2))
story.append(hr())
exam_items = [
("General Examination", [
"Calculate BMI — obesity is a key risk factor for endometrial carcinoma",
"Inspect skin for ecchymosis, petechiae (coagulopathy?), jaundice (liver disease?)",
"Assess for lymphadenopathy (inguinal, supraclavicular) — suggests malignancy with spread",
"Abdominal examination: tenderness, mass, ascites (ovarian pathology?), hepatomegaly",
]),
("Pelvic Examination — Step by Step", [
"Step 1 — Inspection of vulva: look for atrophic changes, lesions, condylomata, skin conditions, lacerations, foreign bodies",
"Step 2 — Speculum examination: inspect vagina (thin friable mucosa = atrophy), cervix (lesion? polyp? os open?), source of bleeding",
"Step 3 — Cervical evaluation: any visible lesion MUST be biopsied even with normal cytology",
"Step 4 — Bimanual examination: uterine size (enlarged = fibroid, cancer?), contour, tenderness (endometritis?), adnexal masses (ovarian/tubal pathology?)",
"Step 5 — Rectal examination if bowel pathology suspected",
]),
("Special Examinations", [
"Urethroscopy/urine dipstick: if urethral/bladder source suspected",
"Examination under anaesthesia: if poor visualisation or uncooperative patient",
"Document intimate partner violence assessment sensitively in appropriate cases",
]),
]
for etitle, ebullets in exam_items:
story.append(Paragraph(f"<b>{etitle}</b>", H4))
for b in ebullets:
story.append(Paragraph(f"\u2022 {b}", BULLET))
story.append(sp(4))
story.append(pitfall_box([
"Forgetting to calculate and document BMI — it is a REQUIRED risk stratification step.",
"Not performing a speculum examination — you CANNOT locate the source of bleeding without visualising the lower genital tract.",
"Biopsying a normal-looking cervix but not checking the vaginal walls — atrophic vaginitis is easily missed.",
"Assuming the uterus is normal because it feels normal bimanually — small endometrial lesions are NOT palpable; always proceed to imaging/biopsy.",
"Not examining for lymphadenopathy — advanced endometrial carcinoma metastasises to inguinal and para-aortic nodes.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6: ENDOMETRIAL EVALUATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 6", "Endometrial Evaluation — Investigations", bg=MED_BLUE))
story.append(sp(8))
story.append(question_box(
"Your patient with PMB is 65, not on HRT. You order a TVUS: it shows an endometrial thickness of 3.5mm with no polyp visible. "
"You tell the patient everything looks fine. Have you completed her workup? What would you do if her endometrial thickness was 6mm? "
"And what if she continues to bleed despite a thickness of 3mm and a negative biopsy?"
))
story.append(sp(8))
story.append(Paragraph("Investigation Algorithm", H2))
story.append(hr())
# Algorithm as a structured table
alg_data = [
["Step", "Test", "Details / Threshold", "Action if Abnormal"],
["1st Line\n(Either/Or)", "Transvaginal Ultrasound\n(TVUS)", "Endometrial thickness (ET):\n\u2022 ET <4mm: Low risk of carcinoma\n\u2022 ET 4–5mm: Equivocal — biopsy\n\u2022 ET >5mm: BIOPSY mandatory\n\u2022 ET ≥4–5mm + PMB: 19% risk of carcinoma", "If ET >4–5mm OR inhomogeneous endometrium OR if bleeding persists: proceed to endometrial biopsy"],
["1st Line\n(Preferred)", "Endometrial Biopsy\n(Pipelle/Novak)", "Gold standard for tissue diagnosis\nSensitivity 90–99% for endometrial carcinoma\nHigh sensitivity, low complication rate, low cost\nCAVEAT: May miss focal lesions (polyps)", "Send for histopathology; treat as per result"],
["2nd Line", "Saline Infusion\nSonography (SIS)", "Better than TVUS for detecting focal lesions (polyps)\nUse when TVUS is inconclusive", "Proceed to hysteroscopy if polyp suspected"],
["2nd Line", "Hysteroscopy +\nDirected Biopsy", "Gold standard for visualisation\nBest for: polyps, focal lesions, failed Pipelle, recurrent bleeding\nCan be diagnostic AND therapeutic", "Directed biopsy of all suspicious areas; polypectomy"],
["2nd Line", "Dilation & Curettage\n(D&C)", "For: failed outpatient biopsy, inadequate sample, cervical stenosis, patient unable to tolerate office biopsy", "Full curettage + histopathology"],
["Select\nPatients", "Cervical Evaluation\n(Cytology + biopsy)", "ALL patients with PMB need cervical cancer exclusion\nAny visible cervical lesion = BIOPSY even with normal cytology", "Refer oncology if malignancy confirmed"],
["Select\nPatients", "Additional labs", "FBC if heavy bleeding; coagulation screen if coagulopathy suspected; TSH/prolactin; beta-hCG (if choriocarcinoma suspected)", "Treat underlying condition"],
]
alg_tbl = Table(alg_data, colWidths=[2*cm, 3.5*cm, 7.5*cm, 4.7*cm])
alg_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), MED_BLUE),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(alg_tbl)
story.append(sp(8))
story.append(Paragraph("Understanding Endometrial Thickness Thresholds", H3))
et_data = [
["ET Measurement", "Interpretation", "Action", "Evidence Base"],
["< 4 mm", "Low risk — atrophic endometrium", "Reassure; treat atrophy if symptomatic; no biopsy unless bleeding persists", "NPV for carcinoma ~99% in non-bleeding postmenopausal women"],
["4–5 mm (equivocal)", "Cannot exclude pathology", "Individualise — biopsy recommended in most guidelines if PMB present", "Risk of carcinoma ~4–6% in this group"],
["> 5 mm", "ABNORMAL — must biopsy", "Endometrial biopsy mandatory", "Risk increases with increasing thickness"],
["≥ 4–5 mm + PMB", "HIGH risk", "19% risk of carcinoma — urgent biopsy", "From meta-analysis of 92 studies"],
["Any thickness + recurrent PMB", "Persistent unexplained bleeding", "Hysteroscopy + directed biopsy regardless of ET", "Persistent bleeding = carcinoma until proven otherwise"],
]
et_tbl = Table(et_data, colWidths=[2.5*cm, 3.8*cm, 4.5*cm, 6.9*cm])
et_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_NAVY),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,2), (0,2), WARN_ORANGE),
("TEXTCOLOR", (0,3), (0,4), DANGER_RED),
("TEXTCOLOR", (0,5), (0,5), DANGER_RED),
("TEXTCOLOR", (0,1), (0,1), GREEN),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREEN_LIGHT, WHITE, WARN_LIGHT, DANGER_LIGHT, DANGER_LIGHT]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(et_tbl)
story.append(sp(8))
story.append(pearl_box([
"TVUS is a good FIRST STEP but endometrial biopsy gives you the TISSUE DIAGNOSIS — always prefer biopsy when in doubt.",
"A thin endometrium does NOT rule out carcinoma in a patient with PERSISTENT or RECURRENT bleeding. Proceed to hysteroscopy.",
"Pipelle biopsy may miss focal lesions like polyps — if the result is 'insufficient tissue' or 'benign' but bleeding continues, go to hysteroscopy.",
"SIS (saline infusion sonography) is excellent for identifying polyps that are invisible on standard TVUS.",
"ALL patients with PMB need cervical evaluation — do not skip the cervix.",
]))
story.append(sp(6))
story.append(pitfall_box([
"Stopping investigation after a thin ET on TVUS in a patient with ongoing bleeding — this is WRONG. ET <4mm does not rule out carcinoma in women with recurrent PMB.",
"Accepting 'insufficient tissue' on Pipelle as a final answer — this is not reassuring; proceed to D&C/hysteroscopy.",
"Not biopsying a visible cervical lesion because cytology was normal — VISIBLE LESIONS MUST ALWAYS BE BIOPSIED.",
"Ordering TVUS via abdominal route instead of TRANSVAGINAL — endometrial thickness cannot be accurately measured abdominally in postmenopausal women.",
"Failing to refer when unable to perform endometrial sampling in your setting — refer early rather than delay.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 7: SPECIAL SITUATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 7", "Special Situations", bg=PURPLE))
story.append(sp(8))
story.append(question_box(
"Three special scenarios: (1) A woman on continuous combined HRT bleeds at 8 months — is this acceptable? "
"(2) A woman had a Pipelle showing a benign proliferative endometrium — your registrar says 'that's fine, discharge her'. Do you agree? "
"(3) A patient with PMB had a biopsy showing 'benign endometrium' and a thin ET, but she bleeds again 3 months later. What is your next step?"
))
story.append(sp(8))
specials = [
("1. Women on Hormone Replacement Therapy (HRT)",
MED_BLUE, [
("Cyclic combined HRT (oestrogen + progestogen alternating)",
"Expected withdrawal bleeding occurs in almost ALL patients. This is NOT abnormal. No investigation needed unless bleeding is out of pattern."),
("Continuous combined HRT (oestrogen + progestogen daily)",
"Breakthrough bleeding is common in the FIRST 6 MONTHS. This is acceptable and usually settles as the endometrium becomes atrophic. "
"HOWEVER: Bleeding persisting BEYOND 6 months on continuous HRT = endometrial evaluation REQUIRED. Do NOT dismiss this as 'breakthrough bleeding'."),
("Unopposed oestrogen therapy",
"NEVER give without progestogen in a woman with a uterus. 4–8x increased risk of endometrial carcinoma. "
"Unopposed oestrogen use = mandatory periodic endometrial surveillance."),
]),
("2. Proliferative or Secretory Endometrium on Biopsy",
WARN_ORANGE, [
("What it means",
"Finding a proliferative or secretory endometrium in a postmenopausal woman is NOT normal. It suggests active endogenous oestrogen secretion "
"or exogenous oestrogen exposure. It is NOT a form of endometrial hyperplasia, but it is NOT a reassuring benign finding either."),
("Causes to investigate",
"1. Obesity/adipose aromatase producing excess oestrogen. "
"2. Oestrogen-secreting ovarian tumour (granulosa cell tumour — check oestradiol levels, pelvic ultrasound for ovarian mass). "
"3. Exogenous oestrogens (HRT, phytoestrogens, medications). "
"4. Adrenal tumour secreting androgens (aromatised peripherally to oestrogen)."),
("Action required",
"Investigate the source of oestrogen. Do NOT simply discharge the patient after finding a 'proliferative' endometrium — this requires further workup."),
]),
("3. Recurrent or Persistent Bleeding Despite Initial Normal Workup",
DANGER_RED, [
("The golden rule",
"Persistent bleeding is a sign of endometrial carcinoma EVEN in the setting of a 'benign' endometrial biopsy OR a thin (≤4mm) endometrial stripe on TVUS."),
("Why this happens",
"Focal cancers can be missed by blind biopsy. Small carcinomas may not thicken the endometrium. Pipelle samples only about 60–70% of the endometrial surface."),
("Required next step",
"Dilation and Curettage (D&C) WITH Hysteroscopy. This is the definitive investigation for recurrent/persistent PMB. "
"Saline infusion sonography (SIS) is an alternative second-line test."),
]),
("4. Choriocarcinoma in Postmenopausal Women",
ACCENT_TEAL, [
("Remember",
"Choriocarcinoma has been described as a cause of PMB — it can arise from a dormant gestational trophoblastic disease or de novo. "
"Check serum beta-hCG if no other cause found."),
]),
("5. When to Refer to Gynaecology",
GREEN, [
("Indications for referral",
"1. Persistent or recurrent bleeding after initial evaluation. "
"2. Suspicion of malignancy on any test. "
"3. Surgery needed (D&C, polypectomy, hysterectomy). "
"4. Primary care clinician not comfortable with endometrial sampling. "
"5. SIS or hysteroscopy needed — refer to a centre with required expertise."),
]),
]
for st_title, st_color, st_items in specials:
st_hdr = Table([[Paragraph(st_title, make_style(
"STH", fontSize=11, textColor=WHITE, fontName="Helvetica-Bold", leading=14))]], colWidths=["100%"])
st_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), st_color),
("LEFTPADDING",(0,0),(-1,-1), 10),
("TOPPADDING",(0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
]))
story.append(st_hdr)
story.append(sp(4))
for item_title, item_text in st_items:
story.append(Paragraph(f"<b>{item_title}:</b> {item_text}", BODY))
story.append(sp(3))
story.append(sp(6))
story.append(pitfall_box([
"Telling a patient on continuous HRT that 'all bleeding is expected' — only in the first 6 months. After that, investigate.",
"Discharging a patient with a 'proliferative endometrium' on biopsy without investigating for the oestrogen source.",
"Stopping investigation after one normal workup in a patient who continues to bleed — recurrent PMB ALWAYS needs re-investigation.",
"Not requesting serum beta-hCG when no cause is found — do not forget choriocarcinoma.",
]))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 8: NEVER-MISS CHART
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 8", "NEVER-MISS CHART", bg=DANGER_RED))
story.append(sp(8))
story.append(Paragraph("Red Flag Findings — You MUST NOT Miss These", H2))
story.append(hr(DANGER_RED))
story.append(sp(4))
never_miss = [
["#", "NEVER MISS THIS", "WHY IT MATTERS", "WHAT TO DO"],
["1", "Endometrial carcinoma in ANY woman with PMB",
"Found in ~9% of all PMB; up to 19% if ET ≥5mm + PMB. 90% of EC presents as vaginal bleeding — it is THE presenting symptom.",
"MANDATORY endometrial evaluation (biopsy/TVUS) in every patient with PMB"],
["2", "Endometrial hyperplasia WITH atypia",
"HIGH malignant potential; coexists with carcinoma in up to 25–43% of cases when EIN (endometrial intraepithelial neoplasia) is found",
"Urgent gynaecology referral; treat as malignancy until proven otherwise"],
["3", "Lynch syndrome / Hereditary endometrial cancer",
"40–60% lifetime risk of endometrial carcinoma; patients and family members need genetic counselling and surveillance",
"Ask EVERY patient with PMB for family history of colorectal AND endometrial cancer"],
["4", "Hematometra from cervical stenosis",
"Cervical stenosis may PREVENT visible bleeding — patient presents with pelvic PAIN, not bleeding. Associated with poor prognosis (pyometra).",
"If elderly patient has pelvic pain without visible bleeding: examine cervix, consider ultrasound for hematometra"],
["5", "Fallopian tube carcinoma",
"75% of primary fallopian tube carcinomas present with PMB — easily attributed to uterine cause. Often diagnosed late.",
"Always examine adnexa; consider CA-125; if adnexal mass found with PMB, urgent referral"],
["6", "Granulosa cell tumour (ovary) causing endometrial stimulation",
"Produces oestrogen, leading to endometrial proliferation and hyperplasia in a postmenopausal woman. Can present as PMB.",
"Investigate a 'proliferative endometrium' on biopsy — measure serum oestradiol, do pelvic ultrasound"],
["7", "Uterine sarcoma in a 'growing fibroid'",
"A fibroid that is increasing in size post-menopause in a woman with PMB should raise suspicion for uterine sarcoma (LMS)",
"Do not reassure based on old fibroid history; urgent imaging and referral"],
["8", "Recurrent PMB after a 'normal' initial workup",
"Persistent/recurrent bleeding = carcinoma until proven otherwise EVEN with benign biopsy and thin ET. Small focal cancers are missed by blind biopsy.",
"D&C with hysteroscopy is mandatory for recurrent PMB"],
["9", "Choriocarcinoma (rare)",
"PMB from gestational trophoblastic disease has been described. Easily missed if beta-hCG not checked.",
"Check serum beta-hCG if no obvious cause found"],
["10", "Intimate partner violence / Sexual assault",
"Traumatic bleeding — especially in nursing home or vulnerable patients — can present as PMB. Missing it means the patient continues to be harmed.",
"Ask sensitively about trauma and IPV in every consultation; examine for lacerations"],
["11", "Bladder cancer or colorectal cancer masquerading as PMB",
"Haematuria or rectal bleeding can be mistaken for vaginal bleeding by both patient and clinician.",
"Always do urine dipstick; ask about bowel symptoms; rectal examination if indicated"],
["12", "Drug/supplement history missed",
"Anticoagulants, tamoxifen, SSRIs, phytoestrogens — all can cause PMB. Missed in up to 30% of histories in junior doctors.",
"Always take a complete medication + supplement history including OTC and herbal products"],
]
nm_tbl = Table(never_miss, colWidths=[0.7*cm, 4*cm, 6.5*cm, 6.5*cm])
nm_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DANGER_RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), DANGER_RED),
("FONTSIZE", (0,0), (-1,-1), 8.5),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, DANGER_LIGHT]),
("GRID", (0,0), (-1,-1), 0.8, DANGER_RED),
("LEFTPADDING",(0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0),(-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(nm_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 9: JUNIOR DOCTOR PITFALLS SUMMARY
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 9", "Junior Doctor Pitfalls — Master Summary", bg=WARN_ORANGE))
story.append(sp(8))
story.append(question_box(
"Before reading this section — from memory, list 5 mistakes you think a junior doctor would make in managing a case of PMB. "
"Then read on and see how many you identified correctly."
))
story.append(sp(8))
pitfalls_all = [
("DIAGNOSTIC ERRORS", [
("Not investigating PMB at all", "Every single episode of PMB must be investigated. 'It was just a little spotting' is not acceptable."),
("Diagnosing 'atrophy' or 'fibroids' without excluding carcinoma first", "Atrophy and fibroids are diagnoses of exclusion. Always rule out malignancy before attributing PMB to benign causes."),
("Accepting a thin ET (<4mm) as reassuring in a woman with recurrent/persistent bleeding", "Persistent bleeding OVERRIDES a thin ET. Focal carcinomas may not thicken the endometrium. Mandatory D&C/hysteroscopy."),
("Accepting 'insufficient tissue' on Pipelle as a reassuring result", "Insufficient tissue = blind spot. The area you couldn't sample might be where the cancer is. Proceed to D&C/hysteroscopy."),
("Not performing transvaginal ultrasound (using only abdominal route)", "Abdominal USS cannot accurately measure endometrial thickness. TVUS is mandatory."),
("Not biopsying a visible cervical lesion because cytology was normal", "Cytology screens, but does not diagnose. Visible lesions must ALWAYS be biopsied."),
("Not asking about a history of pelvic radiation", "Radiation-related bleeding is a specific entity with different management. Always ask about prior RT."),
]),
("HISTORY-TAKING ERRORS", [
("Incomplete drug history — missing tamoxifen, anticoagulants, supplements", "Always ask specifically about tamoxifen (breast cancer history?), all anticoagulants, SSRIs, herbals, and phytoestrogens."),
("Not asking family history of colorectal and endometrial cancer", "Lynch syndrome — the most important hereditary condition to identify. Ask about BOTH colon AND endometrial cancer in family."),
("Equating 'no periods for 12 months' without confirming menopause", "Ensure amenorrhoea is menopausal and not due to another cause before applying the PMB framework."),
("Not asking about trauma, sexual intercourse, or intimate partner violence", "Always ask sensitively. Traumatic genital bleeding is common especially in nursing home/vulnerable populations."),
]),
("EXAMINATION ERRORS", [
("Skipping pelvic examination because 'the patient is elderly and frail'", "Pelvic examination is mandatory in all patients with PMB — frailty requires sensitive approach, NOT omission."),
("Not calculating or documenting BMI", "BMI is a risk stratification tool. It is a required part of the PMB workup."),
("Forgetting to check for adnexal masses", "Fallopian tube carcinoma and granulosa cell tumour present with PMB — always palpate the adnexa."),
("Missing urethral prolapse as the source of 'vaginal' bleeding", "A swollen urethral mass in an elderly postmenopausal woman is urethral prolapse — not uterine bleeding. Look carefully."),
]),
("MANAGEMENT ERRORS", [
("Not referring when unable to perform endometrial sampling", "If you cannot do endometrial biopsy in your setting, REFER EARLY — do not delay."),
("Discharging a woman with PMB after a single normal investigation", "One normal test does not end the workup. Persistent/recurrent symptoms require further evaluation."),
("Telling a woman on continuous HRT that bleeding at 8 months is 'normal'", "Only the first 6 months of continuous HRT may have breakthrough bleeding. Beyond 6 months = investigate."),
("Giving unopposed oestrogen to a woman with a uterus", "NEVER prescribe unopposed oestrogen to a woman with an intact uterus — this is a prescribing error that causes endometrial cancer."),
("Delaying urgent referral in a high-risk patient while awaiting 'routine' investigations", "If clinical suspicion is high, refer urgently — do not allow multiple layers of delay."),
]),
]
for pf_section, pf_items in pitfalls_all:
pf_hdr = Table([[Paragraph(pf_section, make_style(
"PFH2", fontSize=11, textColor=WHITE, fontName="Helvetica-Bold", leading=14))]], colWidths=["100%"])
pf_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), WARN_ORANGE),
("LEFTPADDING",(0,0),(-1,-1), 10),
("TOPPADDING",(0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
]))
story.append(pf_hdr)
story.append(sp(4))
pf_rows = [["Pitfall", "Correct Approach"]]
for mistake, correction in pf_items:
pf_rows.append([
Paragraph(f"\u26a0 {mistake}", make_style("PFR", fontSize=9, textColor=DANGER_RED,
fontName="Helvetica-Bold", leading=12)),
Paragraph(correction, make_style("PFC", fontSize=9, textColor=GREY_DARK,
fontName="Helvetica", leading=12)),
])
pf_tbl = Table(pf_rows, colWidths=[8.5*cm, 9.2*cm])
pf_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREY_BG),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 9),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, DANGER_LIGHT]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#CBD5E1")),
("LEFTPADDING",(0,0), (-1,-1), 8),
("RIGHTPADDING",(0,0),(-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(pf_tbl)
story.append(sp(8))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 10: CLINICAL PEARLS SUMMARY
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("SECTION 10", "Clinical Pearls — Quick Revision Summary", bg=GREEN))
story.append(sp(8))
story.append(Paragraph("The Ten Commandments of PMB", H2))
story.append(hr(GREEN))
story.append(sp(6))
commandments = [
("I.", "Every PMB is endometrial carcinoma until proven otherwise.",
"Your default assumption must always be malignancy first. Every single episode, no matter how small."),
("II.", "Atrophy is a diagnosis of exclusion — not a first assumption.",
"Even though atrophy is the most common cause, you CANNOT diagnose it without first ruling out cancer."),
("III.", "A negative TVUS does not end the workup if bleeding persists.",
"ET <4mm is reassuring, not diagnostic. Persistent/recurrent PMB needs tissue diagnosis regardless."),
("IV.", "Insufficient tissue on Pipelle = go to hysteroscopy.",
"A failed biopsy is not a reassuring result. It means the endometrium has not been sampled adequately."),
("V.", "Never accept fibroids as the cause of PMB.",
"Fibroids rarely cause post-menopausal bleeding. Endometrial pathology must be excluded first."),
("VI.", "Always examine the cervix — and biopsy any visible lesion.",
"Cervical carcinoma and endocervical bleeding can mimic uterine PMB. Normal cytology does not exempt visible lesions from biopsy."),
("VII.", "Beyond 6 months on continuous HRT — bleeding must be investigated.",
"Only the first 6 months of continuous combined HRT allow breakthrough bleeding. After that, investigate."),
("VIII.", "A proliferative endometrium in a postmenopausal woman requires an oestrogen source investigation.",
"Think: obesity, oestrogen-secreting tumour (granulosa cell), exogenous oestrogens, adrenal disease."),
("IX.", "Ask about Lynch syndrome — family history of colon + endometrial cancer.",
"HNPCC gives 40–60% lifetime endometrial cancer risk. Do not miss this in history-taking."),
("X.", "Recurrent PMB after normal initial workup = D&C with hysteroscopy.",
"Do not cycle through the same investigations. Escalate. Persistent PMB = cancer until histologically proven otherwise."),
]
for num, title, detail in commandments:
cmd_row = Table([[
Paragraph(f"<b>{num}</b>", make_style("CMN", fontSize=14, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=18)),
[
Paragraph(f"<b>{title}</b>", make_style("CMT", fontSize=11, textColor=DARK_NAVY,
fontName="Helvetica-Bold", leading=14)),
Paragraph(detail, make_style("CMD", fontSize=9.5, textColor=GREY_DARK,
fontName="Helvetica-Oblique", leading=12)),
]
]], colWidths=[1.5*cm, 16.2*cm])
cmd_row.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GREEN),
("BACKGROUND", (1,0), (1,0), WHITE),
("BOX", (0,0), (-1,-1), 1, GREEN),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(cmd_row)
story.append(sp(4))
story.append(sp(12))
# Final closing box
closing = colored_box([
Paragraph("A Message from Your Professor", make_style(
"CPT", fontSize=10, textColor=DARK_NAVY, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=13)),
sp(6),
Paragraph(
"Every postmenopausal woman who walks into your clinic trusting you with a complaint of vaginal bleeding "
"is counting on you to not miss the one diagnosis that can take her life. You will see many cases of atrophy, "
"many polyps, many reassuring scans — and because of that, the day you see the carcinoma, you must be ready. "
"The secret of good clinical medicine is not memorising facts; it is developing the habit of NEVER being complacent. "
"Investigate every bleed. Question every reassuring result. Push for tissue when bleeding persists. "
"That is what separates a good clinician from an average one.",
make_style("CPB", fontSize=10, textColor=GREY_DARK, fontName="Helvetica-Oblique",
alignment=TA_JUSTIFY, leading=14)
),
sp(8),
Paragraph("— Your GynObs Professor", CAPTION),
], bg=WARN_LIGHT, border=WARN_ORANGE, padding=14)
story.append(closing)
story.append(sp(12))
story.append(Paragraph("Sources: UpToDate 2026 (Goodman et al.) | Berek & Novak's Gynecology | Robbins & Cotran Pathologic Basis of Disease | Swanson's Family Medicine Review", SMALL))
# ─── BUILD ──────────────────────────────────────────────────────────────────
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
print(f"PDF built successfully: {OUTPUT_PATH}")
cd /tmp/workspace/pmb-clinical-notes && python3 build_notes.py 2>&1
ls -lh /tmp/workspace/pmb-clinical-notes/PMB_Clinical_Notes.pdf
python3 -c " from pypdf import PdfReader r = PdfReader('/tmp/workspace/pmb-clinical-notes/PMB_Clinical_Notes.pdf') print(f'Total pages: {len(r.pages)}') for i, p in enumerate(r.pages): t = p.extract_text() first_line = t.strip().split('\n')[0] if t else '(empty)' print(f' Page {i+1}: {first_line[:80]}') "
PMB Clinical Notes
PDF Document · PDF