Teach me as a paeditrician/Neonatologist for the topic Pyloric stenosis

Reading File
Finding Sources
Finding Sources
Searching PubMed

"pyloric stenosis" infantile hypertrophic

Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Reading File
I now have comprehensive, authoritative content from multiple textbooks. Let me compile the full teaching session.

Infantile Hypertrophic Pyloric Stenosis (IHPS) - A Complete Teaching Session for the Paediatrician/Neonatologist


1. OVERVIEW & DEFINITION

Infantile Hypertrophic Pyloric Stenosis (IHPS) is a condition of progressive hypertrophy and hyperplasia of the pyloric muscularis propria, causing gastric outlet obstruction in young infants. It is not a congenital disease - the pyloric muscle is normal at birth and hypertrophies postnatally.
  • Robbins, Cotran & Kumar Pathologic Basis of Disease, p. 703
  • Schwartz's Principles of Surgery, p. 1750

2. EPIDEMIOLOGY

ParameterData
Incidence~1 in 300 live births (~0.2-0.4% in white infants; slightly lower in Black infants)
Sex ratioMale:Female = 4-5:1
Birth orderMore common in first-born males
SiblingsRisk rises to ~6% in siblings of affected patients
TwinsHigh concordance in monozygotic twins
Peak age3-6 weeks (range 2-12 weeks); rare after 12 weeks
At-risk groups:
  • First-born males
  • Premature infants
  • Young maternal age
  • Infants with Turner syndrome or trisomy 18
  • Family history: if a mother had IHPS, offspring risk is markedly increased (Carter effect - females must accumulate more genetic burden to express the disease, so if a mother is affected, her sons are at very high risk)
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

3. PATHOPHYSIOLOGY & EMBRYOLOGY

The Pylorus

The pylorus is the distal sphincteric region of the stomach. In IHPS, both circular (predominantly) and longitudinal muscle layers hypertrophy, causing:
  • Severe stenosis of the pyloric canal
  • Obstruction of food passage
  • Massive gastric distension
  • Progressive projectile vomiting

The "Olive"

The hypertrophied pyloric muscle forms a firm, ovoid, 1-2 cm palpable mass in the right upper quadrant - pathognomonic when found.

Why Hypertrophy Occurs

  • The exact cause is unknown
  • Genome-wide association studies have linked genes related to GI development
  • A significant locus at chromosome 11q23.3 has been identified (related to cholesterol regulation - possible dietary link)
  • Loss of nitric oxide synthase (NOS) in the myenteric plexus has been implicated in failure of pyloric relaxation
  • Mucosal and submucosal edema may further exacerbate muscular outflow obstruction

4. CLINICAL PRESENTATION

Classic Sequence

Week 1-2: Infant feeds well, no vomiting Week 2-4: Onset of non-bilious vomiting after feeds Week 3-6: Vomiting becomes progressively forceful and projectile Late stage: Constant hunger, repeated unsuccessful feeding, dehydration, weight loss

Key Symptoms

  1. Non-bilious projectile vomiting - The hallmark. Non-bilious because obstruction is proximal to the ampulla of Vater. Vomiting can be forceful enough to land across the room.
  2. Hunger after vomiting - The infant vomits and immediately wants to feed again ("hungry vomiter").
  3. Weight loss / failure to thrive - Progressive as caloric intake falls.
  4. Reduced wet diapers - Sign of dehydration and decreased urine output.
  5. Constipation - Decreased stool from poor intake ("starvation stools" - small, green mucoid).

Signs on Examination

FindingDescription
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 peristalsisLeft-to-right peristaltic waves across upper abdomen before vomiting
Dehydration signsSunken fontanelle, dry mucous membranes, reduced skin turgor
JaundiceIndirect 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

5. METABOLIC DERANGEMENT - THE CLASSIC BIOCHEMISTRY

This is one of the most tested topics in neonatology/paediatrics.

Why it happens:

  • Repeated vomiting causes loss of gastric juice (rich in HCl, K⁺, H⁺)
  • This results in: Hypochloraemic, Hypokalaemic Metabolic Alkalosis

Step-by-step mechanism:

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)

Blood Gas Pattern:

