Teach me as a paeditrician/Neonatologist for the topic Pyloric stenosis
"pyloric stenosis" infantile hypertrophic
| Parameter | Data |
|---|---|
| Incidence | ~1 in 300 live births (~0.2-0.4% in white infants; slightly lower in Black infants) |
| Sex ratio | Male:Female = 4-5:1 |
| Birth order | More common in first-born males |
| Siblings | Risk rises to ~6% in siblings of affected patients |
| Twins | High concordance in monozygotic twins |
| Peak age | 3-6 weeks (range 2-12 weeks); rare after 12 weeks |
Macrolide exposure: Erythromycin or azithromycin exposure - oral or via breast milk - in the first 2 weeks of life is associated with IHPS. This is a critical neonatal prescribing point. - Robbins, Cotran & Kumar, p. 703
| Finding | Description |
|---|---|
| Palpable "olive" | Firm, mobile, 1-2 cm ovoid mass in RUQ/midepigastrium - pathognomonic (found in up to 90% by experienced hands; now <30% in early presentations due to earlier diagnosis) |
| Visible gastric peristalsis | Left-to-right peristaltic waves across upper abdomen before vomiting |
| Dehydration signs | Sunken fontanelle, dry mucous membranes, reduced skin turgor |
| Jaundice | Indirect hyperbilirubinemia occurs in ~2-5% (mechanism unclear - possibly decreased hepatic glucuronyl transferase activity from poor caloric intake) |
Clinical pearl: To palpate the olive, you need an empty stomach (pass NGT first), a quiet/sleeping infant, and patience. The examiner places fingertips below the liver edge in the RUQ and feels for a smooth, hard, mobile mass during quiet respiration. - Mulholland and Greenfield's Surgery, p. 5544
Vomiting → Loss of H⁺ + Cl⁻ → Metabolic alkalosis + Hypochloraemia
→ Volume depletion → Aldosterone activation
→ Renal Na⁺ retention + K⁺ loss → Hypokalaemia
Initially: Kidney excretes HCO₃⁻ in urine → Alkaline urine (compensatory)
Late stage: Volume depletion severe → Kidney prioritises Na⁺ over acid-base
→ H⁺ exchanged for Na⁺ in distal tubule
→ PARADOXICAL ACIDURIA (acidic urine despite systemic alkalosis)
| Parameter | Value |
|---|---|
| pH | Elevated (>7.45) |
| pCO₂ | Elevated (compensatory hypoventilation) |
| HCO₃⁻ | Markedly elevated (>30 mEq/L) |
| Cl⁻ | Low (<95 mEq/L) |
| K⁺ | Low |
| Na⁺ | May be low (hyponatraemia from vomiting + free water intake) |
Danger: Bicarbonate >30 mEq/L = relative contraindication to general anaesthesia due to diminished respiratory drive and risk of postoperative apnoea. Surgery must wait until metabolic alkalosis is corrected. - Current Surgical Therapy 14e, p. block 15
| Measurement | Normal | IHPS |
|---|---|---|
| Pyloric muscle thickness | <3 mm | ≥4 mm (most reliable) |
| Pyloric channel length | <12 mm | ≥15-17 mm |
Note for neonates: Younger/preterm babies may have smaller absolute values and still be abnormal. Clinical correlation is mandatory. If pyloric muscle thickness is >3 mm on repeat ultrasound, IHPS is confirmed regardless of age/weight. - Grainger & Allison's Diagnostic Radiology


