~/peds-opd-guide/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas as pdfcanvas
OUTPUT = "/tmp/workspace/peds-opd-guide/Pediatric_OPD_History_Taking_Guide.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MED_BLUE = colors.HexColor("#2e6da4")
LIGHT_BLUE = colors.HexColor("#d6e8f7")
TEAL = colors.HexColor("#0e7c7b")
TEAL_LIGHT = colors.HexColor("#d0efef")
ORANGE = colors.HexColor("#e07b39")
ORANGE_LT = colors.HexColor("#fdebd0")
RED = colors.HexColor("#c0392b")
RED_LIGHT = colors.HexColor("#fadbd8")
GREEN = colors.HexColor("#1e8449")
GREEN_LT = colors.HexColor("#d5f5e3")
GRAY_BG = colors.HexColor("#f4f6f8")
GRAY_LINE = colors.HexColor("#bdc3c7")
WHITE = colors.white
BLACK = colors.HexColor("#1a1a1a")
# ── Page header/footer callback ──────────────────────────────────────────────
def draw_header_footer(c, doc):
w, h = A4
# top bar
c.setFillColor(DARK_BLUE)
c.rect(0, h - 22*mm, w, 22*mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 13)
c.drawCentredString(w/2, h - 13*mm, "PEDIATRIC OPD — HISTORY TAKING REFERENCE GUIDE")
c.setFont("Helvetica", 8)
c.drawRightString(w - 15*mm, h - 18*mm, "Harriet Lane Handbook, 23rd ed. | IMCI | Standard Pediatric Practice")
# bottom bar
c.setFillColor(DARK_BLUE)
c.rect(0, 0, w, 10*mm, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica", 7.5)
c.drawString(15*mm, 3.5*mm, "For clinical reference only — always exercise individual clinical judgment")
c.drawRightString(w - 15*mm, 3.5*mm, f"Page {doc.page}")
# ── Document setup ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=14*mm, rightMargin=14*mm,
topMargin=27*mm, bottomMargin=16*mm,
title="Pediatric OPD History Taking Reference Guide",
author="Orris Medical Reference",
subject="Pediatrics"
)
styles = getSampleStyleSheet()
# Custom styles
def style(name, parent="Normal", **kw):
return ParagraphStyle(name, parent=styles[parent], **kw)
H1 = style("H1", fontSize=13, textColor=WHITE, backColor=DARK_BLUE,
spaceAfter=2, spaceBefore=6, leftIndent=4, rightIndent=4,
leading=18, fontName="Helvetica-Bold", alignment=TA_LEFT,
borderPadding=(4, 6, 4, 6))
H2 = style("H2", fontSize=10.5, textColor=WHITE, backColor=MED_BLUE,
spaceAfter=2, spaceBefore=5, leftIndent=4,
leading=15, fontName="Helvetica-Bold",
borderPadding=(3, 5, 3, 5))
H3 = style("H3", fontSize=9.5, textColor=DARK_BLUE,
spaceAfter=1, spaceBefore=4,
fontName="Helvetica-Bold", leading=13)
BODY = style("BODY", fontSize=8.5, leading=12, spaceAfter=2,
fontName="Helvetica", textColor=BLACK)
SMALL = style("SMALL", fontSize=7.5, leading=11, spaceAfter=1,
fontName="Helvetica", textColor=BLACK)
BULLET = style("BULLET", fontSize=8.5, leading=12, leftIndent=10,
bulletIndent=2, spaceAfter=1, fontName="Helvetica",
textColor=BLACK)
RED_FLAG = style("REDFLAG", fontSize=8.5, leading=12, fontName="Helvetica-Bold",
textColor=RED)
NOTE = style("NOTE", fontSize=8, leading=11, fontName="Helvetica-Oblique",
textColor=colors.HexColor("#555555"))
TITLE_MAIN = style("TITLEMAIN", fontSize=22, textColor=DARK_BLUE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=26)
TITLE_SUB = style("TITLESUB", fontSize=11, textColor=MED_BLUE,
fontName="Helvetica", alignment=TA_CENTER, leading=15)
story = []
# ═══════════════════════════════════════════════════════════════════════════════
# TITLE PAGE BLOCK
# ═══════════════════════════════════════════════════════════════════════════════
story.append(Spacer(1, 8*mm))
story.append(Paragraph("🩺 Pediatric OPD", TITLE_MAIN))
story.append(Paragraph("History Taking & Diagnostic Approach", TITLE_SUB))
story.append(Spacer(1, 3*mm))
story.append(HRFlowable(width="100%", thickness=2, color=MED_BLUE))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"A comprehensive quick-reference guide for clinicians, residents, and medical students covering "
"structured history taking, symptom-specific questionnaires, red flags, and diagnostic frameworks "
"for common presentations in pediatric outpatient practice.",
style("IntroP", fontSize=9, leading=13, alignment=TA_JUSTIFY, textColor=BLACK,
fontName="Helvetica-Oblique", spaceAfter=3)
))
story.append(HRFlowable(width="100%", thickness=1, color=GRAY_LINE))
story.append(Spacer(1, 4*mm))
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 1 — FRAMEWORK
# ═══════════════════════════════════════════════════════════════════════════════
def section_heading(text, color=DARK_BLUE, bg=LIGHT_BLUE):
data = [[Paragraph(f"<b>{text}</b>",
style("SH", fontSize=11, textColor=color,
fontName="Helvetica-Bold", leading=15))]]
t = Table(data, colWidths=[182*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LINEBELOW", (0,0), (-1,-1), 1.5, color),
]))
return t
def sub_heading(text, color=MED_BLUE):
data = [[Paragraph(f"<b>{text}</b>",
style("SubH", fontSize=9.5, textColor=WHITE,
fontName="Helvetica-Bold", leading=13))]]
t = Table(data, colWidths=[182*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("LEFTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
]))
return t
def make_table(headers, rows, col_widths=None, hdr_bg=MED_BLUE, alt_bg=LIGHT_BLUE):
"""Build a styled table with header row."""
hdr_style = style("TH", fontSize=8, textColor=WHITE,
fontName="Helvetica-Bold", leading=11)
cell_style = style("TD", fontSize=8, textColor=BLACK,
fontName="Helvetica", leading=11)
data = [[Paragraph(h, hdr_style) for h in headers]]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), cell_style) for c in row])
n_cols = len(headers)
if col_widths is None:
col_widths = [182*mm / n_cols] * n_cols
t = Table(data, colWidths=col_widths, repeatRows=1)
ts = [
("BACKGROUND", (0, 0), (-1, 0), hdr_bg),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [WHITE, alt_bg]),
("GRID", (0, 0), (-1, -1), 0.4, GRAY_LINE),
("LEFTPADDING", (0, 0), (-1, -1), 5),
("RIGHTPADDING", (0, 0), (-1, -1), 5),
("TOPPADDING", (0, 0), (-1, -1), 3),
("BOTTOMPADDING", (0, 0), (-1, -1), 3),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]
t.setStyle(TableStyle(ts))
return t
def bullet(text, bold_prefix=None):
if bold_prefix:
return Paragraph(f"• <b>{bold_prefix}</b> {text}", BULLET)
return Paragraph(f"• {text}", BULLET)
def sp(n=1):
return Spacer(1, n * 2*mm)
# ─────────────────────────────────────────────────────────────────────────────
story.append(section_heading("SECTION 1 — FRAMEWORK OF PEDIATRIC HISTORY TAKING"))
story.append(sp(1))
# General principles box
gp_data = [
[Paragraph("<b>Principle</b>", style("X", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold")),
Paragraph("<b>Key Consideration</b>", style("X", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold"))],
["Source of history", "Parent/guardian primarily; involve child aged >4 yrs in their own account"],
["Developmental framing", "Tailor questions to the child's age and developmental stage"],
["Observation", "Begin assessment the moment the child enters the room (activity, comfort, interaction)"],
["Adolescent privacy", "Offer private time alone with clinician for patients >12 years (HEADSSS)"],
["Reliability of informant", "Note who provided history and assess reliability/completeness"],
]
gp_table = make_table([], [], col_widths=[60*mm, 122*mm])
# Re-build manually for two-col with header
cell_s = style("GPC", fontSize=8.5, textColor=BLACK, fontName="Helvetica", leading=12)
hdr_s = style("GPH", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=12)
gp_rows = [
[Paragraph("<b>Principle</b>", hdr_s), Paragraph("<b>Key Consideration</b>", hdr_s)],
[Paragraph("Source of history", cell_s), Paragraph("Parent/guardian primarily; involve child aged >4 yrs in their own account", cell_s)],
[Paragraph("Developmental framing", cell_s), Paragraph("Tailor questions to the child's age and developmental stage", cell_s)],
[Paragraph("Observation", cell_s), Paragraph("Begin assessment the moment the child enters the room (activity, comfort, interaction)", cell_s)],
[Paragraph("Adolescent privacy", cell_s), Paragraph("Offer private time alone with clinician for patients >12 yrs (HEADSSS assessment)", cell_s)],
[Paragraph("Informant reliability", cell_s), Paragraph("Note who provided history and assess completeness and reliability", cell_s)],
]
gp_t = Table(gp_rows, colWidths=[58*mm, 124*mm])
gp_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MED_BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(gp_t)
story.append(sp(1))
# Standard components
story.append(sub_heading("Standard Components of Every Pediatric History"))
story.append(sp(1))
comp_s = style("CPS", fontSize=8.5, textColor=BLACK, fontName="Helvetica", leading=12)
comp_h = style("CPH", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=12)
comp_rows = [
[Paragraph("<b>#</b>", comp_h), Paragraph("<b>Component</b>", comp_h), Paragraph("<b>Key Questions / Details</b>", comp_h)],
[Paragraph("1", comp_s), Paragraph("Identifying Information", comp_s),
Paragraph("Name, age, DOB, sex, weight; informant name and relationship; referral source", comp_s)],
[Paragraph("2", comp_s), Paragraph("Chief Complaint (CC)", comp_s),
Paragraph("One or two sentences in informant's own words; duration of complaint", comp_s)],
[Paragraph("3", comp_s), Paragraph("History of Present Illness (HPI)", comp_s),
Paragraph("<b>OLD CARTS:</b> Onset · Location · Duration · Character · Associated Sx · Relieving/Aggravating · Timing · Severity (impact on feeding, sleep, activity, school)", comp_s)],
[Paragraph("4", comp_s), Paragraph("Past Medical History", comp_s),
Paragraph("Birth history (GA, mode of delivery, birth weight, APGAR, NICU); neonatal illness; previous hospitalizations, surgeries, accidents", comp_s)],
[Paragraph("5", comp_s), Paragraph("Immunization History", comp_s),
Paragraph("Up to date? Which vaccines given? Any adverse reactions?", comp_s)],
[Paragraph("6", comp_s), Paragraph("Developmental History", comp_s),
Paragraph("Gross motor · Fine motor · Language · Social milestones; school performance; any regression?", comp_s)],
[Paragraph("7", comp_s), Paragraph("Nutritional / Feeding", comp_s),
Paragraph("Infants: BF vs formula, duration, solids introduction. Older: diet variety, appetite, growth trend", comp_s)],
[Paragraph("8", comp_s), Paragraph("Medications & Allergies", comp_s),
Paragraph("All Rx, OTC, herbal, supplements; drug/food allergies with type and severity of reaction", comp_s)],
[Paragraph("9", comp_s), Paragraph("Family History", comp_s),
Paragraph("Hereditary/genetic conditions; cardiac disease (premature death/disability <50 yrs: HCM, Long-QT, Marfan, arrhythmias); asthma, DM, epilepsy, TB, consanguinity", comp_s)],
[Paragraph("10", comp_s), Paragraph("Social History", comp_s),
Paragraph("Household composition; housing stability; daycare/school; pets, travel; passive smoke exposure; socioeconomic status", comp_s)],
]
comp_t = Table(comp_rows, colWidths=[8*mm, 42*mm, 132*mm])
comp_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ALIGN", (0,0), (0,-1), "CENTER"),
]))
story.append(comp_t)
story.append(sp(2))
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 2 — SYMPTOM-SPECIFIC QUESTIONNAIRES
# ═══════════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(section_heading("SECTION 2 — SYMPTOM-SPECIFIC QUESTIONNAIRES & DIAGNOSTIC APPROACH"))
story.append(sp(1))
# ── helper: symptom block ────────────────────────────────────────────────────
def symptom_block(number, title, color, q_rows, dx_rows, q_col_w=None, dx_col_w=None):
"""Build a symptom card with questions table + diagnostic approach table."""
items = []
# Title bar
title_data = [[Paragraph(f"<b>{number}. {title}</b>",
style("ST", fontSize=10, textColor=WHITE,
fontName="Helvetica-Bold", leading=14))]]
title_t = Table(title_data, colWidths=[182*mm])
title_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("LEFTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
items.append(title_t)
qs = style("QS", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=11)
qh = style("QH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=11)
# Q table
if q_col_w is None:
q_col_w = [72*mm, 110*mm]
q_data = [[Paragraph("<b>Question / Area</b>", qh), Paragraph("<b>Details to Elicit</b>", qh)]]
for r in q_rows:
q_data.append([Paragraph(r[0], qs), Paragraph(r[1], qs)])
qt = Table(q_data, colWidths=q_col_w)
qt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), color),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, colors.HexColor("#eef4fb")]),
("GRID", (0,0), (-1,-1), 0.3, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
items.append(qt)
# Diagnostic approach sub-header
dx_head = [[Paragraph("<b>▸ Diagnostic Approach</b>",
style("DX", fontSize=8, textColor=WHITE,
fontName="Helvetica-Bold", leading=11))]]
dx_ht = Table(dx_head, colWidths=[182*mm])
dx_ht.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("LEFTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
]))
items.append(dx_ht)
# DX table
if dx_col_w is None:
dx_col_w = [72*mm, 110*mm]
dh = style("DH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=11)
ds = style("DS", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=11)
dx_data = [[Paragraph("<b>Pattern / Finding</b>", dh), Paragraph("<b>Key Diagnosis</b>", dh)]]
for r in dx_rows:
dx_data.append([Paragraph(r[0], ds), Paragraph(r[1], ds)])
dxt = Table(dx_data, colWidths=dx_col_w)
dxt.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, TEAL_LIGHT]),
("GRID", (0,0), (-1,-1), 0.3, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
items.append(dxt)
items.append(sp(1.5))
return KeepTogether(items)
# ── 1. FEVER ─────────────────────────────────────────────────────────────────
story.append(symptom_block(1, "FEVER", MED_BLUE,
q_rows=[
("Onset & duration", "How many days? Sudden or gradual onset?"),
("Pattern", "Continuous? Remittent? Intermittent (quotidian/tertian)? Evening spikes?"),
("Height of temperature", "Maximum recorded; how measured (axillary, oral, rectal)?"),
("Response to antipyretics", "Paracetamol/ibuprofen — partial or complete? Duration of response?"),
("Associated symptoms", "Rash, rigors/chills, seizure, vomiting, diarrhea, cough, ear pain, sore throat, dysuria, headache, joint pain"),
("Localizing clues", "Redness/swelling, lymph node swelling, neck stiffness, bulging fontanelle"),
("Exposure history", "Contact with sick persons, travel, animal exposure, recent immunization"),
("Danger signs (WHO/IMCI)", "Unable to drink/breastfeed, persistent vomiting, seizure, lethargic, stiff neck, petechiae/purpura"),
],
dx_rows=[
("<3 months age + fever", "Emergency — full sepsis workup (CBC, blood culture, urine culture, LP)"),
("Fever + non-blanching petechiae/purpura", "<b>Meningococcemia</b> — emergency, IV antibiotics immediately"),
("Fever >5 days + conjunctivitis + rash + strawberry tongue", "Kawasaki disease — echocardiogram, IVIG"),
("Fever + rash cephalocaudal + Koplik spots", "Measles — check immunization status"),
("Remittent fever + relative bradycardia + rose spots", "Enteric (typhoid) fever — Widal, blood culture"),
("Fever + rigors + splenomegaly (endemic area)", "Malaria — thick/thin blood film, RDT"),
("Fever >7 days, no clear source", "Consider TB, lymphoma, JIA, malaria, enteric fever"),
]
))
# ── 2. COUGH ──────────────────────────────────────────────────────────────────
story.append(symptom_block(2, "COUGH", colors.HexColor("#5b7fa6"),
q_rows=[
("Duration", "Acute (<3 weeks), subacute (3–8 weeks), chronic (>8 weeks)?"),
("Character", "Dry/productive? Barking (croup)? Whooping (pertussis)? Brassy? Bovine?"),
("Sputum", "Color, amount, blood-tinged?"),
("Triggers", "Cold air, exercise, allergens, smoke, feeding, positional changes?"),
("Associated symptoms", "Fever, runny nose, wheeze, stridor, difficulty breathing, poor feeding, weight loss, night sweats"),
("Pattern", "Day vs. night predominant? Episodic vs. continuous? Worse on waking?"),
("Response to treatment", "Bronchodilators, antibiotics, antihistamines?"),
("Background history", "Choking episode (FB?), recurrent pneumonias, atopy, family asthma, TB contact"),
],
dx_rows=[
("Barking cough + inspiratory stridor + hoarse voice, night worsening", "Croup (Laryngotracheobronchitis) — viral, age 6m–3yr"),
("Paroxysmal whoop + post-tussive vomiting + apnea in infants", "Pertussis — PCR nasopharyngeal swab"),
("Episodic wheeze + nocturnal cough + exercise/allergen trigger + atopy", "Asthma — spirometry, trial of bronchodilator"),
("First wheeze in infant <2yr + rhinorrhea + low-grade fever", "Bronchiolitis (RSV) — supportive care"),
("Sudden onset after choking/playing with small objects", "Foreign body aspiration — CXR, bronchoscopy"),
("Chronic productive cough + failure to thrive + steatorrhea", "Cystic fibrosis — sweat chloride test"),
("Cough + fever + tachypnea + crackles", "Pneumonia — CXR, CBC"),
]
))
# ── 3. DIARRHEA ───────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(symptom_block(3, "DIARRHEA", colors.HexColor("#4a7c59"),
q_rows=[
("Onset & duration", "Acute (<14 days) or persistent/chronic (>14 days)?"),
("Frequency & volume", "How many stools/day? Large or small volume per stool?"),
("Character", "Watery, mucoid, bloody (dysentery), greasy/frothy (malabsorption)?"),
("Color", "Yellow (rotavirus), green (bile), pale/fatty (cholestasis/malabsorption)"),
("Blood/mucus", "Grossly bloody = dysentery — Shigella, E. coli, ameba"),
("Associated symptoms", "Fever, vomiting, abdominal cramps, rash, weight loss, perianal itch"),
("Feeding/dietary history", "Recent food change, breastfed vs. formula, new food introduction, unsafe water"),
("Dehydration assessment", "Urine output, tears, activity level, able to drink?"),
("Danger signs", "Sunken eyes, absent tears, very dry mouth, skin pinch returns slowly, lethargy"),
],
dx_rows=[
("Acute watery, afebrile/low fever, age <5yr", "Viral gastroenteritis (Rotavirus most common) — ORS, zinc"),
("Bloody mucoid stool + high fever + cramps", "Bacillary dysentery (Shigella most common) — stool culture, antibiotics"),
("Watery diarrhea + rice-water stool + no fever (endemic)", "Cholera — RDT, IV rehydration, doxycycline"),
("Frothy, offensive, greasy stool + bloating + no fever, chronic", "Giardia / Malabsorption — stool O&P, sweat test"),
("Diarrhea >14 days + weight loss", "Persistent diarrhea — assess for celiac, IBD, immunodeficiency"),
("Post-antibiotic diarrhea + pseudomembranes", "C. difficile — stool toxin assay"),
]
))
# ── 4. VOMITING ───────────────────────────────────────────────────────────────
story.append(symptom_block(4, "VOMITING", colors.HexColor("#7d5a9e"),
q_rows=[
("Age of onset", "Newborn vs. infant vs. older child — age determines differential"),
("Character", "Projectile (pyloric stenosis)? Bilious (bowel obstruction)? Blood-streaked?"),
("Frequency & volume", "How many times/day? Effortless regurgitation vs. forceful vomiting?"),
("Timing relative to feeds", "Immediately after every feed (GERD)? Hours after? Unrelated?"),
("Associated symptoms", "Fever, diarrhea, headache, abdominal pain, distension, lethargy, jaundice"),
("Growth & hydration", "Weight gain adequate? Wet diapers? Feeding well between vomits?"),
("Neurological signs", "Headache, visual change, altered sensorium (raised ICP)?"),
],
dx_rows=[
("Newborn: bilious vomiting + abdominal distension", "<b>Surgical emergency</b> — malrotation/volvulus, intestinal atresia"),
("Age 2–8 wk: forceful projectile non-bilious vomiting + hungry baby", "Hypertrophic pyloric stenosis — US abdomen, pyloromyotomy"),
("Infant: vomiting after every feed + arching back + poor weight gain", "Gastroesophageal reflux (GERD) — trial feed thickener/PPI"),
("Any age + fever + diarrhea + nausea", "Viral gastroenteritis"),
("Vomiting + severe headache + papilledema + altered sensorium", "Raised ICP — CT head, neurosurgery"),
("Episodic stereotyped vomiting, well between episodes, family migraine hx", "Cyclic vomiting syndrome"),
("Colicky pain + vomiting + currant-jelly stool, age 3m–2yr", "Intussusception — US abdomen, air enema"),
]
))
# ── 5. ABDOMINAL PAIN ─────────────────────────────────────────────────────────
story.append(symptom_block(5, "ABDOMINAL PAIN", colors.HexColor("#b5651d"),
q_rows=[
("Location", "Periumbilical, RIF, epigastric, LIF, suprapubic, diffuse?"),
("Character", "Colicky (crampy, comes and goes) vs. constant? Sharp vs. dull?"),
("Severity", "1–10 scale; does it wake from sleep? (organic if yes)"),
("Duration & pattern", "Acute vs. chronic/recurrent (>3 episodes in 3 months)?"),
("Associated symptoms", "Fever, vomiting, diarrhea, constipation, hematuria, dysuria, jaundice, pallor"),
("Relation to food", "Before or after eating? Specific food triggers? Relieves with defecation?"),
("School/social stressors", "Pain only on school days? Anxiety, bullying? (functional pain)"),
("In adolescent females", "Menstrual history, dysmenorrhea, last menstrual period, sexually active?"),
],
dx_rows=[
("RIF pain + fever + anorexia + rebound tenderness + migration from periumbilicus", "Appendicitis — Alvarado score, USS, WBC"),
("Colicky pain + vomiting + sausage mass + currant-jelly stool (3m–2yr)", "Intussusception — US abdomen, air enema"),
("Diffuse pain + constipation + palpable fecal mass", "Constipation (most common cause in OPD)"),
("Recurrent periumbilical pain, no organic features, school-aged", "Functional abdominal pain — Rome IV criteria"),
("Epigastric pain + nausea + hematemesis", "PUD / H. pylori — UBT, endoscopy"),
("RIF/LIF pain + dysuria + frequency + fever", "UTI / Pyelonephritis — urine MCS"),
("Severe RUQ pain + jaundice + clay stools + fever", "Biliary/hepatic cause — LFTs, USS abdomen"),
]
))
# ── 6. RESPIRATORY DISTRESS ───────────────────────────────────────────────────
story.append(PageBreak())
story.append(symptom_block(6, "RESPIRATORY DISTRESS / DIFFICULTY BREATHING", colors.HexColor("#1a6b8a"),
q_rows=[
("Onset", "Sudden (FB, anaphylaxis) or gradual (pneumonia, asthma)?"),
("Type of breathing difficulty", "Stridor (inspiratory = upper airway) vs. wheeze (expiratory = lower airway)?"),
("Severity", "Ability to feed, speak, cry; color change (central cyanosis)?"),
("Choking history", "Sudden onset while eating/playing with small objects — FB aspiration?"),
("Fever", "Infection: croup, bronchiolitis, pneumonia, epiglottitis"),
("Previous similar episodes", "Recurrent bronchospasm — asthma; recurrent pneumonia — CF, immune deficiency"),
("Atopic history", "Personal/family history of eczema, allergic rhinitis, food allergy?"),
("Age and season", "Infant <2yr in winter — bronchiolitis (RSV); toddler with barking cough — croup"),
],
dx_rows=[
("Infant <2yr + wheeze + rhinorrhea + low-grade fever (winter season)", "Bronchiolitis (RSV) — supportive, nasal suctioning, O2"),
("Child 6m–3yr + barking cough + stridor + hoarse voice + night worsening", "Croup — Westley score, oral/nebulized dexamethasone"),
("Sudden onset wheeze/stridor after eating/playing (any age)", "Foreign body aspiration — CXR, rigid bronchoscopy"),
("Recurrent episodic wheeze + nocturnal cough + trigger-related + atopy", "Asthma — GINA guidelines, peak flow, spirometry"),
("High fever + tachypnea + crackles + dullness to percussion", "Pneumonia — CXR (lobar/patchy), CBC, SpO2"),
("Toxic, drooling, refuses to swallow + muffled voice + no spontaneous cough", "Epiglottitis — do NOT examine throat; ENT, airway team"),
]
))
# ── 7. SEIZURES ───────────────────────────────────────────────────────────────
story.append(symptom_block(7, "SEIZURES", colors.HexColor("#6c3483"),
q_rows=[
("Description by witness", "Tonic? Clonic? Tonic-clonic? Myoclonic? Absence? Focal? Eye deviation?"),
("Duration", "<5 min (simple) vs. >5 min (prolonged/status epilepticus)?"),
("Fever associated", "Febrile (most common) vs. afebrile seizure?"),
("Postictal state", "Confusion, drowsiness, focal weakness (Todd's paralysis) after seizure?"),
("Frequency", "First episode or recurrent? Clustering?"),
("Onset age", "Neonatal (<28 days) vs. infant vs. childhood epilepsy?"),
("Precipitants", "Missed medication, metabolic, sleep deprivation, head trauma, toxin?"),
("Family history", "Epilepsy, febrile seizures, genetic metabolic disorders"),
("Development", "Normal milestones before seizure? Any regression of skills?"),
],
dx_rows=[
("Generalized <15 min + fever + age 6m–5yr + no neurological deficit post-ictally", "Simple febrile seizure — reassure, treat fever, no AED"),
("Focal/prolonged/multiple in 24h OR neurological deficit post-ictally + fever", "Complex febrile seizure — LP, EEG, neuroimaging"),
("First afebrile seizure", "EEG + MRI brain; check glucose, electrolytes, calcium, LFTs"),
("Neonatal seizure (day 1–3)", "HIE, hypoglycemia, hypocalcemia, sepsis — urgent glucose, Ca, Mg, ABG"),
("Seizure >5 min / status epilepticus", "IV/IM lorazepam — Appleton protocol; call senior immediately"),
("Absence episodes — brief staring, eye flutter, abrupt onset/offset, no post-ictal", "Childhood absence epilepsy — EEG (3Hz SWD), ethosuximide"),
]
))
# ── 8. RASH ───────────────────────────────────────────────────────────────────
story.append(symptom_block(8, "RASH / SKIN LESIONS", colors.HexColor("#c0392b"),
q_rows=[
("Distribution", "Face, trunk, extremities, palms & soles, mucous membranes, hairline?"),
("Morphology", "Macular, papular, vesicular, pustular, urticarial, petechial/purpuric?"),
("Evolution", "Where did it start? How did it spread? Changing character?"),
("Pruritus", "Itchy (allergic, eczema, scabies, urticaria) vs. non-itchy (viral exanthem)?"),
("Fever preceding rash", "Viral exanthem, Kawasaki, meningococcemia — timing relative to fever?"),
("New food/drug/contact", "New medication, food, detergent, plant/animal contact in past 2 weeks?"),
("Immunization status", "Measles, varicella, rubella vaccines given?"),
("Contacts with similar illness", "School/household contacts with same rash or fever?"),
],
dx_rows=[
("Maculopapular cephalocaudal spread + Koplik spots + cough/coryza/conjunctivitis", "Measles — notify public health, vitamin A"),
("Rose-pink macular rash after fever suddenly defervesces, age 6m–2yr", "Roseola infantum (HHV-6)"),
("'Slapped cheek' + lacy truncal rash, child well", "Erythema infectiosum (Parvovirus B19)"),
("Vesicles in crops — centripetal + scalp involvement + itchy", "Varicella (chickenpox) — acyclovir if complicated"),
("Petechiae/purpura + fever + ill-looking + rapidly progressive", "<b>Meningococcemia — EMERGENCY</b>; IM benzylpenicillin NOW"),
("Fever >5d + bilateral conjunctivitis + rash + strawberry tongue + extremity changes", "Kawasaki disease — echo, IVIG + aspirin"),
("Intensely itchy burrows in web spaces/genitalia, nocturnal, household members affected", "Scabies — permethrin 5% cream"),
("Moist weeping rash in flexures, atopic child, chronic/relapsing", "Atopic dermatitis (eczema) — emollients, topical steroids"),
]
))
# ── 9. FAILURE TO THRIVE ─────────────────────────────────────────────────────
story.append(PageBreak())
story.append(symptom_block(9, "FAILURE TO THRIVE / POOR GROWTH", colors.HexColor("#2e7d32"),
q_rows=[
("Growth chart trend", "Crossing centile lines downward? Over how many months?"),
("Feeding history", "BF vs. formula (correct preparation?), amounts, frequency, solids"),
("Appetite", "Poor appetite (inadequate intake) vs. adequate intake with poor weight gain?"),
("Stool pattern", "Bulky, frothy stools (malabsorption); loose/fatty (celiac, CF); constipation"),
("Recurrent infections", "Frequent pneumonias/otitis — immunodeficiency, CF, HIV"),
("Vomiting/regurgitation", "GERD, pyloric stenosis — losing calories?"),
("Developmental milestones", "Normal or delayed? (developmental delay suggests syndromic/metabolic)"),
("Psychosocial factors", "Caregiver depression, neglect, poverty, food security, feeding relationship"),
("Family heights", "Mid-parental height — familial short stature?"),
("Systems review", "Cardiac disease (cyanotic CHD), renal, hematologic, endocrine causes"),
],
dx_rows=[
("Inadequate intake + caregiver concern / poverty / poor knowledge", "Non-organic FTT — dietitian input, social support"),
("Adequate intake but poor weight gain + frothy stools + bloating", "Malabsorption — celiac (anti-tTG IgA), CF (sweat test), Giardia"),
("FTT + recurrent chest infections + bronchiectasis + steatorrhea", "Cystic fibrosis — sweat chloride, CFTR mutation panel"),
("FTT + recurrent infections + lymphopenia", "Primary immunodeficiency — immunoglobulins, lymphocyte subsets"),
("Short stature + delayed bone age + low GH", "Growth hormone deficiency — IGF-1, GH stimulation test"),
("FTT + dysmorphic features + developmental delay", "Genetic/chromosomal syndrome — karyotype, microarray"),
]
))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 3 — ADOLESCENT HEEADSSS
# ─────────────────────────────────────────────────────────────────────────────
story.append(section_heading("SECTION 3 — ADOLESCENT HISTORY: HEEADSSS ASSESSMENT",
color=DARK_BLUE, bg=ORANGE_LT))
story.append(sp(1))
headsss_style = style("HDS", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=12)
headsss_hdr = style("HDH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=12)
headsss_data = [
[Paragraph("<b>Letter</b>", headsss_hdr),
Paragraph("<b>Domain</b>", headsss_hdr),
Paragraph("<b>Sample Questions to Ask</b>", headsss_hdr)],
[Paragraph("<b>H</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Home", headsss_style),
Paragraph("Who do you live with? Any recent changes? Do you feel safe at home?", headsss_style)],
[Paragraph("<b>E</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Education / Employment", headsss_style),
Paragraph("How is school going? Attendance, grades? Plans for the future? Any work?", headsss_style)],
[Paragraph("<b>E</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Eating", headsss_style),
Paragraph("Happy with your weight? Any dieting, skipping meals, purging? Who cooks at home?", headsss_style)],
[Paragraph("<b>A</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Activities", headsss_style),
Paragraph("Sports/hobbies? Screen time/social media? Who are your close friends?", headsss_style)],
[Paragraph("<b>D</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Drugs / Substance Use", headsss_style),
Paragraph("Do you or your friends smoke, drink, use any drugs or e-cigarettes?", headsss_style)],
[Paragraph("<b>S</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Sexuality", headsss_style),
Paragraph("Dating anyone? Sexually active? Contraception? STI risk? Gender identity?", headsss_style)],
[Paragraph("<b>S</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Suicide / Depression", headsss_style),
Paragraph("How is your mood? Feeling hopeless? Any self-harm or thoughts of ending life?", headsss_style)],
[Paragraph("<b>S</b>", style("HL", fontSize=10, textColor=ORANGE, fontName="Helvetica-Bold")),
Paragraph("Safety", headsss_style),
Paragraph("Seatbelts, helmet use? Exposure to violence, gang activity, weapons at home?", headsss_style)],
]
headsss_t = Table(headsss_data, colWidths=[14*mm, 40*mm, 128*mm])
headsss_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, ORANGE_LT]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (0,-1), "CENTER"),
]))
story.append(headsss_t)
story.append(sp(1))
story.append(Paragraph(
"<b>Note:</b> Always begin the adolescent encounter with a confidentiality discussion. "
"Offer private time with the clinician. Document sensitively in line with local regulations.",
style("AdolNote", fontSize=8, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", leading=11)
))
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 4 — REVIEW OF SYSTEMS
# ─────────────────────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_heading("SECTION 4 — SYSTEMATIC REVIEW OF SYSTEMS IN PEDIATRICS",
color=DARK_BLUE, bg=LIGHT_BLUE))
story.append(sp(1))
ros_hs = style("RH", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=12)
ros_cs = style("RC", fontSize=8.5, textColor=BLACK, fontName="Helvetica", leading=12)
ros_data = [
[Paragraph("<b>System</b>", ros_hs), Paragraph("<b>Key Screening Questions</b>", ros_hs)],
[Paragraph("CNS / Neurology", ros_cs), Paragraph("Headache, seizures, visual/hearing changes, developmental delay, behavioral change, gait abnormality", ros_cs)],
[Paragraph("Cardiovascular", ros_cs), Paragraph("Cyanosis (central/peripheral), exercise intolerance, syncope/near-syncope, palpitations, edema, poor feeding in infants", ros_cs)],
[Paragraph("Respiratory", ros_cs), Paragraph("Cough (character, duration), wheeze, stridor, breathlessness, recurrent chest infections, snoring/sleep apnea symptoms", ros_cs)],
[Paragraph("Gastrointestinal", ros_cs), Paragraph("Appetite, vomiting, diarrhea, constipation, abdominal pain, jaundice, rectal bleeding, difficulty swallowing", ros_cs)],
[Paragraph("Urinary / Renal", ros_cs), Paragraph("Dysuria, frequency, urgency, hematuria, enuresis, urine output, nocturia, facial/periorbital edema", ros_cs)],
[Paragraph("ENT", ros_cs), Paragraph("Ear pain/discharge, hearing loss, nasal discharge, recurrent sore throat, snoring, mouth breathing, hoarseness", ros_cs)],
[Paragraph("Musculoskeletal", ros_cs), Paragraph("Joint pain/swelling/warmth, limp, morning stiffness, bone pain, muscle weakness, restricted movement", ros_cs)],
[Paragraph("Skin / Integument", ros_cs), Paragraph("Rash, eczema, pallor, jaundice, edema, hair/nail changes, easy bruising, wounds slow to heal", ros_cs)],
[Paragraph("Endocrine / Growth", ros_cs), Paragraph("Polyuria/polydipsia (diabetes), growth concerns, pubertal timing (early/delayed), thyroid symptoms, weight changes", ros_cs)],
[Paragraph("Hematology / Oncology", ros_cs), Paragraph("Pallor, unexplained bruising/bleeding, petechiae, prolonged lymph node enlargement, unexplained weight loss, night sweats, bone pain", ros_cs)],
[Paragraph("Behavioral / Psychiatric", ros_cs), Paragraph("Sleep problems, mood changes, anxiety, school refusal, attention/hyperactivity, social withdrawal, self-harm", ros_cs)],
]
ros_t = Table(ros_data, colWidths=[42*mm, 140*mm])
ros_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(ros_t)
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 5 — RED FLAGS
# ─────────────────────────────────────────────────────────────────────────────
story.append(section_heading("SECTION 5 — RED FLAG SYMPTOMS REQUIRING URGENT ATTENTION",
color=RED, bg=RED_LIGHT))
story.append(sp(1))
rf_hs = style("RFH", fontSize=8.5, textColor=WHITE, fontName="Helvetica-Bold", leading=12)
rf_cs = style("RFC", fontSize=8.5, textColor=BLACK, fontName="Helvetica", leading=12)
rf_rs = style("RFR", fontSize=8.5, textColor=RED, fontName="Helvetica-Bold", leading=12)
rf_data = [
[Paragraph("<b>Presenting Symptom</b>", rf_hs), Paragraph("<b>Red Flag Features — Act Immediately</b>", rf_hs)],
[Paragraph("Fever", rf_cs), Paragraph("• Age <3 months with any fever\n• Non-blanching petechiae/purpura\n• Neck stiffness / bulging fontanelle\n• Toxic/ill appearance, not roused easily", rf_cs)],
[Paragraph("Breathing", rf_cs), Paragraph("• Central cyanosis\n• Severe retractions (intercostal, subcostal, suprasternal, tracheal tug)\n• Silent chest in known asthmatic\n• SpO2 <92% on air", rf_cs)],
[Paragraph("Vomiting", rf_cs), Paragraph("• Bilious vomiting in neonate\n• Forceful projectile in young infant + weight loss\n• Vomiting + headache + papilledema / altered sensorium (raised ICP)", rf_cs)],
[Paragraph("Seizure", rf_cs), Paragraph("• Duration >5 minutes (status epilepticus)\n• Focal or multiple seizures in 24h\n• Any seizure in neonate\n• Post-ictal focal neurological deficit", rf_cs)],
[Paragraph("Diarrhea", rf_cs), Paragraph("• Signs of severe dehydration (absent tears, sunken eyes, reduced skin turgor, lethargy)\n• Bloody stool + fever + pallor + oliguria (HUS)\n• Age <3 months", rf_cs)],
[Paragraph("Abdominal pain", rf_cs), Paragraph("• Peritonism: rigidity, rebound, guarding\n• Bilious vomiting + distension (obstruction)\n• Palpable abdominal mass\n• Pain waking child from sleep", rf_cs)],
[Paragraph("Rash", rf_cs), Paragraph("• Petechiae/purpura + fever + ill-looking = MENINGOCOCCEMIA until proven otherwise\n• Give IM benzylpenicillin and transfer immediately", rf_rs)],
[Paragraph("Altered sensorium", rf_cs), Paragraph("• Any child with reduced Glasgow Coma Scale regardless of cause\n• Assess ABCDE, secure IV access, urgent bloods, call senior", rf_cs)],
]
rf_t = Table(rf_data, colWidths=[40*mm, 142*mm])
rf_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, RED_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(rf_t)
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 6 — DIAGNOSTIC FRAMEWORK + MNEMONICS
# ─────────────────────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_heading("SECTION 6 — DIAGNOSTIC FRAMEWORK & CLINICAL MNEMONICS",
color=DARK_BLUE, bg=LIGHT_BLUE))
story.append(sp(1))
# Two-column layout using a table
left_items = []
right_items = []
left_items.append(Paragraph("<b>Stepwise Diagnostic Approach</b>", H3))
left_items.append(sp(0.5))
steps = [
("STEP 1", "Age-based risk stratification",
"Neonate → Infant → Toddler → School-age → Adolescent"),
("STEP 2", "Vital signs + Anthropometry",
"Temperature, HR, RR, SpO₂, BP, Weight, Height, HC (infants), MUAC"),
("STEP 3", "Pediatric Assessment Triangle (PAT)",
"Appearance (TICLS) + Work of Breathing + Circulation to Skin"),
("STEP 4", "Symptom-focused history",
"Use OLD CARTS or SOCRATES; use this guide's questionnaires"),
("STEP 5", "Focused physical examination",
"Head-to-toe, system-specific based on chief complaint"),
("STEP 6", "Generate differential diagnosis",
"Common > Rare; Serious > Benign — always rule out dangerous diagnoses"),
("STEP 7", "Targeted investigations",
"Only order if results will change management"),
("STEP 8", "Management + Safety netting",
"Treat + educate caregiver + specify clear return precautions"),
]
step_s = style("SS", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=11)
step_n = style("SN", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=11)
step_data = []
for num, title, detail in steps:
step_data.append([
Paragraph(num, step_n),
Paragraph(f"<b>{title}</b><br/>{detail}", step_s)
])
step_t = Table(step_data, colWidths=[16*mm, 72*mm])
step_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), MED_BLUE),
("ROWBACKGROUNDS", (1,0), (1,-1), [WHITE, LIGHT_BLUE]),
("GRID", (0,0), (-1,-1), 0.3, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (0,-1), "CENTER"),
]))
left_items.append(step_t)
right_items.append(Paragraph("<b>Key Mnemonics</b>", H3))
right_items.append(sp(0.5))
mnemonics = [
("OLD CARTS (HPI)", [
"O — Onset", "L — Location", "D — Duration",
"C — Character", "A — Aggravating/Alleviating",
"R — Radiation", "T — Timing", "S — Severity"
], MED_BLUE, LIGHT_BLUE),
("TICLS (Appearance in PAT)", [
"T — Tone", "I — Interactivity",
"C — Consolability", "L — Look / Gaze", "S — Speech / Cry"
], TEAL, TEAL_LIGHT),
("HEEADSSS (Adolescent)", [
"H — Home", "E — Education",
"E — Eating", "A — Activities",
"D — Drugs", "S — Sexuality",
"S — Suicide/Depression", "S — Safety"
], ORANGE, ORANGE_LT),
("IMCI Danger Signs (Child <5 yr)", [
"• Unable to drink/breastfeed",
"• Vomiting everything",
"• Convulsions",
"• Lethargic or unconscious",
"• Chest indrawing",
"• Stridor in calm child"
], RED, RED_LIGHT),
]
for title, items, hdr_c, bg_c in mnemonics:
mn_data = [[Paragraph(f"<b>{title}</b>",
style("MNH", fontSize=8, textColor=WHITE,
fontName="Helvetica-Bold", leading=11))]]
mn_t = Table(mn_data, colWidths=[84*mm])
mn_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), hdr_c),
("LEFTPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
]))
right_items.append(mn_t)
for item in items:
right_items.append(Paragraph(
f" {item}",
style("MNI", fontSize=8, textColor=BLACK,
fontName="Helvetica", leading=11,
leftIndent=6, backColor=bg_c)
))
right_items.append(sp(0.5))
# Combine into two-column table
two_col = Table(
[[left_items, right_items]],
colWidths=[92*mm, 90*mm]
)
two_col.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(two_col)
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 7 — DEVELOPMENTAL MILESTONES QUICK REFERENCE
# ─────────────────────────────────────────────────────────────────────────────
story.append(section_heading("SECTION 7 — DEVELOPMENTAL MILESTONES QUICK REFERENCE",
color=TEAL, bg=TEAL_LIGHT))
story.append(sp(1))
dm_hs = style("DMH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=11)
dm_cs = style("DMC", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=11)
dm_data = [
[Paragraph("<b>Age</b>", dm_hs),
Paragraph("<b>Gross Motor</b>", dm_hs),
Paragraph("<b>Fine Motor</b>", dm_hs),
Paragraph("<b>Language</b>", dm_hs),
Paragraph("<b>Social / Cognitive</b>", dm_hs)],
["2 months", "Lifts head 45°", "Hands fisted, tracks midline", "Social smile, cooing", "Recognizes mother's face"],
["4 months", "Head steady, rolls F→B", "Reaches for objects", "Laughs, vocalizes", "Enjoys social play"],
["6 months", "Sits with support, rolls B→F", "Palmer grasp, transfers", "Babbles (ba-ba)", "Stranger anxiety begins"],
["9 months", "Sits unsupported, crawls", "Pincer grasp developing", "Mama/dada (non-specific)", "Waves bye-bye, plays peek-a-boo"],
["12 months", "Pulls to stand, cruises, 1st steps", "Pincer grasp", "1–3 words with meaning", "Plays simple games, separation anxiety"],
["18 months", "Walks well, runs", "Builds 3–4 block tower", "10–20 words", "Parallel play, imitates housework"],
["2 years", "Runs, kicks ball", "Builds 6-block tower, scribbles", "2-word phrases (50 words)", "Symbolic play, points to pictures"],
["3 years", "Climbs stairs (alternate feet)", "Copies circle, holds crayon", "3-word sentences, names colours", "Group play, knows full name"],
["4 years", "Hops on one foot", "Copies cross, dresses self", "Tells a story, 4+ word sentences", "Cooperative play, understands rules"],
["5 years", "Skips, catches ball", "Copies square, draws a person (6 parts)", "Fluent speech, names days of week", "School ready, knows address"],
]
dm_formatted = [[Paragraph(str(c), dm_cs) if i > 0 else Paragraph(str(c), dm_hs) for i, c in enumerate(row)]
if idx > 0 else row
for idx, row in enumerate(dm_data)]
# Fix header row
dm_formatted[0] = [Paragraph(str(c.text) if hasattr(c, 'text') else str(c), dm_hs) for c in dm_data[0]]
# Rebuild properly
dm_proper = [dm_data[0]]
for row in dm_data[1:]:
dm_proper.append([Paragraph(str(c), dm_cs) for c in row])
dm_t = Table(dm_proper, colWidths=[18*mm, 36*mm, 36*mm, 46*mm, 46*mm])
dm_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, TEAL_LIGHT]),
("GRID", (0,0), (-1,-1), 0.3, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
]))
story.append(dm_t)
story.append(sp(1))
story.append(Paragraph(
"<b>Developmental Red Flags:</b> No social smile by 3 months · No babbling by 12 months · "
"No single words by 16 months · No 2-word phrases by 24 months · "
"Any loss of previously acquired language or social skills at any age.",
style("DRF", fontSize=8, textColor=RED, fontName="Helvetica-Bold",
leading=11, backColor=RED_LIGHT,
borderPadding=(4, 4, 4, 4))
))
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# SECTION 8 — VACCINATION SCHEDULE QUICK REF
# ─────────────────────────────────────────────────────────────────────────────
story.append(section_heading("SECTION 8 — IMMUNIZATION HISTORY CHECKLIST (INDIA / WHO-EPI)",
color=colors.HexColor("#5d4037"), bg=colors.HexColor("#efebe9")))
story.append(sp(1))
vax_hs = style("VH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold", leading=11)
vax_cs = style("VC", fontSize=8, textColor=BLACK, fontName="Helvetica", leading=11)
vax_data = [
[Paragraph("<b>Age</b>", vax_hs),
Paragraph("<b>Vaccines Due (NIS India)</b>", vax_hs),
Paragraph("<b>OPD Checkpoints</b>", vax_hs)],
["Birth", "BCG, OPV-0, Hep B-1", "Birth dose — given before discharge?"],
["6 weeks", "OPV-1, Penta-1 (DPT+HepB+Hib), PCV-1, RV-1, IPV-1", "On time? Any AEFI?"],
["10 weeks", "OPV-2, Penta-2, PCV-2, RV-2", "Card seen? Gaps noted?"],
["14 weeks", "OPV-3, Penta-3, PCV-3, RV-3*, IPV-2", "Complete primary series?"],
["6 months", "Hep B-3, OPV booster if partial", "Check for catch-up needs"],
["9 months", "MR-1 (Measles-Rubella), JE-1 (endemic areas), Vit A-1", "MR timely? Vit A given?"],
["12 months", "Hep A-1, JE-2, Typhoid conjugate (TCV)", "Private vaccines discussed?"],
["15–18 months", "MMR-2, DPT-B1, OPV-B1, Hib-B1, PCV-B1, Varicella-1", "Booster series started?"],
["2 years", "Hep A-2", "Family travel vaccines?"],
["4–6 years", "DPT-B2, OPV-B2, MMR-3, Varicella-2, Typhoid booster", "Pre-school booster done?"],
["10–12 years", "Tdap, Td (if DPT not given), HPV (girls)-2 doses, MenACWY", "Adolescent vaccines initiated?"],
]
vax_formatted = [vax_data[0]]
for row in vax_data[1:]:
vax_formatted.append([Paragraph(str(c), vax_cs) for c in row])
vax_t = Table(vax_formatted, colWidths=[22*mm, 100*mm, 60*mm])
vax_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#5d4037")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, colors.HexColor("#efebe9")]),
("GRID", (0,0), (-1,-1), 0.3, GRAY_LINE),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(vax_t)
story.append(sp(2))
# ─────────────────────────────────────────────────────────────────────────────
# FOOTER NOTE
# ─────────────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=MED_BLUE))
story.append(sp(0.5))
story.append(Paragraph(
"<b>Sources:</b> The Harriet Lane Handbook, 23rd ed. (The Johns Hopkins Hospital) · "
"WHO Integrated Management of Childhood Illness (IMCI) · "
"National Immunization Schedule, India (NIS 2024) · "
"Standard pediatric clinical practice. "
"<b>This guide is for clinical reference only — always apply individual clinical judgment.</b>",
style("FootNote", fontSize=7.5, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", leading=10, alignment=TA_CENTER)
))
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ═══════════════════════════════════════════════════════════════════════════════
doc.build(story, onFirstPage=draw_header_footer, onLaterPages=draw_header_footer)
print(f"PDF generated: {OUTPUT}")