~/renal-calculi/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Table, TableStyle, Spacer,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.pdfgen import canvas
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
OUTPUT = "/tmp/workspace/renal-calculi/Renal_Calculi_Quick_Reference.pdf"
# ββ Colour palette ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
NAVY = colors.HexColor("#1B3A6B")
STEEL = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
TEAL = colors.HexColor("#1A7A7A")
LIGHT_TEAL = colors.HexColor("#D0EFEF")
ORANGE = colors.HexColor("#C0540A")
ORANGE_LT = colors.HexColor("#FDE8D8")
RED = colors.HexColor("#B22222")
RED_LT = colors.HexColor("#FAE0E0")
GREEN = colors.HexColor("#1B6B2F")
GREEN_LT = colors.HexColor("#D8F0DE")
PURPLE = colors.HexColor("#5B2C8D")
PURPLE_LT = colors.HexColor("#EAD8F8")
GOLD = colors.HexColor("#B8860B")
GOLD_LT = colors.HexColor("#FFF8DC")
GREY_DARK = colors.HexColor("#333333")
GREY_MID = colors.HexColor("#666666")
GREY_LIGHT = colors.HexColor("#F4F6FA")
WHITE = colors.white
# ββ Page dimensions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
W, H = A4 # 595.27 x 841.89 pts
MARGIN = 1.5 * cm
# ββ Styles ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
styles = getSampleStyleSheet()
def style(name, **kw):
s = ParagraphStyle(name, **kw)
return s
HDR1 = style("HDR1", fontSize=18, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_CENTER, leading=22)
HDR2 = style("HDR2", fontSize=11, fontName="Helvetica-Bold",
textColor=WHITE, alignment=TA_LEFT, leading=14)
HDR3 = style("HDR3", fontSize=9.5, fontName="Helvetica-Bold",
textColor=NAVY, alignment=TA_LEFT, leading=12)
BODY = style("BODY", fontSize=8.2, fontName="Helvetica",
textColor=GREY_DARK, leading=11)
BODY_B = style("BODY_B", fontSize=8.2, fontName="Helvetica-Bold",
textColor=GREY_DARK, leading=11)
SMALL = style("SMALL", fontSize=7.5, fontName="Helvetica",
textColor=GREY_MID, leading=10)
SMALL_B = style("SMALL_B", fontSize=7.5, fontName="Helvetica-Bold",
textColor=GREY_DARK, leading=10)
CELL = style("CELL", fontSize=7.8, fontName="Helvetica",
textColor=GREY_DARK, leading=10, alignment=TA_LEFT)
CELL_B = style("CELL_B", fontSize=7.8, fontName="Helvetica-Bold",
textColor=GREY_DARK, leading=10)
CELL_W = style("CELL_W", fontSize=8, fontName="Helvetica-Bold",
textColor=WHITE, leading=10, alignment=TA_LEFT)
ALERT = style("ALERT", fontSize=8, fontName="Helvetica-Bold",
textColor=RED, leading=10)
MNEM = style("MNEM", fontSize=8, fontName="Helvetica-BoldOblique",
textColor=PURPLE, leading=10)
usable_w = W - 2 * MARGIN # β 165 mm
# ββ Helper: section header bar ββββββββββββββββββββββββββββββββββββββββββββββββ
def section_header(text, bg=NAVY, fg=WHITE):
tbl = Table([[Paragraph(text, style("_sh", fontSize=10,
fontName="Helvetica-Bold",
textColor=fg, leading=13))]],
colWidths=[usable_w])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
]))
return tbl
# ββ Helper: two-col layout ββββββββββββββββββββββββββββββββββββββββββββββββββββ
def two_col(left_items, right_items, ratio=(1, 1)):
total = sum(ratio)
lw = usable_w * ratio[0] / total - 3
rw = usable_w * ratio[1] / total - 3
rows = max(len(left_items), len(right_items))
data = []
for i in range(rows):
l = left_items[i] if i < len(left_items) else ""
r = right_items[i] if i < len(right_items) else ""
data.append([l, r])
tbl = Table(data, colWidths=[lw, rw], hAlign="LEFT")
tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING",(0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
return tbl
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Build story
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story = []
# ββ TITLE BANNER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
title_data = [[
Paragraph("πͺ¨ RENAL CALCULI", style("T1", fontSize=20,
fontName="Helvetica-Bold", textColor=WHITE, leading=24)),
Paragraph("Quick Reference Card", style("T2", fontSize=11,
fontName="Helvetica-BoldOblique", textColor=LIGHT_BLUE,
alignment=TA_RIGHT, leading=14)),
]]
title_tbl = Table(title_data, colWidths=[usable_w * 0.6, usable_w * 0.4])
title_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING",(0,0), (-1,-1), 10),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(title_tbl)
sub_data = [[
Paragraph("Nephrolithiasis / Urolithiasis", style("S1", fontSize=9,
fontName="Helvetica", textColor=WHITE, leading=11)),
Paragraph("Classification Β· Diagnosis Β· Treatment Β· Prevention",
style("S2", fontSize=8, fontName="Helvetica-Oblique",
textColor=LIGHT_BLUE, alignment=TA_RIGHT, leading=11)),
]]
sub_tbl = Table(sub_data, colWidths=[usable_w * 0.55, usable_w * 0.45])
sub_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), STEEL),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING",(0,0), (-1,-1), 10),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sub_tbl)
story.append(Spacer(1, 6))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 1 β DEFINITION & EPIDEMIOLOGY
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("1. DEFINITION & EPIDEMIOLOGY", NAVY))
story.append(Spacer(1, 4))
def_items = [
Paragraph("<b>Definition:</b> Crystalline deposits formed in the kidney when urine becomes "
"supersaturated with stone-forming ions, exceeding their solubility "
"β nucleation β crystal growth β stone.", BODY),
Spacer(1, 4),
Paragraph("<b>Mechanism:</b> Supersaturation β Nucleation (homo/heterogeneous) β "
"Aggregation β Stone (inhibitors: citrate, Tamm-Horsfall protein, osteopontin)", BODY),
]
epi_rows = [
[Paragraph("Fact", CELL_W), Paragraph("Value", CELL_W)],
[Paragraph("Lifetime risk β Men", CELL_B), Paragraph("~15%", CELL)],
[Paragraph("Lifetime risk β Women", CELL_B), Paragraph("~10%", CELL)],
[Paragraph("Peak incidence", CELL_B), Paragraph("4thβ5th decade", CELL)],
[Paragraph("Annual incidence", CELL_B), Paragraph(">1 per 1,000 persons", CELL)],
[Paragraph("Most common stone", CELL_B), Paragraph("Calcium oxalate (60%)", CELL)],
[Paragraph("Key risk factors", CELL_B), Paragraph("Obesity, hot climate, low fluid intake, family Hx", CELL)],
]
epi_tbl = Table(epi_rows, colWidths=[usable_w*0.42, usable_w*0.58])
epi_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), STEEL),
("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_BLUE),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), LIGHT_BLUE),
("BACKGROUND", (0,6), (-1,6), WHITE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBCDE0")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(two_col(def_items, [epi_tbl], ratio=(55, 45)))
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 2 β STONE CLASSIFICATION
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("2. STONE CLASSIFICATION (Composition & Key Features)", TEAL))
story.append(Spacer(1, 4))
hdr = [Paragraph(h, CELL_W) for h in
["Stone Type", "% of All", "X-Ray", "Urine pH", "Crystal Shape", "Key Association"]]
stone_rows = [
hdr,
[Paragraph("Calcium Oxalate", CELL_B),
Paragraph("60%", CELL),
Paragraph("Radiopaque", CELL),
Paragraph("Acidic", CELL),
Paragraph("Envelope/bipyramid (dihydrate)\nDumbbell (monohydrate)", SMALL),
Paragraph("Hypercalciuria, hyperoxaluria, hypocitraturia", CELL)],
[Paragraph("Calcium Phosphate\n(Hydroxyapatite)", CELL_B),
Paragraph("20%", CELL),
Paragraph("Radiopaque", CELL),
Paragraph("Alkaline", CELL),
Paragraph("Amorphous / needle", SMALL),
Paragraph("Hyperparathyroidism, RTA type 1", CELL)],
[Paragraph("Struvite\n(MgNHβPOβ)", CELL_B),
Paragraph("7%", CELL),
Paragraph("Radiopaque", CELL),
Paragraph("Alkaline >7", CELL),
Paragraph("Coffin-lid", SMALL),
Paragraph("Urease bacteria (Proteus #1)\nStaghorn calculi, recurrent UTIs", CELL)],
[Paragraph("Uric Acid", CELL_B),
Paragraph("7%", CELL),
Paragraph("Radiolucent β
", CELL),
Paragraph("Acidic <5.5", CELL),
Paragraph("Rhomboid/needle", SMALL),
Paragraph("Gout, obesity, high-purine diet,\ntumor lysis, diabetes", CELL)],
[Paragraph("Cystine", CELL_B),
Paragraph("1β3%", CELL),
Paragraph("Faint opaque", CELL),
Paragraph("Acidic", CELL),
Paragraph("Hexagonal β
β
", SMALL),
Paragraph("Cystinuria (AR) β COLA: Cystine,\nOrnithine, Lysine, Arginine", CELL)],
[Paragraph("Drug Stones", CELL_B),
Paragraph("<1%", CELL),
Paragraph("Radiolucent\n(indinavir)", CELL),
Paragraph("Variable", CELL),
Paragraph("Variable", SMALL),
Paragraph("Indinavir, triamterene, ephedrine,\nsilica", CELL)],
]
stone_tbl = Table(stone_rows, colWidths=[
usable_w*0.16, usable_w*0.07, usable_w*0.10,
usable_w*0.10, usable_w*0.17, usable_w*0.40
])
stone_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_TEAL, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#A0C8C8")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 7.8),
]))
story.append(stone_tbl)
story.append(Paragraph(
"β
Uric acid stones are the ONLY radiolucent stones on plain X-ray but are visible on CT "
"β
β
Hexagonal crystals on urine microscopy are pathognomonic for cystinuria",
style("fn", fontSize=7, fontName="Helvetica-Oblique",
textColor=GREY_MID, leading=9)))
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 3 β CAUSES / RISK FACTORS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("3. CAUSES & RISK FACTORS", ORANGE))
story.append(Spacer(1, 4))
# Hypercalciuria box
hyper_rows = [
[Paragraph("HYPERCALCIURIA (3 types)", CELL_W), Paragraph("Prevalence", CELL_W), Paragraph("Mechanism", CELL_W)],
[Paragraph("Absorptive (most common)", CELL_B), Paragraph("20β40%", CELL), Paragraph("β GI Ca absorption β β urinary Ca", CELL)],
[Paragraph("Renal", CELL_B), Paragraph("5β8%", CELL), Paragraph("Impaired tubular Ca reabsorption", CELL)],
[Paragraph("Resorptive (1Β° HPT)", CELL_B), Paragraph("3β5%", CELL), Paragraph("β PTH β bone Ca release β β urine Ca", CELL)],
]
hyper_tbl = Table(hyper_rows, colWidths=[usable_w*0.35, usable_w*0.15, usable_w*0.50])
hyper_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [ORANGE_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#E0A080")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
# Other causes
other_rows = [
[Paragraph("Condition", CELL_W), Paragraph("Mechanism / Association", CELL_W)],
[Paragraph("Hyperoxaluria", CELL_B),
Paragraph("Dietary (spinach, nuts, tea) | Enteric (Crohn's, celiac β malabsorbed fat binds Ca, free oxalate absorbed) | Primary (AGXT gene defect)", CELL)],
[Paragraph("Hypocitraturia", CELL_B),
Paragraph("RTA type 1, chronic diarrhea, hypokalemia β citrate is key stone INHIBITOR", CELL)],
[Paragraph("Hyperuricosuria", CELL_B),
Paragraph("High purine diet, gout, myeloproliferative disease β promotes Ca-oxalate & uric acid stones", CELL)],
[Paragraph("Dehydration / Low UO", CELL_B),
Paragraph("Hot climate, occupation, exercise β concentrated urine β supersaturation", CELL)],
[Paragraph("Urease bacteria", CELL_B),
Paragraph("Proteus mirabilis, Klebsiella, Pseudomonas β alkaline pH β struvite", CELL)],
[Paragraph("Cystinuria (AR)", CELL_B),
Paragraph("SLC3A1 / SLC7A9 mutations β defective COLA transport β cystine in urine", CELL)],
]
other_tbl = Table(other_rows, colWidths=[usable_w*0.22, usable_w*0.78])
other_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#7A3A0A")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [ORANGE_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#E0A080")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(hyper_tbl)
story.append(Spacer(1, 4))
story.append(other_tbl)
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 4 β CLINICAL FEATURES
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("4. CLINICAL FEATURES", RED))
story.append(Spacer(1, 4))
# Symptoms column
sx_items = [
Paragraph("<b>Classic Renal Colic:</b>", BODY_B),
Paragraph("β’ Sudden, severe, colicky flank pain", BODY),
Paragraph("β’ Radiates: flank β groin β labia/scrotum", BODY),
Paragraph("β’ Restless (can't get comfortable)", BODY),
Paragraph("β’ Nausea & vomiting", BODY),
Paragraph("β’ Haematuria (90% β gross or micro)", BODY),
Paragraph("β’ Dysuria/urgency (stone near UVJ)", BODY),
Spacer(1, 4),
Paragraph("<b>No peritoneal signs</b> (patient constantly moves)", ALERT),
]
# 3 points of obstruction
obs_data = [
[Paragraph("Site", CELL_W), Paragraph("Location / Pain", CELL_W)],
[Paragraph("β UPJ", CELL_B), Paragraph("Ureteropelvic junction β flank pain", CELL)],
[Paragraph("β‘ Iliac brim", CELL_B), Paragraph("Ureter crosses iliac vessels β lower abdomen", CELL)],
[Paragraph("β’ UVJ β
", CELL_B), Paragraph("Ureterovesical junction (narrowest) β urgency, groin pain β MOST COMMON OBSTRUCTION SITE", CELL)],
]
obs_tbl = Table(obs_data, colWidths=[usable_w*0.20, usable_w*0.80])
obs_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("ROWBACKGROUNDS", (0,1), (-1,-1), [RED_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#D08080")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
obs_block = [
Paragraph("<b>3 Sites of Stone Impaction (Exam Favourite)</b>", BODY_B),
Spacer(1, 3),
obs_tbl,
]
story.append(two_col(sx_items, obs_block, ratio=(45, 55)))
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 5 β DIAGNOSTIC CRITERIA
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("5. DIAGNOSTIC CRITERIA & INVESTIGATIONS", PURPLE))
story.append(Spacer(1, 4))
diag_rows = [
[Paragraph("Investigation", CELL_W), Paragraph("Findings / Purpose", CELL_W), Paragraph("Notes", CELL_W)],
[Paragraph("Urinalysis", CELL_B),
Paragraph("Haematuria (90%), crystalluria, pyuria (if infected), urine pH", CELL),
Paragraph("Essential first step", CELL)],
[Paragraph("Urine culture", CELL_B),
Paragraph("Rule out/confirm infection, especially struvite", CELL),
Paragraph("Alkaline pH + bacteriuria = struvite until proven otherwise", CELL)],
[Paragraph("24-hr urine (recurrent stones)", CELL_B),
Paragraph("Calcium, oxalate, uric acid, citrate, phosphate, Na, Cr", CELL),
Paragraph("Guides prevention therapy", CELL)],
[Paragraph("Serum BMP/Ca/Uric acid", CELL_B),
Paragraph("Renal function, hypercalcaemia, hyperuricaemia", CELL),
Paragraph("Check Cr before contrast", CELL)],
[Paragraph("PTH", CELL_B),
Paragraph("If serum Ca elevated β exclude 1Β° hyperparathyroidism", CELL),
Paragraph("Resorptive hypercalciuria", CELL)],
[Paragraph("β
NCCT (Non-contrast CT) KUB", CELL_B),
Paragraph("GOLD STANDARD β >95% sensitivity & specificity. Detects all stone types, hydronephrosis, ureteral stranding", CELL),
Paragraph("Preferred in ED. Visible: ALL stones incl. uric acid", CELL)],
[Paragraph("KUB X-ray (Plain)", CELL_B),
Paragraph("Radiopaque stones only (~60% sensitive). Useful for follow-up of known radiopaque stones", CELL),
Paragraph("MISSES uric acid/indinavir stones", CELL)],
[Paragraph("Ultrasound", CELL_B),
Paragraph("Detects hydronephrosis, larger stones >5 mm. Hyperechoic + posterior acoustic shadowing", CELL),
Paragraph("1st line in PREGNANCY & CHILDREN (no radiation)", CELL)],
[Paragraph("IVP/IVU", CELL_B),
Paragraph("Anatomic detail, radiolucent stones visible as filling defects", CELL),
Paragraph("Largely replaced by NCCT", CELL)],
[Paragraph("MRI", CELL_B),
Paragraph("Poor sensitivity for calculi (no MR signal from most stones)", CELL),
Paragraph("Not standard", CELL)],
]
diag_tbl = Table(diag_rows, colWidths=[usable_w*0.22, usable_w*0.46, usable_w*0.32])
diag_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), PURPLE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [PURPLE_LT, WHITE]),
# Highlight CT row
("BACKGROUND", (0,6), (-1,6), colors.HexColor("#D0B8F5")),
("FONTNAME", (0,6), (0,6), "Helvetica-Bold"),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#C0A0E0")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(diag_tbl)
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 6 β TREATMENT PROTOCOLS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("6. TREATMENT PROTOCOLS", GREEN))
story.append(Spacer(1, 4))
# Treatment algorithm as a highlighted box
algo_data = [
[Paragraph("SIZE", CELL_W),
Paragraph("SPONTANEOUS PASSAGE", CELL_W),
Paragraph("FIRST-LINE APPROACH", CELL_W)],
[Paragraph("β€ 4 mm", CELL_B),
Paragraph("~95% pass", CELL),
Paragraph("Conservative: hydration + NSAIDs + tamsulosin. Wait up to 4 weeks", CELL)],
[Paragraph("4β6 mm", CELL_B),
Paragraph("~50% pass", CELL),
Paragraph("MET with Ξ±-blocker (tamsulosin 0.4 mg). Monitor 4 weeks", CELL)],
[Paragraph("6β10 mm", CELL_B),
Paragraph("Unlikely", CELL),
Paragraph("Plan intervention. MET while waiting; consider ESWL or URS", CELL)],
[Paragraph("> 10 mm", CELL_B),
Paragraph("Will not pass", CELL),
Paragraph("Intervention required (see procedural table)", CELL)],
]
algo_tbl = Table(algo_data, colWidths=[usable_w*0.12, usable_w*0.20, usable_w*0.68])
algo_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREEN),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREEN_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#80C090")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(algo_tbl)
story.append(Spacer(1, 4))
# Acute management + Procedural
acute_rows = [
[Paragraph("Acute Pain Management", CELL_W), Paragraph("", CELL_W)],
[Paragraph("NSAIDs (ketorolac/ibuprofen)", CELL_B), Paragraph("First-line analgesia (β ureteral spasm, superior to opioids)", CELL)],
[Paragraph("Opioids (morphine)", CELL_B), Paragraph("Rescue analgesia when NSAIDs insufficient", CELL)],
[Paragraph("Tamsulosin (Ξ±-blocker)", CELL_B), Paragraph("Medical expulsive therapy β relaxes ureteral smooth muscle", CELL)],
[Paragraph("Anti-emetics (ondansetron)", CELL_B), Paragraph("For nausea/vomiting", CELL)],
[Paragraph("IV hydration", CELL_B), Paragraph("Dilute urine, encourage passage", CELL)],
]
acute_tbl = Table(acute_rows, colWidths=[usable_w*0.38, usable_w*0.62])
acute_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREEN),
("SPAN", (0,0), (-1,0)),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREEN_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#80C090")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
proc_rows = [
[Paragraph("Procedure", CELL_W), Paragraph("Indication", CELL_W), Paragraph("Mechanism", CELL_W)],
[Paragraph("ESWL", CELL_B),
Paragraph("Stones <2 cm, renal pelvis, upper ureter", CELL),
Paragraph("External shock waves fragment stone", CELL)],
[Paragraph("URS + Laser", CELL_B),
Paragraph("Ureteral stones (any size), failed ESWL", CELL),
Paragraph("Endoscope + holmium laser lithotripsy", CELL)],
[Paragraph("PCNL", CELL_B),
Paragraph("Large stones >2 cm, staghorn calculi", CELL),
Paragraph("Nephroscope via percutaneous flank access", CELL)],
[Paragraph("Open/Laparoscopic", CELL_B),
Paragraph("Complex/rare cases only", CELL),
Paragraph("Rarely needed today", CELL)],
]
proc_tbl = Table(proc_rows, colWidths=[usable_w*0.15, usable_w*0.45, usable_w*0.40])
proc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#1B5C30")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREEN_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#80C090")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(two_col(
[Paragraph("<b>Acute / Medical Management</b>", BODY_B), Spacer(1,3), acute_tbl],
[Paragraph("<b>Interventional Procedures</b>", BODY_B), Spacer(1,3), proc_tbl],
ratio=(45, 55)
))
story.append(Spacer(1, 4))
# Emergency box
emerg_data = [[
Paragraph("β EMERGENCY INDICATIONS (Immediate Intervention Required):", ALERT),
Paragraph("β’ Infected obstructing stone / Urosepsis (most urgent) "
"β’ Bilateral obstruction "
"β’ Solitary kidney obstruction "
"β’ Refractory pain/vomiting "
"β’ Deteriorating renal function",
style("em2", fontSize=7.8, fontName="Helvetica", textColor=GREY_DARK, leading=11))
]]
emerg_tbl = Table(emerg_data, colWidths=[usable_w])
emerg_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED_LT),
("BOX", (0,0), (-1,-1), 1.2, RED),
("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(emerg_tbl)
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 7 β PREVENTION (Stone-specific)
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("7. STONE-SPECIFIC PREVENTION", GOLD))
story.append(Spacer(1, 4))
prev_rows = [
[Paragraph("Stone Type", CELL_W),
Paragraph("Dietary Measures", CELL_W),
Paragraph("Medical Therapy", CELL_W)],
[Paragraph("Calcium Oxalate", CELL_B),
Paragraph("β fluids >2.5 L/day; normal (not low) Ca diet; low oxalate, low Na, low protein", CELL),
Paragraph("Thiazide diuretics (hypercalciuria); Potassium citrate (hypocitraturia); Pyridoxine (hyperoxaluria)", CELL)],
[Paragraph("Calcium Phosphate", CELL_B),
Paragraph("β fluids; low Na", CELL),
Paragraph("Thiazide diuretics; Potassium citrate", CELL)],
[Paragraph("Uric Acid", CELL_B),
Paragraph("Low-purine diet (avoid red meat, organ meats, shellfish); β fluids", CELL),
Paragraph("Potassium citrate (alkalinise urine pH 6β7); Allopurinol (hyperuricosuria)", CELL)],
[Paragraph("Struvite", CELL_B),
Paragraph("Adequate hydration; treat UTIs promptly", CELL),
Paragraph("Antibiotics; Acetohydroxamic acid (urease inhibitor); Surgical stone removal", CELL)],
[Paragraph("Cystine", CELL_B),
Paragraph("High fluid intake >3 L/day (most important!); limit Na & protein", CELL),
Paragraph("Alkalinise urine; D-penicillamine or Tiopronin (chelates cystine)", CELL)],
]
prev_tbl = Table(prev_rows, colWidths=[usable_w*0.17, usable_w*0.42, usable_w*0.41])
prev_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GOLD),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GOLD_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#D0B000")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(prev_tbl)
story.append(Spacer(1, 7))
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# SECTION 8 β HIGH-YIELD EXAM FACTS
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(section_header("8. HIGH-YIELD EXAM FACTS & MNEMONICS", NAVY))
story.append(Spacer(1, 4))
hyfacts_l = [
[Paragraph("Q", CELL_W), Paragraph("A", CELL_W)],
[Paragraph("Most common stone?", CELL_B), Paragraph("Calcium oxalate (60%)", CELL)],
[Paragraph("Only radiolucent stone?", CELL_B), Paragraph("Uric acid (+ indinavir)", CELL)],
[Paragraph("Staghorn calculus =?", CELL_B), Paragraph("Struvite (infection stone)", CELL)],
[Paragraph("Stone in alkaline urine?", CELL_B), Paragraph("Struvite, Ca-phosphate", CELL)],
[Paragraph("Stone in acidic urine?", CELL_B), Paragraph("Uric acid, Ca-oxalate", CELL)],
[Paragraph("Gold standard imaging?", CELL_B), Paragraph("Non-contrast CT (NCCT)", CELL)],
[Paragraph("Imaging in pregnancy?", CELL_B), Paragraph("Ultrasound", CELL)],
[Paragraph("Most common obstruction site?", CELL_B), Paragraph("Ureterovesical junction (UVJ)", CELL)],
[Paragraph("Key stone inhibitor?", CELL_B), Paragraph("Citrate", CELL)],
[Paragraph("Urease bacteria #1?", CELL_B), Paragraph("Proteus mirabilis", CELL)],
[Paragraph("Medical expulsive therapy?", CELL_B), Paragraph("Tamsulosin (Ξ±-blocker)", CELL)],
[Paragraph("Large stone >2 cm treatment?", CELL_B), Paragraph("PCNL", CELL)],
]
hyfacts_tbl_l = Table(hyfacts_l, colWidths=[usable_w*0.32, usable_w*0.28])
hyfacts_tbl_l.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_BLUE, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BBCDE0")),
("TOPPADDING", (0,0), (-1,-1), 2.5),
("BOTTOMPADDING", (0,0), (-1,-1), 2.5),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
# Mnemonics box
mnem_items = [
Paragraph("<b>MNEMONICS</b>", BODY_B),
Spacer(1, 4),
Paragraph("<b>COLA</b> (Cystinuria β amino acids lost)", MNEM),
Paragraph("Cystine Β· Ornithine Β· Lysine Β· Arginine", SMALL),
Spacer(1, 4),
Paragraph("<b>Radiolucent = U</b>nidentified on X-ray = <b>U</b>ric Acid", MNEM),
Spacer(1, 4),
Paragraph("<b>STRUVITE</b> bugs = Urease-splitting", MNEM),
Paragraph("Proteus, Klebsiella, Pseudomonas, Staph saprophyticus", SMALL),
Spacer(1, 4),
Paragraph("<b>Crystal shapes (exam key):</b>", BODY_B),
Paragraph("β’ Hexagonal β Cystine", SMALL_B),
Paragraph("β’ Coffin-lid β Struvite", SMALL),
Paragraph("β’ Envelope/bipyramid β CaOx dihydrate", SMALL),
Paragraph("β’ Rhomboid/needle β Uric acid", SMALL),
Spacer(1, 4),
Paragraph("<b>Complications:</b>", BODY_B),
Paragraph("Obstruction β Hydronephrosis β Obstructive nephropathy β CKD", SMALL),
Paragraph("Infected stone + obstruction = UROSEPSIS (surgical emergency)", ALERT),
Spacer(1, 4),
Paragraph("<b>Recurrence rate:</b> ~50% within 5β10 yrs without prevention", SMALL),
]
story.append(two_col([hyfacts_tbl_l], mnem_items, ratio=(60, 40)))
story.append(Spacer(1, 8))
# ββ FOOTER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(HRFlowable(width=usable_w, thickness=0.8, color=NAVY))
story.append(Spacer(1, 3))
footer_data = [[
Paragraph("Sources: Goldman-Cecil Medicine 26e | Campbell-Walsh-Wein Urology | "
"NKF Primer on Kidney Diseases 8e | Robbins Pathologic Basis of Disease",
style("ft", fontSize=6.8, fontName="Helvetica-Oblique",
textColor=GREY_MID, leading=9)),
Paragraph("Renal Calculi Quick Reference Β· Orris",
style("ft2", fontSize=6.8, fontName="Helvetica-Bold",
textColor=GREY_MID, alignment=TA_RIGHT, leading=9)),
]]
footer_tbl = Table(footer_data, colWidths=[usable_w*0.72, usable_w*0.28])
footer_tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
story.append(footer_tbl)
# ββ Build PDF βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=MARGIN,
rightMargin=MARGIN,
topMargin=MARGIN,
bottomMargin=MARGIN,
title="Renal Calculi Quick Reference Card",
author="Orris",
subject="Nephrolithiasis β Classification, Diagnosis, Treatment",
)
doc.build(story)
print(f"PDF saved to: {OUTPUT}")