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.

Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Reading File
I now have excellent, detailed content from authoritative biochemistry sources. Let me compile the full, easy-to-understand answer.

Pancreas Function Tests - Biochemistry Exam Notes

First, understand the pancreas quickly

The pancreas has two parts that do very different jobs:
PartTypeWhat it does
Exocrine~98% of the glandMakes 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.
Normal pancreatic juice: colorless, odorless, pH 8.0-8.3, can produce up to 3000 mL/day.

Big Picture: Two Categories of Tests

PANCREAS FUNCTION TESTS
        |
   _____|_____
   |         |
INVASIVE   NONINVASIVE
(need a    (tubeless -
  tube)     easier but
           less accurate)

CATEGORY 1: INVASIVE TESTS (Direct Tests)

These are the most accurate (gold standard). A tube is placed into the duodenum and pancreatic juice is collected.

A. Secretin Test

  • What you do: Give secretin (a hormone) by IV injection
  • What you measure: Volume of juice + bicarbonate (HCO3-) output in duodenum
  • What it tells you: Tests the ductal cells of the pancreas (which make bicarbonate and water)
  • Normal: Bicarbonate should rise significantly after secretin
  • In disease: Bicarbonate output drops = pancreatitis, pancreatic cancer

B. CCK (Cholecystokinin) Test

  • What you do: Give CCK (or synthetic ceruletide) by IV
  • What you measure: Enzymes in duodenum - amylase, lipase, trypsin, chymotrypsin
  • What it tells you: Tests the acinar cells (which make enzymes)
  • In disease: Enzyme output is reduced

C. Secretin + CCK Test (GOLD STANDARD)

  • Best of both worlds: Combines both tests above
  • Measures volume + bicarbonate + all enzymes together
  • Most sensitive and specific test for exocrine pancreatic function
  • Drawback: Needs duodenal intubation + IV hormones, not available everywhere

D. Lundh Test Meal (historical)

  • Patient eats a standardized meal (6% fat, 15% carbohydrate, 74% non-nutrient fiber)
  • Meal stimulates pancreas naturally (no IV hormone needed)
  • Trypsin concentration measured in duodenal fluid
  • No longer used routinely - mainly of historical interest

CATEGORY 2: NONINVASIVE TESTS (Indirect / Tubeless Tests)

These are simpler but less sensitive - they only catch moderate to severe disease, not mild.
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.

A. Fecal Elastase-1 (Most important noninvasive test!)

  • What: Measure elastase-1 enzyme in a stool sample
  • Why it's good: Elastase-1 is stable as it passes through the gut (not broken down)
  • Result:
    • Normal: >200 mcg/g stool
    • Severe insufficiency: <100 mcg/g stool
  • Uses: Screening for pancreatic insufficiency, especially in cystic fibrosis children and chronic pancreatitis
  • This is the most commonly used noninvasive test today

B. Fecal Chymotrypsin

  • Measure chymotrypsin in stool
  • Less reliable than elastase (chymotrypsin can be degraded in the gut)
  • Only detects severe dysfunction

C. Fecal Fat (72-hour stool collection)

  • Patient eats 100g fat/day for 3 days, all stool collected
  • Normal: <7g fat per day in stool
  • Steatorrhea (disease): >7g fat/day
  • Confirms malabsorption but does not pinpoint pancreas as the cause
  • Inconvenient for patient (stool collection for 3 days!)

D. NBT-PABA Test (Bentiromide Test)

  • Patient swallows N-benzoyl-L-tyrosyl-para-aminobenzoic acid (NBT-PABA) with a meal
  • Chymotrypsin in the gut cleaves it → releases PABA
  • PABA is absorbed, enters blood, excreted in urine
  • Measure PABA in blood or urine = indirect measure of chymotrypsin activity
  • Problem: Can be falsely abnormal if the small intestine is damaged (not specific to pancreas)

E. Fluorescein Dilaurate Test (Pancreolauryl Test)

  • Similar idea to PABA test
  • Patient eats fluorescein dilaurate + a test meal
  • Pancreatic esterase cleaves it → fluorescein absorbed → measured in urine/serum
  • Same problem: small bowel disease can give false positives

Quick Comparison Table (memorize this!)

