Leucorrhea definition causes clinical features management
vaginal discharge vaginitis management
| Situation | Mechanism |
|---|---|
| Neonatal period | Residual maternal estrogen stimulation (thin, clear/white discharge in first 2-3 weeks of life) |
| Puberty / adolescence | Rising estrogen levels increase vaginal secretions |
| Ovulation | Mid-cycle cervical mucus (egg-white, stretchy) |
| Pregnancy | Increased vascularity and estrogen effect |
| Sexual arousal | Transudation from vaginal walls |
| Oral contraceptive use | Estrogen-driven glandular stimulation |
| Feature | Physiological | BV | Trichomoniasis | VVC |
|---|---|---|---|---|
| Amount | Variable, scant | Moderate | Profuse | Scant to moderate |
| Color | Clear / white | White or gray | Yellow-green | White |
| Consistency | Heterogeneous | Homogeneous, low viscosity (milk-like) | Homogeneous, frothy | Clumped, adherent plaques ("curdy") |
| Odor | None | Fishy (positive whiff test) | May be fishy | None |
| Vulvar inflammation | No | No | Yes | Yes |
| Pruritus | No | No | Yes | Prominent |
| pH | <4.5 | >4.5 | >5.0 | <4.5 |
| Microscopy | Normal epithelial cells, Lactobacillus | Clue cells, few WBCs | Motile trichomonads, many WBCs | Budding yeast/pseudohyphae |
| Test | Purpose |
|---|---|
| Vaginal pH | <4.5 = normal/candida; >4.5 = BV/trichomonas |
| Wet mount (saline) | Clue cells (BV), motile trichomonads (TV) |
| 10% KOH preparation | Budding yeast/pseudohyphae (Candida); positive whiff/amine test (BV) |
| Gram stain (vaginal smear) | Clue cells in BV; gram-negative diplococci for gonorrhea |
| Gram stain (endocervical smear) | Intracellular gonococci |
| NAAT (Nucleic Acid Amplification Test) | Gold standard for C. trachomatis, N. gonorrhoeae, T. vaginalis (more sensitive than culture) |
| Fungal culture | Gold standard for VVC, rarely performed |
| Condition | Treatment |
|---|---|
| Candidiasis (1st trimester) | Clotrimazole vaginal pessary/cream only; oral fluconazole contraindicated |
| Candidiasis (2nd/3rd trimester) | Clotrimazole pessary OR cream |
| BV / Trichomoniasis | Metronidazole intravaginally (pessaries/cream); oral metronidazole is generally considered safe after 1st trimester |
| Per speculum always done | To rule out abortion, PPROM |
| Concurrent DM, HIV | Consider these factors in recurrent infections |
Create a clinical decision tree PDF for vaginal discharge diagnosis and management
mkdir -p /home/daytona/workspace/vaginal-discharge-pdf && ls /home/daytona/workspace/
~/vaginal-discharge-pdf/build_pdf.py
"""
Clinical Decision Tree PDF - Vaginal Discharge Diagnosis & Management
Uses ReportLab to draw a full A3-landscape flowchart with colour-coded boxes.
"""
from reportlab.lib.pagesizes import A3, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import Paragraph
from reportlab.lib.enums import TA_CENTER, TA_LEFT
# ── colour palette ────────────────────────────────────────────────
C_START = colors.HexColor("#1A237E") # dark navy – start node
C_DECISION = colors.HexColor("#0277BD") # blue – decision diamond
C_PHYSIO = colors.HexColor("#2E7D32") # dark green – physiological
C_BV = colors.HexColor("#6A1B9A") # purple – BV
C_CANDIDA = colors.HexColor("#E65100") # orange – candida
C_TRICH = colors.HexColor("#C62828") # red – trichomoniasis
C_CERVICITIS = colors.HexColor("#00695C") # teal – cervicitis
C_TREATMENT = colors.HexColor("#1565C0") # mid-blue – treatment boxes
C_INVESTIGATE= colors.HexColor("#4527A0") # indigo – investigation
C_NOTE = colors.HexColor("#F9A825") # amber – note/warning
C_TEXT_LIGHT = colors.white
C_TEXT_DARK = colors.HexColor("#212121")
C_LINE = colors.HexColor("#455A64")
C_BG = colors.HexColor("#F5F5F5")
PAGE = landscape(A3)
W, H = PAGE
out_path = "/home/daytona/workspace/vaginal-discharge-pdf/Vaginal_Discharge_Decision_Tree.pdf"
c = canvas.Canvas(out_path, pagesize=PAGE)
# ── helper: wrapped text in a box ────────────────────────────────
def draw_box(c, x, y, w, h, text, fill_color, text_color=colors.white,
font="Helvetica-Bold", font_size=7.5, radius=4, border_color=None):
"""Draw a rounded-rect box with centred wrapped text."""
if border_color is None:
border_color = fill_color.clone() if hasattr(fill_color, 'clone') else fill_color
c.saveState()
c.setFillColor(fill_color)
c.setStrokeColor(C_LINE)
c.setLineWidth(0.6)
c.roundRect(x, y, w, h, radius, fill=1, stroke=1)
# text
c.setFillColor(text_color)
c.setFont(font, font_size)
lines = text.split("\n")
total_h = len(lines) * (font_size + 1.5)
start_y = y + h/2 + total_h/2 - font_size
for line in lines:
c.drawCentredString(x + w/2, start_y, line)
start_y -= (font_size + 1.5)
c.restoreState()
def draw_diamond(c, cx, cy, hw, hh, text, fill_color, font_size=7.5):
"""Draw a diamond shape for decision nodes."""
c.saveState()
c.setFillColor(fill_color)
c.setStrokeColor(C_LINE)
c.setLineWidth(0.6)
p = c.beginPath()
p.moveTo(cx, cy + hh)
p.lineTo(cx + hw, cy)
p.lineTo(cx, cy - hh)
p.lineTo(cx - hw, cy)
p.closePath()
c.drawPath(p, fill=1, stroke=1)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", font_size)
lines = text.split("\n")
total_h = len(lines) * (font_size + 1.5)
start_y = cy + total_h/2 - font_size
for line in lines:
c.drawCentredString(cx, start_y, line)
start_y -= (font_size + 1.5)
c.restoreState()
def arrow(c, x1, y1, x2, y2, label="", color=C_LINE, label_color=C_TEXT_DARK):
"""Draw an arrow between two points with optional label."""
c.saveState()
c.setStrokeColor(color)
c.setLineWidth(1.0)
c.line(x1, y1, x2, y2)
# arrowhead
import math
angle = math.atan2(y2 - y1, x2 - x1)
size = 5
c.setFillColor(color)
p = c.beginPath()
p.moveTo(x2, y2)
p.lineTo(x2 - size * math.cos(angle - 0.35),
y2 - size * math.sin(angle - 0.35))
p.lineTo(x2 - size * math.cos(angle + 0.35),
y2 - size * math.sin(angle + 0.35))
p.closePath()
c.drawPath(p, fill=1, stroke=0)
if label:
mx, my = (x1 + x2) / 2, (y1 + y2) / 2
c.setFillColor(label_color)
c.setFont("Helvetica-Bold", 6.5)
c.drawCentredString(mx, my + 3, label)
c.restoreState()
def bent_arrow(c, x1, y1, xmid, ymid, x2, y2, label="", color=C_LINE):
"""L-shaped arrow via a midpoint."""
arrow(c, x1, y1, xmid, ymid, color=color)
arrow(c, xmid, ymid, x2, y2, label=label, color=color)
# ═══════════════════════════════════════════════════════════════════
# PAGE 1 – OVERVIEW DECISION TREE
# ═══════════════════════════════════════════════════════════════════
def page1(c):
# Background
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
# Header bar
c.setFillColor(C_START)
c.rect(0, H - 22*mm, W, 22*mm, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(W/2, H - 14*mm, "CLINICAL DECISION TREE: Vaginal Discharge – Diagnosis & Management")
c.setFont("Helvetica", 8)
c.drawCentredString(W/2, H - 20*mm,
"Based on Harrison's Principles of Internal Medicine 22E | Rosen's Emergency Medicine | "
"Park's Textbook of PSM | Harriet Lane Handbook 23E | Berek & Novak's Gynecology")
# Footer
c.setFillColor(C_START)
c.rect(0, 0, W, 7*mm, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica", 7)
c.drawCentredString(W/2, 2*mm, "Page 1 of 2 | For educational use only | Always apply clinical judgment")
bw = 52*mm # standard box width
bh = 12*mm # standard box height
dw = 38*mm # diamond half-width
dh = 10*mm # diamond half-height
# ── ROW 1: START ─────────────────────────────────────────
sx = W/2 - bw/2
sy = H - 44*mm
draw_box(c, sx, sy, bw, bh,
"PATIENT WITH VAGINAL DISCHARGE", C_START, font_size=8.5)
# ── ROW 2: HISTORY diamond ──────────────────────────────
d1cx = W/2
d1cy = sy - 26*mm
draw_diamond(c, d1cx, d1cy, dw, dh,
"ASSESS CHARACTER\nOF DISCHARGE", C_DECISION, font_size=8)
arrow(c, sx + bw/2, sy, d1cx, d1cy + dh)
# Five branches from diamond
# Branch labels & positions
branches = [
# (label, cx_offset, colour, name)
("Physiological\n(clear/white,\nscant, no sx)", -2.05*bw, C_PHYSIO),
("White curdy,\npruritus\n(Candida?)", -0.95*bw, C_CANDIDA),
("Thin gray,\nfishy odour\n(BV?)", 0.05*bw, C_BV),
("Yellow-green\nfrothy, profuse\n(Trich?)", 1.10*bw, C_TRICH),
("Mucopurulent\ncervical dc\n(Cervicitis?)", 2.15*bw, C_CERVICITIS),
]
branch_y = d1cy - 26*mm
branch_centres = []
for label, xoff, col in branches:
bx = W/2 + xoff - bw/2
draw_box(c, bx, branch_y, bw, bh + 2*mm, label, col, font_size=7.5)
bxc = bx + bw/2
branch_centres.append((bxc, branch_y + bh + 2*mm, col))
# arrow from diamond
bent_arrow(c, d1cx, d1cy - dh, d1cx, branch_y + bh + 2*mm + 3*mm, bxc, branch_y + bh + 2*mm, color=col)
# ── ROW 3: INVESTIGATIONS ─────────────────────────────────
inv_y = branch_y - 25*mm
inv_data = [
("PHYSIOLOGICAL\nLEUKORRHEA\n\npH <4.5\nLactobacillus on smear\nNo inflammation", C_PHYSIO),
("VVC WORKUP\n\npH <4.5\n10% KOH → pseudohyphae\nWet mount: budding yeast", C_CANDIDA),
("BV WORKUP\n\npH >4.5 | Whiff test +ve\nClue cells on wet mount\n≥3 Amsel criteria", C_BV),
("TRICHOMONIASIS\nWORKUP\n\npH >5 | Frothy dc\nMotile trichomonads\n(NAAT preferred)", C_TRICH),
("CERVICITIS\nWORKUP\n\nEndocervical swab\nGram stain / NAAT\nfor GC + Chlamydia", C_CERVICITIS),
]
inv_centres = []
for i, ((bxc, byc, col), (itxt, icol)) in enumerate(zip(branch_centres, inv_data)):
bx = bxc - bw/2
draw_box(c, bx, inv_y, bw, 18*mm, itxt, icol, font_size=6.8, radius=3)
arrow(c, bxc, branch_y, bxc, inv_y + 18*mm)
inv_centres.append((bxc, inv_y, icol))
# ── ROW 4: MANAGEMENT ─────────────────────────────────────
mgmt_y = inv_y - 34*mm
mgmt_data = [
("NO TREATMENT\nNEEDED\n\nReassure patient\nHygiene counselling\nReview if symptoms\nappear", C_PHYSIO),
("TREAT VVC\n\nFluconazole 150 mg PO\nsingle dose\n(non-pregnant)\n\nOR topical azole\n(clotrimazole/miconazole)\n\nPregnancy: topical\n7-day azole ONLY\n(no oral fluconazole)", C_CANDIDA),
("TREAT BV\n\nMetronidazole 500 mg\nPO BD × 7 days\n\nOR Metro gel 0.75%\n5 g intravag × 5 days\n\nOR Clindamycin cream 2%\n5 g intravag × 7 days\n\nNo partner treatment", C_BV),
("TREAT TRICHOMONIASIS\n\nMetronidazole 500 mg\nPO BD × 7 days\n(preferred)\n\nOR Tinidazole\n(metronidazole-resistant)\n\nTREAT PARTNER\n(mandatory)\n\nNo intravag metro", C_TRICH),
("TREAT CERVICITIS\n(GC + Chlamydia)\n\nCefixime 400 mg PO\nsingle dose\n+\nAzithromycin 1 g PO\nsingle dose\n\nPartner notification\nmandatory", C_CERVICITIS),
]
for i, ((bxc, byc, col), (mtxt, mcol)) in enumerate(zip(inv_centres, mgmt_data)):
bx = bxc - bw/2
draw_box(c, bx, mgmt_y, bw, 28*mm, mtxt, mcol, font_size=6.5, radius=3)
arrow(c, bxc, byc, bxc, mgmt_y + 28*mm)
# ── FOLLOW-UP note bar ─────────────────────────────────────
note_y = mgmt_y - 12*mm
draw_box(c, 18*mm, note_y, W - 36*mm, 9*mm,
"FOLLOW-UP: Review all cases in 1 week | "
"BV / Trich: screen for HIV, other STIs | "
"Recurrent VVC: check for DM, HIV, antibiotic use | "
"Persistent symptoms: re-evaluate, consider NAAT / culture",
C_NOTE, text_color=C_TEXT_DARK, font_size=7.2, radius=3)
# ── SPECIAL POPULATIONS note ───────────────────────────────
sp_y = note_y - 10*mm
draw_box(c, 18*mm, sp_y, (W - 36*mm)/2 - 3*mm, 8*mm,
"PREGNANCY: Avoid oral fluconazole (all trimesters) | "
"Oral metronidazole generally safe after 1st trimester | "
"Always do per-speculum exam to exclude PPROM",
C_INVESTIGATE, font_size=6.8, radius=3)
draw_box(c, W/2 + 3*mm, sp_y, (W - 36*mm)/2 - 3*mm, 8*mm,
"PREPUBERTAL GIRLS: Consider sexual abuse | "
"Foreign body (toilet paper most common) → remove | "
"Non-specific: hygiene + short-course topical oestrogen",
C_INVESTIGATE, font_size=6.8, radius=3)
# ═══════════════════════════════════════════════════════════════════
# PAGE 2 – SYNDROMIC MANAGEMENT + COMPARISON TABLE
# ═══════════════════════════════════════════════════════════════════
def page2(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
# Header
c.setFillColor(C_DECISION)
c.rect(0, H - 22*mm, W, 22*mm, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 15)
c.drawCentredString(W/2, H - 13*mm,
"Vaginal Discharge: Syndromic Management & Diagnostic Comparison")
c.setFont("Helvetica", 8)
c.drawCentredString(W/2, H - 20*mm,
"WHO Syndromic Approach (Park's PSM) | Amsel Criteria (Harriet Lane) | Harrison's Principles 22E")
# Footer
c.setFillColor(C_START)
c.rect(0, 0, W, 7*mm, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica", 7)
c.drawCentredString(W/2, 2*mm, "Page 2 of 2 | For educational use only | Always apply clinical judgment")
# ── SYNDROMIC FLOWCHART ────────────────────────────────────
sec_y = H - 28*mm
c.setFont("Helvetica-Bold", 10)
c.setFillColor(C_START)
c.drawString(18*mm, sec_y, "SYNDROMIC MANAGEMENT (Resource-Limited / No Lab)")
c.setStrokeColor(C_START)
c.setLineWidth(1.2)
c.line(18*mm, sec_y - 1.5*mm, W/2, sec_y - 1.5*mm)
bw2 = 60*mm
# Step boxes left-to-right
steps = [
("STEP 1\nHistory & Symptoms\n\n• Amount, colour, smell\n• Pruritus / dysuria\n• Partner symptoms\n• LMP / pregnancy", C_DECISION),
("STEP 2\nPer Speculum Exam\n\n• Frothy green → Trich\n• Curdy white → Candida\n• Thin adherent → BV\n• Mucopurulent cx → GC/CT", C_INVESTIGATE),
("STEP 3\nTreat Vaginitis\n(TV + BV + Candida)\n\nSecnidazole 2 g PO stat\n+ Fluconazole 150 mg PO\n+ Metoclopramide (prophylactic)", C_BV),
("STEP 4\nIf Cervicitis Present\nAlso Add:\n\nCefixime 400 mg PO\n+ Azithromycin 1 g PO", C_CERVICITIS),
("STEP 5\nCounselling\n\n• Partner notification\n• Avoid douching\n• Condom use\n• Follow-up 1 week", C_PHYSIO),
]
sx_start = 18*mm
step_y = sec_y - 30*mm
prev_ex = sx_start + bw2
for i, (stxt, scol) in enumerate(steps):
bx = sx_start + i * (bw2 + 8*mm)
draw_box(c, bx, step_y - 22*mm, bw2, 22*mm, stxt, scol, font_size=7, radius=4)
if i > 0:
arrow(c, bx - 8*mm + 3*mm, step_y - 22*mm + 11*mm,
bx - 1*mm, step_y - 22*mm + 11*mm, color=C_LINE)
# Pregnancy note box under steps
preg_y = step_y - 22*mm - 14*mm
draw_box(c, 18*mm, preg_y, W/2 - 26*mm, 11*mm,
"PREGNANCY (Syndromic)\n"
"1st trimester: Clotrimazole pessary ONLY (no oral fluconazole, no oral metro)\n"
"2nd/3rd trim: Metronidazole intravag + clotrimazole pessary\n"
"Cervicitis: Cefixime + Azithromycin safe in pregnancy",
C_NOTE, text_color=C_TEXT_DARK, font_size=6.8, radius=3)
# ── COMPARISON TABLE ───────────────────────────────────────
# Position: right half of page
tbl_x = W/2 + 5*mm
tbl_y_top = H - 28*mm - 3*mm
tbl_w = W/2 - 23*mm
c.setFont("Helvetica-Bold", 10)
c.setFillColor(C_START)
c.drawString(tbl_x, tbl_y_top, "DIAGNOSTIC COMPARISON TABLE")
c.setStrokeColor(C_START)
c.setLineWidth(1.2)
c.line(tbl_x, tbl_y_top - 1.5*mm, W - 18*mm, tbl_y_top - 1.5*mm)
col_headers = ["Feature", "Physiological", "BV", "VVC", "Trichomoniasis"]
col_colors = [C_START, C_PHYSIO, C_BV, C_CANDIDA, C_TRICH]
rows = [
["Colour", "Clear/white", "White/gray", "White", "Yellow-green"],
["Consistency", "Heterogeneous", "Homogeneous thin", "Curdy/adherent", "Frothy"],
["Amount", "Scant", "Moderate", "Scant-moderate", "Profuse"],
["Odour", "None", "Fishy (+whiff)", "None", "May be fishy"],
["Pruritus", "No", "No", "Prominent", "Yes (vulvar)"],
["Inflammation", "No", "No", "Yes (erythema)", "Yes (strawberry cx)"],
["pH", "<4.5", ">4.5", "<4.5", ">5.0"],
["KOH prep", "–", "+Whiff amine", "+Pseudohyphae", "May be +"],
["Wet mount", "Lactobacilli", "Clue cells", "Budding yeast", "Motile trichomonads"],
["WBCs", "Few", "Few", "Many", "Many"],
["Causative org", "Normal flora", "G.vaginalis +", "C.albicans", "T.vaginalis"],
["", "", "anaerobes", "", ""],
["Tx (1st line)", "None", "Metro 500mg BD×7d","Fluconazole 150mg","Metro 500mg BD×7d"],
["Partner Tx", "N/A", "Not needed", "Not needed", "Mandatory"],
["Pregnancy Tx", "N/A", "Metro intravag", "Clotrimazole pess","Metro intravag"],
]
col_w = [tbl_w * f for f in [0.22, 0.165, 0.165, 0.165, 0.185]]
row_h = 6.2*mm
tbl_y = tbl_y_top - 8*mm
# header row
cx = tbl_x
for j, (hdr, hcol) in enumerate(zip(col_headers, col_colors)):
draw_box(c, cx, tbl_y, col_w[j], row_h + 1*mm, hdr, hcol, font_size=7.5, radius=2)
cx += col_w[j] + 0.3*mm
# data rows
alt_colors = [colors.HexColor("#ECEFF1"), colors.white]
for i, row in enumerate(rows):
row_y = tbl_y - (i + 1) * (row_h + 0.3*mm)
cx = tbl_x
bg = alt_colors[i % 2]
for j, cell in enumerate(row):
cell_col = bg if j > 0 else colors.HexColor("#E3F2FD")
draw_box(c, cx, row_y, col_w[j], row_h, cell,
cell_col, text_color=C_TEXT_DARK,
font="Helvetica" if j > 0 else "Helvetica-Bold",
font_size=6.5, radius=1)
cx += col_w[j] + 0.3*mm
# Amsel Criteria box
amsel_y = tbl_y - (len(rows) + 1) * (row_h + 0.3*mm) - 3*mm
draw_box(c, tbl_x, amsel_y, tbl_w, 14*mm,
"AMSEL CRITERIA for BV (≥3 of 4 required)\n"
"1. Homogeneous thin grayish-white discharge\n"
"2. Vaginal pH >4.5\n"
"3. Positive whiff-amine test (10% KOH)\n"
"4. Clue cells on wet mount",
C_BV, font_size=7, radius=3)
# ── RENDER BOTH PAGES ─────────────────────────────────────────────
page1(c)
c.showPage()
page2(c)
c.save()
print(f"PDF saved to: {out_path}")
cd /home/daytona/workspace/vaginal-discharge-pdf && python build_pdf.py 2>&1
~/vaginal-discharge-pdf/build_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/vaginal-discharge-pdf/build_pdf.py'
cd /home/daytona/workspace/vaginal-discharge-pdf && python build_pdf.py 2>&1
~/vaginal-discharge-pdf/build_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/vaginal-discharge-pdf/build_pdf.py'
cd /home/daytona/workspace/vaginal-discharge-pdf && python build_pdf.py 2>&1
ls -lh /home/daytona/workspace/vaginal-discharge-pdf/Vaginal_Discharge_Decision_Tree.pdf
Vaginal Discharge Decision Tree
PDF Document · PDF