Make pdf on liver abscess in 5yr old male

Reading File
Finding Sources
Finding Sources
Searching PubMed

"liver abscess" AND child

Reading File
Reading File
Reading File
Reading Article

PMID: 34794678

Reading File
Reading File
Searching Images

liver abscess ultrasound CT scan child pediatric

Reading File
Reading File
Finding Sources
Reading File
Reading File
Running Command

mkdir -p /home/daytona/workspace/liver-abscess-pdf

Writing File

~/liver-abscess-pdf/generate_pdf.py

#!/usr/bin/env python3
"""Generate a comprehensive PDF on Liver Abscess in a 5-year-old male."""

import requests
import io
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import HexColor, white, black
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, PageBreak, Image, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus.flowables import BalancedColumns
from PIL import Image as PILImage
import os

OUTPUT_PATH = "/home/daytona/workspace/liver-abscess-pdf/Liver_Abscess_5yr_Male.pdf"

# ── Colors ──────────────────────────────────────────────────────────────────
DARK_BLUE   = HexColor("#1a3a5c")
MID_BLUE    = HexColor("#2e6da4")
LIGHT_BLUE  = HexColor("#d6e8f7")
ACCENT_RED  = HexColor("#c0392b")
ACCENT_ORANGE = HexColor("#e67e22")
LIGHT_GRAY  = HexColor("#f5f5f5")
MED_GRAY    = HexColor("#cccccc")
DARK_GRAY   = HexColor("#444444")
GREEN       = HexColor("#27ae60")
WARN_BG     = HexColor("#fff3e0")
WARN_BORDER = HexColor("#ff9800")

# ── Styles ───────────────────────────────────────────────────────────────────
def build_styles():
    styles = getSampleStyleSheet()

    styles.add(ParagraphStyle(
        'CoverTitle', fontName='Helvetica-Bold', fontSize=26,
        textColor=white, alignment=TA_CENTER, spaceAfter=6, leading=32
    ))
    styles.add(ParagraphStyle(
        'CoverSub', fontName='Helvetica', fontSize=14,
        textColor=HexColor("#cce0f5"), alignment=TA_CENTER, spaceAfter=4, leading=18
    ))
    styles.add(ParagraphStyle(
        'CoverPatient', fontName='Helvetica-Bold', fontSize=13,
        textColor=HexColor("#ffe082"), alignment=TA_CENTER, spaceAfter=4, leading=16
    ))
    styles.add(ParagraphStyle(
        'SectionHeader', fontName='Helvetica-Bold', fontSize=14,
        textColor=white, spaceBefore=10, spaceAfter=4, leading=18,
        backColor=DARK_BLUE, leftIndent=-8, rightIndent=-8, borderPadding=(4,8,4,8)
    ))
    styles.add(ParagraphStyle(
        'SubHeader', fontName='Helvetica-Bold', fontSize=11,
        textColor=DARK_BLUE, spaceBefore=8, spaceAfter=3, leading=14
    ))
    styles.add(ParagraphStyle(
        'Body', fontName='Helvetica', fontSize=10,
        textColor=DARK_GRAY, spaceAfter=5, leading=14, alignment=TA_JUSTIFY
    ))
    styles.add(ParagraphStyle(
        'BulletBody', fontName='Helvetica', fontSize=10,
        textColor=DARK_GRAY, spaceAfter=3, leading=13,
        leftIndent=14, bulletIndent=0
    ))
    styles.add(ParagraphStyle(
        'TableHeader', fontName='Helvetica-Bold', fontSize=9,
        textColor=white, alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'TableCell', fontName='Helvetica', fontSize=9,
        textColor=DARK_GRAY, alignment=TA_LEFT, leading=12
    ))
    styles.add(ParagraphStyle(
        'Caption', fontName='Helvetica-Oblique', fontSize=8,
        textColor=DARK_GRAY, alignment=TA_CENTER, spaceAfter=6
    ))
    styles.add(ParagraphStyle(
        'Warning', fontName='Helvetica-Bold', fontSize=10,
        textColor=ACCENT_RED, spaceAfter=3, leading=13
    ))
    styles.add(ParagraphStyle(
        'SmallBody', fontName='Helvetica', fontSize=8.5,
        textColor=DARK_GRAY, spaceAfter=3, leading=12
    ))
    styles.add(ParagraphStyle(
        'FooterStyle', fontName='Helvetica', fontSize=7.5,
        textColor=HexColor("#888888"), alignment=TA_CENTER
    ))
    styles.add(ParagraphStyle(
        'HighlightBox', fontName='Helvetica', fontSize=10,
        textColor=DARK_GRAY, spaceAfter=3, leading=13,
        leftIndent=8, rightIndent=8
    ))
    return styles

S = build_styles()

# ── Helper: download image ────────────────────────────────────────────────────
def get_image_from_url(url, max_width, max_height):
    try:
        r = requests.get(url, timeout=10)
        r.raise_for_status()
        img_data = io.BytesIO(r.content)
        pil_img = PILImage.open(img_data)
        w, h = pil_img.size
        ratio = min(max_width/w, max_height/h)
        new_w, new_h = w*ratio, h*ratio
        img_data.seek(0)
        rl_img = Image(img_data, width=new_w, height=new_h)
        return rl_img
    except Exception as e:
        print(f"Image fetch failed: {e}")
        return None