TestTypeMeasuresSensitivity
Secretin + CCKInvasiveVolume, HCO3-, enzymesBEST (gold standard)
Secretin aloneInvasiveVolume + HCO3-High
Fecal Elastase-1NoninvasiveElastase in stoolGood for moderate-severe
Fecal FatNoninvasiveFat in stoolOnly severe disease
NBT-PABANoninvasivePABA in urine/serumOnly severe disease
Lundh test mealSemi-invasiveTrypsin in duodenumHistorical only

SERUM ENZYME TESTS (Used for Acute Pancreatitis Diagnosis)

These are not the same as function tests but you may be asked about them:

Serum Amylase

  • Rises within 2-12 hours of acute pancreatitis
  • Peaks at 12-72 hours
  • Returns to normal in 3-5 days
  • Problem: Not specific (can rise in salivary gland disease, intestinal obstruction, renal failure)

Serum Lipase

  • Rises within 4-8 hours
  • Stays elevated longer than amylase (8-14 days)
  • More specific to the pancreas than amylase
  • Preferred test for acute pancreatitis today
Rule of thumb: Amylase = quick rise, quick fall. Lipase = slower rise, stays longer, more specific.

Clinical Conditions Where These Tests Are Used

DiseaseKey Tests Used
Acute pancreatitisSerum amylase + lipase
Chronic pancreatitisSecretin-CCK test (best), fecal elastase
Cystic fibrosisFecal elastase-1 (especially in infants)
Pancreatic cancerSecretin test (function may be reduced)
Malabsorption / steatorrheaFecal fat, fecal elastase

Quick Memory Tricks

  1. "DIRECT tests = BEST results" - direct tests need a tube but give the most accurate answer
  2. "Secretin = Bicarbonate, CCK = Enzymes" - secretin stimulates juice/bicarb, CCK stimulates enzyme release
  3. "Elastase stays alive in stool" - that is why fecal elastase is the best tubeless test
  4. "Lipase outlasts amylase" - lipase stays high longer in acute pancreatitis
  5. "Pancreas has huge reserve" - tests only become abnormal when 90-95% of function is lost (for tubeless tests)

One-Line Summary for Each Test

  • Secretin test = give secretin IV, measure bicarbonate in duodenum
  • CCK test = give CCK IV, measure enzymes in duodenum
  • Secretin + CCK = gold standard, measures everything
  • Fecal elastase = best noninvasive test, stool sample, stable enzyme
  • Fecal fat = steatorrhea confirmation, 72-hr stool, not pancreas-specific
  • NBT-PABA = oral substrate, urine PABA measures chymotrypsin
  • Serum amylase = rises fast in acute pancreatitis, not specific
  • Serum lipase = rises slower, more specific, preferred marker

Good luck on your exam tomorrow! Focus especially on: (1) the secretin-CCK gold standard, (2) fecal elastase as the best noninvasive test, and (3) lipase being preferred over amylase for acute pancreatitis.
Sources: Tietz Textbook of Laboratory Medicine, 7th Ed. | Sleisenger and Fordtran's Gastrointestinal and Liver Disease

Create a quick reference PDF I can download before the exam tomorrow

Reading File
Reading File
Running Command

mkdir -p /home/daytona/workspace/pancreas-exam && ls /home/daytona/workspace/

Writing File

~/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>&bull;</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  &amp;  Sleisenger &amp; 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 &amp; 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 &lt;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 = &lt;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 &amp; 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}")
Running Command

python /home/daytona/workspace/pancreas-exam/generate_pdf.py

Running Command

ls -lh /home/daytona/workspace/pancreas-exam/

Your PDF is ready! Download it here:

Pancreas Function Tests QuickRef

PDF Document · PDF


Here's what's inside the 2-page quick reference:
Page 1
  • Pancreas at a glance (exocrine vs endocrine side by side)
  • Two main categories explained (invasive vs noninvasive)
  • Full invasive tests table (Secretin, CCK, Gold Standard combo, Lundh)
  • Memory trick for Secretin vs CCK
Page 2
  • Full noninvasive tests table (Fecal Elastase, Fecal Fat, NBT-PABA, Fluorescein Dilaurate)
  • Serum enzyme tests for acute pancreatitis (Amylase vs Lipase)
  • Master comparison table - all tests in one place
  • Which test for which disease (clinical applications)
  • 8 memory tricks box
  • Key numbers to memorize (normal values, timing, thresholds)
All sourced from Tietz Textbook of Laboratory Medicine 7e and Sleisenger & Fordtran. Good luck tomorrow!

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.

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
I now have rich, authoritative content from multiple textbooks. Let me compile the complete, easy-to-understand answer.