| Condition | Key Distinguishing Feature |
|---|---|
| Gastroesophageal Reflux Disease (GORD) | Most common DDx; non-projectile, positional, responds to thickened feeds; normal US |
| Pylorospasm | Temporary; ultrasound normal or equivocal; usually resolves spontaneously |
| Overfeeding | History; no organic cause; thriving infant |
| Malrotation with midgut volvulus | BILIOUS vomiting - urgent emergency |
| Duodenal atresia/stenosis | BILIOUS vomiting, "double bubble" on AXR; often Down syndrome |
| Antral/prepyloric web | Similar presentation; seen on contrast study |
| Adrenal insufficiency (CAH) | Vomiting + hypoglycaemia + hyponatraemia + hyperkalaemia (opposite electrolytes) |
| Raised ICP | Projectile vomiting without feeds; other neuro signs |
| Gastritis / cow's milk protein allergy | Blood in stool; eosinophils; responds to formula change |
The single most important differentiator from surgical emergencies: IHPS vomiting is NON-BILIOUS. Bilious vomiting in a neonate = surgical emergency until proven otherwise.
| Parameter | Target |
|---|---|
| Chloride | ≥90-95 mEq/L |
| Bicarbonate | ≤30 mEq/L |
| Potassium | ≥3 mEq/L |
| Urine output | >2 mL/kg/hr (wet diapers returning) |
| Approach | Notes |
|---|---|
| Laparoscopic (preferred at most paediatric centres) | 3 ports: umbilical port + two 3-mm stab incisions; shorter hospital stay, better cosmesis |
| Open - umbilical incision | Cosmetically favourable |
| Open - right upper quadrant transverse incision | Traditional approach |
| Complication | Presentation | Management |
|---|---|---|
| Incomplete myotomy | Ongoing projectile vomiting at 48-72 hrs | Return to OR for revision myotomy |
| Mucosal perforation | Intraoperative: air leak on leak test; Postoperative: sepsis, peritonitis | Close perforation + buttress with omentum; OR rotate pylorus 180° and redo myotomy |
| Wound infection | Erythema, discharge | Antibiotics ± wound care |
| Postoperative apnoea | 1st 24 hrs - especially if alkalosis not corrected | Apnoea monitoring; correct alkalosis pre-op |
| Peritonitis | If unrecognised mucosal perforation | Emergency laparotomy + peritoneal washout |
A 2024 meta-analysis found that prophylactic preoperative antibiotics reduce surgical site infections in IHPS patients undergoing pyloromyotomy (Gulia et al., Pediatr Surg Int 2024).
| Feature | IHPS |
|---|---|
| Incidence | 1 in 300 |
| Sex | Male >>Female (4-5:1) |
| Age | 3-6 weeks |
| Vomiting | Non-bilious, projectile |
| Key sign | Palpable "olive" |
| Electrolytes | Hypochloraemic, hypokalaemic metabolic alkalosis |
| Imaging | Ultrasound gold standard (muscle ≥4mm, length ≥16mm) |
| Surgery | Fredet-Ramstedt pyloromyotomy |
| Pre-op must | Correct metabolic alkalosis (HCO₃⁻ <30, Cl⁻ >90) |
| Prognosis | Excellent - virtually 100% cure |
above all content in pdf
ls /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace/pylo…
mkdir -p /home/daytona/workspace/pyloric-stenosis && echo "done"
~/pyloric-stenosis/generate_pdf.py
#!/usr/bin/env python3
"""Generate a comprehensive Pyloric Stenosis teaching PDF for Paediatricians/Neonatologists."""
import urllib.request
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, KeepTogether, PageBreak
)
from reportlab.platypus.tableofcontents import TableOfContents
from reportlab.pdfgen import canvas
from io import BytesIO
OUTPUT = "/home/daytona/workspace/pyloric-stenosis/Pyloric_Stenosis_Paediatrics.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
C_HEADER = colors.HexColor("#1a3a5c") # deep navy
C_SUBHDR = colors.HexColor("#2e6da4") # medium blue
C_ACCENT = colors.HexColor("#e8f0fa") # light blue fill
C_TABLE_HDR = colors.HexColor("#2e6da4")
C_TABLE_ALT = colors.HexColor("#f0f5fb")
C_RED = colors.HexColor("#c0392b")
C_GREEN = colors.HexColor("#1e8449")
C_ORANGE = colors.HexColor("#d35400")
C_YELLOW = colors.HexColor("#fff9e6")
C_PEARL = colors.HexColor("#f4f8ff")
C_BORDER = colors.HexColor("#2e6da4")
PAGE_W, PAGE_H = A4
MARGIN = 2 * cm
# ── Styles ───────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def S(name, **kw):
s = ParagraphStyle(name, **kw)
return s
title_style = S("TitleMain",
fontName="Helvetica-Bold", fontSize=22, textColor=colors.white,
alignment=TA_CENTER, spaceAfter=4)
subtitle_style = S("Subtitle",
fontName="Helvetica", fontSize=13, textColor=colors.HexColor("#d0e8ff"),
alignment=TA_CENTER, spaceAfter=2)
author_style = S("Author",
fontName="Helvetica-Oblique", fontSize=10, textColor=colors.HexColor("#b0cce8"),
alignment=TA_CENTER, spaceAfter=0)
h1 = S("H1",
fontName="Helvetica-Bold", fontSize=14, textColor=colors.white,
backColor=C_HEADER, borderPad=6,
spaceBefore=14, spaceAfter=6, leftIndent=0, rightIndent=0,
leading=18)
h2 = S("H2",
fontName="Helvetica-Bold", fontSize=12, textColor=C_SUBHDR,
spaceBefore=10, spaceAfter=4, leading=16,
borderPadding=(0, 0, 2, 0))
h3 = S("H3",
fontName="Helvetica-BoldOblique", fontSize=10.5, textColor=C_HEADER,
spaceBefore=7, spaceAfter=3, leading=14)
body = S("Body",
fontName="Helvetica", fontSize=9.5, leading=14,
spaceBefore=2, spaceAfter=3, alignment=TA_JUSTIFY)
bullet = S("Bullet",
fontName="Helvetica", fontSize=9.5, leading=13,
leftIndent=14, bulletIndent=4, spaceBefore=1, spaceAfter=1,
bulletText="\u2022")
bullet2 = S("Bullet2",
fontName="Helvetica", fontSize=9, leading=12,
leftIndent=28, bulletIndent=18, spaceBefore=1, spaceAfter=1,
bulletText="\u25e6")
note_style = S("Note",
fontName="Helvetica-Oblique", fontSize=8.8, leading=12,
backColor=C_YELLOW, borderColor=C_ORANGE, borderWidth=1,
borderPadding=6, textColor=colors.HexColor("#7d3600"),
spaceBefore=5, spaceAfter=5)
pearl_style = S("Pearl",
fontName="Helvetica-BoldOblique", fontSize=9, leading=13,
backColor=C_PEARL, borderColor=C_BORDER, borderWidth=1,
borderPadding=7, textColor=C_HEADER,
spaceBefore=4, spaceAfter=4)
caption_style = S("Caption",
fontName="Helvetica-Oblique", fontSize=8, textColor=colors.HexColor("#555555"),
alignment=TA_CENTER, spaceBefore=2, spaceAfter=6)
code_style = S("Code",
fontName="Courier", fontSize=8.5, leading=12,
backColor=colors.HexColor("#f5f5f5"), borderColor=colors.HexColor("#cccccc"),
borderWidth=0.5, borderPadding=5,
spaceBefore=4, spaceAfter=4)
# ── Helpers ───────────────────────────────────────────────────────────────────
def H1(text):
return Paragraph(f" {text}", h1)
def H2(text):
return [
HRFlowable(width="100%", thickness=1.5, color=C_SUBHDR, spaceAfter=1),
Paragraph(text, h2)
]
def H3(text):
return Paragraph(text, h3)
def B(text):
return Paragraph(text, body)
def BUL(text):
return Paragraph(text, bullet)
def BUL2(text):
return Paragraph(text, bullet2)
def NOTE(text):
return Paragraph(f"<b>Note:</b> {text}", note_style)
def PEARL(text):
return Paragraph(f"<b>Clinical Pearl:</b> {text}", pearl_style)
def SP(n=4):
return Spacer(1, n)
def tbl(data, col_widths, header=True, alt=True):
t = Table(data, colWidths=col_widths, repeatRows=1 if header else 0)
style_cmds = [
("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
("FONTSIZE", (0, 0), (-1, -1), 8.5),
("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#aabbcc")),
("ROWBACKGROUNDS", (0, 0), (-1, -1), [colors.white, C_TABLE_ALT] if alt else [colors.white]),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("LEFTPADDING", (0, 0), (-1, -1), 5),
("RIGHTPADDING",(0, 0), (-1, -1), 5),
]
if header:
style_cmds += [
("BACKGROUND", (0, 0), (-1, 0), C_TABLE_HDR),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 9),
]
t.setStyle(TableStyle(style_cmds))
return t
def download_image(url, filename):
path = f"/home/daytona/workspace/pyloric-stenosis/{filename}"
if not os.path.exists(path):
try:
urllib.request.urlretrieve(url, path)
except Exception as e:
print(f"Failed to download {url}: {e}")
return None
return path
# ── Page number canvas ────────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
super().showPage()
super().save()
def draw_page_number(self, page_count):
pg = self._pageNumber
self.setFont("Helvetica", 8)
self.setFillColor(colors.HexColor("#888888"))
self.drawRightString(PAGE_W - MARGIN, 1.2 * cm,
f"Page {pg} of {page_count}")
self.drawString(MARGIN, 1.2 * cm,
"Infantile Hypertrophic Pyloric Stenosis | Paediatrics/Neonatology Teaching")
self.setStrokeColor(colors.HexColor("#cccccc"))
self.line(MARGIN, 1.5 * cm, PAGE_W - MARGIN, 1.5 * cm)
# ── Build content ─────────────────────────────────────────────────────────────
def build_story():
story = []
avail_w = PAGE_W - 2 * MARGIN
# ── COVER PAGE ────────────────────────────────────────────────────────────
story.append(Spacer(1, 1.5 * cm))
# Cover banner
cover_data = [[Paragraph(
"<b>INFANTILE HYPERTROPHIC<br/>PYLORIC STENOSIS</b>",
S("CoverTitle", fontName="Helvetica-Bold", fontSize=26,
textColor=colors.white, alignment=TA_CENTER, leading=34))]]
ct = Table(cover_data, colWidths=[avail_w])
ct.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_HEADER),
("TOPPADDING", (0, 0), (-1, -1), 22),
("BOTTOMPADDING", (0, 0), (-1, -1), 22),
("LEFTPADDING", (0, 0), (-1, -1), 10),
("RIGHTPADDING", (0, 0), (-1, -1), 10),
("ROUNDEDCORNERS", [8, 8, 8, 8]),
]))
story.append(ct)
story.append(SP(12))
# Subtitle box
sub_data = [[Paragraph(
"A Comprehensive Teaching Module for Paediatricians & Neonatologists",
S("SubTitle2", fontName="Helvetica-Oblique", fontSize=13,
textColor=C_HEADER, alignment=TA_CENTER))]]
st = Table(sub_data, colWidths=[avail_w])
st.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_ACCENT),
("TOPPADDING", (0, 0), (-1, -1), 12),
("BOTTOMPADDING", (0, 0), (-1, -1), 12),
("BOX", (0, 0), (-1, -1), 1, C_BORDER),
]))
story.append(st)
story.append(SP(20))
# Key facts panel on cover
kf_data = [
[Paragraph("<b>Key Facts at a Glance</b>",
S("kfhdr", fontName="Helvetica-Bold", fontSize=11,
textColor=C_HEADER, alignment=TA_CENTER)), "", ""],
[Paragraph("Incidence\n<b>1 in 300 live births</b>",
S("kf", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13)),
Paragraph("Peak Age\n<b>3 - 6 weeks</b>",
S("kf", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13)),
Paragraph("Sex Ratio\n<b>Male : Female = 4-5:1</b>",
S("kf", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13))],
[Paragraph("Vomiting\n<b>Non-bilious, Projectile</b>",
S("kf2", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13)),
Paragraph("Metabolics\n<b>Hypochloraemic Hypokalaemic Alkalosis</b>",
S("kf2", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13)),
Paragraph("Treatment\n<b>Fredet-Ramstedt Pyloromyotomy</b>",
S("kf2", fontName="Helvetica", fontSize=9.5,
textColor=C_HEADER, alignment=TA_CENTER, leading=13))],
]
kf_col = avail_w / 3
kft = Table(kf_data, colWidths=[kf_col, kf_col, kf_col])
kft.setStyle(TableStyle([
("SPAN", (0, 0), (2, 0)),
("BACKGROUND", (0, 0), (2, 0), C_HEADER),
("TEXTCOLOR", (0, 0), (2, 0), colors.white),
("BACKGROUND", (0, 1), (2, 2), C_ACCENT),
("BOX", (0, 0), (-1, -1), 1.2, C_BORDER),
("INNERGRID", (0, 0), (-1, -1), 0.5, C_BORDER),
("TOPPADDING", (0, 0), (-1, -1), 8),
("BOTTOMPADDING", (0, 0), (-1, -1), 8),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
]))
story.append(kft)
story.append(SP(16))
sources = Paragraph(
"<i>Sources: Schwartz's Principles of Surgery 11e • Mulholland & Greenfield's Surgery 7e • "
"Current Surgical Therapy 14e • Robbins, Cotran & Kumar Pathologic Basis of Disease • "
"Grainger & Allison's Diagnostic Radiology • Harriet Lane Handbook 23e • "
"The Developing Human (Clinically Oriented Embryology)</i>",
S("src", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=colors.HexColor("#666666"), alignment=TA_CENTER))
story.append(sources)
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 1 – Overview & Definition
# ═══════════════════════════════════════════════════════════════════════════
story.append(H1("1. OVERVIEW & DEFINITION"))
story.append(SP(6))
story.append(B(
"Infantile Hypertrophic Pyloric Stenosis (IHPS) is a condition of progressive <b>hypertrophy "
"and hyperplasia of the pyloric muscularis propria</b>, causing gastric outlet obstruction "
"in young infants. It is <b>not a congenital disease</b> - the pyloric muscle is normal at "
"birth and hypertrophies postnatally, typically manifesting between 3-6 weeks of age."))
story.append(SP(4))
story.append(PEARL(
"IHPS is NOT congenital - it is acquired postnatally. Do not confuse with congenital atresias "
"or webs. The pylorus is histologically and functionally normal at birth."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 2 – Epidemiology
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("2. EPIDEMIOLOGY"))
story.append(SP(6))
epi_data = [
["Parameter", "Data / Detail"],
["Incidence", "~1 in 300 live births (0.1-0.4% in White infants; slightly lower in Black infants)"],
["Sex ratio", "Male : Female = 4-5 : 1"],
["Birth order", "More common in first-born males"],
["Peak age", "3-6 weeks (range 2-12 weeks); rare after 12 weeks"],
["Siblings", "Risk rises to ~6% in siblings of affected patients"],
["Twins", "High concordance in monozygotic twins; lesser risk in dizygotic twins"],
["Associated syndromes", "Turner syndrome, Trisomy 18 - confer increased risk"],
["Premature infants", "Increased risk; presentation may be later (corrected age)"],
["Macrolide exposure", "Erythromycin/Azithromycin in first 2 weeks of life (oral or via breast milk) - associated trigger"],
]
story.append(tbl(epi_data, [5.5*cm, avail_w - 5.5*cm]))
story.append(SP(6))
story.append(NOTE(
"Macrolide antibiotics (especially erythromycin) given to neonates or nursing mothers "
"in the first 2 weeks of life are a well-recognised risk factor for IHPS. Always warn "
"parents when prescribing to neonates or breastfeeding mothers of young infants."))
story.append(SP(4))
story.append(H3("Carter Effect (Genetic Liability)"))
story.append(B(
"Females must accumulate more variant alleles to express IHPS. Therefore, when a <b>mother</b> "
"is the affected individual, her sons have a very high risk (~20%) of IHPS. Genome-wide "
"association studies have identified a significant locus at <b>chromosome 11q23.3</b> "
"related to cholesterol regulation. The exact pathogenic mechanism remains unclear."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 3 – Pathophysiology
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("3. PATHOPHYSIOLOGY & EMBRYOLOGY"))
story.append(SP(6))
story.append(H3("The Pyloric Muscle"))
story.append(B(
"The pylorus is the distal sphincteric region of the stomach, guarding the gastroduodenal "
"junction. In IHPS, both the <b>circular (predominantly)</b> and longitudinal muscle layers "
"undergo hypertrophy, resulting in:"))
for txt in [
"Severe stenosis of the pyloric canal",
"Failure of normal pyloric relaxation",
"Progressive gastric outlet obstruction",
"Massive gastric distension with forceful projectile vomiting",
]:
story.append(BUL(txt))
story.append(SP(4))
story.append(H3("Proposed Mechanisms"))
path_data = [
["Mechanism", "Evidence/Detail"],
["Loss of nitric oxide synthase (NOS)", "NOS deficiency in myenteric plexus → failure of pyloric relaxation → functional obstruction"],
["Mucosal/submucosal oedema", "Exacerbates mechanical obstruction on top of muscular hypertrophy"],
["Reduced peptidergic innervation", "Decreased VIP, substance P and other neuropeptides in pyloric wall"],
["Genetic factors", "Chromosome 11q23.3 locus; multiple GWAS-identified GI development genes"],
["Environmental triggers", "Macrolide exposure (motilin receptor agonist - stimulates pyloric contraction)"],
["Hypergastrinaemia", "Proposed but not confirmed; maternal prostaglandins also implicated"],
]
story.append(tbl(path_data, [5.5*cm, avail_w - 5.5*cm]))
story.append(SP(4))
story.append(PEARL(
"The hypertrophied pyloric mass forms the classic palpable 'olive' - a firm, smooth, mobile, "
"ovoid 1-2 cm mass in the right upper quadrant. When found, it is essentially pathognomonic "
"and no further imaging is strictly required."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 4 – Clinical Presentation
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("4. CLINICAL PRESENTATION"))
story.append(SP(6))
story.append(H3("Classic Temporal Sequence"))
timeline_data = [
["Age / Stage", "Clinical Features"],
["Birth to 2 weeks", "Normal feeding, no vomiting. Pyloric muscle histologically normal."],
["2-3 weeks", "Onset of postprandial non-bilious vomiting. Initially mild, intermittent."],
["3-6 weeks (peak)", "Vomiting becomes FORCEFUL and PROJECTILE. Infant hungry immediately after vomiting."],
["Late / untreated", "Complete gastric outlet obstruction, severe dehydration, weight loss, metabolic alkalosis."],
]
story.append(tbl(timeline_data, [4*cm, avail_w - 4*cm]))
story.append(SP(6))
story.append(H3("Symptoms"))
syms = [
("<b>Non-bilious projectile vomiting</b> - The HALLMARK. Non-bilious because obstruction is "
"PROXIMAL to the ampulla of Vater. Vomiting can propel across the room."),
("<b>Hunger after vomiting</b> - Infant vomits and immediately demands re-feeding ('hungry vomiter'). "
"Helps distinguish from neurological vomiting."),
("<b>Weight loss / failure to thrive</b> - Progressive as caloric intake falls."),
("<b>Reduced wet diapers</b> - Dehydration and decreased urine output."),
("<b>Constipation / scanty stools</b> - 'Starvation stools' - small, dark green mucoid stools."),
("<b>Jaundice</b> - Indirect hyperbilirubinaemia in ~2-5% of cases (reduced hepatic glucuronyl "
"transferase activity from poor caloric intake / starvation)."),
]
for s in syms:
story.append(BUL(s))
story.append(SP(6))
story.append(H3("Physical Examination Findings"))
exam_data = [
["Finding", "Description", "Significance"],
["Palpable 'Olive'", "Firm, mobile, ovoid 1-2 cm mass in RUQ/midepigastrium", "Nearly pathognomonic; found in up to 90% by experienced examiner (now <30% in early presentations)"],
["Visible gastric peristalsis", "Left-to-right waves across upper abdomen just before vomiting", "Also seen in any gastric/duodenal obstruction"],
["Sunken fontanelle", "Sign of moderate-severe dehydration", "Late finding"],
["Dry mucous membranes", "Sign of dehydration", "Late finding"],
["Reduced skin turgor", "Pinch test - skin returns slowly", "Severe dehydration"],
["Jaundice", "Scleral icterus, yellow skin", "Indirect (unconjugated) type"],
]
story.append(tbl(exam_data, [3.5*cm, 5.5*cm, avail_w - 9*cm]))
story.append(SP(4))
story.append(NOTE(
"To palpate the 'olive': decompress stomach via NGT first, wait for a quiet/sleeping infant, "
"place fingertips below the right liver edge during quiet respiration. Repeat examinations "
"may be necessary. An experienced clinician should be able to palpate the pylorus in "
"nearly all cases. Inability to palpate in a quiet/anaesthetised infant should question the diagnosis."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 5 – Metabolic Derangement
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("5. METABOLIC DERANGEMENT - THE CLASSIC BIOCHEMISTRY"))
story.append(SP(6))
story.append(B(
"The repeated vomiting of gastric juice (rich in H⁺ and Cl⁻) produces the classical "
"metabolic disturbance: <b>Hypochloraemic, Hypokalaemic Metabolic Alkalosis</b> with "
"<b>Paradoxical Aciduria</b>."))
story.append(SP(6))
story.append(H3("Step-by-Step Pathophysiology"))
steps = [
"<b>Step 1:</b> Vomiting → Loss of HCl (H⁺ + Cl⁻) from stomach → Metabolic alkalosis + Hypochloraemia",
"<b>Step 2:</b> Volume depletion → Activation of renin-angiotensin-aldosterone system (RAAS)",
"<b>Step 3:</b> Aldosterone → Renal Na⁺ retention + K⁺ secretion → Hypokalaemia",
"<b>Step 4 (Early):</b> Kidney excretes excess HCO₃⁻ in urine to compensate → ALKALINE urine",
"<b>Step 5 (Late):</b> Severe volume depletion → Kidney prioritises Na⁺ retention over acid-base",
"<b>Step 6:</b> H⁺ is preferentially exchanged for Na⁺ in distal tubule → PARADOXICAL ACIDURIA",
" (Acidic urine despite systemic metabolic alkalosis - hallmark of severe/late IHPS)",
]
for s in steps:
story.append(BUL(s))
story.append(SP(6))
story.append(H3("Blood Gas / Electrolyte Pattern"))
bg_data = [
["Parameter", "Direction", "Typical Value / Range"],
["pH", "↑ HIGH", "> 7.45 (often 7.50-7.55)"],
["pCO₂", "↑ HIGH (compensatory)", "Elevated (respiratory compensation)"],
["HCO₃⁻", "↑ MARKEDLY HIGH", "> 30 mEq/L (often 35-45)"],
["Chloride (Cl⁻)", "↓ LOW", "< 90 mEq/L (sometimes <80)"],
["Potassium (K⁺)", "↓ LOW", "< 3.5 mEq/L"],
["Sodium (Na⁺)", "Variable", "May be low (hypovolaemic hyponatraemia)"],
["Urine pH (early)", "↑ HIGH (alkaline)", "> 7 (bicarbonate excretion)"],
["Urine pH (late)", "↓ LOW (acidic)", "< 6 - PARADOXICAL ACIDURIA"],
]
story.append(tbl(bg_data, [4*cm, 3.5*cm, avail_w - 7.5*cm]))
story.append(SP(6))
story.append(PEARL(
"DANGER THRESHOLD: Bicarbonate > 30 mEq/L is a relative contraindication to general "
"anaesthesia. These infants have diminished respiratory drive and are at HIGH RISK for "
"postoperative apnoea and respiratory arrest. Surgery MUST WAIT until HCO₃⁻ < 30 mEq/L."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 6 – Differential Diagnosis
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("6. DIFFERENTIAL DIAGNOSIS"))
story.append(SP(6))
story.append(B(
"The single most important step: <b>Is the vomiting bilious or non-bilious?</b> "
"Bilious vomiting in a neonate = SURGICAL EMERGENCY until proven otherwise."))
story.append(SP(4))
ddx_data = [
["Condition", "Key Distinguishing Feature", "Bilious?"],
["GORD (most common DDx)", "Non-projectile; positional; responds to thickened feeds; normal US", "No"],
["Pylorospasm", "Temporary, transient; US equivocal or normal; self-resolving", "No"],
["Overfeeding", "History; thriving infant; no organic cause", "No"],
["Antral/prepyloric web", "Similar to IHPS; contrast study diagnostic", "No"],
["Malrotation + midgut volvulus", "BILIOUS; haemodynamic compromise; URGENT emergency", "YES"],
["Duodenal atresia/stenosis", "BILIOUS; 'double bubble' on AXR; often Down syndrome; prenatal polyhydramnios", "YES"],
["Jejunal/ileal atresia", "BILIOUS; abdominal distension", "YES"],
["Raised ICP", "Projectile vomiting NOT related to feeds; bulging fontanelle; other neuro signs", "No"],
["Congenital Adrenal Hyperplasia (CAH)", "Vomiting + hypoglycaemia + HYPONATRAEMIA + HYPERKALAEMIA (OPPOSITE electrolytes!)", "No"],
["Cow's milk protein allergy", "Blood in stool; eosinophils; responds to hypoallergenic formula", "No"],
["Sepsis/meningitis", "Ill-looking; fever; altered tone; bulging fontanelle", "Variable"],
]
story.append(tbl(ddx_data, [4.5*cm, avail_w - 7*cm, 2.5*cm]))
story.append(SP(4))
story.append(NOTE(
"CAH vs IHPS electrolytes: IHPS gives hypOnatraemia, hypOkalaemia, hypOchloraemia, alkalosis. "
"CAH gives hypOnatraemia, hypERkalaemia. The potassium direction is the KEY differentiator."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 7 – Investigations
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("7. INVESTIGATIONS"))
story.append(SP(6))
story.append(H3("Step 1: Blood Tests (All Infants)"))
for txt in [
"<b>Electrolytes panel:</b> Na⁺, K⁺, Cl⁻, HCO₃⁻ - identify and quantify metabolic alkalosis",
"<b>Blood gas (VBG):</b> pH, pCO₂, HCO₃⁻ - confirm alkalosis and respiratory compensation",
"<b>Blood glucose:</b> Hypoglycaemia from poor caloric intake is common",
"<b>Urea and creatinine:</b> Assess degree of dehydration",
"<b>Full blood count:</b> Baseline; haemoconcentration in dehydration",
"<b>Serum bilirubin:</b> If clinically jaundiced - confirm unconjugated hyperbilirubinaemia",
]:
story.append(BUL(txt))
story.append(SP(6))
story.append(H3("Step 2: Imaging - Ultrasound is GOLD STANDARD"))
story.append(B(
"Pyloric ultrasound has replaced all other modalities as the investigation of choice. "
"It has a <b>sensitivity of 95-98% and specificity of up to 100%</b>."))
story.append(SP(4))
us_data = [
["Measurement", "Normal", "IHPS (Diagnostic Threshold)"],
["Pyloric muscle thickness (most reliable)", "< 3 mm", "≥ 4 mm"],
["Pyloric channel length", "< 12 mm", "≥ 15-17 mm"],
["Pyloric diameter (transverse)", "< 10 mm", "> 13-14 mm"],
]
story.append(tbl(us_data, [6*cm, 3*cm, avail_w - 9*cm]))
story.append(SP(4))
story.append(H3("Ultrasound Signs to Recognise"))
us_signs = [
"<b>Target / Doughnut sign:</b> Hypoechoic thickened muscle ring on transverse view - the classic appearance",
"<b>Shoulder sign:</b> Hypertrophied pyloric mass bulging into the gastric antrum",
"<b>Nipple sign:</b> Double-layered hypertrophic mucosa protruding into the stomach",
"<b>Cervix sign:</b> Overall appearance resembles a uterine cervix on sagittal view",
"<b>Real-time:</b> Failure of fluid to pass through the pyloric channel; exaggerated peristaltic waves",
"<b>Hypoechoic muscle:</b> Thickened hypoechoic muscle surrounding hyperechoic mucosa",
]
for s in us_signs:
story.append(BUL(s))
story.append(SP(4))
story.append(NOTE(
"Neonatal caution: Younger/preterm babies may have smaller absolute measurements yet still "
"have IHPS. If pyloric muscle thickness > 3 mm on REPEAT ultrasound (regardless of age/weight), "
"IHPS is confirmed. Close clinical correlation is MANDATORY. When equivocal, repeat US in "
"24-48 hours or proceed to contrast study."))
story.append(SP(6))
# Download and insert ultrasound images
img1_path = download_image(
"https://cdn.orris.care/cdss_images/700217f7cd566645b516ab50679b279c80a7a9e054c354e6c61675aaaa6a785a.png",
"us_3panel.png")
if img1_path and os.path.exists(img1_path):
img1 = Image(img1_path, width=avail_w * 0.85, height=avail_w * 0.85 * 0.37)
story.append(img1)
story.append(Paragraph(
"<i>Fig 1. A: Transverse US - pyloric muscle wall thickness >4 mm. "
"B: Horizontal US - channel length >14 mm. "
"C: Contrast study - narrowed pyloric channel (arrow) and distended gastric fundus (F). "
"[Source: The Developing Human - Clinically Oriented Embryology]</i>",
caption_style))
story.append(SP(6))
img2_path = download_image(
"https://cdn.orris.care/cdss_images/b5830f70e685d5b742fbb1dfc488dda080f33d2037c2fd145d5167429ca575a9.png",
"us_coronal.png")
if img2_path and os.path.exists(img2_path):
img2 = Image(img2_path, width=avail_w * 0.6, height=avail_w * 0.6 * 0.78)
img2.hAlign = "CENTER"
story.append(img2)
story.append(Paragraph(
"<i>Fig 2. Pylorus ultrasound (coronal plane): muscle wall thickness 4.3 mm (measurement 2), "
"pyloric channel length 2.26 cm (measurement 1) - both exceeding diagnostic thresholds for IHPS. "
"[Source: Mulholland and Greenfield's Surgery, 7e]</i>",
caption_style))
story.append(SP(6))
story.append(H3("Upper GI Contrast Series (when US equivocal)"))
contrast_signs = [
"<b>'String sign' / 'Railroad track sign':</b> Contrast trickling as a thin line through the narrow elongated pyloric channel",
"<b>'Mushroom / Umbrella sign':</b> Indentation of pyloric mass on the gastric antrum",
"<b>'Shoulder sign' on contrast:</b> Filling defect at the gastroduodenal junction",
"<b>Advantage:</b> Evaluates for other causes of vomiting (malrotation, GOR, antroduodenal webs)",
"<b>Disadvantage:</b> Contrast remains in a poorly-emptying stomach; radiation exposure",
]
for s in contrast_signs:
story.append(BUL(s))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 8 – Management
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("8. MANAGEMENT"))
story.append(SP(6))
story.append(PEARL(
"IHPS is NEVER a surgical emergency. The child must be metabolically optimised "
"BEFORE going to theatre. Rushing to surgery with uncorrected alkalosis risks "
"fatal postoperative respiratory depression."))
story.append(SP(6))
story.append(H3("Phase 1: Resuscitation (typically 24-48 hours)"))
story.append(B("<b>IV Fluid Protocol:</b>"))
fluids = [
"Give 1-2 boluses of <b>Normal Saline (0.9% NaCl) 20 mL/kg</b> for dehydration correction",
"Maintenance: <b>5% Dextrose / 0.45% NaCl + KCl 2-4 mEq/kg/day</b> at 1.5× maintenance rate (~150-175 mL/kg/day)",
"<b>DO NOT add potassium</b> until urine output is confirmed (> 2 mL/kg/hr)",
"Nasogastric tube (NGT) for gastric decompression + nil by mouth",
"Monitor electrolytes every 6-12 hours",
"Monitor urine output - aim > 2 mL/kg/hr (wet diapers returning = good sign)",
]
for f in fluids:
story.append(BUL(f))
story.append(SP(4))
story.append(B("<b>Surgical 'Go' Criteria (safe to proceed to theatre):</b>"))
go_data = [
["Parameter", "Target Before Anaesthesia"],
["Serum Chloride", "≥ 90-95 mEq/L"],
["Serum Bicarbonate (CO₂)", "≤ 30 mEq/L"],
["Serum Potassium", "≥ 3.0-3.5 mEq/L"],
["Urine output", "> 2 mL/kg/hr (adequate hydration)"],
["Serum Sodium", "Normal range"],
]
story.append(tbl(go_data, [5*cm, avail_w - 5*cm]))
story.append(SP(8))
story.append(H3("Phase 2: Surgery - Fredet-Ramstedt Pyloromyotomy"))
story.append(B(
"The definitive, essentially curative treatment. It is a <b>myotomy only</b> - not a "
"pyloroplasty or bypass. The mucosa is left intact."))
story.append(SP(4))
story.append(B("<b>Surgical Technique:</b>"))
steps_surg = [
"A single <b>longitudinal incision</b> through the <b>anterior wall</b> of the hypertrophied pyloric muscle (avascular plane)",
"Carried from the stomach side to the duodenal side (where muscle softens and transitions to normal bowel wall)",
"<b>Submucosal bulging</b> into the myotomy site = adequate pyloromyotomy",
"Both edges of divided muscle must be <b>freely mobile</b>",
"<b>Leak test:</b> 30-60 mL of air injected via orogastric tube - confirms pyloric patency and NO mucosal perforation",
]
for s in steps_surg:
story.append(BUL(s))
story.append(SP(4))
story.append(H3("Surgical Approaches"))
approach_data = [
["Approach", "Description", "Notes"],
["Laparoscopic (preferred)", "3 ports: umbilical + two 3-mm stab incisions bilaterally", "Standard at most paediatric surgical centres; shorter stay, better cosmesis"],
["Open - umbilical incision", "Circumferential incision around umbilicus", "Cosmetically superior; comparable outcomes to laparoscopic"],
["Open - RUQ transverse", "Right upper quadrant transverse incision", "Traditional; slightly higher wound visibility"],
]
story.append(tbl(approach_data, [4*cm, 6.5*cm, avail_w - 10.5*cm]))
story.append(SP(4))
story.append(NOTE(
"A 2024 systematic review and meta-analysis (Khandelia et al., Pediatr Surg Int) comparing "
"umbilical vs. right upper transverse incisions found no significant difference in clinical outcomes. "
"Choice of approach depends on surgeon preference and training."))
story.append(SP(6))
story.append(H3("Phase 3: Postoperative Feeding & Discharge"))
post_op = [
"Start feeds <b>6-8 hours</b> after recovery from anaesthesia",
"<b>Ad libitum feeding with 60 mL limit</b> per feed (current best practice)",
"Some post-op vomiting is <b>expected and normal</b> (residual mucosal oedema at myotomy site)",
"True failure to feed at 48-72 hrs post-op → suspect <b>incomplete myotomy</b>",
"Most infants can be <b>discharged within 24-36 hours</b> after tolerating at least two consecutive feeds",
"Outcomes are significantly better with <b>specialty-trained paediatric surgeons</b> vs. general surgeons",
]
for p in post_op:
story.append(BUL(p))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 9 – Complications
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("9. COMPLICATIONS OF PYLOROMYOTOMY"))
story.append(SP(6))
comp_data = [
["Complication", "Presentation", "Management"],
["Incomplete myotomy", "Ongoing projectile vomiting at 48-72 hrs post-op", "Return to OR for revision myotomy on posterior pyloric wall (180° from first)"],
["Mucosal perforation\n(intraoperative)", "Air leak on leak test", "Close perforation + omental buttress; OR close myotomy and rotate pylorus 180° for new myotomy"],
["Unrecognised mucosal\nperforation", "Sepsis, peritonitis, deterioration", "Emergency laparotomy + peritoneal washout"],
["Postoperative apnoea", "First 24 hrs; esp. if alkalosis not corrected pre-op", "Pre-operative correction of alkalosis; apnoea monitoring post-op"],
["Wound infection", "Erythema, discharge, fever", "Antibiotics ± wound care; prophylactic antibiotics reduce risk"],
["Inadvertent duodenotomy", "Air leak on leak test (duodenal side)", "Repair + close myotomy + rotate for new myotomy"],
]
story.append(tbl(comp_data, [4*cm, 5*cm, avail_w - 9*cm]))
story.append(SP(4))
story.append(NOTE(
"A 2024 meta-analysis (Gulia et al., Pediatr Surg Int) demonstrated that prophylactic "
"preoperative antibiotics significantly reduce surgical site infections in IHPS patients "
"undergoing pyloromyotomy. This supports routine prophylactic antibiotic use."))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 10 – Non-surgical / Medical Management
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("10. NON-SURGICAL (MEDICAL) MANAGEMENT"))
story.append(SP(6))
story.append(B(
"IV Atropine has been used as an alternative to surgery in selected centres "
"(Japan, some European centres). Atropine sulphate acts as an anticholinergic agent "
"relaxing the pyloric smooth muscle."))
story.append(SP(4))
atrop_data = [
["Feature", "Detail"],
["Drug", "Atropine sulphate IV (or oral after initial IV phase)"],
["Mechanism", "Anticholinergic → pyloric muscle relaxation → allows gradual pyloric dilation"],
["Success rate", "~85-90% with prolonged protocol treatment"],
["Duration", "Weeks to months of treatment; prolonged hospitalisation"],
["Limitations", "Relapse possible; not widely adopted; requires strict protocols"],
["When to consider", "High anaesthetic risk infants; parental preference in experienced centres"],
["Current status", "Surgery (pyloromyotomy) remains FIRST-LINE in most international guidelines"],
]
story.append(tbl(atrop_data, [4.5*cm, avail_w - 4.5*cm]))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 11 – Prognosis
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("11. PROGNOSIS"))
story.append(SP(6))
for p in [
"<b>Surgical cure rate: virtually 100%</b> with adequate pyloromyotomy",
"Mortality is distinctly rare in the absence of concomitant medical problems",
"Recovery is typically uncomplicated; most infants discharged within 24-36 hours",
"Outcomes are better when operated by <b>specialty-trained paediatric surgeons</b>",
"<b>Long-term:</b> No predisposition to adult peptic ulcer disease or GI malignancy",
"Normal GI function restored; infants grow and thrive normally after adequate repair",
]:
story.append(BUL(p))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 12 – Mnemonics & Exam Pearls
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("12. MNEMONICS & EXAM PEARLS"))
story.append(SP(6))
story.append(H3("Mnemonic 1: IHPS Profile - 'HNMM'"))
mnemo_data = [
["Letter", "Stands For", "Key Point"],
["H", "Hungry after vomiting", "Infant vomits and immediately wants to re-feed"],
["N", "Non-bilious vomiting", "Critical differentiator - obstruction proximal to ampulla of Vater"],
["M", "Male predominance (4-5:1)", "Especially first-born males"],
["M", "3-6 weeks (Mid-infancy)", "Rare before 2 weeks, rare after 12 weeks"],
]
story.append(tbl(mnemo_data, [1.5*cm, 5*cm, avail_w - 6.5*cm]))
story.append(SP(6))
story.append(H3("Mnemonic 2: Metabolic Alkalosis - 'HypoC, HypoK, High HCO₃, Paradox Acid Urine'"))
met_mnemo = [
"<b>Cl⁻ goes DOWN</b> (lost in vomit directly)",
"<b>K⁺ goes DOWN</b> (lost in vomit + aldosterone-driven renal losses)",
"<b>HCO₃⁻ goes UP</b> (metabolic alkalosis)",
"<b>Urine pH initially UP</b> (early compensation - kidney excretes HCO₃⁻)",
"<b>Urine pH eventually DOWN</b> = PARADOXICAL ACIDURIA (late, severe dehydration)",
]
for m in met_mnemo:
story.append(BUL(m))
story.append(SP(6))
story.append(H3("Mnemonic 3: Ultrasound Criteria - '4 and 16'"))
us_mnemo_data = [
["Measurement", "Threshold", "Memory Aid"],
["Muscle thickness", "≥ 4 mm", "'4 mm = 4 letters in IHPS'"],
["Channel length", "≥ 16 mm (or ≥15 mm)", "'16 weeks = typical first trimester scan age'"],
]
story.append(tbl(us_mnemo_data, [4.5*cm, 3*cm, avail_w - 7.5*cm]))
story.append(SP(6))
story.append(H3("Key Rule: NEVER Rush to Theatre"))
rule_data = [[Paragraph(
"<b>Pyloric Stenosis = NEVER a surgical emergency</b><br/>"
"Correct FIRST: Cl⁻ ≥ 90 • HCO₃⁻ ≤ 30 • K⁺ ≥ 3 • Urine output > 2 mL/kg/hr<br/>"
"HCO₃⁻ > 30 mEq/L + General Anaesthesia = Risk of fatal respiratory arrest",
S("rule", fontName="Helvetica-Bold", fontSize=10, textColor=C_RED,
alignment=TA_CENTER, leading=16))]]
rt = Table(rule_data, colWidths=[avail_w])
rt.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#fff0f0")),
("BOX", (0, 0), (-1, -1), 2, C_RED),
("TOPPADDING", (0, 0), (-1, -1), 12),
("BOTTOMPADDING", (0, 0), (-1, -1), 12),
]))
story.append(rt)
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 13 – Clinical Scenario
# ═══════════════════════════════════════════════════════════════════════════
story.append(SP(8))
story.append(H1("13. CLINICAL SCENARIO - PRACTICE CASE"))
story.append(SP(6))
scenario_text = (
"A 5-week-old male infant, first-born, is brought with a 1-week history of increasing "
"non-bilious, projectile vomiting after every feed. His mother reports he vomits 'across "
"the room' and then immediately wants to feed again. He has had fewer wet diapers over "
"the last 3 days. On examination: slightly sunken anterior fontanelle, dry mucous membranes, "
"a firm mobile mass palpable in the right upper quadrant. "
"VBG: pH 7.54, pCO₂ 52 mmHg, HCO₃⁻ 38 mEq/L, Cl⁻ 83 mEq/L, K⁺ 2.7 mEq/L."
)
sbox = [[Paragraph(scenario_text,
S("scen", fontName="Helvetica-Oblique", fontSize=9.5,
textColor=C_HEADER, leading=14))]]
st2 = Table(sbox, colWidths=[avail_w])
st2.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, -1), C_ACCENT),
("BOX", (0, 0), (-1, -1), 1.5, C_BORDER),
("TOPPADDING", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 10),
("LEFTPADDING", (0, 0), (-1, -1), 12),
("RIGHTPADDING", (0, 0), (-1, -1), 12),
]))
story.append(st2)
story.append(SP(6))
qa = [
("Q: What is the diagnosis?",
"Infantile Hypertrophic Pyloric Stenosis (IHPS). Supported by: age 5 weeks, male, "
"first-born, non-bilious projectile vomiting, hunger after vomiting, palpable 'olive', "
"and hypochloraemic hypokalaemic metabolic alkalosis."),
("Q: What investigation will confirm the diagnosis?",
"Pyloric ULTRASOUND - looking for muscle wall thickness ≥4 mm and channel length ≥16 mm. "
"Also assess real-time: inability of fluid to pass through the pylorus."),
("Q: Why NOT take this child to theatre immediately?",
"HCO₃⁻ is 38 mEq/L (>30 = danger threshold). General anaesthesia under severe metabolic "
"alkalosis causes diminished respiratory drive and high risk of postoperative apnoea/respiratory "
"arrest. IHPS is NEVER a surgical emergency."),
("Q: What is your immediate management plan?",
"1. IV access + NGT (gastric decompression, NBM). "
"2. Normal saline 20 mL/kg bolus × 1-2 for volume resuscitation. "
"3. D5/0.45% NaCl at 1.5× maintenance. Add KCl only after urine output confirmed. "
"4. Electrolytes every 6-12 hours. 5. Surgery when Cl⁻ ≥90, HCO₃⁻ ≤30, K⁺ ≥3, UO adequate."),
("Q: What surgical procedure will be performed?",
"Fredet-Ramstedt Pyloromyotomy - a single longitudinal incision through the hypertrophied "
"pyloric muscle (anterior wall). Confirmed complete when submucosa bulges into myotomy site. "
"Performed laparoscopically at most paediatric centres."),
]
for q, a in qa:
story.append(Paragraph(q, S("q", fontName="Helvetica-Bold", fontSize=9.5,
textColor=C_HEADER, leading=14, spaceBefore=5)))
story.append(Paragraph(a, S("a", fontName="Helvetica", fontSize=9.5,
textColor=colors.HexColor("#222222"), leading=14,
leftIndent=12, spaceAfter=4)))
# ═══════════════════════════════════════════════════════════════════════════
# SECTION 14 – Summary Table
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(H1("14. QUICK REFERENCE SUMMARY TABLE"))
story.append(SP(6))
summary_data = [
["Feature", "IHPS Summary"],
["Full name", "Infantile Hypertrophic Pyloric Stenosis"],
["Incidence", "1 in 300 live births (~0.2-0.4%)"],
["Sex ratio", "Male >> Female (4-5:1)"],
["Peak age", "3-6 weeks (range 2-12 weeks)"],
["Aetiology", "Unknown; postnatal pyloric muscle hypertrophy; genetic + environmental"],
["Key symptom", "NON-BILIOUS, PROJECTILE vomiting with hunger after vomiting"],
["Key sign", "Palpable 'olive' - firm, mobile, ovoid RUQ mass"],
["Metabolics", "Hypochloraemic, Hypokalaemic, Metabolic Alkalosis (+/- Paradoxical Aciduria)"],
["Imaging", "Ultrasound GOLD STANDARD: muscle ≥4 mm, channel ≥16 mm"],
["Treatment", "Fredet-Ramstedt Pyloromyotomy (open or laparoscopic)"],
["Pre-op MUST", "Correct metabolic alkalosis first: HCO₃⁻ <30, Cl⁻ >90, K⁺ >3"],
["Surgery timing", "NEVER an emergency - resuscitate first (24-48 hrs)"],
["Post-op feeding", "Start 6-8 hrs post-op; ad lib with 60 mL limit; discharge at 24-36 hrs"],
["Complications", "Incomplete myotomy, mucosal perforation, wound infection, apnoea"],
["Prognosis", "EXCELLENT - virtually 100% cure with adequate pyloromyotomy"],
]
story.append(tbl(summary_data, [5.5*cm, avail_w - 5.5*cm]))
story.append(SP(10))
story.append(HRFlowable(width="100%", thickness=1, color=C_SUBHDR))
story.append(SP(6))
story.append(Paragraph(
"<b>References & Sources</b>",
S("refhdr", fontName="Helvetica-Bold", fontSize=10, textColor=C_HEADER,
spaceBefore=4, spaceAfter=4)))
refs = [
"Schwartz's Principles of Surgery, 11th Edition - Chapter on Paediatric Surgery",
"Mulholland and Greenfield's Surgery: Scientific Principles and Practice, 7th Edition",
"Current Surgical Therapy, 14th Edition",
"Robbins, Cotran & Kumar: Pathologic Basis of Disease (10th Ed) - GI Chapter",
"Grainger & Allison's Diagnostic Radiology - Paediatric Radiology",
"The Developing Human: Clinically Oriented Embryology",
"The Harriet Lane Handbook, 23rd Edition (Johns Hopkins Hospital)",
"Khandelia H et al. Comparison between umbilical and right upper transverse abdominal incisions "
"for pyloromyotomy: systematic review and meta-analysis. Pediatr Surg Int. 2024. PMID: 38935193",
"Gulia A et al. Utility of preoperative prophylactic antibiotics for preventing surgical site "
"infections in children with IHPS: systematic review and meta-analysis. Pediatr Surg Int. 2024. PMID: 39503899",
]
for i, r in enumerate(refs, 1):
story.append(Paragraph(
f"{i}. {r}",
S("ref", fontName="Helvetica", fontSize=8.2, textColor=colors.HexColor("#444444"),
leading=12, leftIndent=10, spaceBefore=2)))
return story
# ── Build PDF ─────────────────────────────────────────────────────────────────
def main():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=MARGIN,
leftMargin=MARGIN,
topMargin=MARGIN,
bottomMargin=2.2 * cm,
title="Infantile Hypertrophic Pyloric Stenosis - Paediatrics Teaching",
author="Orris Medical Education",
subject="Neonatology / Paediatric Surgery Teaching Module",
)
story = build_story()
doc.build(story, canvasmaker=NumberedCanvas)
print(f"PDF created: {OUTPUT}")
if __name__ == "__main__":
main()
cd /home/daytona/workspace/pyloric-stenosis && python3 generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/pyloric-stenosis/Pyloric_Stenosis_Paediatrics.pdf
Pyloric Stenosis Paediatrics
PDF Document · PDF