ParameterValue
pHElevated (>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

6. INVESTIGATIONS

Step 1: Bloods (ALL infants)

  • Full metabolic panel: Na⁺, K⁺, Cl⁻, HCO₃⁻, pH
  • Blood glucose (hypoglycaemia from poor intake)
  • Bilirubin if jaundiced

Step 2: Imaging

Ultrasound - GOLD STANDARD

Sensitivity 95-98%, Specificity up to 100%
MeasurementNormalIHPS
Pyloric muscle thickness<3 mm≥4 mm (most reliable)
Pyloric channel length<12 mm≥15-17 mm
Ultrasound signs to know:
  • Target/doughnut sign: Hypoechoic thickened muscle ring on transverse view
  • Shoulder sign: Hypertrophied pylorus bulging into the gastric antrum
  • Nipple sign: Hypertrophic double-layered mucosa protruding into the stomach
  • Cervix sign: The overall sonographic appearance resembles a uterine cervix
  • Real-time: failure of fluid to pass through the pylorus; exaggerated gastric peristalsis
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
Ultrasound image - pyloric stenosis showing thickened pyloric wall with measurements:
Ultrasound of pyloric stenosis - coronal plane showing pylorus with muscle wall thickness 4.3 mm and channel length 2.26 cm
Pylorus ultrasound (coronal plane): muscle wall thickness 4.3 mm (measurement 2), pyloric channel length 2.26 cm (measurement 1) - both above diagnostic thresholds for IHPS.
Classic 3-panel imaging (ultrasound + barium):
Three-panel image: A - transverse US showing pyloric wall thickness >4mm; B - horizontal US showing channel length >14mm; C - barium contrast showing narrowed pyloric end with distended fundus (F)
A: Transverse US - pyloric muscle wall thickness >4mm. B: US showing channel length >14mm. C: Contrast study - narrowed pyloric channel (arrow) and distended stomach fundus (F). - The Developing Human, Clinically Oriented Embryology

Upper GI Contrast Series (when US equivocal)

  • "String sign" / "railroad track sign" - contrast trickling through narrow elongated pyloric channel
  • "Mushroom/umbrella sign" - indentation of pyloric mass on the gastric antrum
  • Advantages: also evaluates for malrotation, GOR disease, antroduodenal webs in the differential
  • Disadvantage: contrast remains in a poorly emptying stomach; radiation

7. DIFFERENTIAL DIAGNOSIS

ConditionKey Distinguishing Feature
Gastroesophageal Reflux Disease (GORD)Most common DDx; non-projectile, positional, responds to thickened feeds; normal US
PylorospasmTemporary; ultrasound normal or equivocal; usually resolves spontaneously
OverfeedingHistory; no organic cause; thriving infant
Malrotation with midgut volvulusBILIOUS vomiting - urgent emergency
Duodenal atresia/stenosisBILIOUS vomiting, "double bubble" on AXR; often Down syndrome
Antral/prepyloric webSimilar presentation; seen on contrast study
Adrenal insufficiency (CAH)Vomiting + hypoglycaemia + hyponatraemia + hyperkalaemia (opposite electrolytes)
Raised ICPProjectile vomiting without feeds; other neuro signs
Gastritis / cow's milk protein allergyBlood 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.

8. MANAGEMENT

Phase 1: Resuscitation (NEVER rush to theatre)

IHPS is never a surgical emergency. The child must be metabolically optimised first.
IV Fluid Protocol:
  • 1-2 boluses of normal saline (20 mL/kg) for dehydration
  • Maintenance: D5/0.45% NaCl + KCl 2-4 mEq/kg/day at 1.5x maintenance rate (~150-175 mL/kg/day)
  • NO potassium until urine output is confirmed (>2 mL/kg/hr)
  • Check electrolytes every 6-12 hours
Goals before surgery ("Go criteria"):
ParameterTarget
Chloride≥90-95 mEq/L
Bicarbonate≤30 mEq/L
Potassium≥3 mEq/L
Urine output>2 mL/kg/hr (wet diapers returning)
  • Nasogastric tube (NGT) to decompress the stomach
  • NBM (nil by mouth)
  • Resuscitation typically takes 24-48 hours

Phase 2: Surgery - Fredet-Ramstedt Pyloromyotomy

The definitive treatment. Not a pyloroplasty or bypass - a myotomy only.
Technique:
  • A single longitudinal incision through the anterior wall of the hypertrophied pyloric muscle (avascular plane)
  • Carried from stomach side to the duodenal side (where muscle softens)
  • Submucosal bulging confirms complete myotomy - both muscle edges must be freely mobile
  • Leak test: 30-60 mL of air injected via orogastric tube - confirms no mucosal perforation
Approaches:
ApproachNotes
Laparoscopic (preferred at most paediatric centres)3 ports: umbilical port + two 3-mm stab incisions; shorter hospital stay, better cosmesis
Open - umbilical incisionCosmetically favourable
Open - right upper quadrant transverse incisionTraditional approach
Recent systematic review (2024) comparing umbilical vs. right upper transverse incisions found no significant difference in outcomes.

Phase 3: Postoperative Feeding

  • Start feeds 6-8 hours after recovery from anaesthesia
  • Ad libitum feeding with a 60 mL limit (easiest and best approach per current evidence)
  • Most infants tolerate feeds and are discharged within 24-36 hours
  • Some post-op vomiting is expected and normal (mucosal oedema); true failure to feed at 48-72 hours suggests incomplete myotomy

9. COMPLICATIONS OF PYLOROMYOTOMY

ComplicationPresentationManagement
Incomplete myotomyOngoing projectile vomiting at 48-72 hrsReturn to OR for revision myotomy
Mucosal perforationIntraoperative: air leak on leak test; Postoperative: sepsis, peritonitisClose perforation + buttress with omentum; OR rotate pylorus 180° and redo myotomy
Wound infectionErythema, dischargeAntibiotics ± wound care
Postoperative apnoea1st 24 hrs - especially if alkalosis not correctedApnoea monitoring; correct alkalosis pre-op
PeritonitisIf unrecognised mucosal perforationEmergency 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).