Creatinine Estimation & Urea vs Serum Creatinine - Biochemistry Exam Notes


First: Where Do These Come From?

Before understanding the tests, you need to know where creatinine and urea come from in the body.

Creatinine - Where It Comes From

Creatine (stored in muscle)
        |
        | (broken down during muscle metabolism)
        |
   Creatinine
        |
        | (filtered by kidneys)
        |
   Excreted in urine
  • Creatinine is a waste product of muscle metabolism
  • It is produced at a constant, steady rate every day
  • It depends on your muscle mass - more muscle = more creatinine
  • It is freely filtered by the glomerulus (the kidney's filter)
  • Very little is reabsorbed - mostly just filtered and thrown out

Urea (BUN = Blood Urea Nitrogen) - Where It Comes From

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
  • Urea is the end product of protein breakdown
  • Made in the liver from ammonia
  • Amount depends hugely on how much protein you eat

THE BIG QUESTION: Why is Serum Creatinine PREFERRED Over Urea?

Short answer: Serum creatinine is a MORE RELIABLE marker of kidney function than blood urea (BUN).
Here is why in detail:

Creatinine - Advantages (Why It Is Preferred)

PointExplanation
Constant productionProduced at same rate every day from muscle breakdown - no big daily variation
Not affected by dietEating more food does NOT change creatinine significantly
Directly reflects GFRCreatinine is freely filtered at the glomerulus, so when GFR falls, creatinine rises predictably
More specific to kidneyMainly excreted by kidneys - not affected by liver function
Standard testUsed to calculate eGFR (estimated Glomerular Filtration Rate) - the gold standard of kidney function
Normal values (serum creatinine):
  • Adult men: 0.6 - 1.2 mg/dL
  • Adult women: 0.5 - 1.1 mg/dL
  • Upper limit (older textbooks): 1.4 mg/100 mL

Urea (BUN) - Disadvantages (Why It Is NOT Preferred Alone)

This is the most important part to remember - BUN is affected by many non-kidney factors:

Factors that RAISE BUN (without kidney disease):

  1. High protein diet - eat a lot of meat, BUN goes up
  2. Fever / infection - body breaks down more protein (catabolism)
  3. Glucocorticoids (steroids) - increase protein breakdown
  4. Tetracycline antibiotics - antianabolic effect
  5. GI bleeding (upper) - blood in gut is digested as protein, raises BUN
  6. Dehydration - low blood volume causes more urea reabsorption
  7. Old age - more catabolism

Factors that LOWER BUN (without kidney improvement):

  1. Low protein diet / starvation / malnutrition - less protein = less urea
  2. Liver disease - liver cannot convert ammonia to urea properly
  3. Pregnancy - dilutional effect
Because BUN is affected by so many outside factors, it does NOT reliably reflect kidney function alone. Serum creatinine is preferred.
Normal BUN: 5-20 mg/dL (wide range because it fluctuates!)

How Creatinine Is Measured - The Methods

This is the core biochemistry part you need for your exam.

Method 1: Jaffe Method (Alkaline Picrate Method) - Most Common Historically

Principle:
Creatinine + Alkaline Picrate (sodium picrate in NaOH)
                    |
              Orange-red colour
                    |
        Measured by colorimetry (spectrophotometry)
Step by step:
  1. Patient's serum is added to alkaline picrate solution
  2. Creatinine reacts with picric acid in alkaline conditions
  3. Forms an orange-red complex (Janovsky complex)
  4. Colour intensity is measured by colorimeter/spectrophotometer
  5. Compare to known standard = calculate creatinine concentration
Problems with Jaffe Method:
  • Overestimates creatinine by ~25% because other substances also react with picrate (these are called "non-creatinine chromogens")
    • Glucose, proteins, acetoacetate (ketones), bilirubin, ascorbic acid, pyruvate
  • High bilirubin causes falsely LOW readings
  • Diabetic patients are especially affected (high glucose + ketones interfere)
  • Modified Jaffe methods try to remove these interfering substances first

Method 2: Enzymatic Method - More Accurate (Now Recommended)

Principle:
Creatinine
    |  (creatininase enzyme)
    v
Creatine
    |  (creatine amidino-hydrolase)
    v
Sarcosine + Urea
    |  (sarcosine oxidase)
    v
Glycine + Formaldehyde + H2O2
    |  (peroxidase + dye)
    v
Coloured product -> Measured photometrically
Advantages of enzymatic method:
  • More specific for creatinine - fewer interferences
  • Less affected by glucose, proteins, bilirubin
  • Expert bodies and KDIGO recommend this method

Method 3: IDMS (Isotope Dilution Mass Spectrometry) - Reference Method (Gold Standard)

  • The most accurate method - reference standard
  • All lab methods should be calibrated to IDMS
  • Not used routinely in hospitals (too complex/expensive)
  • Used to standardize other methods

Method 4: HPLC (High-Performance Liquid Chromatography)

  • Very specific and accurate
  • More analytical specificity than conventional methods
  • Severe limitation: low throughput (too slow for routine use)

Creatinine Clearance - The Functional Test

Creatinine clearance gives you the GFR (Glomerular Filtration Rate) - how well the kidney filters.
Formula:
Creatinine Clearance (mL/min) = (Urine Creatinine x Urine Volume) / Serum Creatinine
Or more precisely:
CrCl = (U_cr x V) / P_cr
Where:
  • U_cr = urine creatinine concentration (mg/dL)
  • V = urine volume in mL/min (usually from 24-hour collection)
  • P_cr = plasma/serum creatinine (mg/dL)
Normal creatinine clearance: 72-140 mL/min
Important exam fact: Kidneys have a large functional reserve - 70% of kidney function must be lost before renal failure shows up in tests!

eGFR - Estimated GFR (Calculated from Serum Creatinine)

Instead of collecting 24-hour urine (annoying and error-prone), we use formulas to estimate GFR from serum creatinine alone.
CKD-EPI equation (most current - recommended by KDIGO):
  • Uses serum creatinine + age + sex + race
  • Standard for reporting eGFR in adults
Cockcroft-Gault formula (older, commonly tested):
CrCl = [(140 - age) x weight in kg] / (72 x serum creatinine)
       Multiply by 0.85 for females

BUN:Creatinine Ratio - Exam Favourite!

Even though urea alone is not preferred, the BUN:Creatinine ratio is very clinically useful to figure out the CAUSE of kidney failure.
Normal ratio: 10:1 to 20:1
RatioWhat it meansCause
>20:1 (HIGH ratio)BUN raised more than creatininePre-renal (dehydration, heart failure, GI bleeding, high protein diet)
10-20:1 (Normal ratio)Both raised proportionallyIntrinsic renal disease (glomerulonephritis, AKI, CKD)
<10:1 (LOW ratio)Creatinine raised more than BUNLow protein intake, severe liver disease, rhabdomyolysis (large muscle breakdown)

Limitations of Serum Creatinine (Do NOT Ignore These!)

Even though creatinine is preferred, it also has limitations:
  1. Muscle mass effect - bodybuilders have high creatinine naturally; elderly/malnourished people have low creatinine even with bad kidneys
  2. Late marker - GFR must fall to ~50% before serum creatinine clearly rises (due to compensatory hyperfiltration)
  3. Tubular secretion - some creatinine is secreted by tubules (not just filtered), which slightly overestimates GFR
  4. Drug interference - trimethoprim and cimetidine block tubular secretion of creatinine, raising serum creatinine WITHOUT actual kidney damage
  5. Jaffe method interferences - as described above

Acute Kidney Injury (AKI) - Creatinine Criteria

AKI is defined by (any one of):
  1. Serum creatinine rises by ≥0.3 mg/dL within 48 hours
  2. Serum creatinine rises to ≥1.5 times baseline within 7 days
  3. Urine output <0.5 mL/kg/hour for 6 hours
In anuric (no urine output) kidney injury, creatinine rises by 1-2 mg/dL per day.

Quick Comparison: Urea vs Serum Creatinine

FeatureUrea (BUN)Serum Creatinine
SourceProtein breakdown (liver)Muscle metabolism
Production rateVariable (diet-dependent)Constant (muscle mass)
Affected by dietYES - big effectNO - minimal effect
Affected by liverYES - liver disease lowers itNO
GI bleeding effectRaises BUN (blood = protein)Minimal effect
Dehydration effectRaises BUN moreLess affected
Preferred for kidneyNO - too many variablesYES - preferred
Normal value5-20 mg/dL0.6-1.2 mg/dL (men)
Best useBUN:Cr ratio to find causeeGFR calculation

Memory Tricks

  1. "Creatinine is CONSTANT, Urea is UNSTABLE" - creatinine comes from muscle at a steady rate; urea depends on what you ate
  2. "BUN = BIG Uncertainty Nitrogen" - too many things affect it
  3. "Jaffe = Orange fake" - Jaffe method gives falsely high reading (fake creatinine from glucose/proteins)
  4. "IDMS = I Don't Miss Specificity" - IDMS is the gold standard, most accurate
  5. "10:20 ratio is normal - above 20 = pre-RENAL (dehydrated)" - BUN:Cr ratio >20 = prerenal problem
  6. "70% kidney loss before tests go abnormal" - kidneys have huge reserve
  7. "Creatinine rises 1-2 mg/dL per day in anuric AKI"
  8. "Trimethoprim tricks you" - raises creatinine without damaging the kidney

One-Line Summary for Each Test

  • Serum creatinine = preferred kidney marker, constant production, reflects GFR
  • Jaffe method = alkaline picrate + colorimetry, overestimates by 25%, interferences from glucose/proteins/bilirubin
  • Enzymatic method = more specific, fewer interferences, now recommended
  • IDMS = gold standard reference method, not routine
  • Creatinine clearance = actual GFR measurement from 24-hr urine + serum creatinine, normal 72-140 mL/min
  • eGFR (CKD-EPI) = estimated GFR from serum creatinine formula, no urine needed
  • BUN = not preferred alone, affected by diet/liver/dehydration/GI bleed
  • BUN:Cr ratio = very useful to find cause: >20 = prerenal, normal = intrinsic renal, <10 = low protein/liver

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

Create a quick reference PDF for creatinine estimation before exam

Running Command

mkdir -p /home/daytona/workspace/creatinine-exam && ls /home/daytona/workspace/

Writing File

~/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'>&#8595;</font>  broken down by muscle metabolism<br/>"
        "<b>Creatinine</b><br/>"
        "  <font color='#0e7490'>&#8595;</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'>&#8595;</font>  broken down (catabolism)<br/>"
        "Amino Acids<br/>"
        "  <font color='#c2410c'>&#8595;</font>  deamination in liver<br/>"
        "Ammonia (toxic!) &#8594; <b>Liver converts to UREA</b><br/>"
        "  <font color='#c2410c'>&#8595;</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) "
               "&#8594; <b>Orange-red complex (Janovsky complex)</b> "
               "&#8594; measured by colorimetry (spectrophotometer)", body)],
    [Paragraph("<b>Steps</b>", bold9),
     Paragraph("1. Add serum to alkaline picrate solution<br/>"
               "2. Creatinine reacts &#8594; 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> &#8594; Creatine<br/>"
        "Creatine <i>(creatine amidino-hydrolase)</i> &#8594; Sarcosine + Urea<br/>"
        "Sarcosine <i>(sarcosine oxidase)</i> &#8594; Glycine + Formaldehyde + H<sub>2</sub>O<sub>2</sub><br/>"
        "H<sub>2</sub>O<sub>2</sub> + dye <i>(peroxidase)</i> &#8594; <b>Coloured product</b> &#8594; 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 &amp; 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}")
Running Command

python /home/daytona/workspace/creatinine-exam/generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/creatinine-exam/

Your PDF is ready! Download it here:

Creatinine Estimation QuickRef

PDF Document · PDF


Here's what's packed into the 2-page quick reference:
Page 1
  • Where creatinine vs urea come from (side-by-side origin boxes)
  • Why serum creatinine is preferred - full comparison table (8 factors)
  • All factors that raise/lower BUN falsely (colour-coded red/green)
  • Jaffe method - principle, steps, interferences, what causes false highs/lows
  • Enzymatic method - full enzyme cascade reaction, why it's better
  • IDMS and HPLC methods summary table
Page 2
  • Methods comparison table (all 4 methods side by side)
  • Creatinine clearance formula + eGFR (CKD-EPI + Cockcroft-Gault)
  • BUN:Creatinine ratio table (>20 = prerenal, 10-20 = intrinsic renal, <10 = low protein/liver)
  • 7 limitations of serum creatinine
  • 14 key numbers to memorize (normal values, cutoffs, AKI criteria)
  • 11 memory tricks box
Best of luck on your exam tomorrow!
This is a shared conversation. Sign in to Orris to start your own chat.