# ── Header/Footer ─────────────────────────────────────────────────────────────
def header_footer(canvas, doc):
    canvas.saveState()
    W, H = A4
    # Header bar
    canvas.setFillColor(DARK_BLUE)
    canvas.rect(0, H - 1.5*cm, W, 1.5*cm, fill=1, stroke=0)
    canvas.setFillColor(white)
    canvas.setFont("Helvetica-Bold", 9)
    canvas.drawString(1.5*cm, H - 0.95*cm, "LIVER ABSCESS IN PEDIATRIC PATIENT")
    canvas.setFont("Helvetica", 8)
    canvas.drawRightString(W - 1.5*cm, H - 0.95*cm, "Clinical Reference · 5-Year-Old Male")
    # Footer bar
    canvas.setFillColor(MID_BLUE)
    canvas.rect(0, 0, W, 1.0*cm, fill=1, stroke=0)
    canvas.setFillColor(white)
    canvas.setFont("Helvetica", 7.5)
    canvas.drawString(1.5*cm, 0.35*cm, "Sources: Sleisenger & Fordtran's GI Disease · Bailey & Love's Surgery · Current Surgical Therapy 14e · Harrison's 22e")
    canvas.drawRightString(W - 1.5*cm, 0.35*cm, f"Page {doc.page}")
    canvas.restoreState()

# ── Cover page builder ────────────────────────────────────────────────────────
def cover_page():
    elements = []
    W, H = A4

    # Full-bleed cover gradient via canvas is tricky in platypus; use Table approach
    cover_data = [[
        Paragraph("LIVER ABSCESS", S['CoverTitle']),
    ]]
    cover_table = Table([[
        Paragraph("<br/><br/><br/>", S['CoverTitle']),
        Paragraph("LIVER ABSCESS", S['CoverTitle']),
        Paragraph("in a Pediatric Patient", S['CoverSub']),
        Paragraph("Clinical Case: 5-Year-Old Male", S['CoverPatient']),
        Paragraph("Aetiology · Pathogenesis · Diagnosis · Management · Complications", S['CoverSub']),
        Paragraph("<br/>", S['CoverSub']),
    ]], colWidths=[16.5*cm])

    # Use a drawing for the cover
    from reportlab.platypus import Flowable
    class CoverBlock(Flowable):
        def __init__(self):
            Flowable.__init__(self)
            self.width, self.height = A4
        def draw(self):
            c = self.canv
            W, H = A4
            # Background gradient (top dark, bottom medium)
            c.setFillColor(DARK_BLUE)
            c.rect(0, 0, W, H, fill=1, stroke=0)
            # Accent stripe
            c.setFillColor(MID_BLUE)
            c.rect(0, H*0.32, W, H*0.04, fill=1, stroke=0)
            c.setFillColor(ACCENT_ORANGE)
            c.rect(0, H*0.29, W, H*0.03, fill=1, stroke=0)
            # Title
            c.setFillColor(white)
            c.setFont("Helvetica-Bold", 38)
            c.drawCentredString(W/2, H*0.65, "LIVER ABSCESS")
            c.setFont("Helvetica-Bold", 18)
            c.setFillColor(HexColor("#cce0f5"))
            c.drawCentredString(W/2, H*0.57, "in the Pediatric Patient")
            # Patient info box
            c.setFillColor(HexColor("#ffe082"))
            c.roundRect(W/2 - 5*cm, H*0.44, 10*cm, 1.4*cm, 6, fill=1, stroke=0)
            c.setFillColor(DARK_BLUE)
            c.setFont("Helvetica-Bold", 14)
            c.drawCentredString(W/2, H*0.485, "Clinical Case: 5-Year-Old Male")
            # Subtitle
            c.setFillColor(HexColor("#90caf9"))
            c.setFont("Helvetica", 11)
            c.drawCentredString(W/2, H*0.39, "Aetiology  ·  Pathogenesis  ·  Diagnosis  ·  Management  ·  Complications")
            # Bottom line
            c.setFillColor(HexColor("#90caf9"))
            c.setFont("Helvetica", 9)
            c.drawCentredString(W/2, H*0.10, "Based on: Sleisenger & Fordtran's GI Disease | Bailey & Love's Surgery")
            c.drawCentredString(W/2, H*0.07, "Current Surgical Therapy 14e | Harrison's Principles 22e | PubMed Review 2022")
            c.setFont("Helvetica-Oblique", 8)
            c.setFillColor(HexColor("#aaaaaa"))
            c.drawCentredString(W/2, H*0.04, "Prepared with Orris Medical AI · May 2026")

    elements.append(CoverBlock())
    elements.append(PageBreak())
    return elements

# ── Section header helper ─────────────────────────────────────────────────────
def section_header(title, color=DARK_BLUE):
    tbl = Table([[Paragraph(f"  {title}", ParagraphStyle(
        'SH2', fontName='Helvetica-Bold', fontSize=12,
        textColor=white, leading=16
    ))]], colWidths=[16.5*cm])
    tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), color),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 8),
        ('RIGHTPADDING', (0,0), (-1,-1), 8),
    ]))
    return tbl

def sub_header(title):
    return Paragraph(f"<b><font color='#{DARK_BLUE.hexval()[2:]}'>{title}</font></b>", S['SubHeader'])

def body(text):
    return Paragraph(text, S['Body'])

def bullet(text, symbol="•"):
    return Paragraph(f"{symbol}  {text}", S['BulletBody'])

def spacer(h=0.3):
    return Spacer(1, h*cm)

# ── INFO BOX ─────────────────────────────────────────────────────────────────
def info_box(title, lines, bg=LIGHT_BLUE, title_color=DARK_BLUE, border=MID_BLUE):
    rows = []
    title_row = [Paragraph(f"<b>{title}</b>", ParagraphStyle(
        'IB_T', fontName='Helvetica-Bold', fontSize=10,
        textColor=title_color
    ))]
    rows.append(title_row)
    for line in lines:
        rows.append([Paragraph(line, S['SmallBody'])])
    tbl = Table(rows, colWidths=[16.0*cm])
    tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), bg),
        ('BACKGROUND', (0,1), (-1,-1), LIGHT_GRAY),
        ('GRID', (0,0), (-1,-1), 0.3, border),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 8),
    ]))
    return tbl