10. NON-SURGICAL MANAGEMENT (Medical Pyloromyotomy)

IV Atropine has been used as an alternative to surgery in selected centres (Japan, some European centres):
  • Atropine sulphate IV relaxes the pyloric muscle
  • Requires prolonged hospitalisation (weeks) and strict protocols
  • Success rate ~85-90% with prolonged treatment
  • Not widely adopted - surgery remains first-line in most guidelines
  • May be considered in high anaesthetic risk infants

11. PROGNOSIS

  • Surgical cure rate: virtually 100% with adequate pyloromyotomy
  • Mortality is distinctly rare in the absence of concomitant medical problems
  • Recovery typically uncomplicated
  • Outcomes are better when operated by specialty-trained paediatric surgeons (lower complication rate, shorter hospital stay vs. general surgeons)
  • Long-term: no predisposition to adult GI disease; normal GI function

12. KEY MNEMONICS & EXAM PEARLS

"IHPS = Hungry, Non-bilious, 3-6 weeks, Male"

  • H - Hungry after vomiting (wants to re-feed immediately)
  • N - Non-bilious vomiting (key distinguisher)
  • 3-6 - Age of presentation in weeks
  • M - Male predominantly (4-5:1 ratio)

Metabolic Alkalosis Mnemonic: "HypoChloraemic HypoKalaemic Metabolic Alkalosis with Paradoxical Aciduria"

  • Cl⁻ goes DOWN (lost in vomit)
  • K⁺ goes DOWN (aldosterone effect + lost in vomit)
  • HCO₃⁻ goes UP (metabolic alkalosis)
  • Urine pH goes DOWN (paradoxical aciduria - late sign)

US Criteria: "4 and 14" (or "4 and 16")

  • Muscle thickness ≥4 mm
  • Channel length ≥14-16 mm (varies by source; use ≥16 mm as diagnostic threshold)

"Never rush to theatre" - Surgery is never an emergency

  • Correct the metabolic derangement FIRST
  • BiCarbonate >30 = do NOT anaesthetise

13. CLINICAL SCENARIO PRACTICE

