Tomorrow is my biochemistry exam and I don't know nothing about pancreas function test. Please provide me answer in easy language and I will remember every points.
| Part | Type | What it does |
|---|---|---|
| Exocrine | ~98% of the gland | Makes digestive enzymes + bicarbonate juice, poured into the gut |
| Endocrine | ~2% (islets of Langerhans) | Makes hormones (insulin, glucagon) into the blood |
Pancreas function tests mostly focus on the exocrine part, because that is what biochemistry labs can measure.
PANCREAS FUNCTION TESTS
|
_____|_____
| |
INVASIVE NONINVASIVE
(need a (tubeless -
tube) easier but
less accurate)
Key fact to remember: The pancreas has a huge reserve. Malabsorption only appears when enzyme output falls to 5-10% of normal. So tubeless tests miss mild disease.
| Test | Type | Measures | Sensitivity |
|---|---|---|---|
| Secretin + CCK | Invasive | Volume, HCO3-, enzymes | BEST (gold standard) |
| Secretin alone | Invasive | Volume + HCO3- | High |
| Fecal Elastase-1 | Noninvasive | Elastase in stool | Good for moderate-severe |
| Fecal Fat | Noninvasive | Fat in stool | Only severe disease |
| NBT-PABA | Noninvasive | PABA in urine/serum | Only severe disease |
| Lundh test meal | Semi-invasive | Trypsin in duodenum | Historical only |
Rule of thumb: Amylase = quick rise, quick fall. Lipase = slower rise, stays longer, more specific.
| Disease | Key Tests Used |
|---|---|
| Acute pancreatitis | Serum amylase + lipase |
| Chronic pancreatitis | Secretin-CCK test (best), fecal elastase |
| Cystic fibrosis | Fecal elastase-1 (especially in infants) |
| Pancreatic cancer | Secretin test (function may be reduced) |
| Malabsorption / steatorrhea | Fecal fat, fecal elastase |
Create a quick reference PDF I can download before the exam tomorrow
mkdir -p /home/daytona/workspace/pancreas-exam && ls /home/daytona/workspace/
~/pancreas-exam/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import KeepTogether
OUTPUT = "/home/daytona/workspace/pancreas-exam/Pancreas_Function_Tests_QuickRef.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MID_BLUE = colors.HexColor("#2563a8")
LIGHT_BLUE = colors.HexColor("#dbeafe")
GOLD = colors.HexColor("#f59e0b")
LIGHT_GOLD = colors.HexColor("#fef3c7")
GREEN = colors.HexColor("#15803d")
LIGHT_GREEN = colors.HexColor("#dcfce7")
RED = colors.HexColor("#b91c1c")
LIGHT_RED = colors.HexColor("#fee2e2")
PURPLE = colors.HexColor("#7c3aed")
LIGHT_PURPLE= colors.HexColor("#ede9fe")
GREY_BG = colors.HexColor("#f1f5f9")
WHITE = colors.white
BLACK = colors.black
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
title_style = S("MyTitle",
fontName="Helvetica-Bold", fontSize=22, textColor=WHITE,
alignment=TA_CENTER, spaceAfter=4)
subtitle_style = S("MySub",
fontName="Helvetica", fontSize=11, textColor=LIGHT_BLUE,
alignment=TA_CENTER, spaceAfter=2)
section_head = S("SecHead",
fontName="Helvetica-Bold", fontSize=13, textColor=WHITE,
alignment=TA_LEFT, spaceBefore=6, spaceAfter=4,
leftIndent=6, rightIndent=6,
backColor=MID_BLUE, borderPad=4)
sub_head = S("SubHead",
fontName="Helvetica-Bold", fontSize=10, textColor=DARK_BLUE,
spaceBefore=4, spaceAfter=2)
body = S("Body",
fontName="Helvetica", fontSize=9, textColor=BLACK,
spaceBefore=2, spaceAfter=2, leading=13)
body_bold = S("BodyBold",
fontName="Helvetica-Bold", fontSize=9, textColor=BLACK,
spaceBefore=1, spaceAfter=1)
bullet_style = S("Bullet",
fontName="Helvetica", fontSize=9, textColor=BLACK,
spaceBefore=1, spaceAfter=1, leftIndent=12,
bulletIndent=4, leading=13)
tip_style = S("Tip",
fontName="Helvetica-Oblique", fontSize=9, textColor=GREEN,
spaceBefore=2, spaceAfter=2, leftIndent=8)
warn_style = S("Warn",
fontName="Helvetica-Oblique", fontSize=9, textColor=RED,
spaceBefore=1, spaceAfter=1, leftIndent=8)
footer_style = S("Footer",
fontName="Helvetica", fontSize=7, textColor=colors.grey,
alignment=TA_CENTER)
# ── Helper functions ──────────────────────────────────────────────────────────
def colored_box(text, bg, text_color=WHITE, font="Helvetica-Bold", size=10):
"""A single-cell table that acts as a coloured banner."""
t = Table([[Paragraph(text, ParagraphStyle("cb",
fontName=font, fontSize=size, textColor=text_color,
alignment=TA_CENTER))]],
colWidths=[170*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS",(0,0), (-1,-1), [3,3,3,3]),
]))
return t
def info_box(paragraphs, bg=LIGHT_BLUE, border=MID_BLUE):
"""A framed box holding a list of Paragraph/flowable objects."""
t = Table([[p] for p in paragraphs], colWidths=[170*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1, border),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
return t
def make_table(headers, rows, col_widths, header_bg=MID_BLUE):
data = [[Paragraph(f"<b>{h}</b>", ParagraphStyle("th",
fontName="Helvetica-Bold", fontSize=8.5, textColor=WHITE,
alignment=TA_CENTER)) for h in headers]]
for row in rows:
data.append([Paragraph(str(c), ParagraphStyle("td",
fontName="Helvetica", fontSize=8.5, textColor=BLACK,
alignment=TA_LEFT, leading=12)) for c in row])
t = Table(data, colWidths=col_widths, repeatRows=1)
row_count = len(data)
ts = [
("BACKGROUND", (0,0), (-1,0), header_bg),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#cbd5e1")),
("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), "MIDDLE"),
]
t.setStyle(TableStyle(ts))
return t
def bp(text):
return Paragraph(f"<bullet>•</bullet> {text}", bullet_style)
# ── Document build ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=18*mm, rightMargin=18*mm,
topMargin=14*mm, bottomMargin=14*mm,
title="Pancreas Function Tests - Quick Reference",
author="Biochemistry Exam Prep"
)
story = []
# ════════════════════════════════════════════════════════════════
# HEADER BANNER
# ════════════════════════════════════════════════════════════════
header_data = [[
Paragraph("PANCREAS FUNCTION TESTS", title_style),
Paragraph("Biochemistry Quick Reference | Exam Edition", subtitle_style),
Paragraph("Tietz Textbook of Laboratory Medicine, 7e & Sleisenger & Fordtran GI Disease",
ParagraphStyle("src", fontName="Helvetica", fontSize=7.5,
textColor=LIGHT_BLUE, alignment=TA_CENTER))
]]
header_tbl = Table(header_data, colWidths=[170*mm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("ROUNDEDCORNERS",(0,0),(-1,-1),[4,4,4,4]),
]))
story.append(header_tbl)
story.append(Spacer(1, 6*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 0 – BASICS
# ════════════════════════════════════════════════════════════════
story.append(colored_box("PANCREAS AT A GLANCE", DARK_BLUE))
story.append(Spacer(1, 2*mm))
basics_data = [
[Paragraph("<b>Exocrine Part (~98%)</b>", ParagraphStyle("bh", fontName="Helvetica-Bold",
fontSize=9, textColor=DARK_BLUE)),
Paragraph("<b>Endocrine Part (~2%)</b>", ParagraphStyle("bh", fontName="Helvetica-Bold",
fontSize=9, textColor=DARK_BLUE))],
[Paragraph("Makes digestive <b>enzymes + bicarbonate</b> juice<br/>"
"Poured into duodenum via pancreatic duct<br/>"
"Normal juice: pH 8.0-8.3, up to 3000 mL/day<br/>"
"Enzymes: amylase, lipase, trypsin, chymotrypsin, elastase", body),
Paragraph("Islets of Langerhans<br/>"
"Beta cells - <b>Insulin</b> (lowers glucose)<br/>"
"Alpha cells - <b>Glucagon</b> (raises glucose)<br/>"
"Delta cells - Somatostatin", body)]
]
basics_tbl = Table(basics_data, colWidths=[85*mm, 85*mm])
basics_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_BLUE),
("BACKGROUND", (0,1), (0,1), LIGHT_BLUE),
("BACKGROUND", (1,1), (1,1), LIGHT_GOLD),
("GRID", (0,0), (-1,-1), 0.5, MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 7),
("RIGHTPADDING", (0,0), (-1,-1), 7),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(basics_tbl)
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 1 – TEST OVERVIEW
# ════════════════════════════════════════════════════════════════
story.append(colored_box("TWO MAIN CATEGORIES OF FUNCTION TESTS", MID_BLUE))
story.append(Spacer(1, 2*mm))
cat_data = [
[Paragraph("<b>INVASIVE (Direct)</b>", ParagraphStyle("ch", fontName="Helvetica-Bold",
fontSize=10, textColor=DARK_BLUE, alignment=TA_CENTER)),
Paragraph("<b>NONINVASIVE (Tubeless)</b>", ParagraphStyle("ch", fontName="Helvetica-Bold",
fontSize=10, textColor=GREEN, alignment=TA_CENTER))],
[Paragraph(
"<b>Needs a tube</b> placed in duodenum<br/>"
"IV hormone given (secretin / CCK)<br/>"
"Pancreatic juice is collected & analysed<br/>"
"<b>Most accurate</b> - gold standard<br/>"
"Good for: mild, moderate, severe disease<br/>"
"<i>Drawback: invasive, not widely available</i>", body),
Paragraph(
"<b>No tube needed</b> - patient-friendly<br/>"
"Stool sample OR oral substrate + urine<br/>"
"<b>Less accurate</b> - misses mild disease<br/>"
"Good for: moderate to severe disease only<br/>"
"<i>Key reason:</i> Pancreas has 90-95% reserve<br/>"
"<i>Malabsorption only appears at <5-10% function</i>", body)]
]
cat_tbl = Table(cat_data, colWidths=[85*mm, 85*mm])
cat_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), LIGHT_BLUE),
("BACKGROUND", (1,0), (1,0), LIGHT_GREEN),
("BACKGROUND", (0,1), (0,1), LIGHT_BLUE),
("BACKGROUND", (1,1), (1,1), LIGHT_GREEN),
("GRID", (0,0), (-1,-1), 0.8, colors.HexColor("#94a3b8")),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(cat_tbl)
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 2 – INVASIVE TESTS
# ════════════════════════════════════════════════════════════════
story.append(colored_box("INVASIVE TESTS (Direct) - GOLD STANDARD", colors.HexColor("#1d4ed8")))
story.append(Spacer(1, 2*mm))
inv_headers = ["Test", "Hormone Given", "What is Measured", "What it Tests", "Key Point"]
inv_rows = [
["Secretin Test", "Secretin IV\n(1 unit/kg BW)",
"Volume + Bicarbonate (HCO3-)\nin duodenum",
"Ductal cells\n(duct cell mass)",
"HCO3- drops in\nchronic pancreatitis"],
["CCK Test", "CCK IV\n(or ceruletide)",
"Enzymes: amylase, lipase,\ntrypsin, chymotrypsin",
"Acinar cells\n(enzyme output)",
"Enzyme output drops\nin pancreatic disease"],
["Secretin + CCK\n(GOLD STANDARD)", "Both: Secretin\n+ CCK IV",
"Volume + HCO3- + ALL enzymes",
"Both ductal AND\nacinar cells",
"Most sensitive &\nspecific test overall"],
["Lundh Test Meal\n(Historical only)", "None - oral\ntest meal",
"Trypsin concentration\nin duodenum",
"Overall enzyme\nresponse",
"No longer used;\nmainly of historical interest"],
]
story.append(make_table(inv_headers, inv_rows,
[30*mm, 30*mm, 42*mm, 33*mm, 35*mm], header_bg=colors.HexColor("#1d4ed8")))
story.append(Spacer(1, 3*mm))
story.append(info_box([
Paragraph("<b>SECRETIN:</b> stimulates ductal cells -> more bicarbonate + juice volume", body),
Paragraph("<b>CCK:</b> stimulates acinar cells -> more digestive enzymes", body),
Paragraph("<b>Memory trick:</b> Secretin = Bicarb (S=B). CCK = enCzymes (C=C).", tip_style),
], bg=LIGHT_BLUE, border=MID_BLUE))
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 3 – NONINVASIVE TESTS
# ════════════════════════════════════════════════════════════════
story.append(colored_box("NONINVASIVE TESTS (Tubeless) - INDIRECT", GREEN))
story.append(Spacer(1, 2*mm))
ni_headers = ["Test", "How It Works", "What is Measured", "Sensitivity", "Key Notes"]
ni_rows = [
["Fecal Elastase-1\n(MOST USED)", "Collect stool sample;\nmeasure elastase-1",
"Elastase-1 in stool\n(mcg/g stool)",
"Good for moderate-\nsevere disease",
"BEST noninvasive test.\nStable in stool. Normal: >200 mcg/g\nSevere insuff: <100 mcg/g"],
["Fecal Chymotrypsin", "Collect stool sample;\nmeasure chymotrypsin",
"Chymotrypsin activity\nin stool",
"Only severe disease",
"Less reliable than elastase;\nenzyme can degrade in gut"],
["Fecal Fat\n(72-hr collection)", "Eat 100g fat/day x 3 days;\ncollect ALL stool",
"Grams of fat in\nstool per day",
"Only severe\nmalabsorption",
"Normal: <7g fat/day in stool\nSteatorrhea: >7g/day\nNot pancreas-specific"],
["NBT-PABA Test\n(Bentiromide)", "Swallow NBT-PABA + meal;\nchymotrypsin cleaves it",
"PABA in urine\nor serum",
"Only severe disease",
"False +ve if small bowel disease.\nNBT-PABA = N-benzoyl-L-tyrosyl-\npara-aminobenzoic acid"],
["Fluorescein\nDilaurate Test\n(Pancreolauryl)", "Swallow fluorescein\ndilaurate + meal;\nesterase cleaves it",
"Fluorescein in\nurine/serum",
"Only severe disease",
"Same problem as PABA:\nsmall bowel disease\ncauses false positives"],
]
story.append(make_table(ni_headers, ni_rows,
[28*mm, 35*mm, 34*mm, 25*mm, 48*mm], header_bg=GREEN))
story.append(Spacer(1, 3*mm))
story.append(info_box([
Paragraph("<b>WHY noninvasive tests miss mild disease:</b> The pancreas has a huge functional reserve. "
"Malabsorption (detectable by these tests) only happens when output falls to "
"<b>5-10% of normal</b>. By that stage, disease is already moderate-severe.", body),
], bg=LIGHT_GOLD, border=GOLD))
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# PAGE BREAK -> PAGE 2
# ════════════════════════════════════════════════════════════════
story.append(PageBreak())
# Re-print mini banner
story.append(colored_box("PANCREAS FUNCTION TESTS | PAGE 2", DARK_BLUE))
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 4 – SERUM ENZYME TESTS (ACUTE PANCREATITIS)
# ════════════════════════════════════════════════════════════════
story.append(colored_box("SERUM ENZYME TESTS - ACUTE PANCREATITIS", RED))
story.append(Spacer(1, 2*mm))
ae_headers = ["Enzyme", "Rises After Onset", "Peaks", "Returns to Normal", "Specificity", "Preferred?"]
ae_rows = [
["Serum Amylase", "2-12 hours", "12-72 hours", "3-5 days",
"LOW (rises in salivary disease,\nrenal failure, bowel obstruction)", "No"],
["Serum Lipase", "4-8 hours", "24-48 hours", "8-14 days",
"HIGH (more specific\nto pancreas)", "YES - preferred test"],
]
story.append(make_table(ae_headers, ae_rows,
[24*mm, 25*mm, 22*mm, 28*mm, 48*mm, 23*mm], header_bg=RED))
story.append(Spacer(1, 2*mm))
story.append(info_box([
Paragraph("<b>Memory trick:</b> Amylase = quick rise, quick fall. "
"Lipase = slower rise, stays LONGER, MORE specific. "
"<b>Lipase is the preferred test for acute pancreatitis today.</b>", body),
], bg=LIGHT_RED, border=RED))
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 5 – MASTER COMPARISON TABLE
# ════════════════════════════════════════════════════════════════
story.append(colored_box("MASTER COMPARISON TABLE - ALL TESTS", PURPLE))
story.append(Spacer(1, 2*mm))
master_headers = ["Test", "Type", "Stimulant/Method", "Measures", "Sensitivity", "Use For"]
master_rows = [
["Secretin + CCK", "INVASIVE\nDirect", "IV hormones + duodenal tube",
"Vol + HCO3- + Enzymes", "HIGHEST\n(Gold Standard)", "All degrees of exocrine dysfunction"],
["Secretin alone", "INVASIVE\nDirect", "IV secretin + duodenal tube",
"Volume + Bicarbonate", "High", "Ductal function / mild-severe disease"],
["CCK alone", "INVASIVE\nDirect", "IV CCK + duodenal tube",
"Amylase, lipase, trypsin", "High", "Acinar cell function"],
["Fecal Elastase-1", "NONINVASIVE\nIndirect", "Stool sample only",
"Elastase-1 (mcg/g)", "Mod-severe disease", "Best tubeless test; CF, chronic pancreatitis"],
["Fecal Chymotrypsin", "NONINVASIVE\nIndirect", "Stool sample only",
"Chymotrypsin activity", "Severe only", "Pancreatic insufficiency (less reliable)"],
["Fecal Fat", "NONINVASIVE\nIndirect", "72-hr stool collection",
"Fat output (g/day)", "Severe only", "Confirm steatorrhea; not pancreas-specific"],
["NBT-PABA", "NONINVASIVE\nIndirect", "Oral substrate + meal",
"PABA in urine/serum", "Severe only", "Chymotrypsin activity; false +ve in bowel disease"],
["Fluorescein Dilaurate", "NONINVASIVE\nIndirect", "Oral substrate + meal",
"Fluorescein in urine", "Severe only", "Same as PABA test"],
["Serum Amylase", "SERUM\nEnzyme", "Blood test after pancreatitis",
"Amylase in serum", "Mod-high for acute", "Acute pancreatitis (not specific)"],
["Serum Lipase", "SERUM\nEnzyme", "Blood test after pancreatitis",
"Lipase in serum", "High for acute", "PREFERRED for acute pancreatitis"],
]
story.append(make_table(master_headers, master_rows,
[28*mm, 20*mm, 32*mm, 28*mm, 26*mm, 36*mm], header_bg=PURPLE))
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 6 – CLINICAL APPLICATIONS
# ════════════════════════════════════════════════════════════════
story.append(colored_box("WHICH TEST FOR WHICH DISEASE?", GOLD))
story.append(Spacer(1, 2*mm))
clin_data = [
[Paragraph("<b>Disease / Condition</b>", ParagraphStyle("ch", fontName="Helvetica-Bold",
fontSize=9, textColor=DARK_BLUE)),
Paragraph("<b>Best Tests to Use</b>", ParagraphStyle("ch", fontName="Helvetica-Bold",
fontSize=9, textColor=DARK_BLUE)),
Paragraph("<b>Notes</b>", ParagraphStyle("ch", fontName="Helvetica-Bold",
fontSize=9, textColor=DARK_BLUE))],
[Paragraph("<b>Acute Pancreatitis</b>", body),
Paragraph("Serum Lipase (preferred)\nSerum Amylase", body),
Paragraph("Lipase stays elevated longer; more specific", body)],
[Paragraph("<b>Chronic Pancreatitis</b>", body),
Paragraph("Secretin-CCK test (best)\nFecal Elastase-1 (tubeless)", body),
Paragraph("Direct test most sensitive for mild disease", body)],
[Paragraph("<b>Cystic Fibrosis\n(paediatric)</b>", body),
Paragraph("Fecal Elastase-1", body),
Paragraph("Reliable from 2 weeks of age; monitors onset of insufficiency", body)],
[Paragraph("<b>Pancreatic Cancer</b>", body),
Paragraph("Secretin test\n(+ imaging)", body),
Paragraph("Function may be reduced; imaging now more important", body)],
[Paragraph("<b>Malabsorption /\nSteatorrhea</b>", body),
Paragraph("Fecal Fat\nFecal Elastase-1", body),
Paragraph("Fecal fat confirms malabsorption; elastase points to pancreas", body)],
[Paragraph("<b>Shwachman-Diamond\nvs Cystic Fibrosis\n(infants)</b>", body),
Paragraph("Secretin-CCK test", body),
Paragraph("CF: mucus plugs reduce juice but enzymes rise normally with CCK\n"
"Shwachman: enzyme secretion absent or minimal", body)],
]
clin_tbl = Table(clin_data, colWidths=[40*mm, 48*mm, 82*mm])
clin_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_GOLD),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#d97706")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(clin_tbl)
story.append(Spacer(1, 4*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 7 – MEMORY TRICKS BOX
# ════════════════════════════════════════════════════════════════
story.append(colored_box("MEMORY TRICKS - READ THESE BEFORE SLEEP!", colors.HexColor("#7c3aed")))
story.append(Spacer(1, 2*mm))
tricks = [
("<b>1. DIRECT = BEST</b>", "Invasive direct tests need a tube but give the MOST ACCURATE results."),
("<b>2. S = B, C = E</b>", "Secretin stimulates Bicarbonate (duct cells). CCK stimulates Enzymes (acinar cells)."),
("<b>3. Gold Standard</b>", "Secretin + CCK combined = gold standard for exocrine pancreatic function."),
("<b>4. Elastase stays alive</b>", "Fecal elastase-1 is STABLE in stool (not destroyed). Best tubeless test."),
("<b>5. Lipase outlasts amylase</b>", "Lipase stays high for 8-14 days. Amylase normalises in 3-5 days. Lipase = preferred for acute pancreatitis."),
("<b>6. 90-95% reserve rule</b>", "Pancreas has a HUGE reserve. Tubeless tests only become positive when 90-95% of function is already lost."),
("<b>7. PABA & Fluorescein = false positives</b>", "Both tubeless chemical tests give false positives if the SMALL BOWEL is also damaged (not specific to pancreas)."),
("<b>8. Fecal fat normal = <7g/day</b>", "Anything above 7g fat per day in stool = steatorrhea = malabsorption."),
]
tricks_data = [[Paragraph(t, ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=9,
textColor=PURPLE)),
Paragraph(d, body)] for t, d in tricks]
tricks_tbl = Table(tricks_data, colWidths=[42*mm, 128*mm])
tricks_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0), (-1,-1), [WHITE, LIGHT_PURPLE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#c4b5fd")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(tricks_tbl)
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════
# SECTION 8 – QUICK NUMBERS
# ════════════════════════════════════════════════════════════════
story.append(colored_box("KEY NUMBERS TO REMEMBER", RED))
story.append(Spacer(1, 2*mm))
nums = [
["Pancreatic juice pH", "8.0 - 8.3"],
["Pancreatic juice volume (24h)", "Up to 3000 mL/day"],
["Pancreatic reserve - malabsorption threshold", "Function must fall to 5-10% of normal"],
["Fecal elastase-1 - Normal", "> 200 mcg/g stool"],
["Fecal elastase-1 - Severe insufficiency", "< 100 mcg/g stool"],
["Fecal fat - Normal", "< 7 g/day"],
["Serum amylase - Rises after pancreatitis", "2-12 hours post onset"],
["Serum amylase - Returns to normal", "3-5 days"],
["Serum lipase - Rises after pancreatitis", "4-8 hours post onset"],
["Serum lipase - Returns to normal", "8-14 days"],
["Secretin dose", "1 unit/kg body weight IV"],
["Sample collection intervals (secretin test)", "Every 15 minutes for at least 1 hour"],
]
nums_data = [[Paragraph(f"<b>{k}</b>", body), Paragraph(v, ParagraphStyle("val",
fontName="Helvetica-Bold", fontSize=9, textColor=RED))] for k, v in nums]
nums_tbl = Table(nums_data, colWidths=[100*mm, 70*mm])
nums_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0), (-1,-1), [WHITE, LIGHT_RED]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#fca5a5")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 7),
("RIGHTPADDING", (0,0), (-1,-1), 7),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(nums_tbl)
story.append(Spacer(1, 3*mm))
# ════════════════════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════════════════════
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey))
story.append(Spacer(1, 2*mm))
story.append(Paragraph(
"Sources: Tietz Textbook of Laboratory Medicine 7e | Sleisenger & Fordtran's GI and Liver Disease | "
"Harrison's Principles of Internal Medicine 22e | Prepared for Biochemistry Exam - June 2026",
footer_style))
# ════════════════════════════════════════════════════════════════
# BUILD
# ════════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF saved to: {OUTPUT}")
python /home/daytona/workspace/pancreas-exam/generate_pdf.py
ls -lh /home/daytona/workspace/pancreas-exam/
Pancreas Function Tests QuickRef
PDF Document · PDF
Tomorrow is my biochemistry exam and I don't know nothing about creatinine estimation test ( which prefer urea or serum creatinine). Please provide me answer in easy language and I will remember every points.
Creatine (stored in muscle)
|
| (broken down during muscle metabolism)
|
Creatinine
|
| (filtered by kidneys)
|
Excreted in urine
Dietary Protein / Body Protein
|
| (broken down in liver)
|
Amino Acids
|
| (deamination - liver)
|
Ammonia (toxic!) --> Liver converts it to UREA (safe)
|
| (urea goes to blood)
|
Filtered by kidneys --> Excreted in urine
Short answer: Serum creatinine is a MORE RELIABLE marker of kidney function than blood urea (BUN).
| Point | Explanation |
|---|---|
| Constant production | Produced at same rate every day from muscle breakdown - no big daily variation |
| Not affected by diet | Eating more food does NOT change creatinine significantly |
| Directly reflects GFR | Creatinine is freely filtered at the glomerulus, so when GFR falls, creatinine rises predictably |
| More specific to kidney | Mainly excreted by kidneys - not affected by liver function |
| Standard test | Used to calculate eGFR (estimated Glomerular Filtration Rate) - the gold standard of kidney function |
Because BUN is affected by so many outside factors, it does NOT reliably reflect kidney function alone. Serum creatinine is preferred.
Creatinine + Alkaline Picrate (sodium picrate in NaOH)
|
Orange-red colour
|
Measured by colorimetry (spectrophotometry)
Creatinine
| (creatininase enzyme)
v
Creatine
| (creatine amidino-hydrolase)
v
Sarcosine + Urea
| (sarcosine oxidase)
v
Glycine + Formaldehyde + H2O2
| (peroxidase + dye)
v
Coloured product -> Measured photometrically
Creatinine Clearance (mL/min) = (Urine Creatinine x Urine Volume) / Serum Creatinine
CrCl = (U_cr x V) / P_cr
Important exam fact: Kidneys have a large functional reserve - 70% of kidney function must be lost before renal failure shows up in tests!
CrCl = [(140 - age) x weight in kg] / (72 x serum creatinine)
Multiply by 0.85 for females
| Ratio | What it means | Cause |
|---|---|---|
| >20:1 (HIGH ratio) | BUN raised more than creatinine | Pre-renal (dehydration, heart failure, GI bleeding, high protein diet) |
| 10-20:1 (Normal ratio) | Both raised proportionally | Intrinsic renal disease (glomerulonephritis, AKI, CKD) |
| <10:1 (LOW ratio) | Creatinine raised more than BUN | Low protein intake, severe liver disease, rhabdomyolysis (large muscle breakdown) |
| Feature | Urea (BUN) | Serum Creatinine |
|---|---|---|
| Source | Protein breakdown (liver) | Muscle metabolism |
| Production rate | Variable (diet-dependent) | Constant (muscle mass) |
| Affected by diet | YES - big effect | NO - minimal effect |
| Affected by liver | YES - liver disease lowers it | NO |
| GI bleeding effect | Raises BUN (blood = protein) | Minimal effect |
| Dehydration effect | Raises BUN more | Less affected |
| Preferred for kidney | NO - too many variables | YES - preferred |
| Normal value | 5-20 mg/dL | 0.6-1.2 mg/dL (men) |
| Best use | BUN:Cr ratio to find cause | eGFR calculation |
Create a quick reference PDF for creatinine estimation before exam
mkdir -p /home/daytona/workspace/creatinine-exam && ls /home/daytona/workspace/
~/creatinine-exam/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
OUTPUT = "/home/daytona/workspace/creatinine-exam/Creatinine_Estimation_QuickRef.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_TEAL = colors.HexColor("#0f4c5c")
MID_TEAL = colors.HexColor("#0e7490")
LIGHT_TEAL = colors.HexColor("#cffafe")
ORANGE = colors.HexColor("#c2410c")
LIGHT_ORANGE= colors.HexColor("#ffedd5")
GREEN = colors.HexColor("#15803d")
LIGHT_GREEN = colors.HexColor("#dcfce7")
PURPLE = colors.HexColor("#6d28d9")
LIGHT_PURPLE= colors.HexColor("#ede9fe")
RED = colors.HexColor("#b91c1c")
LIGHT_RED = colors.HexColor("#fee2e2")
GOLD = colors.HexColor("#b45309")
LIGHT_GOLD = colors.HexColor("#fef3c7")
SLATE = colors.HexColor("#334155")
GREY_BG = colors.HexColor("#f1f5f9")
WHITE = colors.white
BLACK = colors.black
# ── Style helpers ─────────────────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
title_s = S("T", fontName="Helvetica-Bold", fontSize=21, textColor=WHITE, alignment=TA_CENTER)
sub_s = S("Su",fontName="Helvetica", fontSize=10, textColor=colors.HexColor("#a5f3fc"), alignment=TA_CENTER)
src_s = S("Sc",fontName="Helvetica-Oblique", fontSize=7.5,textColor=colors.HexColor("#67e8f9"), alignment=TA_CENTER)
sh_s = S("SH", fontName="Helvetica-Bold", fontSize=11, textColor=WHITE, alignment=TA_LEFT, spaceBefore=2, spaceAfter=2)
body = S("B", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=13, spaceBefore=1, spaceAfter=1)
bold9 = S("B9", fontName="Helvetica-Bold", fontSize=9, textColor=BLACK, leading=13)
tip_s = S("Ti", fontName="Helvetica-Oblique", fontSize=9, textColor=GREEN, spaceBefore=1, spaceAfter=1, leftIndent=6)
warn_s = S("W", fontName="Helvetica-Oblique", fontSize=9, textColor=RED, spaceBefore=1, spaceAfter=1, leftIndent=6)
footer_s = S("F", fontName="Helvetica", fontSize=7, textColor=colors.grey, alignment=TA_CENTER)
num_s = S("N", fontName="Helvetica-Bold", fontSize=9, textColor=RED)
trick_k = S("TK", fontName="Helvetica-Bold", fontSize=9, textColor=PURPLE)
trick_v = S("TV", fontName="Helvetica", fontSize=9, textColor=BLACK, leading=13)
FULL = 174*mm
# ── Reusable builders ─────────────────────────────────────────────────────────
def banner(text, bg, fg=WHITE, size=11):
t = Table([[Paragraph(text, ParagraphStyle("cb", fontName="Helvetica-Bold",
fontSize=size, textColor=fg, alignment=TA_CENTER))]],
colWidths=[FULL])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("TOPPADDING", (0,0),(-1,-1), 7),
("BOTTOMPADDING", (0,0),(-1,-1), 7),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING", (0,0),(-1,-1), 8),
]))
return t
def box(items, bg, border):
"""Framed info box from list of Paragraphs."""
t = Table([[p] for p in items], colWidths=[FULL])
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), bg),
("BOX", (0,0),(-1,-1), 1, border),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 9),
("RIGHTPADDING", (0,0),(-1,-1), 9),
]))
return t
def mtable(headers, rows, widths, hbg=MID_TEAL):
hstyle = ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=8.5,
textColor=WHITE, alignment=TA_CENTER)
cstyle = ParagraphStyle("td", fontName="Helvetica", fontSize=8.5,
textColor=BLACK, alignment=TA_LEFT, leading=12)
data = [[Paragraph(h, hstyle) for h in headers]]
for row in rows:
data.append([Paragraph(str(c), cstyle) for c in row])
t = Table(data, colWidths=widths, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), hbg),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#cbd5e1")),
("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), "MIDDLE"),
]))
return t
def sp(n=3): return Spacer(1, n*mm)
# ── Document ──────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=18*mm, rightMargin=18*mm,
topMargin=13*mm, bottomMargin=13*mm,
title="Creatinine Estimation - Quick Reference",
author="Biochemistry Exam Prep"
)
story = []
# ════════════════════════════════════════════════════════════
# HEADER
# ════════════════════════════════════════════════════════════
hdr = Table([[
Paragraph("CREATININE ESTIMATION", title_s),
Paragraph("Urea vs Serum Creatinine | Methods | Clinical Use | Biochemistry Quick Ref", sub_s),
Paragraph("Brenner & Rector's The Kidney | Tietz Lab Medicine 7e | Frameworks for Internal Medicine", src_s),
]], colWidths=[FULL])
hdr.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), DARK_TEAL),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 12),
("RIGHTPADDING", (0,0),(-1,-1), 12),
]))
story += [hdr, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 1 – ORIGINS
# ════════════════════════════════════════════════════════════
story.append(banner("1. WHERE DO CREATININE AND UREA COME FROM?", MID_TEAL))
story.append(sp(2))
origin_data = [
[Paragraph("<b>CREATININE</b>", ParagraphStyle("oh", fontName="Helvetica-Bold",
fontSize=10, textColor=MID_TEAL, alignment=TA_CENTER)),
Paragraph("<b>UREA (BUN = Blood Urea Nitrogen)</b>", ParagraphStyle("oh", fontName="Helvetica-Bold",
fontSize=10, textColor=ORANGE, alignment=TA_CENTER))],
[Paragraph(
"Creatine (stored in muscle)<br/>"
" <font color='#0e7490'>↓</font> broken down by muscle metabolism<br/>"
"<b>Creatinine</b><br/>"
" <font color='#0e7490'>↓</font> freely filtered by glomerulus<br/>"
"Excreted in urine (little reabsorption)<br/><br/>"
"<b>Key facts:</b><br/>"
"- Produced at a <b>CONSTANT steady rate</b><br/>"
"- Depends on <b>muscle mass</b> (not diet!)<br/>"
"- NOT affected by liver function<br/>"
"- More muscle = more creatinine", body),
Paragraph(
"Dietary Protein / Body Protein<br/>"
" <font color='#c2410c'>↓</font> broken down (catabolism)<br/>"
"Amino Acids<br/>"
" <font color='#c2410c'>↓</font> deamination in liver<br/>"
"Ammonia (toxic!) → <b>Liver converts to UREA</b><br/>"
" <font color='#c2410c'>↓</font> filtered by kidneys<br/>"
"Excreted in urine<br/><br/>"
"<b>Key facts:</b><br/>"
"- Production rate is <b>VARIABLE</b> (diet-dependent)<br/>"
"- Depends on protein intake, liver, hydration<br/>"
"- <b>AFFECTED</b> by liver disease, diet, many drugs", body)]
]
orig_tbl = Table(origin_data, colWidths=[87*mm, 87*mm])
orig_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), LIGHT_TEAL),
("BACKGROUND", (1,0),(1,0), LIGHT_ORANGE),
("BACKGROUND", (0,1),(0,1), LIGHT_TEAL),
("BACKGROUND", (1,1),(1,1), LIGHT_ORANGE),
("GRID", (0,0),(-1,-1), 0.6, colors.HexColor("#94a3b8")),
("TOPPADDING", (0,0),(-1,-1), 7),
("BOTTOMPADDING", (0,0),(-1,-1), 7),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING", (0,0),(-1,-1), 8),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
story += [orig_tbl, sp(3)]
# ════════════════════════════════════════════════════════════
# SECTION 2 – WHY CREATININE IS PREFERRED
# ════════════════════════════════════════════════════════════
story.append(banner("2. WHY IS SERUM CREATININE PREFERRED OVER UREA?", GREEN))
story.append(sp(2))
pref_headers = ["Factor", "Serum Creatinine", "Urea (BUN)", "Winner"]
pref_rows = [
["Production rate", "Constant (stable daily)", "Variable (depends on protein intake)", "Creatinine"],
["Effect of diet", "Minimal - NOT affected", "BIG effect - high protein raises BUN", "Creatinine"],
["Effect of liver disease", "NOT affected", "LOWERED (liver can't make urea)", "Creatinine"],
["Effect of dehydration", "Mildly raised", "Raised MORE (urea is reabsorbed)", "Creatinine"],
["Effect of GI bleeding", "Minimal", "RAISED (blood digested as protein)", "Creatinine"],
["Effect of steroids/fever", "Minimal", "RAISED (increased protein breakdown)", "Creatinine"],
["Reflects GFR directly", "Yes - reliable", "Unreliable alone", "Creatinine"],
["Used to calculate eGFR", "YES - standard method", "Not used for eGFR", "Creatinine"],
["Useful for cause of AKI", "BUN:Cr ratio needed", "BUN:Cr ratio needed", "BOTH together"],
]
story.append(mtable(pref_headers, pref_rows,
[40*mm, 50*mm, 55*mm, 29*mm], hbg=GREEN))
story.append(sp(2))
story.append(box([
Paragraph("<b>VERDICT:</b> Serum creatinine is a MORE RELIABLE predictor of renal function than BUN "
"because its production is constant and it is not influenced by diet, liver function, "
"or hydration status. BUN has too many variables.", body),
], bg=LIGHT_GREEN, border=GREEN))
story.append(sp(4))
# ════════════════════════════════════════════════════════════
# SECTION 3 – FACTORS AFFECTING BUN
# ════════════════════════════════════════════════════════════
story.append(banner("3. FACTORS THAT RAISE / LOWER BUN (WITHOUT KIDNEY DISEASE)", ORANGE))
story.append(sp(2))
bun_data = [
[Paragraph("<b>RAISES BUN (false high)</b>", ParagraphStyle("bh", fontName="Helvetica-Bold",
fontSize=9.5, textColor=RED, alignment=TA_CENTER)),
Paragraph("<b>LOWERS BUN (false low)</b>", ParagraphStyle("bh", fontName="Helvetica-Bold",
fontSize=9.5, textColor=GREEN, alignment=TA_CENTER))],
[Paragraph(
"1. High protein diet<br/>"
"2. Fever / infection (catabolism)<br/>"
"3. Glucocorticoids (steroids)<br/>"
"4. Tetracycline antibiotics<br/>"
"5. Upper GI bleeding (blood = protein)<br/>"
"6. Dehydration (urea gets reabsorbed)<br/>"
"7. Old age (more protein breakdown)<br/>"
"8. Trauma / surgery", body),
Paragraph(
"1. Low protein diet / starvation<br/>"
"2. Malnutrition<br/>"
"3. Liver failure (cannot make urea)<br/>"
"4. Pregnancy (dilutional effect)<br/>"
"5. Overhydration (dilution)<br/>"
"6. SIADH (dilutional)", body)]
]
bun_tbl = Table(bun_data, colWidths=[87*mm, 87*mm])
bun_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), colors.HexColor("#fecaca")),
("BACKGROUND", (1,0),(1,0), LIGHT_GREEN),
("BACKGROUND", (0,1),(0,1), colors.HexColor("#fff1f2")),
("BACKGROUND", (1,1),(1,1), colors.HexColor("#f0fdf4")),
("GRID", (0,0),(-1,-1), 0.6, colors.HexColor("#94a3b8")),
("TOPPADDING", (0,0),(-1,-1), 7),
("BOTTOMPADDING", (0,0),(-1,-1), 7),
("LEFTPADDING", (0,0),(-1,-1), 9),
("RIGHTPADDING", (0,0),(-1,-1), 9),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
story += [bun_tbl, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 4 – MEASUREMENT METHODS
# ════════════════════════════════════════════════════════════
story.append(banner("4. HOW IS CREATININE MEASURED? — THE BIOCHEMISTRY METHODS", PURPLE))
story.append(sp(2))
# Method cards side by side
m1 = Table([[
Paragraph("<b>METHOD 1: Jaffe Method (Alkaline Picrate)</b>", ParagraphStyle("mh",
fontName="Helvetica-Bold", fontSize=9.5, textColor=PURPLE)),
Paragraph("<b>Historically most common — still widely used</b>", ParagraphStyle("ms",
fontName="Helvetica-Oblique", fontSize=8.5, textColor=SLATE, alignment=TA_LEFT)),
]], colWidths=[FULL])
m1.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), LIGHT_PURPLE),
("TOPPADDING",(0,0),(-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING",(0,0),(-1,-1), 8), ("RIGHTPADDING",(0,0),(-1,-1), 8),
]))
story.append(m1)
jaffe_data = [
[Paragraph("<b>Principle</b>", bold9),
Paragraph("Creatinine + Alkaline Picrate (picric acid in NaOH) "
"→ <b>Orange-red complex (Janovsky complex)</b> "
"→ measured by colorimetry (spectrophotometer)", body)],
[Paragraph("<b>Steps</b>", bold9),
Paragraph("1. Add serum to alkaline picrate solution<br/>"
"2. Creatinine reacts → orange-red colour<br/>"
"3. Measure colour intensity at ~490-520 nm<br/>"
"4. Compare to known creatinine standard<br/>"
"5. Calculate concentration", body)],
[Paragraph("<b>Overestimates by</b>", bold9),
Paragraph("<b>~25%</b> due to interference from non-creatinine chromogens (substances that also "
"react with picrate and produce colour)", body)],
[Paragraph("<b>Interfering\nsubstances\n(FALSE HIGH)</b>", bold9),
Paragraph("Glucose | Proteins (plasma) | Acetoacetate (ketones) | Ascorbic acid | Pyruvate | "
"Cephalosporins | Acetaminophen<br/>"
"<i>Diabetic patients especially affected (high glucose + ketones)</i>", body)],
[Paragraph("<b>FALSE LOW</b>", bold9),
Paragraph("Very high serum bilirubin (competes, reduces colour)", body)],
[Paragraph("<b>Fix?</b>", bold9),
Paragraph("<b>Modified Jaffe methods</b> remove interfering chromogens before analysis. "
"Some manufacturers subtract an estimated bias value.", body)],
]
jaffe_tbl = Table(jaffe_data, colWidths=[32*mm, 142*mm])
jaffe_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0),(-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#c4b5fd")),
("TOPPADDING", (0,0),(-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 6), ("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
story += [jaffe_tbl, sp(3)]
# Enzymatic method
m2_hdr = Table([[Paragraph("<b>METHOD 2: Enzymatic Method — MORE SPECIFIC (Now Recommended)</b>",
ParagraphStyle("mh2", fontName="Helvetica-Bold", fontSize=9.5, textColor=GREEN))]],
colWidths=[FULL])
m2_hdr.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1), LIGHT_GREEN),
("TOPPADDING",(0,0),(-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING",(0,0),(-1,-1), 8), ("RIGHTPADDING",(0,0),(-1,-1), 8),
]))
story.append(m2_hdr)
enz_data = [
[Paragraph("<b>Principle\n(Enzyme chain)</b>", bold9),
Paragraph(
"Creatinine <i>(creatininase)</i> → Creatine<br/>"
"Creatine <i>(creatine amidino-hydrolase)</i> → Sarcosine + Urea<br/>"
"Sarcosine <i>(sarcosine oxidase)</i> → Glycine + Formaldehyde + H<sub>2</sub>O<sub>2</sub><br/>"
"H<sub>2</sub>O<sub>2</sub> + dye <i>(peroxidase)</i> → <b>Coloured product</b> → measured photometrically", body)],
[Paragraph("<b>Advantages</b>", bold9),
Paragraph("More <b>specific</b> for creatinine | Less interference from glucose, proteins, bilirubin | "
"<b>KDIGO and expert bodies recommend this method</b>", body)],
[Paragraph("<b>Interfering\nsubstances</b>", bold9),
Paragraph("Some substances do interfere but <b>much less than Jaffe</b>. "
"Dobutamine and N-acetylcysteine can interfere.", body)],
]
enz_tbl = Table(enz_data, colWidths=[32*mm, 142*mm])
enz_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0),(-1,-1), [WHITE, GREY_BG]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#86efac")),
("TOPPADDING", (0,0),(-1,-1), 5), ("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 6), ("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
story += [enz_tbl, sp(3)]
# Other methods side by side
other_headers = ["Method", "Principle", "Accuracy", "Routine Use?"]
other_rows = [
["IDMS\n(Isotope Dilution Mass Spectrometry)",
"Uses isotope-labelled creatinine as internal standard; mass spectrometer separates and measures",
"HIGHEST - Reference Gold Standard", "NO - too complex/expensive. All methods calibrated TO this."],
["HPLC\n(High-Performance Liquid Chromatography)",
"Separates creatinine from other molecules by chromatography, then detects it",
"Very high - greater analytic specificity than Jaffe/enzymatic",
"NO - very low throughput (too slow for routine labs)"],
]
story.append(mtable(other_headers, other_rows,
[32*mm, 60*mm, 42*mm, 40*mm], hbg=SLATE))
story.append(sp(4))
# PAGE BREAK
story.append(PageBreak())
story.append(banner("CREATININE ESTIMATION | PAGE 2", DARK_TEAL, size=10))
story.append(sp(3))
# ════════════════════════════════════════════════════════════
# SECTION 5 – METHODS COMPARISON TABLE
# ════════════════════════════════════════════════════════════
story.append(banner("5. METHODS AT A GLANCE — COMPARISON", MID_TEAL))
story.append(sp(2))
mc_headers = ["Method", "Reagent", "Measures", "Overestimates?", "Interferences", "Status"]
mc_rows = [
["Jaffe (Alkaline\nPickrate)", "Picric acid +\nNaOH", "Orange-red\ncomplex by\ncolorimetry",
"YES ~25%\n(non-creatinine\nchromogens)",
"Glucose, proteins,\nketones, bilirubin\n(false LOW)",
"Widely used;\nbeing replaced"],
["Enzymatic", "Enzyme\ncascade +\ndye/peroxidase", "H2O2 product\nby photometry",
"Minimal", "Much less than\nJaffe",
"RECOMMENDED\nby KDIGO"],
["IDMS", "Isotope-labelled\ninternal standard", "Mass/charge\nratio", "NO - most accurate",
"Minimal", "Reference\nmethod only"],
["HPLC", "Chromatography\ncolumn", "Separated\ncreatinine peak", "NO - very specific",
"Minimal", "Research only\n(low throughput)"],
]
story.append(mtable(mc_headers, mc_rows,
[28*mm, 28*mm, 28*mm, 30*mm, 32*mm, 28*mm], hbg=MID_TEAL))
story.append(sp(4))
# ════════════════════════════════════════════════════════════
# SECTION 6 – CREATININE CLEARANCE & eGFR
# ════════════════════════════════════════════════════════════
story.append(banner("6. CREATININE CLEARANCE AND eGFR", GOLD))
story.append(sp(2))
cc_left = [
Paragraph("<b>Creatinine Clearance (CrCl)</b>", bold9),
Paragraph("Measures actual GFR using 24-hour urine collection.", body),
Paragraph("<b>Formula:</b>", bold9),
Paragraph("CrCl = (U<sub>cr</sub> x V) / P<sub>cr</sub>", ParagraphStyle("formula",
fontName="Helvetica-Bold", fontSize=10, textColor=GOLD,
alignment=TA_CENTER, spaceBefore=3, spaceAfter=3)),
Paragraph("U<sub>cr</sub> = urine creatinine (mg/dL)<br/>"
"V = urine volume (mL/min) from 24-hr collection<br/>"
"P<sub>cr</sub> = plasma/serum creatinine (mg/dL)", body),
Paragraph("<b>Normal: 72-140 mL/min</b>", ParagraphStyle("nv", fontName="Helvetica-Bold",
fontSize=10, textColor=RED, spaceBefore=3)),
]
cc_right = [
Paragraph("<b>eGFR (Estimated GFR)</b>", bold9),
Paragraph("No urine collection needed - calculated from serum creatinine alone.", body),
Paragraph("<b>CKD-EPI Equation</b> (current gold standard):", bold9),
Paragraph("Uses: serum creatinine + age + sex + race<br/>"
"Recommended by KDIGO for reporting in adults.", body),
Paragraph("<b>Cockcroft-Gault Formula</b> (commonly exam-tested):", bold9),
Paragraph("CrCl = [(140 - age) x weight(kg)] / (72 x serum Cr)<br/>"
"Multiply by <b>0.85 for females</b>", ParagraphStyle("cg",
fontName="Helvetica-Bold", fontSize=9, textColor=GOLD, leading=14)),
]
cc_data = [
["\n".join([""]), ""], # spacer row handled differently
]
cc_cols = Table([
[box(cc_left, bg=LIGHT_GOLD, border=GOLD),
box(cc_right, bg=LIGHT_ORANGE, border=ORANGE)]
], colWidths=[87*mm, 87*mm])
cc_cols.setStyle(TableStyle([
("LEFTPADDING", (0,0),(-1,-1), 0),
("RIGHTPADDING", (0,0),(-1,-1), 0),
("TOPPADDING", (0,0),(-1,-1), 0),
("BOTTOMPADDING",(0,0),(-1,-1), 0),
("INNERGRID", (0,0),(-1,-1), 0, WHITE),
]))
story += [cc_cols, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 7 – BUN:CREATININE RATIO
# ════════════════════════════════════════════════════════════
story.append(banner("7. BUN : CREATININE RATIO — FIND THE CAUSE OF KIDNEY FAILURE", RED))
story.append(sp(2))
ratio_headers = ["Ratio", "Interpretation", "Causes"]
ratio_rows = [
["> 20 : 1\n(HIGH)",
"BUN raised more than creatinine\n= PRE-RENAL problem",
"Dehydration | Heart failure | GI bleeding (upper) | High protein diet | "
"Glucocorticoids | Fever | Shock | Burns"],
["10 - 20 : 1\n(NORMAL)",
"Both raised proportionally\n= INTRINSIC RENAL disease",
"Acute kidney injury (AKI) | Chronic kidney disease (CKD) | "
"Glomerulonephritis | Acute tubular necrosis"],
["< 10 : 1\n(LOW)",
"Creatinine raised more than BUN\n= Low urea production",
"Low protein diet / starvation | Severe liver disease | "
"Rhabdomyolysis (massive muscle breakdown) | Malnutrition | SIADH"],
]
ratio_tbl = Table(
[[Paragraph(h, ParagraphStyle("rh", fontName="Helvetica-Bold", fontSize=9,
textColor=WHITE, alignment=TA_CENTER)) for h in ratio_headers]] +
[[Paragraph(str(c), ParagraphStyle("rc", fontName="Helvetica" if i>0 else "Helvetica-Bold",
fontSize=9, textColor=BLACK if i>0 else (RED if ri==0 else (GREEN if ri==2 else GOLD)),
leading=13, alignment=TA_CENTER if i==0 else TA_LEFT))
for i, c in enumerate(row)] for ri, row in enumerate(ratio_rows)],
colWidths=[28*mm, 55*mm, 91*mm], repeatRows=1)
ratio_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), RED),
("BACKGROUND", (0,1),(-1,1), colors.HexColor("#fff1f2")),
("BACKGROUND", (0,2),(-1,2), GREY_BG),
("BACKGROUND", (0,3),(-1,3), LIGHT_GREEN),
("GRID", (0,0),(-1,-1), 0.5, colors.HexColor("#fca5a5")),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story += [ratio_tbl, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 8 – LIMITATIONS OF SERUM CREATININE
# ════════════════════════════════════════════════════════════
story.append(banner("8. LIMITATIONS OF SERUM CREATININE (Don't Ignore These!)", SLATE))
story.append(sp(2))
lim_data = [
["1", "Muscle mass effect",
"Bodybuilders have naturally HIGH creatinine; elderly/malnourished have LOW creatinine "
"even with bad kidneys. Same serum level does NOT mean same GFR in different people."],
["2", "Late marker of kidney damage",
"GFR must fall ~50% before creatinine clearly rises, due to compensatory hyperfiltration "
"of remaining nephrons. Insensitive for EARLY kidney disease."],
["3", "Tubular secretion",
"Some creatinine is secreted by renal tubules (not just filtered). This means creatinine "
"OVERESTIMATES GFR slightly. More secretion occurs as kidney function declines."],
["4", "Drug interference\n(trimethoprim, cimetidine)",
"These drugs block tubular SECRETION of creatinine, raising serum creatinine WITHOUT "
"actual kidney damage. Can falsely suggest worse kidney function."],
["5", "Jaffe method interferences",
"Glucose, ketones (DKA), proteins, bilirubin - all can give falsely high or low readings. "
"Enzymatic method is more specific."],
["6", "Not AKI-sensitive early",
"Small changes in serum creatinine have high false-positive rates, especially in patients "
"who already have CKD. Creatinine takes time to accumulate."],
["7", "Newer biomarkers better",
"Cystatin C is BETTER than creatinine for GFR estimation (not affected by muscle mass or "
"nutrition). NGAL, KIM-1 detect tubular injury earlier."],
]
lim_tbl_data = [[Paragraph(f"<b>{n}</b>", ParagraphStyle("ln", fontName="Helvetica-Bold",
fontSize=11, textColor=WHITE, alignment=TA_CENTER)),
Paragraph(f"<b>{t}</b>", bold9),
Paragraph(d, body)] for n, t, d in lim_data]
lim_tbl = Table(lim_tbl_data, colWidths=[10*mm, 42*mm, 122*mm])
lim_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0),(-1,-1), [colors.HexColor("#334155"), GREY_BG]*4),
("BACKGROUND", (0,0),(0,-1), SLATE),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#94a3b8")),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,0),(-1,-1), [WHITE, GREY_BG]),
("BACKGROUND", (0,0),(0,-1), SLATE),
]))
story += [lim_tbl, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 9 – KEY NORMAL VALUES
# ════════════════════════════════════════════════════════════
story.append(banner("9. KEY NUMBERS — MEMORIZE THESE!", RED))
story.append(sp(2))
nums = [
["Serum creatinine - Adult men", "0.6 - 1.2 mg/dL"],
["Serum creatinine - Adult women", "0.5 - 1.1 mg/dL"],
["Serum creatinine - Upper limit (older ref)","1.4 mg/100 mL"],
["Blood urea nitrogen (BUN) - Normal", "5 - 20 mg/dL"],
["Creatinine clearance - Normal", "72 - 140 mL/min"],
["Fecal fat - Normal (for comparison)", "< 7 g/day"],
["BUN : Creatinine ratio - Normal", "10:1 to 20:1"],
["BUN : Cr > 20 = Pre-renal", "> 20:1"],
["BUN : Cr < 10 = Low urea production", "< 10:1"],
["Jaffe overestimates creatinine by", "~25%"],
["Kidney functional reserve lost before tests go abnormal", "70% function must be lost"],
["Creatinine rise per day in anuric AKI", "1 - 2 mg/dL per day"],
["AKI definition (creatinine rise within 48h)", ">= 0.3 mg/dL rise"],
["AKI definition (creatinine x baseline rise)", ">= 1.5x baseline within 7 days"],
]
nums_tbl = Table([[Paragraph(f"<b>{k}</b>", body), Paragraph(v, num_s)] for k, v in nums],
colWidths=[120*mm, 54*mm])
nums_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0),(-1,-1), [WHITE, LIGHT_RED]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#fca5a5")),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 7),
("RIGHTPADDING", (0,0),(-1,-1), 7),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story += [nums_tbl, sp(4)]
# ════════════════════════════════════════════════════════════
# SECTION 10 – MEMORY TRICKS
# ════════════════════════════════════════════════════════════
story.append(banner("10. MEMORY TRICKS — READ BEFORE SLEEP!", PURPLE))
story.append(sp(2))
tricks = [
("Creatinine = CONSTANT, Urea = UNSTABLE",
"Creatinine from muscle is steady. BUN depends on what you ate, your liver, and your hydration."),
("BUN = Big Uncertainty Nitrogen",
"Too many things affect BUN - don't use it alone to judge kidney function."),
("Jaffe = ORANGE FAKE",
"Jaffe gives false high readings - glucose and proteins mimic creatinine (non-creatinine chromogens)."),
("Jaffe OVER-estimates by 25%",
"The standard number to remember. Modified Jaffe and enzymatic methods reduce this bias."),
("IDMS = I Don't Miss Specificity",
"IDMS is the gold standard, most accurate method. All others are calibrated to it."),
("Enzymatic = RECOMMENDED",
"KDIGO recommends enzymatic method. More specific, less interference."),
("BUN:Cr > 20 = Pre-RENAL (DRY patient)",
"High ratio means BUN shot up more than creatinine = patient is dehydrated / GI bleed / heart failure."),
("Trimethoprim TRICKS you",
"Trimethoprim and cimetidine block creatinine secretion -> raises serum Cr WITHOUT kidney damage."),
("70% loss before tests become abnormal",
"Kidneys have HUGE reserve. GFR can fall 50% before creatinine clearly rises."),
("Cockcroft-Gault x 0.85 for females",
"Women have less muscle mass -> lower creatinine production -> adjust formula by 0.85."),
("Cystatin C > Creatinine for early GFR",
"Cystatin C is not affected by muscle mass or diet - better for elderly and malnourished patients."),
]
tricks_tbl = Table([[Paragraph(k, trick_k), Paragraph(v, trick_v)] for k, v in tricks],
colWidths=[56*mm, 118*mm])
tricks_tbl.setStyle(TableStyle([
("ROWBACKGROUNDS",(0,0),(-1,-1), [WHITE, LIGHT_PURPLE]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#c4b5fd")),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story += [tricks_tbl, sp(3)]
# ════════════════════════════════════════════════════════════
# FOOTER
# ════════════════════════════════════════════════════════════
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey))
story.append(sp(1))
story.append(Paragraph(
"Sources: Brenner & Rector's The Kidney 2-vol set | Tietz Textbook of Laboratory Medicine 7e | "
"Frameworks for Internal Medicine | Comprehensive Clinical Nephrology 7e | S. Das Manual of Clinical Surgery 13e "
"| Biochemistry Exam Prep — June 2026",
footer_s))
# ════════════════════════════════════════════════════════════
# BUILD
# ════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF saved: {OUTPUT}")
python /home/daytona/workspace/creatinine-exam/generate_pdf.py 2>&1
ls -lh /home/daytona/workspace/creatinine-exam/
Creatinine Estimation QuickRef
PDF Document · PDF