# ── WARNING BOX ──────────────────────────────────────────────────────────────
def warning_box(text):
    tbl = Table([[Paragraph(f"⚠  {text}", ParagraphStyle(
        'WB', fontName='Helvetica-Bold', fontSize=9.5,
        textColor=HexColor("#7d3c00")
    ))]], colWidths=[16.0*cm])
    tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,-1), WARN_BG),
        ('BOX', (0,0), (-1,-1), 1.5, WARN_BORDER),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 10),
    ]))
    return tbl

# ── MAIN CONTENT ──────────────────────────────────────────────────────────────
def build_content():
    e = []

    # ── 1. INTRODUCTION & CASE ────────────────────────────────────────────────
    e.append(section_header("1. INTRODUCTION & CLINICAL CASE", DARK_BLUE))
    e.append(spacer(0.2))

    # Patient case summary box
    case_data = [
        [Paragraph("<b>PATIENT PROFILE</b>", ParagraphStyle('PP', fontName='Helvetica-Bold', fontSize=11, textColor=white))],
        [Paragraph("Age: 5 years  |  Sex: Male  |  Presentation: 5-day history of fever, right upper quadrant abdominal pain, anorexia, malaise", S['SmallBody'])],
        [Paragraph("Examination: Fever 38.9°C, tachycardia, hepatomegaly, tender RUQ on percussion, no jaundice", S['SmallBody'])],
        [Paragraph("Key History: Recent travel to endemic area, no prior biliary disease, no immunosuppression noted", S['SmallBody'])],
    ]
    case_tbl = Table(case_data, colWidths=[16.0*cm])
    case_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), MID_BLUE),
        ('BACKGROUND', (0,1), (-1,-1), LIGHT_BLUE),
        ('GRID', (0,0), (-1,-1), 0.3, MID_BLUE),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 10),
    ]))
    e.append(case_tbl)
    e.append(spacer(0.3))

    e.append(body(
        "Liver abscess is an uncommon but potentially life-threatening condition in children. It occurs at a rate of approximately "
        "<b>1 per 5,000 hospital admissions</b> but carries significant morbidity if not promptly diagnosed and treated. "
        "Three distinct categories exist: <b>pyogenic (bacterial), amoebic (parasitic), and fungal</b>. In a 5-year-old male, "
        "amoebic liver abscess (ALA) must be considered alongside pyogenic causes, particularly with a travel history. "
        "S. aureus liver abscess — most common in children — and conditions causing immunodeficiency (e.g., chronic granulomatous disease) are important paediatric considerations."
    ))
    e.append(spacer(0.2))

    e.append(warning_box(
        "S. aureus liver abscesses are most common in children and in patients with chronic granulomatous disease or other immunodeficiencies. "
        "Always screen for underlying immunodeficiency in a child with pyogenic liver abscess and no biliary cause."
    ))
    e.append(spacer(0.4))

    # ── 2. CLASSIFICATION ─────────────────────────────────────────────────────
    e.append(section_header("2. CLASSIFICATION OF LIVER ABSCESS", DARK_BLUE))
    e.append(spacer(0.2))

    class_data = [
        [Paragraph("Type", S['TableHeader']), Paragraph("Causative Agent", S['TableHeader']),
         Paragraph("Paediatric Relevance", S['TableHeader']), Paragraph("Key Features", S['TableHeader'])],
        [Paragraph("Pyogenic", S['TableCell']), Paragraph("E. coli, Klebsiella, S. aureus, Streptococcus milleri, Bacteroides, anaerobes", S['TableCell']),
         Paragraph("⭐ Most common in children (especially S. aureus)", S['TableCell']),
         Paragraph("Often polymicrobial; biliary/portal/haematogenous route", S['TableCell'])],
        [Paragraph("Amoebic", S['TableCell']), Paragraph("Entamoeba histolytica", S['TableCell']),
         Paragraph("Rare in children; consider after travel to endemic area", S['TableCell']),
         Paragraph("Monomicrobial; chocolate-brown 'anchovy paste' pus", S['TableCell'])],
        [Paragraph("Fungal", S['TableCell']), Paragraph("Candida spp., Aspergillus", S['TableCell']),
         Paragraph("Immunocompromised children (haematologic malignancy)", S['TableCell']),
         Paragraph("Highest mortality; multiple microabscesses", S['TableCell'])],
    ]
    class_tbl = Table(class_data, colWidths=[2.5*cm, 4.5*cm, 5.0*cm, 4.5*cm])
    class_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
        ('BACKGROUND', (0,1), (-1,1), LIGHT_BLUE),
        ('BACKGROUND', (0,2), (-1,2), LIGHT_GRAY),
        ('BACKGROUND', (0,3), (-1,3), LIGHT_BLUE),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 6),
        ('ALIGN', (0,0), (-1,0), 'CENTER'),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(class_tbl)
    e.append(spacer(0.4))

    # ── 3. PATHOGENESIS ───────────────────────────────────────────────────────
    e.append(section_header("3. PATHOGENESIS & AETIOLOGY", MID_BLUE))
    e.append(spacer(0.2))

    e.append(sub_header("Routes of Infection"))
    routes = [
        ("<b>Biliary route (35%):</b> Cholangitis, cholecystitis → most common identifiable source overall. Less common in children than adults."),
        ("<b>Portal venous route (20%):</b> Ascending infection from appendicitis, omphalitis (neonates), pylephlebitis — historically the main paediatric cause."),
        ("<b>Haematogenous/Systemic (hepatic artery):</b> S. aureus bacteraemia, infective endocarditis, septicaemia — important in children."),
        ("<b>Direct extension:</b> Subphrenic abscess, penetrating trauma, infected foreign body."),
        ("<b>Cryptogenic (~40%):</b> No identifiable source; often Klebsiella pneumoniae; in children consider dental/periodontal sources."),
        ("<b>Amoebic (E. histolytica):</b> Trophozoites invade colonic mucosa → portal blood → liver. Leads to single large right lobe abscess with 'anchovy paste' aspirate."),
    ]
    for r in routes:
        e.append(bullet(r))
    e.append(spacer(0.3))

    e.append(sub_header("Paediatric-Specific Predisposing Factors"))
    ped_factors = [
        "Chronic granulomatous disease (CGD) — defective NADPH oxidase → recurrent catalase-positive infections (S. aureus, Klebsiella)",
        "Immunosuppression: leukaemia, post-transplant, HIV",
        "Omphalitis / umbilical vein catheterisation in neonates",
        "Complicated appendicitis with portal pyaemia",
        "Sickle cell disease, malnutrition",
        "Travel to or immigration from endemic areas (amoebiasis)",
    ]
    for f in ped_factors:
        e.append(bullet(f))
    e.append(spacer(0.4))

    # ── 4. MICROBIOLOGY ───────────────────────────────────────────────────────
    e.append(section_header("4. MICROBIOLOGY", DARK_BLUE))
    e.append(spacer(0.2))
    e.append(body(
        "Pyogenic liver abscesses are usually polymicrobial. In children, <b>Staphylococcus aureus</b> is the most common single organism. "
        "Gram-negative enteric organisms predominate in biliary/portal-source abscesses."
    ))
    e.append(spacer(0.15))

    micro_data = [
        [Paragraph("Gram-Negative Aerobic", S['TableHeader']), Paragraph("Gram-Positive Aerobic", S['TableHeader']),
         Paragraph("Anaerobes", S['TableHeader']), Paragraph("Other", S['TableHeader'])],
        [Paragraph("Escherichia coli\nKlebsiella pneumoniae\nEnterobacter spp.\nPseudomonas spp.\nProteus spp.", S['SmallBody']),
         Paragraph("Staphylococcus aureus ★\nStreptococcus milleri group\nStreptococcus pyogenes\nEnterococcus spp.", S['SmallBody']),
         Paragraph("Bacteroides fragilis\nFusobacterium spp.\nAnaerobic streptococci\nClostridium spp.", S['SmallBody']),
         Paragraph("Entamoeba histolytica\nCandida spp.\nMycobacterium tuberculosis\n(rare, immunocomp.)", S['SmallBody'])],
    ]
    micro_tbl = Table(micro_data, colWidths=[4.0*cm, 4.0*cm, 4.0*cm, 4.5*cm])
    micro_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), MID_BLUE),
        ('BACKGROUND', (0,1), (-1,-1), LIGHT_GRAY),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 5),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(micro_tbl)
    e.append(spacer(0.15))
    e.append(Paragraph("★ S. aureus: most common cause of liver abscess in children; also predominant organism in chronic granulomatous disease.", S['SmallBody']))
    e.append(spacer(0.4))

    # ── 5. CLINICAL FEATURES ──────────────────────────────────────────────────
    e.append(section_header("5. CLINICAL FEATURES", MID_BLUE))
    e.append(spacer(0.2))

    signs_data = [
        [Paragraph("Symptom/Sign", S['TableHeader']), Paragraph("Frequency", S['TableHeader']),
         Paragraph("Notes", S['TableHeader'])],
        [Paragraph("Fever (often spiking)", S['TableCell']), Paragraph("~90%", S['TableCell']),
         Paragraph("May be low-grade or insidious onset in children", S['TableCell'])],
        [Paragraph("Abdominal pain (RUQ)", S['TableCell']), Paragraph("~60%", S['TableCell']),
         Paragraph("Worse with movement; referred to right shoulder if near dome", S['TableCell'])],
        [Paragraph("Hepatomegaly + tenderness", S['TableCell']), Paragraph("Common", S['TableCell']),
         Paragraph("Accentuated by percussion (fist percussion sign)", S['TableCell'])],
        [Paragraph("Anorexia, malaise, weight loss", S['TableCell']), Paragraph("Common", S['TableCell']),
         Paragraph("May be the predominant presentation; often precedes diagnosis by weeks", S['TableCell'])],
        [Paragraph("Nausea / vomiting", S['TableCell']), Paragraph("Variable", S['TableCell']),
         Paragraph("More common in amoebic ALA", S['TableCell'])],
        [Paragraph("Jaundice", S['TableCell']), Paragraph("<25%", S['TableCell']),
         Paragraph("Late sign; absent without cholangitis; suggests biliary source", S['TableCell'])],
        [Paragraph("Cough / dyspnoea", S['TableCell']), Paragraph("Variable", S['TableCell']),
         Paragraph("Diaphragmatic irritation; may suggest pleuropulmonary involvement", S['TableCell'])],
        [Paragraph("Diarrhoea (bloody)", S['TableCell']), Paragraph("Rare in pyogenic", S['TableCell']),
         Paragraph("Concurrent amoebic dysentery suggests ALA", S['TableCell'])],
    ]
    signs_tbl = Table(signs_data, colWidths=[4.5*cm, 2.5*cm, 9.5*cm])
    signs_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_GRAY]),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 6),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(signs_tbl)
    e.append(spacer(0.4))

    # ── 6. INVESTIGATIONS ─────────────────────────────────────────────────────
    e.append(section_header("6. INVESTIGATIONS", DARK_BLUE))
    e.append(spacer(0.2))
    e.append(sub_header("Laboratory Studies"))
    labs = [
        "<b>FBC:</b> Leukocytosis (~90% of cases); anaemia (chronic disease)",
        "<b>LFTs:</b> Elevated ALP (~80%); elevated bilirubin (~50%); transaminitis (~50%)",
        "<b>CRP/ESR:</b> Elevated; monitor response to treatment",
        "<b>Blood cultures:</b> Positive in >50% of pyogenic cases — essential before antibiotics",
        "<b>Serology:</b> Amoebic serology (IHA, ELISA) — high sensitivity/specificity for ALA; useful in children",
        "<b>Stool microscopy &amp; culture:</b> Cysts/trophozoites in ~50% of ALA — send stool O&P",
        "<b>Procalcitonin:</b> Elevated; helps differentiate bacterial from other causes",
        "<b>Immunological workup:</b> DHR/NBT test to exclude CGD if recurrent or unusual organisms",
    ]
    for l in labs:
        e.append(bullet(l))
    e.append(spacer(0.3))

    e.append(sub_header("Imaging"))
    e.append(spacer(0.15))
    imaging_data = [
        [Paragraph("Modality", S['TableHeader']), Paragraph("Sensitivity", S['TableHeader']),
         Paragraph("Paediatric Preference", S['TableHeader']), Paragraph("Key Findings", S['TableHeader'])],
        [Paragraph("Ultrasound (US)", S['TableCell']), Paragraph("75–90%", S['TableCell']),
         Paragraph("⭐ FIRST-LINE in children (no radiation)", S['TableCell']),
         Paragraph("Hypoechoic/heterogeneous mass; internal echoes; can guide aspiration", S['TableCell'])],
        [Paragraph("CT (with IV contrast)", S['TableCell']), Paragraph("~100%", S['TableCell']),
         Paragraph("If US inconclusive; surgical planning", S['TableCell']),
         Paragraph("Hypodense lesion; peripheral rim enhancement (<20%); gas = bad sign; loculations", S['TableCell'])],
        [Paragraph("MRI", S['TableCell']), Paragraph("High", S['TableCell']),
         Paragraph("Small abscesses; no radiation", S['TableCell']),
         Paragraph("T1 low SI, T2 high SI; gadolinium enhances rim; superior for small lesions", S['TableCell'])],
        [Paragraph("CXR", S['TableCell']), Paragraph("Low", S['TableCell']),
         Paragraph("Initial screen", S['TableCell']),
         Paragraph("Elevated R hemi-diaphragm; basal atelectasis; R pleural effusion", S['TableCell'])],
    ]
    img_tbl = Table(imaging_data, colWidths=[2.8*cm, 2.2*cm, 4.0*cm, 7.5*cm])
    img_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), MID_BLUE),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_BLUE]),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 5),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(img_tbl)
    e.append(spacer(0.4))

    # Images from textbooks/search
    e.append(sub_header("Imaging Gallery"))
    e.append(spacer(0.15))

    img_urls = [
        ("https://cdn.orris.care/cdss_images/ebae9395b11200b4a01df42abd7bc211f945ea4280987d316d8341d60a5acaf2.png",
         "Fig. 1: CT scan showing liver abscess with rim enhancement (Current Surgical Therapy 14e, p.440)"),
        ("https://cdn.orris.care/cdss_images/pmc_clinical_VQA_3dd86f2681fae8c08e5ca532d6f8ca61d584447826be6c3d4a287343cc8207fe.jpg",
         "Fig. 2: CT – Pediatric pyogenic liver abscess (left lobe) with rim enhancement and extension to falciform ligament"),
        ("https://cdn.orris.care/cdss_images/a2e4d1eb80dab7d2a77764277a685f9db881e6c15b11e0bfd8d3ba744bc95665.png",
         "Fig. 3: Ultrasound – Liver abscess (Bailey & Love's Surgery 28e)"),
        ("https://cdn.orris.care/cdss_images/pmc_clinical_VQA_47510f064ff2f15ba9c569f0d46a9322100b4291a902ef1ca6a91a60e261ec7b.jpg",
         "Fig. 4: Serial imaging showing resolution of liver abscess (US at 1 month and 3 months; CT at 8 months post-treatment)"),
    ]

    image_rows = []
    for url, cap in img_urls:
        img = get_image_from_url(url, 7.5*cm, 5.5*cm)
        if img:
            image_rows.append([img, Paragraph(cap, S['Caption'])])

    if len(image_rows) >= 2:
        for i in range(0, len(image_rows), 2):
            row1_img = image_rows[i][0]
            row1_cap = image_rows[i][1]
            if i+1 < len(image_rows):
                row2_img = image_rows[i+1][0]
                row2_cap = image_rows[i+1][1]
            else:
                row2_img = Spacer(1, 0.1*cm)
                row2_cap = Paragraph("", S['Caption'])
            img_grid = Table(
                [[row1_img, row2_img], [row1_cap, row2_cap]],
                colWidths=[8.0*cm, 8.0*cm]
            )
            img_grid.setStyle(TableStyle([
                ('ALIGN', (0,0), (-1,-1), 'CENTER'),
                ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
                ('TOPPADDING', (0,0), (-1,-1), 4),
                ('BOTTOMPADDING', (0,0), (-1,-1), 4),
            ]))
            e.append(img_grid)
            e.append(spacer(0.2))

    e.append(spacer(0.4))

    # ── 7. DIAGNOSIS ──────────────────────────────────────────────────────────
    e.append(section_header("7. DIFFERENTIAL DIAGNOSIS", MID_BLUE))
    e.append(spacer(0.2))
    diffs = [
        "<b>Hepatocellular carcinoma / hepatoblastoma:</b> Hepatic mass; AFP elevated",
        "<b>Hepatic cysts:</b> Simple cysts / hydatid (Echinococcus) — thin-walled, no fever",
        "<b>Haemangioma:</b> Vascular pattern on US/CT; no fever",
        "<b>Focal nodular hyperplasia / adenoma:</b> Young patients; incidental finding",
        "<b>Necrotic/post-chemotherapy hepatic lesion:</b> History of malignancy",
        "<b>Hepatic haematoma:</b> History of trauma",
        "<b>Inflammatory pseudotumour of liver:</b> Rare benign; young men; simulates abscess/tumour on imaging",
        "<b>Biliary pathology (cholangitis, cholecystitis):</b> Biliary dilation on imaging",
        "<b>Sub-phrenic abscess:</b> Elevated diaphragm; may mimic liver lesion",
    ]
    for d in diffs:
        e.append(bullet(d))
    e.append(spacer(0.4))

    # ── 8. MANAGEMENT ─────────────────────────────────────────────────────────
    e.append(section_header("8. MANAGEMENT", DARK_BLUE))
    e.append(spacer(0.2))

    e.append(sub_header("A. Pyogenic Liver Abscess — Treatment Algorithm"))
    e.append(spacer(0.1))
    e.append(body(
        "Treatment requires: (1) identification and treatment of the source, (2) microbiological diagnosis via blood and abscess cultures, "
        "(3) appropriate antibiotic therapy, and (4) drainage of the abscess in most cases."
    ))
    e.append(spacer(0.15))

    mgmt_data = [
        [Paragraph("Step", S['TableHeader']), Paragraph("Intervention", S['TableHeader']),
         Paragraph("Details", S['TableHeader'])],
        [Paragraph("1", S['TableCell']), Paragraph("Blood cultures + abscess aspiration", S['TableCell']),
         Paragraph("Obtain before starting antibiotics. Send aerobic + anaerobic cultures. Positive in >50% of cases.", S['TableCell'])],
        [Paragraph("2", S['TableCell']), Paragraph("Empiric IV antibiotics", S['TableCell']),
         Paragraph("Piperacillin/tazobactam (monotherapy) OR ceftriaxone + metronidazole. Consider carbapenems if ESBL risk/post-biliary instrumentation. Cover anaerobes (metronidazole/clindamycin) and Gram-negatives.", S['TableCell'])],
        [Paragraph("3", S['TableCell']), Paragraph("Percutaneous drainage (US/CT-guided)", S['TableCell']),
         Paragraph("Standard of care. Catheter drainage recommended for abscesses >5 cm. Aspiration alone for <3 cm if technically feasible. Multiple catheters for >10 cm abscesses.", S['TableCell'])],
        [Paragraph("4", S['TableCell']), Paragraph("Target antibiotics on culture results", S['TableCell']),
         Paragraph("IV antibiotics until clinical response achieved, then switch to PO for total 4–6 weeks (guided by imaging follow-up at 4–6 weeks).", S['TableCell'])],
        [Paragraph("5", S['TableCell']), Paragraph("Treat underlying cause", S['TableCell']),
         Paragraph("Biliary decompression if biliary origin (ERCP/sphincterotomy/PTD). Treat appendicitis, omphalitis, etc.", S['TableCell'])],
        [Paragraph("6", S['TableCell']), Paragraph("Surgical drainage (if percutaneous fails)", S['TableCell']),
         Paragraph("Incomplete percutaneous drainage, multiloculated abscess, ruptured abscess, unresolved jaundice, renal impairment. Laparoscopic approach preferred.", S['TableCell'])],
    ]
    mgmt_tbl = Table(mgmt_data, colWidths=[1.2*cm, 4.3*cm, 11.0*cm])
    mgmt_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), DARK_BLUE),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_BLUE]),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 5),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
        ('ALIGN', (0,0), (0,-1), 'CENTER'),
    ]))
    e.append(mgmt_tbl)
    e.append(spacer(0.3))

    e.append(sub_header("B. Antibiotic Regimens (Paediatric Dosing)"))
    e.append(spacer(0.1))
    abx_data = [
        [Paragraph("Scenario", S['TableHeader']), Paragraph("Preferred Regimen", S['TableHeader']),
         Paragraph("Alternative", S['TableHeader'])],
        [Paragraph("Empiric (all-source, pyogenic)", S['TableCell']),
         Paragraph("Piperacillin/tazobactam\n90 mg/kg/dose IV q6–8h", S['TableCell']),
         Paragraph("Ceftriaxone 50 mg/kg IV OD + Metronidazole 7.5 mg/kg IV q8h", S['TableCell'])],
        [Paragraph("Biliary source / ESBL risk", S['TableCell']),
         Paragraph("Meropenem 20 mg/kg IV q8h", S['TableCell']),
         Paragraph("Imipenem-cilastatin + vancomycin (if MRSA risk)", S['TableCell'])],
        [Paragraph("S. aureus (CGD/paediatric)", S['TableCell']),
         Paragraph("Flucloxacillin 50 mg/kg IV q6h (MSSA)\nVancomycin 15 mg/kg IV q6h (MRSA)", S['TableCell']),
         Paragraph("Clindamycin (if sensitive)", S['TableCell'])],
        [Paragraph("Amoebic (ALA)", S['TableCell']),
         Paragraph("Metronidazole 35–50 mg/kg/day PO/IV in 3 divided doses × 7–10 days", S['TableCell']),
         Paragraph("Tinidazole 50 mg/kg PO OD × 5 days (≥3 years)", S['TableCell'])],
        [Paragraph("Luminal eradication (post-ALA)", S['TableCell']),
         Paragraph("Paromomycin 25–35 mg/kg/day PO in 3 doses × 7 days", S['TableCell']),
         Paragraph("Diloxanide furoate (not available in all countries)", S['TableCell'])],
    ]
    abx_tbl = Table(abx_data, colWidths=[3.5*cm, 6.5*cm, 6.5*cm])
    abx_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), MID_BLUE),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [white, LIGHT_GRAY]),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 5),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(abx_tbl)
    e.append(spacer(0.15))
    e.append(warning_box(
        "Always start metronidazole BEFORE aspiration if amoebic liver abscess is suspected — aspiration of ALA without metronidazole cover risks spreading infection. "
        "ALA drainage is generally NOT required unless failure of medical therapy, left lobe ALA (risk of pericardial rupture), or large abscess at risk of rupture."
    ))
    e.append(spacer(0.4))

    # ── 9. COMPLICATIONS ──────────────────────────────────────────────────────
    e.append(section_header("9. COMPLICATIONS", DARK_BLUE))
    e.append(spacer(0.2))
    comp_data = [
        [Paragraph("Complication", S['TableHeader']), Paragraph("Notes", S['TableHeader'])],
        [Paragraph("Rupture → peritonitis", S['TableCell']),
         Paragraph("Most feared complication; higher risk with abscesses >10 cm; requires emergency surgical drainage", S['TableCell'])],
        [Paragraph("Pleuropulmonary disease", S['TableCell']),
         Paragraph("~20% of ALA; empyema, hepatobronchial fistula, lung abscess; right pleural effusion (10–15%)", S['TableCell'])],
        [Paragraph("Pericardial extension", S['TableCell']),
         Paragraph("Left lobe ALA — pericardial effusion/tamponade; rare but life-threatening", S['TableCell'])],
        [Paragraph("Portal/splenic vein thrombosis", S['TableCell']),
         Paragraph("Post-infective portal hypertension may follow", S['TableCell'])],
        [Paragraph("Septic emboli / bacteraemia", S['TableCell']),
         Paragraph("Metastatic endophthalmitis (Klebsiella, up to 10% diabetics); brain abscess", S['TableCell'])],
        [Paragraph("Recurrent abscess (12–38%)", S['TableCell']),
         Paragraph("When source not identified or treated; more common with diabetes and unknown organism", S['TableCell'])],
        [Paragraph("Mortality", S['TableCell']),
         Paragraph("4–10% overall in modern series; worse with delay in diagnosis, multiple abscesses, sepsis, malignancy, fungal cause", S['TableCell'])],
    ]
    comp_tbl = Table(comp_data, colWidths=[4.5*cm, 12.0*cm])
    comp_tbl.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), ACCENT_RED),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [HexColor("#fff5f5"), LIGHT_GRAY]),
        ('GRID', (0,0), (-1,-1), 0.5, MED_GRAY),
        ('TOPPADDING',  (0,0), (-1,-1), 5),
        ('BOTTOMPADDING', (0,0), (-1,-1), 5),
        ('LEFTPADDING', (0,0), (-1,-1), 6),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
    ]))
    e.append(comp_tbl)
    e.append(spacer(0.4))

    # ── 10. AMOEBIC LIVER ABSCESS IN CHILDREN ────────────────────────────────
    e.append(section_header("10. AMOEBIC LIVER ABSCESS (ALA) IN CHILDREN", MID_BLUE))
    e.append(spacer(0.2))
    e.append(body(
        "Although amoebic liver abscess is predominantly a disease of adult males (10:1 male:female), it must be considered in children "
        "with <b>travel history</b> to endemic areas (South Asia, Mexico, West/South Africa). ALA is the most common form of "
        "extraintestinal amebiasis and is caused by E. histolytica trophozoites invading the portal circulation."
    ))
    e.append(spacer(0.15))
    ala_points = [
        "<b>Presentation:</b> Fever, RUQ pain, tender hepatomegaly. Concurrent diarrhoea only in 50% — absence does not exclude.",
        "<b>Aspirate:</b> Chocolate-brown 'anchovy paste' — sterile on bacterial culture (unless secondary infection).",
        "<b>Serology:</b> Anti-amoeba antibodies (IHA/ELISA) — high sensitivity; may be negative in early acute illness.",
        "<b>Stool O&P:</b> Positive in ~50% of cases with ALA.",
        "<b>Imaging:</b> Single large abscess in right lobe (>80%); hypoechoic on US; homogeneous low-density on CT.",
        "<b>Treatment:</b> Metronidazole (or tinidazole) is curative in >95%. Follow with luminal cysticidal agent (paromomycin).",
        "<b>Drainage:</b> Not routinely required. Reserve for: left lobe (pericardial risk), large abscess >10 cm, no improvement after 3–5 days of metronidazole, secondary infection.",
        "<b>Reference (PMID 34794678):</b> Gupta et al., Pediatr Clin North Am 2022 — comprehensive review on amebiasis and ALA in children.",
    ]
    for a in ala_points:
        e.append(bullet(a))
    e.append(spacer(0.4))

    # ── 11. FOLLOW-UP ─────────────────────────────────────────────────────────
    e.append(section_header("11. FOLLOW-UP & PROGNOSIS", DARK_BLUE))
    e.append(spacer(0.2))
    fu_points = [
        "Repeat imaging (US or CT) at <b>4–6 weeks</b> after treatment to confirm resolution.",
        "Duration of antibiotics guided by clinical response and imaging — typically <b>4–6 weeks total</b> (IV until response, then oral).",
        "Drainage catheters remain until output is minimal and clear for several days.",
        "Monitor for biliary communication (bile in drains → HIDA scan / MRCP / ERCP).",
        "<b>Investigate for underlying immunodeficiency</b> in all children with pyogenic liver abscess, especially if caused by S. aureus or atypical organisms — DHR assay to exclude CGD.",
        "ALA: follow-up imaging may show persistent cavity for months even after clinical cure — do not over-interpret.",
        "Recurrence prevention: treat source (biliary, appendicitis); complete luminal cysticidal therapy for ALA.",
        "Mortality has dramatically improved with modern antibiotics and percutaneous drainage; overall 4–10%.",
    ]
    for f in fu_points:
        e.append(bullet(f))
    e.append(spacer(0.4))

    # ── 12. KEY POINTS SUMMARY ────────────────────────────────────────────────
    e.append(section_header("12. KEY CLINICAL PEARLS", MID_BLUE))
    e.append(spacer(0.2))
    pearls = [
        ("In a 5-year-old male with fever + RUQ pain, liver abscess must be on the differential. US is the first-line imaging modality.", DARK_BLUE),
        ("S. aureus is the most common cause of pyogenic liver abscess in children — always screen for chronic granulomatous disease.", ACCENT_RED),
        ("Amoebic liver abscess is rare in children but critical to consider with travel history. Start metronidazole before aspiration.", ACCENT_ORANGE),
        ("Blood cultures before antibiotics — positive in >50% of pyogenic cases.", MID_BLUE),
        ("Percutaneous catheter drainage is standard of care for abscesses >5 cm. Surgery reserved for failures.", GREEN),
        ("Empiric therapy: pip-tazo monotherapy OR ceftriaxone + metronidazole. Duration: 4–6 weeks total.", DARK_BLUE),
        ("Investigate all paediatric liver abscess for underlying immunodeficiency (DHR test / NBT for CGD).", ACCENT_RED),
        ("ALA drainage is not routinely required — most respond to metronidazole alone. Always follow with luminal cysticidal agent.", ACCENT_ORANGE),
    ]
    for i, (pearl, col) in enumerate(pearls, 1):
        row_bg = LIGHT_BLUE if i % 2 == 0 else white
        p_tbl = Table([[
            Paragraph(f"<b>{i}</b>", ParagraphStyle('PNum', fontName='Helvetica-Bold', fontSize=12,
                                                      textColor=white, alignment=TA_CENTER)),
            Paragraph(pearl, S['SmallBody'])
        ]], colWidths=[1.0*cm, 15.0*cm])
        p_tbl.setStyle(TableStyle([
            ('BACKGROUND', (0,0), (0,-1), col),
            ('BACKGROUND', (1,0), (1,-1), row_bg),
            ('TOPPADDING', (0,0), (-1,-1), 5),
            ('BOTTOMPADDING', (0,0), (-1,-1), 5),
            ('LEFTPADDING', (0,0), (-1,-1), 6),
            ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
        ]))
        e.append(p_tbl)
        e.append(spacer(0.05))
    e.append(spacer(0.4))

    # ── 13. REFERENCES ────────────────────────────────────────────────────────
    e.append(section_header("13. REFERENCES", DARK_BLUE))
    e.append(spacer(0.15))
    refs = [
        "1. Feldman M, et al. <i>Sleisenger and Fordtran's Gastrointestinal and Liver Disease</i>, 10e. Chapter 84: Liver Abscess (pp. 1591–1598).",
        "2. Williams NS, et al. <i>Bailey and Love's Short Practice of Surgery</i>, 28e. Chapter 69: Liver abscess (pp. 2667–2720).",
        "3. Cameron JL (ed). <i>Current Surgical Therapy</i>, 14e. Pyogenic and Amebic Liver Abscess (pp. 440–447).",
        "4. Kasper DL, et al. <i>Harrison's Principles of Internal Medicine</i>, 22e (2025). Amebiasis: Amebic Liver Abscess.",
        "5. Gupta S, Smith L, Diakiw A. Amebiasis and Amebic Liver Abscess in Children. <i>Pediatr Clin North Am</i>. 2022;69(1):69–85. PMID: 34794678.",
        "6. Murray & Nadel's Textbook of Respiratory Medicine: Pleuropulmonary complications of amebic liver abscess.",
        "7. Goodman & Gilman's Pharmacological Basis of Therapeutics, 13e: Metronidazole, paromomycin — antiparasitic agents.",
    ]
    for ref in refs:
        e.append(Paragraph(ref, S['SmallBody']))
        e.append(spacer(0.1))

    return e

# ── BUILD PDF ──────────────────────────────────────────────────────────────────
def main():
    doc = SimpleDocTemplate(
        OUTPUT_PATH,
        pagesize=A4,
        leftMargin=2.0*cm,
        rightMargin=2.0*cm,
        topMargin=2.2*cm,
        bottomMargin=1.8*cm,
        title="Liver Abscess in a 5-Year-Old Male",
        author="Orris Medical AI",
        subject="Paediatric Liver Abscess — Clinical Reference",
    )

    story = []
    story.extend(cover_page())
    story.extend(build_content())

    doc.build(story, onFirstPage=lambda c, d: None, onLaterPages=header_footer)
    print(f"PDF saved to: {OUTPUT_PATH}")

if __name__ == "__main__":
    main()
Running Command

cd /home/daytona/workspace/liver-abscess-pdf && python generate_pdf.py 2>&1

Editing File

~/liver-abscess-pdf/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/liver-abscess-pdf/generate_pdf.py'
Your credits are exhausted. Your daily credits reset tomorrow, or you can purchase a top-up for more.
This is a shared conversation. Sign in to Orris to start your own chat.