Discuss the metabolism of bilirubin and types of jaundice
bilirubin metabolism jaundice


| Condition | Defect | Bilirubin Type | Features |
|---|---|---|---|
| Gilbert Syndrome | Reduced UGT1A1 expression (A[TA]₇TAA promoter polymorphism) + mildly reduced uptake | Unconjugated | Benign, intermittent, triggered by fasting/stress; prevalence ~5-10% |
| Crigler-Najjar Type I (CN-I) | Complete absence of UGT1A1 | Severe unconjugated | Life-threatening; kernicterus without phototherapy/transplant |
| Crigler-Najjar Type II (CN-II) | Severely reduced UGT1A1 (<10% of normal) | Unconjugated | Less severe; responds to phenobarbital |
| Dubin-Johnson Syndrome (DJS) | Absent MRP2 (ABCC2) - defective canalicular excretion | Predominantly conjugated | Benign; black liver pigment; typical BSP excretion pattern; worsened by pregnancy/OCPs |
| Rotor Syndrome | Absent OATP1B1 + OATP1B3 - defective hepatic reuptake of secreted conjugates | Predominantly conjugated | Benign; normal liver histology; no pigment deposits |
| Feature | Pre-hepatic | Hepatic | Post-hepatic |
|---|---|---|---|
| Bilirubin type | Unconjugated | Mixed | Conjugated |
| Urine bilirubin | Absent | Present | Present |
| Urine urobilinogen | Increased | Decreased | Absent |
| Stool color | Normal/dark | Normal/pale | Pale (clay) |
| ALP / GGT | Normal | Mildly raised | Markedly raised |
| ALT / AST | Normal | Markedly raised | Mildly raised |
| Pruritus | No | Variable | Yes |
Generate a clinical summary PDF on diagnosing jaundice with decision trees
ls -la /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace && echo "workspace ready"
mkdir -p /home/daytona/workspace/jaundice-pdf && echo "dir created"
~/jaundice-pdf/generate_jaundice_pdf.py
"""
Clinical Summary PDF: Diagnosing Jaundice with Decision Trees
Uses reportlab for professional layout with embedded decision tree diagrams drawn programmatically.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.lib.colors import (
HexColor, black, white, Color
)
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.graphics.shapes import (
Drawing, Rect, String, Line, Polygon, Group, Circle
)
from reportlab.graphics.renderPDF import draw as renderDraw
from reportlab.graphics import renderPDF
from reportlab.platypus import Image as RLImage
import io
# ─────────────────────────── COLOURS ────────────────────────────
C_NAVY = HexColor("#1A2E4A")
C_TEAL = HexColor("#0E7C7B")
C_AMBER = HexColor("#E8A838")
C_RED = HexColor("#C0392B")
C_GREEN = HexColor("#27AE60")
C_LIGHT_BG = HexColor("#F0F4F8")
C_BORDER = HexColor("#CBD5E0")
C_TEXT = HexColor("#2D3748")
C_SUBTEXT = HexColor("#718096")
C_YELLOW_BG = HexColor("#FFFBEB")
C_BLUE_BG = HexColor("#EBF8FF")
C_GREEN_BG = HexColor("#F0FFF4")
C_RED_BG = HexColor("#FFF5F5")
C_ORANGE = HexColor("#D97706")
C_PURPLE = HexColor("#6B46C1")
C_PRE_HEPATIC = HexColor("#D97706")
C_HEPATIC = HexColor("#2B6CB0")
C_POST_HEPATIC= HexColor("#276749")
C_BOX_PRE = HexColor("#FFFBEB")
C_BOX_HEP = HexColor("#EBF8FF")
C_BOX_POST = HexColor("#F0FFF4")
W, H = A4 # 595.28 x 841.89 pts
# ─────────────────────────── STYLES ─────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kwargs):
s = ParagraphStyle(name, parent=styles[parent], **kwargs)
return s
S_TITLE = make_style("Title2", fontSize=22, fontName="Helvetica-Bold",
textColor=C_NAVY, alignment=TA_CENTER, spaceAfter=4)
S_SUBTITLE = make_style("Subtitle2", fontSize=11, fontName="Helvetica",
textColor=C_TEAL, alignment=TA_CENTER, spaceAfter=2)
S_DATE = make_style("Date2", fontSize=9, fontName="Helvetica",
textColor=C_SUBTEXT, alignment=TA_CENTER, spaceAfter=12)
S_H1 = make_style("H1", fontSize=13, fontName="Helvetica-Bold",
textColor=white, spaceAfter=6, spaceBefore=10,
leftIndent=6, rightIndent=6)
S_H2 = make_style("H2", fontSize=11, fontName="Helvetica-Bold",
textColor=C_NAVY, spaceAfter=4, spaceBefore=8,
borderPad=3)
S_BODY = make_style("Body2", fontSize=9, fontName="Helvetica",
textColor=C_TEXT, spaceAfter=4, leading=14)
S_BODY_SMALL = make_style("BodySm", fontSize=8, fontName="Helvetica",
textColor=C_TEXT, spaceAfter=3, leading=12)
S_BULLET = make_style("Bullet2", fontSize=9, fontName="Helvetica",
textColor=C_TEXT, leftIndent=14, firstLineIndent=-10,
spaceAfter=2, leading=13)
S_CAPTION = make_style("Caption2", fontSize=8, fontName="Helvetica-Oblique",
textColor=C_SUBTEXT, alignment=TA_CENTER, spaceAfter=4)
S_TABLE_H = make_style("TableH", fontSize=8, fontName="Helvetica-Bold",
textColor=white, alignment=TA_CENTER)
S_TABLE_C = make_style("TableC", fontSize=8, fontName="Helvetica",
textColor=C_TEXT, alignment=TA_CENTER, leading=11)
S_TABLE_L = make_style("TableL", fontSize=8, fontName="Helvetica",
textColor=C_TEXT, alignment=TA_LEFT, leading=11)
S_WARN = make_style("Warn", fontSize=9, fontName="Helvetica-Bold",
textColor=C_RED, spaceAfter=4)
S_NOTE = make_style("Note", fontSize=8, fontName="Helvetica-Oblique",
textColor=C_SUBTEXT, spaceAfter=6)
# ═══════════════════════════════════════════════════════════════
# CUSTOM FLOWABLES
# ═══════════════════════════════════════════════════════════════
class SectionHeader(Flowable):
"""Coloured section header bar."""
def __init__(self, text, color=C_NAVY, width=None, height=24):
super().__init__()
self.text = text
self.color = color
self._width = width
self.height = height
def wrap(self, aw, ah):
self._avail = aw
w = self._width or aw
return w, self.height
def draw(self):
w = self._width or self._avail
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, w, self.height, 4, fill=1, stroke=0)
c.setFillColor(white)
c.setFont("Helvetica-Bold", 11)
c.drawString(10, 7, self.text)
class DecisionTreeFlowable(Flowable):
"""
Draws a vertical decision tree for jaundice diagnosis.
The tree has a root node (question), two branches (yes/no),
then sub-branches.
"""
def __init__(self, width=None, height=None):
super().__init__()
self._w = width or (W - 80)
self._h = height or 340
def wrap(self, aw, ah):
self._avail = aw
return self._w, self._h
def draw(self):
c = self.canv
w = self._w
# ── helper functions ──────────────────────────────────────
def box(x, y, bw, bh, fill_color, stroke_color, text,
font="Helvetica", fsize=8, text_color=white, radius=5):
c.setFillColor(fill_color)
c.setStrokeColor(stroke_color)
c.setLineWidth(1)
c.roundRect(x, y, bw, bh, radius, fill=1, stroke=1)
c.setFillColor(text_color)
c.setFont(font, fsize)
# wrap text manually
lines = text.split('\n')
total_h = len(lines) * (fsize + 2)
start_y = y + bh/2 + total_h/2 - (fsize + 2)
for i, line in enumerate(lines):
c.drawCentredString(x + bw/2, start_y - i*(fsize+2), line)
def diamond(x, y, dw, dh, fill_color, stroke_color, text,
fsize=8, text_color=white):
pts = [x+dw/2, y+dh, x+dw, y+dh/2, x+dw/2, y, x, y+dh/2]
c.setFillColor(fill_color)
c.setStrokeColor(stroke_color)
c.setLineWidth(1.2)
c.setDash([])
p = c.beginPath()
p.moveTo(pts[0], pts[1])
p.lineTo(pts[2], pts[3])
p.lineTo(pts[4], pts[5])
p.lineTo(pts[6], pts[7])
p.close()
c.drawPath(p, fill=1, stroke=1)
c.setFillColor(text_color)
c.setFont("Helvetica-Bold", fsize)
lines = text.split('\n')
for i, line in enumerate(lines):
offset = (len(lines)-1)/2 - i
c.drawCentredString(x + dw/2, y + dh/2 - fsize/2 + offset*(fsize+2), line)
def arrow(x1, y1, x2, y2, label="", label_side="right"):
c.setStrokeColor(C_NAVY)
c.setLineWidth(1.2)
c.setFillColor(C_NAVY)
c.setDash([])
c.line(x1, y1, x2, y2)
# arrowhead
import math
angle = math.atan2(y2-y1, x2-x1)
al = 7
aw2 = 3
ax1 = x2 - al*math.cos(angle) + aw2*math.sin(angle)
ay1 = y2 - al*math.sin(angle) - aw2*math.cos(angle)
ax2 = x2 - al*math.cos(angle) - aw2*math.sin(angle)
ay2 = y2 - al*math.sin(angle) + aw2*math.cos(angle)
p = c.beginPath()
p.moveTo(x2, y2)
p.lineTo(ax1, ay1)
p.lineTo(ax2, ay2)
p.close()
c.drawPath(p, fill=1, stroke=0)
if label:
c.setFillColor(C_SUBTEXT)
c.setFont("Helvetica-Oblique", 7)
if label_side == "right":
c.drawString(x2+3, (y1+y2)/2, label)
elif label_side == "left":
c.drawRightString(x2-3, (y1+y2)/2, label)
else:
c.drawCentredString((x1+x2)/2, (y1+y2)/2+4, label)
def horiz_line(x1, y, x2):
c.setStrokeColor(C_NAVY)
c.setLineWidth(1.2)
c.line(x1, y, x2, y)
# ── layout constants ──────────────────────────────────────
TOP = self._h - 10
bw_root = 260; bh_root = 32
bw_q = 160; bh_q = 36
bw_leaf = 140; bh_leaf = 46
dw = 140; dh = 36
# ─────────── Row 1: ROOT ──────────────────────────────────
rx = w/2 - bw_root/2
ry = TOP - bh_root
box(rx, ry, bw_root, bh_root, C_NAVY, C_NAVY,
"Patient presents with JAUNDICE", "Helvetica-Bold", 10)
# arrow down
arrow(w/2, ry, w/2, ry - 22)
# ─────────── Row 2: Q1 diamond ────────────────────────────
q1y = ry - 22 - dh
q1x = w/2 - dw/2
diamond(q1x, q1y, dw, dh, C_TEAL, C_TEAL,
"Is bilirubin\nconjugated?", 8)
arrow_top = q1y + dh/2
# ─────────── Row 3: two branches ─────────────────────────
branch_y = q1y - 30
# LEFT branch: Unconjugated (NO)
left_cx = w * 0.20
left_box_x = left_cx - bw_q/2
left_box_y = branch_y - bh_q
# RIGHT branch: Conjugated (YES)
right_cx = w * 0.80
right_box_x = right_cx - bw_q/2
right_box_y = branch_y - bh_q
# horizontal line from diamond to branches
horiz_line(left_cx, branch_y, right_cx, branch_y)
# vertical from diamond to horiz
c.setStrokeColor(C_NAVY); c.setLineWidth(1.2)
c.line(w/2, q1y, w/2, branch_y)
# vertical drops to boxes
c.line(left_cx, branch_y, left_cx, left_box_y + bh_q)
c.line(right_cx, branch_y, right_cx, right_box_y + bh_q)
# labels on horizontal
c.setFillColor(C_RED); c.setFont("Helvetica-Bold", 8)
c.drawCentredString(left_cx + 18, branch_y + 4, "NO (Unconjugated)")
c.setFillColor(C_GREEN); c.setFont("Helvetica-Bold", 8)
c.drawCentredString(right_cx - 18, branch_y + 4, "YES (Conjugated)")
# Q2 LEFT: elevated?
box(left_box_x, left_box_y, bw_q, bh_q, C_AMBER, C_AMBER,
"Check: Haemolysis\nmarkers (LDH, retics,\nhaptoglobin)", "Helvetica", 7.5)
# Q2 RIGHT
box(right_box_x, right_box_y, bw_q, bh_q, C_TEAL, C_TEAL,
"Check: ALP, GGT\nvs ALT, AST\nRatio", "Helvetica", 7.5)
# ─────────── Row 4: leaf nodes ────────────────────────────
row4_y = left_box_y - 50
leaf_h = 48
# Left branch sub-nodes
ll_cx = w * 0.10
lm_cx = w * 0.30
box(ll_cx - bw_leaf/2, row4_y - leaf_h, bw_leaf, leaf_h,
C_BOX_PRE, C_PRE_HEPATIC,
"PRE-HEPATIC\nHaemolytic anaemia\nIneffective erythropoiesis\nPigment stones",
"Helvetica", 7, C_PRE_HEPATIC)
box(lm_cx - bw_leaf/2, row4_y - leaf_h, bw_leaf, leaf_h,
C_BOX_PRE, C_ORANGE,
"INHERITED (Unconj.)\nGilbert syndrome\nCrigler-Najjar I/II\nNeonatal jaundice",
"Helvetica", 7, C_ORANGE)
# Right branch sub-nodes
rl_cx = w * 0.65
rr_cx = w * 0.87
box(rl_cx - bw_leaf/2, row4_y - leaf_h, bw_leaf, leaf_h,
C_BOX_HEP, C_HEPATIC,
"HEPATOCELLULAR\nViral/Alcoholic hepatitis\nDrug injury, Cirrhosis\nAST/ALT >>ALP",
"Helvetica", 7, C_HEPATIC)
box(rr_cx - bw_leaf/2, row4_y - leaf_h, bw_leaf, leaf_h,
C_BOX_POST, C_POST_HEPATIC,
"POST-HEPATIC\nCBD stone, Pancreatic Ca\nCholangiocarcinoma\nALP/GGT >> AST",
"Helvetica", 7, C_POST_HEPATIC)
# connecting arrows to leaf nodes
# left box bottom -> two sub-nodes
lbx_cx = left_cx
lbx_by = left_box_y
# horizontal
c.setStrokeColor(C_NAVY); c.setLineWidth(1)
c.line(ll_cx, row4_y, lm_cx, row4_y)
c.line(lbx_cx, lbx_by, lbx_cx, row4_y)
c.line(ll_cx, row4_y, ll_cx, row4_y - leaf_h + leaf_h)
c.line(lm_cx, row4_y, lm_cx, row4_y)
arrow(ll_cx, row4_y, ll_cx, row4_y - leaf_h + leaf_h + 2)
arrow(lm_cx, row4_y, lm_cx, row4_y - leaf_h + leaf_h + 2)
# right box -> two sub-nodes
rbx_cx = right_cx
rbx_by = right_box_y
c.line(rl_cx, row4_y, rr_cx, row4_y)
c.line(rbx_cx, rbx_by, rbx_cx, row4_y)
arrow(rl_cx, row4_y, rl_cx, row4_y - leaf_h + leaf_h + 2)
arrow(rr_cx, row4_y, rr_cx, row4_y - leaf_h + leaf_h + 2)
# ── legend ────────────────────────────────────────────────
leg_y = row4_y - leaf_h - 18
c.setFont("Helvetica-Bold", 7.5)
c.setFillColor(C_PRE_HEPATIC)
c.rect(10, leg_y, 10, 8, fill=1, stroke=0)
c.setFillColor(C_TEXT)
c.drawString(24, leg_y + 1, "Pre-hepatic")
c.setFillColor(C_HEPATIC)
c.rect(95, leg_y, 10, 8, fill=1, stroke=0)
c.setFillColor(C_TEXT)
c.drawString(109, leg_y + 1, "Hepatocellular")
c.setFillColor(C_POST_HEPATIC)
c.rect(200, leg_y, 10, 8, fill=1, stroke=0)
c.setFillColor(C_TEXT)
c.drawString(214, leg_y + 1, "Post-hepatic")
c.setFillColor(C_TEAL)
c.rect(295, leg_y, 10, 8, fill=1, stroke=0)
c.setFillColor(C_TEXT)
c.drawString(309, leg_y + 1, "Decision node")
class UrineStoolDiagram(Flowable):
"""Visual summary of urine/stool findings per jaundice type."""
def __init__(self, width=None):
super().__init__()
self._w = width or (W - 80)
self._h = 110
def wrap(self, aw, ah):
return self._w, self._h
def draw(self):
c = self.canv
w = self._w
col_w = w / 3.2
titles = ["PRE-HEPATIC", "HEPATOCELLULAR", "POST-HEPATIC"]
colors = [C_PRE_HEPATIC, C_HEPATIC, C_POST_HEPATIC]
data = [
[
("Bilirubin (serum)", "Unconjugated ↑"),
("Urine bilirubin", "Absent"),
("Urine urobilinogen", "Increased ↑"),
("Stool colour", "Normal / Dark"),
("ALP/GGT", "Normal"),
("AST/ALT", "Normal"),
],
[
("Bilirubin (serum)", "Mixed ↑↑"),
("Urine bilirubin", "Present (+)"),
("Urine urobilinogen", "Decreased"),
("Stool colour", "Normal / Pale"),
("ALP/GGT", "Mildly raised"),
("AST/ALT", "Markedly raised"),
],
[
("Bilirubin (serum)", "Conjugated ↑↑"),
("Urine bilirubin", "Strongly Present"),
("Urine urobilinogen", "Absent"),
("Stool colour", "Pale / Clay"),
("ALP/GGT", "Markedly raised"),
("AST/ALT", "Mildly raised"),
],
]
for i, (title, col, entries) in enumerate(zip(titles, colors, data)):
x = i * (w / 3) + 2
# header
c.setFillColor(col)
c.roundRect(x, self._h - 18, col_w - 4, 16, 3, fill=1, stroke=0)
c.setFillColor(white)
c.setFont("Helvetica-Bold", 8)
c.drawCentredString(x + (col_w-4)/2, self._h - 12, title)
# rows
row_h = 13
for j, (label, value) in enumerate(entries):
row_y = self._h - 18 - (j+1)*row_h
bg = C_LIGHT_BG if j % 2 == 0 else white
c.setFillColor(bg)
c.rect(x, row_y, col_w - 4, row_h - 1, fill=1, stroke=0)
c.setFillColor(C_TEXT)
c.setFont("Helvetica", 7)
c.drawString(x + 3, row_y + 3, label + ":")
c.setFont("Helvetica-Bold", 7)
# colour code value
if "Normal" in value or "Absent" == value or "Decreased" in value:
c.setFillColor(C_SUBTEXT)
elif "Present" in value or "raised" in value.lower() or "↑" in value:
c.setFillColor(col)
else:
c.setFillColor(C_TEXT)
c.drawRightString(x + col_w - 6, row_y + 3, value)
class ObstructionTreeFlowable(Flowable):
"""Decision tree for differentiating intrahepatic vs extrahepatic cholestasis."""
def __init__(self, width=None):
super().__init__()
self._w = width or (W - 80)
self._h = 260
def wrap(self, aw, ah):
return self._w, self._h
def draw(self):
c = self.canv
w = self._w
def box(x, y, bw, bh, fc, sc, text, font="Helvetica", fs=8, tc=white, r=5):
c.setFillColor(fc); c.setStrokeColor(sc)
c.setLineWidth(1); c.roundRect(x, y, bw, bh, r, fill=1, stroke=1)
c.setFillColor(tc); c.setFont(font, fs)
lines = text.split('\n')
th = len(lines) * (fs + 2)
sy = y + bh/2 + th/2 - (fs + 2)
for i, l in enumerate(lines):
c.drawCentredString(x+bw/2, sy - i*(fs+2), l)
def arr(x1, y1, x2, y2):
import math
c.setStrokeColor(C_NAVY); c.setLineWidth(1.2); c.setDash([])
c.line(x1, y1, x2, y2)
c.setFillColor(C_NAVY)
ang = math.atan2(y2-y1, x2-x1)
al=6; aw2=2.5
ax1 = x2 - al*math.cos(ang) + aw2*math.sin(ang)
ay1 = y2 - al*math.sin(ang) - aw2*math.cos(ang)
ax2 = x2 - al*math.cos(ang) - aw2*math.sin(ang)
ay2 = y2 - al*math.sin(ang) + aw2*math.cos(ang)
p = c.beginPath()
p.moveTo(x2, y2); p.lineTo(ax1,ay1); p.lineTo(ax2,ay2); p.close()
c.drawPath(p, fill=1, stroke=0)
def hline(x1, y, x2):
c.setStrokeColor(C_NAVY); c.setLineWidth(1.2); c.line(x1,y,x2,y)
TOP = self._h - 8
bw=200; bh=28
# Root
box(w/2-bw/2, TOP-bh, bw, bh, C_TEAL, C_TEAL,
"Conjugated hyperbilirubinaemia confirmed", "Helvetica-Bold", 9)
arr(w/2, TOP-bh, w/2, TOP-bh-22)
# Q1 diamond
dw=180; dh=32
q1y = TOP-bh-22-dh; q1x=w/2-dw/2
# draw diamond
pts = [w/2, q1y+dh, q1x+dw, q1y+dh/2, w/2, q1y, q1x, q1y+dh/2]
c.setFillColor(C_NAVY); c.setStrokeColor(C_NAVY); c.setLineWidth(1)
p = c.beginPath()
p.moveTo(pts[0],pts[1]); p.lineTo(pts[2],pts[3])
p.lineTo(pts[4],pts[5]); p.lineTo(pts[6],pts[7]); p.close()
c.drawPath(p, fill=1, stroke=1)
c.setFillColor(white); c.setFont("Helvetica-Bold", 8)
c.drawCentredString(w/2, q1y+dh/2-4, "Ultrasound: Bile duct dilated?")
branch_y = q1y - 25
lcx = w*0.22; rcx = w*0.78
hline(lcx, branch_y, rcx, )
c.setStrokeColor(C_NAVY); c.setLineWidth(1.2)
c.line(w/2, q1y, w/2, branch_y)
c.line(lcx, branch_y, lcx, branch_y-28)
c.line(rcx, branch_y, rcx, branch_y-28)
c.setFillColor(C_RED); c.setFont("Helvetica-Bold", 8)
c.drawCentredString(lcx+14, branch_y+4, "NO")
c.setFillColor(C_GREEN); c.setFont("Helvetica-Bold", 8)
c.drawCentredString(rcx-14, branch_y+4, "YES")
bw2=170; bh2=32
# LEFT
box(lcx-bw2/2, branch_y-28-bh2, bw2, bh2, C_BOX_HEP, C_HEPATIC,
"INTRAHEPATIC cholestasis\nLiver biopsy / LFT pattern / serology",
"Helvetica", 7.5, C_HEPATIC)
# RIGHT
box(rcx-bw2/2, branch_y-28-bh2, bw2, bh2, C_BOX_POST, C_POST_HEPATIC,
"EXTRAHEPATIC obstruction\nMRCP / ERCP / CT abdomen",
"Helvetica", 7.5, C_POST_HEPATIC)
leaf_y = branch_y - 28 - bh2 - 22
# Left sub-nodes
bw3=120; bh3=36
ll=w*0.12; lm=w*0.34
box(ll-bw3/2, leaf_y-bh3, bw3, bh3, C_BOX_HEP, C_HEPATIC,
"PBC / PSC\nAIH / Drug-induced\nAlcoholic hepatitis",
"Helvetica", 7, C_HEPATIC)
box(lm-bw3/2, leaf_y-bh3, bw3, bh3, C_BOX_HEP, HexColor("#2C5282"),
"Viral hepatitis\nWilson's disease\nInfiltrative liver dz",
"Helvetica", 7, HexColor("#2C5282"))
arr(lcx-bw2/2+bw2*0.33, branch_y-28-bh2, ll, leaf_y)
arr(lcx-bw2/2+bw2*0.67, branch_y-28-bh2, lm, leaf_y)
# Right sub-nodes
rl=w*0.64; rr=w*0.88
box(rl-bw3/2, leaf_y-bh3, bw3, bh3, C_BOX_POST, C_POST_HEPATIC,
"Choledocholithiasis\nBiliary stricture\nPancreatitis",
"Helvetica", 7, C_POST_HEPATIC)
box(rr-bw3/2, leaf_y-bh3, bw3, bh3, C_BOX_POST, HexColor("#1C4532"),
"Pancreatic Ca\nCholangiocarcinoma\nAmpullary Ca",
"Helvetica", 7, HexColor("#1C4532"))
arr(rcx-bw2/2+bw2*0.33, branch_y-28-bh2, rl, leaf_y)
arr(rcx-bw2/2+bw2*0.67, branch_y-28-bh2, rr, leaf_y)
# ═══════════════════════════════════════════════════════════════
# PAGE TEMPLATES
# ═══════════════════════════════════════════════════════════════
def header_footer(canvas, doc):
canvas.saveState()
page = doc.page
# Header stripe
canvas.setFillColor(C_NAVY)
canvas.rect(0, H - 28, W, 28, fill=1, stroke=0)
canvas.setFillColor(white)
canvas.setFont("Helvetica-Bold", 9)
canvas.drawString(20, H - 18, "CLINICAL SUMMARY: DIAGNOSING JAUNDICE")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(W - 20, H - 18, f"Page {page}")
# Footer
canvas.setFillColor(C_LIGHT_BG)
canvas.rect(0, 0, W, 20, fill=1, stroke=0)
canvas.setFillColor(C_SUBTEXT)
canvas.setFont("Helvetica-Oblique", 7)
canvas.drawString(20, 6, "For educational use only. Not a substitute for clinical judgment. Sources: Harrison's 22E, Ganong's 26E, Schwartz's Surgery 11E.")
canvas.drawRightString(W - 20, 6, "June 2026")
canvas.restoreState()
# ═══════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ═══════════════════════════════════════════════════════════════
OUT = "/home/daytona/workspace/jaundice-pdf/Diagnosing_Jaundice_Clinical_Summary.pdf"
doc = SimpleDocTemplate(
OUT,
pagesize=A4,
leftMargin=25*mm, rightMargin=25*mm,
topMargin=22*mm, bottomMargin=18*mm,
title="Clinical Summary: Diagnosing Jaundice",
author="Orris Clinical AI",
subject="Jaundice Diagnosis with Decision Trees",
)
story = []
P = Paragraph
SP = lambda n=6: Spacer(1, n)
HR = lambda: HRFlowable(width="100%", thickness=0.5, color=C_BORDER, spaceAfter=4)
# ──────────────────────────────────────────────────────────────
# PAGE 1: TITLE + OVERVIEW + METABOLISM SUMMARY
# ──────────────────────────────────────────────────────────────
story.append(SP(8))
story.append(P("CLINICAL SUMMARY", S_TITLE))
story.append(P("Diagnosing Jaundice: A Systematic Approach with Decision Trees", S_SUBTITLE))
story.append(P("Based on Harrison's 22E | Ganong's Review of Medical Physiology 26E | Schwartz's Surgery 11E", S_DATE))
story.append(HR())
story.append(SP(4))
# Definition box
def_data = [[P(
"<b>JAUNDICE (Icterus)</b>: Yellow discoloration of skin, sclera, and mucous membranes caused by "
"bilirubin deposition. Clinically detectable when total serum bilirubin <b>>2 mg/dL (34 μmol/L)</b>. "
"Normal range: 0.2–1.0 mg/dL. The sclera is affected earliest due to its high elastin content.",
S_BODY)]]
story.append(Table(def_data, colWidths=["100%"],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_YELLOW_BG),
("BOX", (0,0), (-1,-1), 1, C_AMBER),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [5]),
])))
story.append(SP(8))
# ── Bilirubin Metabolism ──────────────────────────────────────
story.append(SectionHeader(" BILIRUBIN METABOLISM", C_NAVY))
story.append(SP(6))
story.append(P("<b>Source:</b> 70–90% from senescent RBC haemoglobin breakdown; remainder from myoglobin, cytochromes, ineffective erythropoiesis.", S_BODY))
story.append(SP(4))
metab_steps = [
["Step", "Location", "Process", "Key Enzyme/Transporter"],
["1. Production", "RE System\n(spleen, liver)", "Haem → Biliverdin → Bilirubin\n(unconjugated, lipid-soluble)", "Haem oxygenase\nBiliverdin reductase"],
["2. Transport", "Blood", "Bound to albumin\n(not filtered by kidney)", "Albumin (tight binding)"],
["3. Uptake", "Hepatocyte\nsinusoidal surface", "UCB dissociates from albumin\nEnters hepatocyte", "OATP1B1 / OATP1B3"],
["4. Binding", "Hepatocyte\ncytoplasm", "Bound to ligandins\n(prevents back-diffusion)", "Glutathione-S-transferases\n(GST)"],
["5. Conjugation", "Smooth ER\n(hepatocyte)", "Bilirubin + 2× UDPGA →\nBilirubin diglucuronide (water-soluble)", "UGT1A1\n(bilirubin-UDP-glucuronosyltransferase)"],
["6. Excretion", "Bile canaliculus", "Active transport of conjugated\nbilirubin into bile", "MRP2 (ABCC2)"],
["7. Gut metabolism", "Terminal ileum\n& colon", "Conjugated bilirubin → Urobilinogen\n(by intestinal bacteria)", "Bacterial reduction"],
["8. Enterohepatic\ncirculation", "Portal circulation\n→ liver → kidney", "~10–20% urobilinogen reabsorbed;\nrest excreted in stool as stercobilin", "Liver re-excretion\nRenal filtration"],
]
metab_col_w = [28*mm, 32*mm, 65*mm, 55*mm]
metab_table = Table(
[[P(str(c), S_TABLE_H if r==0 else (S_TABLE_L if j<2 else S_TABLE_L)) for j, c in enumerate(row)] for r, row in enumerate(metab_steps)],
colWidths=metab_col_w,
repeatRows=1
)
metab_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_NAVY),
("BACKGROUND", (0,1), (-1,1), C_BLUE_BG),
("BACKGROUND", (0,2), (-1,2), white),
("BACKGROUND", (0,3), (-1,3), C_BLUE_BG),
("BACKGROUND", (0,4), (-1,4), white),
("BACKGROUND", (0,5), (-1,5), C_BLUE_BG),
("BACKGROUND", (0,6), (-1,6), white),
("BACKGROUND", (0,7), (-1,7), C_BLUE_BG),
("BACKGROUND", (0,8), (-1,8), white),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 8),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TEXTCOLOR", (0,0), (-1,0), white),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_BLUE_BG, white]),
]))
story.append(metab_table)
story.append(SP(6))
story.append(P(
"<b>Key point:</b> Conjugation by UGT1A1 converts water-insoluble unconjugated bilirubin to water-soluble "
"bilirubin diglucuronide, enabling excretion into bile. Only <b>conjugated bilirubin</b> appears in urine.",
S_NOTE))
# ──────────────────────────────────────────────────────────────
# PAGE 2: MAIN DECISION TREE
# ──────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(SectionHeader(" DIAGNOSTIC DECISION TREE — OVERVIEW", C_NAVY))
story.append(SP(8))
story.append(DecisionTreeFlowable(width=W - 50*mm, height=330))
story.append(SP(4))
story.append(P(
"Start by determining whether the predominant bilirubin fraction is <b>conjugated (direct) or unconjugated (indirect)</b>. "
"This single test narrows the differential significantly before further workup.",
S_CAPTION))
story.append(SP(10))
story.append(HR())
story.append(SectionHeader(" URINE, STOOL & LAB PATTERN SUMMARY", C_TEAL))
story.append(SP(6))
story.append(UrineStoolDiagram(width=W - 50*mm))
story.append(SP(4))
story.append(P(
"<b>Note:</b> ALP = alkaline phosphatase; GGT = gamma-glutamyl transferase; "
"AST/ALT = aminotransferases. Cholestatic pattern: ALP/GGT >> AST. "
"Hepatocellular pattern: AST/ALT >> ALP.",
S_NOTE))
# ──────────────────────────────────────────────────────────────
# PAGE 3: TYPES OF JAUNDICE + INHERITED SYNDROMES
# ──────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(SectionHeader(" TYPES OF JAUNDICE — DETAILED COMPARISON", C_NAVY))
story.append(SP(8))
types_data = [
[P("Feature", S_TABLE_H), P("Pre-Hepatic", S_TABLE_H), P("Hepatocellular", S_TABLE_H), P("Post-Hepatic", S_TABLE_H)],
[P("Mechanism", S_TABLE_L), P("Excess bilirubin production", S_TABLE_C), P("Impaired uptake/conjugation/excretion", S_TABLE_C), P("Bile flow obstruction", S_TABLE_C)],
[P("Bilirubin type", S_TABLE_L), P("Unconjugated ↑", S_TABLE_C), P("Mixed ↑↑ (both fractions)", S_TABLE_C), P("Conjugated ↑↑", S_TABLE_C)],
[P("Max bilirubin\n(isolated)", S_TABLE_L), P("~4 mg/dL\n(liver compensates)", S_TABLE_C), P("Variable (up to 30+)", S_TABLE_C), P("Variable (progressive)", S_TABLE_C)],
[P("Urine colour", S_TABLE_L), P("Normal", S_TABLE_C), P("Dark (bilirubin)", S_TABLE_C), P("Very dark (bilirubin)", S_TABLE_C)],
[P("Urine bilirubin", S_TABLE_L), P("Absent", S_TABLE_C), P("Present", S_TABLE_C), P("Strongly present", S_TABLE_C)],
[P("Urine urobilinogen", S_TABLE_L), P("Increased ↑↑", S_TABLE_C), P("Decreased", S_TABLE_C), P("Absent", S_TABLE_C)],
[P("Stool colour", S_TABLE_L), P("Normal / Dark", S_TABLE_C), P("Normal / Pale", S_TABLE_C), P("Pale / Clay (acholic)", S_TABLE_C)],
[P("ALP / GGT", S_TABLE_L), P("Normal", S_TABLE_C), P("Mildly raised", S_TABLE_C), P("Markedly raised ↑↑↑", S_TABLE_C)],
[P("AST / ALT", S_TABLE_L), P("Normal", S_TABLE_C), P("Markedly raised ↑↑↑", S_TABLE_C), P("Mildly raised", S_TABLE_C)],
[P("Pruritus", S_TABLE_L), P("No", S_TABLE_C), P("Variable", S_TABLE_C), P("Yes (bile salts in skin)", S_TABLE_C)],
[P("Splenomegaly", S_TABLE_L), P("Yes (haemolysis)", S_TABLE_C), P("Yes (portal HTN)", S_TABLE_C), P("Sometimes", S_TABLE_C)],
[P("Key investigations", S_TABLE_L), P("Blood film, reticulocytes,\nLDH, haptoglobin, Coombs", S_TABLE_C), P("Viral serology, ANA,\nSMA, LKM, liver biopsy", S_TABLE_C), P("US/CT abdomen, MRCP,\nCA 19-9, ERCP", S_TABLE_C)],
[P("Common causes", S_TABLE_L), P("Haemolytic anaemia,\nG6PD, thalassaemia,\nPigment gallstones", S_TABLE_C), P("Viral hepatitis A/B/C/E,\nalcoholic hepatitis,\ndrug-induced, cirrhosis", S_TABLE_C), P("CBD stone, pancreatic Ca,\ncholangiocarcinoma,\nPSC, PBC, stricture", S_TABLE_C)],
]
tw = W - 50*mm
types_col_w = [tw*0.22, tw*0.26, tw*0.26, tw*0.26]
types_table = Table(types_data, colWidths=types_col_w, repeatRows=1)
types_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), C_LIGHT_BG),
("BACKGROUND", (0,0), (-1,0), C_NAVY),
("BACKGROUND", (1,0), (1,0), C_PRE_HEPATIC),
("BACKGROUND", (2,0), (2,0), C_HEPATIC),
("BACKGROUND", (3,0), (3,0), C_POST_HEPATIC),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_BG, white]),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
]))
story.append(types_table)
story.append(SP(10))
story.append(SectionHeader(" INHERITED DISORDERS OF BILIRUBIN METABOLISM", C_PURPLE))
story.append(SP(6))
inh_data = [
[P("Disorder", S_TABLE_H), P("Defect", S_TABLE_H), P("Bilirubin\nType", S_TABLE_H), P("Level", S_TABLE_H), P("Key Features", S_TABLE_H), P("Treatment", S_TABLE_H)],
[P("Gilbert Syndrome", S_TABLE_L), P("↓ UGT1A1 (promoter TATAA box)", S_TABLE_L), P("Unconj.", S_TABLE_C), P("<3 mg/dL", S_TABLE_C), P("Benign; triggered by fasting, illness, stress; 5–10% prevalence", S_TABLE_L), P("Reassurance only", S_TABLE_L)],
[P("Crigler-Najjar\nType I", S_TABLE_L), P("Complete absence of UGT1A1", S_TABLE_L), P("Unconj.", S_TABLE_C), P(">20 mg/dL", S_TABLE_C), P("Severe; kernicterus risk; requires lifelong phototherapy", S_TABLE_L), P("16–18h phototherapy/day;\nliver transplant", S_TABLE_L)],
[P("Crigler-Najjar\nType II", S_TABLE_L), P("Severely ↓ UGT1A1 (<10%)", S_TABLE_L), P("Unconj.", S_TABLE_C), P("6–20 mg/dL", S_TABLE_C), P("Milder; responds to phenobarbital", S_TABLE_L), P("Phenobarbital", S_TABLE_L)],
[P("Dubin-Johnson\nSyndrome", S_TABLE_L), P("Absent MRP2 (ABCC2) –\ncanalicular excretion defect", S_TABLE_L), P("Conj.", S_TABLE_C), P("2–5 mg/dL\n(up to 25)", S_TABLE_C), P("Benign; black liver pigment on biopsy; BSP test abnormal; worsened by OCP/pregnancy", S_TABLE_L), P("None required", S_TABLE_L)],
[P("Rotor Syndrome", S_TABLE_L), P("Absent OATP1B1+1B3 –\ndefective hepatic reuptake", S_TABLE_L), P("Conj.", S_TABLE_C), P("2–7 mg/dL", S_TABLE_C), P("Benign; normal liver histology (no pigment); elevated coproporphyrin I in urine", S_TABLE_L), P("None required", S_TABLE_L)],
]
inh_col_w = [tw*0.16, tw*0.20, tw*0.09, tw*0.09, tw*0.28, tw*0.18]
inh_table = Table(inh_data, colWidths=inh_col_w, repeatRows=1)
inh_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_PURPLE),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_BG, white]),
("TEXTCOLOR", (0,0), (-1,0), white),
]))
story.append(inh_table)
# ──────────────────────────────────────────────────────────────
# PAGE 4: CHOLESTASIS DECISION TREE + INVESTIGATIONS + RED FLAGS
# ──────────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(SectionHeader(" DIFFERENTIATING INTRAHEPATIC vs EXTRAHEPATIC CHOLESTASIS", C_POST_HEPATIC))
story.append(SP(8))
story.append(ObstructionTreeFlowable(width=W - 50*mm))
story.append(SP(4))
story.append(P(
"Ultrasound is the first-line imaging for jaundice. Dilated bile ducts strongly suggest extrahepatic obstruction. "
"MRCP is preferred over ERCP for initial diagnosis; ERCP is reserved for cases where therapeutic intervention is planned.",
S_CAPTION))
story.append(SP(8))
story.append(HR())
# ── Investigations table ──────────────────────────────────────
story.append(SectionHeader(" STEP-WISE INVESTIGATIONS FOR JAUNDICE", C_NAVY))
story.append(SP(6))
inv_data = [
[P("Step", S_TABLE_H), P("Investigation", S_TABLE_H), P("Purpose / Interpretation", S_TABLE_H)],
[P("1st line\n(all patients)", S_TABLE_C),
P("Serum bilirubin (total, direct, indirect)\nLFTs: ALT, AST, ALP, GGT, albumin\nCBC, PT/INR\nUrine dipstick for bilirubin", S_TABLE_L),
P("Fractionation guides pre/hepatic/post differentiation.\nAlbumin & PT reflect synthetic function.\nBilirubinuria = conjugated hyperbilirubinaemia.", S_TABLE_L)],
[P("2nd line\n(if hepatocellular)", S_TABLE_C),
P("Viral serology: HBsAg, anti-HBc, anti-HCV, anti-HAV IgM\nAutoimmune: ANA, ASMA, AMA, anti-LKM1\nSerum immunoglobulins\nAlpha-1-antitrypsin, ceruloplasmin (if <40 yrs)", S_TABLE_L),
P("Differentiate viral vs autoimmune vs metabolic liver disease.\nCeruloplasmin low in Wilson's disease.\nAMA positive in primary biliary cholangitis (PBC).", S_TABLE_L)],
[P("2nd line\n(if haemolytic)", S_TABLE_C),
P("Peripheral blood film\nReticulocyte count\nLDH, haptoglobin\nDirect Coombs test\nHaemoglobin electrophoresis", S_TABLE_L),
P("Reticulocytosis + low haptoglobin + high LDH = haemolysis.\nCoombs+ = autoimmune haemolysis.\nElectrophoresis for haemoglobinopathies.", S_TABLE_L)],
[P("2nd line\n(if obstructive)", S_TABLE_C),
P("Abdominal ultrasound (FIRST)\nCA 19-9, CEA\nMRCP\nCT abdomen with contrast\nERCP (if intervention needed)", S_TABLE_L),
P("US: bile duct dilatation, stones, mass.\nCA 19-9 raised in pancreatic/biliary malignancy.\nMRCP: non-invasive ductal imaging.\nERCP: diagnostic + therapeutic (stent, stone removal).", S_TABLE_L)],
[P("Liver biopsy", S_TABLE_C),
P("Percutaneous / transjugular liver biopsy", S_TABLE_L),
P("When diagnosis remains uncertain after non-invasive workup. Transjugular preferred if coagulopathy or ascites.", S_TABLE_L)],
]
inv_col_w = [tw*0.15, tw*0.38, tw*0.47]
inv_table = Table(inv_data, colWidths=inv_col_w, repeatRows=1)
inv_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_NAVY),
("BACKGROUND", (0,1), (0,-1), C_LIGHT_BG),
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TEXTCOLOR", (0,0), (-1,0), white),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_BG, white]),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("FONTSIZE", (0,1), (0,-1), 8),
]))
story.append(inv_table)
story.append(SP(10))
story.append(HR())
# ── Red Flags ─────────────────────────────────────────────────
story.append(SectionHeader(" RED FLAG FEATURES REQUIRING URGENT EVALUATION", C_RED))
story.append(SP(6))
red_flags = [
("Encephalopathy + jaundice", "Suggests acute liver failure — URGENT referral to liver unit"),
("PT/INR >1.5 + jaundice", "Indicates significant hepatic synthetic failure"),
("Painless progressive jaundice in >50 yrs", "Pancreatic/biliary malignancy until proven otherwise"),
("Courvoisier's sign (palpable GB + jaundice)", "Malignant CBD obstruction (pancreatic Ca likely)"),
("Fever + rigors + jaundice (Charcot's triad)", "Ascending cholangitis — URGENT biliary decompression"),
("Charcot's triad + hypotension + confusion (Reynolds' pentad)", "Suppurative cholangitis — emergency ERCP/surgery"),
("Jaundice + ascites + spider naevi", "Decompensated cirrhosis — hepatology referral"),
("Neonatal jaundice: >20 mg/dL total or direct bilirubin >1 mg/dL", "Risk of kernicterus — urgent phototherapy/exchange transfusion"),
]
red_data = [[P(f"<b>⚠ {flag}</b>", S_BODY_SMALL), P(action, S_BODY_SMALL)] for flag, action in red_flags]
red_table = Table(red_data, colWidths=[tw*0.45, tw*0.55])
red_table.setStyle(TableStyle([
("GRID", (0,0), (-1,-1), 0.3, C_BORDER),
("BACKGROUND", (0,0), (0,-1), C_RED_BG),
("BACKGROUND", (1,0), (1,-1), white),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ROWBACKGROUNDS", (0,0), (-1,-1), [C_RED_BG, HexColor("#FFF0F0")]),
]))
story.append(red_table)
story.append(SP(10))
# ── Special populations note ──────────────────────────────────
special_data = [[
P("<b>Special populations</b>: "
"(1) <b>Neonates:</b> Physiological jaundice peaks at day 3–5; treat with phototherapy if above threshold. "
"(2) <b>Pregnancy:</b> Intrahepatic cholestasis of pregnancy (ICP) — intense pruritus, elevated bile acids, risk of fetal distress. "
"(3) <b>HIV/immunocompromised:</b> Consider opportunistic infections (CMV, MAC, Cryptosporidium), drug hepatotoxicity, and lymphoma. "
"(4) <b>Elderly:</b> Malignancy must be excluded early even with mild symptoms.",
S_BODY_SMALL)
]]
story.append(Table(special_data, colWidths=["100%"],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_GREEN_BG),
("BOX", (0,0), (-1,-1), 1, C_GREEN),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
])))
story.append(SP(6))
story.append(P(
"Sources: Harrison's Principles of Internal Medicine 22E (2025) | "
"Ganong's Review of Medical Physiology 26E | Schwartz's Principles of Surgery 11E | "
"Sleisenger and Fordtran's Gastrointestinal and Liver Disease",
S_NOTE))
# ─────────────────────────── BUILD ───────────────────────────
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
print(f"PDF saved to: {OUT}")
cd /home/daytona/workspace/jaundice-pdf && python generate_jaundice_pdf.py 2>&1
~/jaundice-pdf/generate_jaundice_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/jaundice-pdf/generate_jaundice_pdf.py'
cd /home/daytona/workspace/jaundice-pdf && python generate_jaundice_pdf.py 2>&1
ls -lh /home/daytona/workspace/jaundice-pdf/Diagnosing_Jaundice_Clinical_Summary.pdf
Diagnosing Jaundice Clinical Summary
PDF Document · PDF