Make pdf on liver abscess in 5yr old male
"liver abscess" AND child
PMID: 34794678
liver abscess ultrasound CT scan child pediatric
mkdir -p /home/daytona/workspace/liver-abscess-pdf
~/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 & 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()
cd /home/daytona/workspace/liver-abscess-pdf && python generate_pdf.py 2>&1
~/liver-abscess-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/liver-abscess-pdf/generate_pdf.py'