A 5-week-old male, first-born, presents with 1-week history of increasing non-bilious projectile vomiting after feeds. He feeds vigorously immediately after vomiting. On exam: slightly sunken fontanelle, firm mobile mass palpable in RUQ. Blood gas: pH 7.52, PCO₂ 52, HCO₃⁻ 38, Cl⁻ 85, K⁺ 2.8.
Q: What is the diagnosis? IHPS (non-bilious projectile vomiting, age, sex, palpable olive, hypochloraemic hypokalaemic metabolic alkalosis)
Q: What do you do next?
  1. Confirm with pyloric ultrasound (muscle >4 mm, channel >16 mm)
  2. IV resuscitation: NS bolus + D5/0.45% NaCl + KCl
  3. NGT for gastric decompression
  4. Monitor UO - add K⁺ only when voiding
  5. Repeat electrolytes every 6-12 hrs
  6. Surgery when: Cl⁻ ≥90-95, HCO₃⁻ ≤30, K⁺ ≥3, adequate UO
Q: Why NOT take this child to theatre now? HCO₃⁻ 38 mEq/L - dangerous risk of post-op respiratory depression and apnoea under general anaesthesia.

Summary Table

FeatureIHPS
Incidence1 in 300
SexMale >>Female (4-5:1)
Age3-6 weeks
VomitingNon-bilious, projectile
Key signPalpable "olive"
ElectrolytesHypochloraemic, hypokalaemic metabolic alkalosis
ImagingUltrasound gold standard (muscle ≥4mm, length ≥16mm)
SurgeryFredet-Ramstedt pyloromyotomy
Pre-op mustCorrect metabolic alkalosis (HCO₃⁻ <30, Cl⁻ >90)
PrognosisExcellent - virtually 100% cure

Sources:

above all content in pdf

Reading File
Running Command

ls /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace/pylo…

Running Command

mkdir -p /home/daytona/workspace/pyloric-stenosis && echo "done"

Writing File

~/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"&nbsp; {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 &amp; 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 &bull; Mulholland &amp; Greenfield's Surgery 7e &bull; "
        "Current Surgical Therapy 14e &bull; Robbins, Cotran &amp; Kumar Pathologic Basis of Disease &bull; "
        "Grainger &amp; Allison's Diagnostic Radiology &bull; Harriet Lane Handbook 23e &bull; "
        "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",
        "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(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 &bull; HCO₃⁻ ≤ 30 &bull; K⁺ ≥ 3 &bull; 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 &amp; 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 &amp; Kumar: Pathologic Basis of Disease (10th Ed) - GI Chapter",
        "Grainger &amp; 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()
Running Command

cd /home/daytona/workspace/pyloric-stenosis && python3 generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/pyloric-stenosis/Pyloric_Stenosis_Paediatrics.pdf

Your PDF is ready - 987 KB, professionally formatted with all the content from the teaching session.

Pyloric Stenosis Paediatrics

PDF Document · PDF

What's included in the PDF (14 sections):
  1. Cover page - key facts panel with incidence, age, sex ratio, vomiting type, metabolics, and treatment at a glance
  2. Overview & Definition - IHPS is NOT congenital
  3. Epidemiology - full table including macrolide risk and Carter effect
  4. Pathophysiology & Embryology - NOS deficiency, genetic loci, mechanism table
  5. Clinical Presentation - temporal sequence table, symptoms, signs, olive palpation technique
  6. Metabolic Derangement - step-by-step mechanism, blood gas pattern table, danger threshold highlighted
  7. Differential Diagnosis - full table including CAH vs. IHPS electrolyte comparison
  8. Investigations - ultrasound criteria table, all US signs (target/doughnut/shoulder/nipple/cervix), 2 clinical ultrasound images + 3-panel barium image
  9. Management - Phase 1 (resuscitation), IV fluid protocol, surgical "Go criteria" table, Phase 2 (Ramstedt pyloromyotomy technique), Phase 3 (post-op feeding)
  10. Complications - full table with management of each complication
  11. Non-surgical/Medical management - atropine protocol
  12. Prognosis
  13. Mnemonics & Exam Pearls - HNMM mnemonic, metabolic alkalosis mnemonic, "4 and 16" US mnemonic, red-box NEVER rush to theatre rule
  14. Clinical Scenario - practice case with Q&A + Quick Reference Summary Table
This is a shared conversation. Sign in to Orris to start your own chat.