Dietary guidelines for renal calculi
"kidney stones" AND "dietary" AND "prevention"
| Urinary Abnormality | Dietary Change |
|---|---|
| Hypercalciuria | Restrict sodium and animal protein; maintain adequate (not excessive) calcium; avoid calcium supplements |
| Hyperoxaluria | Avoid high-oxalate foods; maintain adequate dietary calcium; avoid high-dose vitamin C |
| Hypocitraturia | Increase fruits and vegetables; reduce animal protein |
| Low volume | Increase fluid intake |
| Hyperuricosuria | Reduce purine intake |
| Stone Type | Increase | Restrict |
|---|---|---|
| All types | Fluids (>2 L urine/day) | Sodium (<2 g/day), sugar-sweetened drinks |
| Calcium oxalate | Dietary calcium (1000-1200 mg), fruits, vegetables | Animal protein, oxalate-rich foods, vitamin C supplements, sodium |
| Calcium phosphate | Fruits and vegetables, fluid | Animal protein, sodium, excess alkali |
| Uric acid | Fluids, alkaline foods, citrus | Purines (meat, shellfish), fructose, alcohol/beer |
| Cystine | Fluids (>3 L urine/day), alkaline foods | Animal protein, sodium |
Pdf with images
ls -la /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace
mkdir -p /home/daytona/workspace/renal-calculi-pdf && python3 -c "import reportlab; print('reportlab OK')" && python3 -c "import requests; print('requests OK')"
~/renal-calculi-pdf/generate_pdf.py
import requests
import os
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, Image, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from io import BytesIO
OUTPUT = "/home/daytona/workspace/renal-calculi-pdf/Dietary_Guidelines_Renal_Calculi.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
TEAL = colors.HexColor("#0D6E82")
TEAL_LIGHT = colors.HexColor("#E6F4F7")
AMBER = colors.HexColor("#D97706")
AMBER_LIGHT = colors.HexColor("#FFF7ED")
GREEN = colors.HexColor("#15803D")
GREEN_LIGHT = colors.HexColor("#F0FDF4")
RED_LIGHT = colors.HexColor("#FEF2F2")
RED = colors.HexColor("#DC2626")
GREY_DARK = colors.HexColor("#1F2937")
GREY_MID = colors.HexColor("#6B7280")
GREY_LIGHT = colors.HexColor("#F3F4F6")
WHITE = colors.white
# ── Styles ──────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kwargs):
return ParagraphStyle(name, parent=styles[parent], **kwargs)
title_style = make_style("MyTitle",
fontSize=26, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", spaceAfter=4, leading=32)
subtitle_style = make_style("MySubtitle",
fontSize=13, textColor=colors.HexColor("#B2EBF5"),
alignment=TA_CENTER, fontName="Helvetica", spaceAfter=0)
h1_style = make_style("H1",
fontSize=16, textColor=TEAL, fontName="Helvetica-Bold",
spaceBefore=14, spaceAfter=4, leading=20)
h2_style = make_style("H2",
fontSize=12, textColor=GREY_DARK, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=3, leading=16)
body_style = make_style("Body",
fontSize=10, textColor=GREY_DARK, fontName="Helvetica",
leading=15, spaceAfter=6, alignment=TA_JUSTIFY)
bullet_style = make_style("Bullet",
fontSize=10, textColor=GREY_DARK, fontName="Helvetica",
leading=14, leftIndent=14, spaceAfter=3,
bulletIndent=4, bulletFontName="Helvetica", bulletFontSize=10)
caption_style = make_style("Caption",
fontSize=8, textColor=GREY_MID, fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceAfter=6, leading=11)
table_header_style = make_style("TH",
fontSize=9, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=12)
table_cell_style = make_style("TD",
fontSize=9, textColor=GREY_DARK, fontName="Helvetica",
alignment=TA_LEFT, leading=13, leftIndent=4)
small_style = make_style("Small",
fontSize=8, textColor=GREY_MID, fontName="Helvetica-Oblique",
alignment=TA_CENTER, leading=10)
# ── Helper: download image ──────────────────────────────────────────────────
def fetch_image_url(url, max_w, max_h):
"""Download URL and return a ReportLab Image flowable, or None on failure."""
try:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
img_data = BytesIO(resp.content)
img = Image(img_data, width=max_w, height=max_h, kind="proportional")
return img
except Exception as e:
print(f" [WARN] Could not fetch {url}: {e}")
return None
# ── Cover page flowable ─────────────────────────────────────────────────────
class ColorRect(Flowable):
def __init__(self, w, h, fill_color):
super().__init__()
self.w, self.h, self.fill_color = w, h, fill_color
def wrap(self, *args):
return self.w, self.h
def draw(self):
self.canv.setFillColor(self.fill_color)
self.canv.rect(0, 0, self.w, self.h, stroke=0, fill=1)
# ── Build story ─────────────────────────────────────────────────────────────
def build_story():
W, H = A4
story = []
# ── COVER ────────────────────────────────────────────────────────────────
story.append(Spacer(1, 2*cm))
# Teal header band via table background
cover_data = [[Paragraph(
"Dietary Guidelines for<br/>Renal Calculi",
ParagraphStyle("CoverTitle", fontSize=28, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=36))]]
cover_tbl = Table(cover_data, colWidths=[W - 4*cm])
cover_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 22),
("BOTTOMPADDING", (0,0), (-1,-1), 22),
("LEFTPADDING", (0,0), (-1,-1), 18),
("RIGHTPADDING", (0,0), (-1,-1), 18),
("ROUNDEDCORNERS", [6]),
]))
story.append(cover_tbl)
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph(
"Evidence-based nutritional strategies for prevention and management of kidney stones",
ParagraphStyle("CoverSub", fontSize=12, textColor=GREY_MID,
fontName="Helvetica-Oblique", alignment=TA_CENTER, leading=16)))
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=1, color=TEAL_LIGHT))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Sources: Harrison's Principles of Internal Medicine 22E (2025) · Comprehensive Clinical Nephrology 7th Ed. · "
"NKF Primer on Kidney Diseases 8th Ed. · Campbell-Walsh-Wein Urology",
small_style))
story.append(Spacer(1, 1.5*cm))
# ── SECTION 1: Overview ──────────────────────────────────────────────────
story.append(Paragraph("Overview", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=6))
story.append(Paragraph(
"Renal calculi (kidney stones) are a chronic condition with a lifetime prevalence of approximately 10%. "
"Dietary modification is the cornerstone of prevention and recurrence reduction. Recommendations must be "
"tailored to stone composition, identified urinary abnormalities (from 24-hour urine collection), and individual "
"risk factors. The 'stone clinic effect' - a substantial decrease in recurrence seen simply by consulting a "
"stone specialist - is largely attributable to dietary and fluid intake modifications.",
body_style))
# Stone type distribution box
dist_data = [
[Paragraph("<b>Stone Type</b>", table_header_style),
Paragraph("<b>Frequency</b>", table_header_style),
Paragraph("<b>Key Feature</b>", table_header_style)],
[Paragraph("Calcium oxalate", table_cell_style),
Paragraph("70-80%", table_cell_style),
Paragraph("Most common; radiopaque", table_cell_style)],
[Paragraph("Calcium phosphate", table_cell_style),
Paragraph("~10%", table_cell_style),
Paragraph("Associated with high urine pH", table_cell_style)],
[Paragraph("Uric acid", table_cell_style),
Paragraph("5-10%", table_cell_style),
Paragraph("Radiolucent; dissolves with alkalinization", table_cell_style)],
[Paragraph("Struvite", table_cell_style),
Paragraph("~5%", table_cell_style),
Paragraph("Infection stones; Proteus/Klebsiella", table_cell_style)],
[Paragraph("Cystine", table_cell_style),
Paragraph("~1%", table_cell_style),
Paragraph("Genetic; requires very high fluid intake", table_cell_style)],
]
dist_tbl = Table(dist_data, colWidths=[5.5*cm, 3.5*cm, 8.0*cm])
dist_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("BACKGROUND", (0,1), (-1,1), TEAL_LIGHT),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), TEAL_LIGHT),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), TEAL_LIGHT),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TEAL_LIGHT, WHITE]),
("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), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(dist_tbl)
story.append(Spacer(1, 0.4*cm))
# ── SECTION 2: Universal Dietary Measures ───────────────────────────────
story.append(Paragraph("Universal Dietary Measures (All Stone Types)", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=6))
# 2a Fluid intake
story.append(Paragraph("1. Fluid Intake - The Most Important Intervention", h2_style))
story.append(Paragraph(
"The single most effective dietary measure is increasing urine output to <b>more than 2 L/day</b>. "
"High urine volume dilutes all stone-forming solutes, reducing supersaturation of calcium oxalate, "
"calcium phosphate, and uric acid. Patients with cystine stones require even higher targets (>3 L/day).",
body_style))
fluid_bullets = [
"Target: urine output <b>>2 L/day</b> (not simply drinking 2 L - insensible losses vary)",
"Risk is highest at night (physiologic concentration); drink in the evening, induce nocturia, drink again before returning to bed",
"<b>Avoid</b> sugar-sweetened beverages and fructose-containing drinks - increase urinary calcium and uric acid",
"Avoid dark colas (high phosphoric acid), energy drinks, and excess alcohol",
"Water, diluted citrus juices (lemon/lime), and herbal teas are preferred",
"Educate patients on their 24-hour urine volume baseline and how much more they need to drink",
]
for b in fluid_bullets:
story.append(Paragraph(f"• {b}", bullet_style))
story.append(Spacer(1, 0.3*cm))
# 2b Sodium
story.append(Paragraph("2. Sodium Restriction", h2_style))
story.append(Paragraph(
"Urinary sodium directly drives urinary calcium excretion. Each increase in sodium intake "
"raises calcium excretion in the urine. Restricting sodium to <b><2 g/day (87 mmol/day)</b> "
"reduces hypercalciuria and potentiates thiazide diuretics when used pharmacologically. "
"Sodium restriction is effective for calcium stones of all subtypes.",
body_style))
# 2c Animal protein
story.append(Paragraph("3. Animal Protein Restriction", h2_style))
story.append(Paragraph(
"Excess animal protein increases stone risk through several parallel mechanisms:",
body_style))
protein_bullets = [
"Sulfur amino acids (methionine, cysteine) are metabolized to sulfate, acidifying urine and reducing calcium solubility",
"Metabolic acidosis causes calcium release from bone, increasing filtered calcium load",
"Acid load decreases tubular calcium reabsorption, worsening hypercalciuria",
"Protein catabolism raises uric acid excretion",
"Acid load reduces urinary citrate - the primary endogenous inhibitor of stone formation",
]
for b in protein_bullets:
story.append(Paragraph(f"• {b}", bullet_style))
story.append(Paragraph(
"A landmark RCT showed that a diet with <b>1200 mg calcium + low sodium + low animal protein</b> "
"significantly reduced stone recurrence compared with a low-calcium diet alone in men with "
"recurrent calcium oxalate stones.",
body_style))
# 2d Potassium
story.append(Paragraph("4. Fruits, Vegetables, and Potassium", h2_style))
story.append(Paragraph(
"Higher potassium intake reduces urinary calcium excretion. Many fruits and vegetables contain organic "
"anions (citrate, malate) metabolized to bicarbonate, which alkalinizes urine and increases citrate "
"excretion - the key inhibitor of calcium crystal nucleation. A plant-rich diet benefits most stone formers.",
body_style))
# ── SECTION 3: Stone-Type Specific ──────────────────────────────────────
story.append(PageBreak())
story.append(Paragraph("Stone-Type Specific Recommendations", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=8))
# --- 3a Calcium oxalate ---
ca_data = [[Paragraph("CALCIUM OXALATE STONES (~70-80%)",
ParagraphStyle("StoneHead", fontSize=12, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER))]]
ca_hdr = Table(ca_data, colWidths=[W - 4*cm])
ca_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(ca_hdr)
story.append(Spacer(1, 0.2*cm))
# Critical calcium nuance box
cal_box_data = [[
Paragraph(
"<b>Key Nuance: Do NOT Restrict Dietary Calcium</b><br/><br/>"
"Restricting dietary calcium is a common but harmful misconception. "
"Low dietary calcium increases intestinal oxalate absorption (less calcium in the gut to bind oxalate), "
"raising urinary oxalate and stone risk. It also promotes bone demineralization.<br/><br/>"
"<b>Recommended intake:</b> 1000-1200 mg/day (age- and sex-appropriate dietary calcium)<br/>"
"<b>Calcium supplements:</b> May increase stone risk - if needed, take with meals to bind food oxalate. "
"Check 24-hr urine calcium with and without supplement.",
ParagraphStyle("NuanceBox", fontSize=10, textColor=GREY_DARK,
fontName="Helvetica", leading=15, leftIndent=4))
]]
cal_box_tbl = Table(cal_box_data, colWidths=[W - 4*cm])
cal_box_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), AMBER_LIGHT),
("BOX", (0,0), (-1,-1), 1.5, AMBER),
("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.append(cal_box_tbl)
story.append(Spacer(1, 0.3*cm))
# Calcium oxalate dietary table
co_table_data = [
[Paragraph("<b>Urinary Abnormality</b>", table_header_style),
Paragraph("<b>Dietary Change</b>", table_header_style),
Paragraph("<b>Avoid</b>", table_header_style)],
[Paragraph("Hypercalciuria", table_cell_style),
Paragraph("Restrict sodium; maintain adequate dietary Ca (1000-1200 mg/day)", table_cell_style),
Paragraph("Calcium supplements; excess Na; sucrose", table_cell_style)],
[Paragraph("Hyperoxaluria", table_cell_style),
Paragraph("Maintain adequate dietary Ca to bind gut oxalate", table_cell_style),
Paragraph("Spinach, rhubarb, almonds, beets, wheat bran, chocolate; vitamin C supplements", table_cell_style)],
[Paragraph("Hypocitraturia", table_cell_style),
Paragraph("Increase fruits and vegetables; lemon juice", table_cell_style),
Paragraph("Animal protein; low-potassium diets", table_cell_style)],
[Paragraph("Hyperuricosuria", table_cell_style),
Paragraph("Reduce purine intake", table_cell_style),
Paragraph("Red meat, organ meats, shellfish, beer", table_cell_style)],
[Paragraph("Low urine volume", table_cell_style),
Paragraph("Increase total fluid to achieve >2 L urine/day", table_cell_style),
Paragraph("Sugar-sweetened drinks, dark colas", table_cell_style)],
]
co_tbl = Table(co_table_data, colWidths=[4*cm, 7*cm, 6*cm])
co_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TEAL_LIGHT, WHITE]),
("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), "TOP"),
]))
story.append(co_tbl)
story.append(Spacer(1, 0.4*cm))
# High oxalate foods list (visual)
story.append(Paragraph("High-Oxalate Foods to Restrict:", h2_style))
oxalate_data = [
[Paragraph("<b>Very High</b><br/>(avoid)",
ParagraphStyle("ox1", fontSize=9, textColor=RED, fontName="Helvetica-Bold",
alignment=TA_CENTER)),
Paragraph("<b>High</b><br/>(limit)",
ParagraphStyle("ox2", fontSize=9, textColor=AMBER, fontName="Helvetica-Bold",
alignment=TA_CENTER)),
Paragraph("<b>Moderate</b><br/>(use in moderation)",
ParagraphStyle("ox3", fontSize=9, textColor=GREEN, fontName="Helvetica-Bold",
alignment=TA_CENTER))],
[Paragraph("Spinach\nRhubarb\nBeets\nPeanuts\nSwiss chard", table_cell_style),
Paragraph("Almonds\nChocolate\nWheat bran\nSweet potatoes\nBlueberries", table_cell_style),
Paragraph("Oranges\nTomatoes\nBroccoli\nCoffee\nBeer", table_cell_style)],
]
ox_tbl = Table(oxalate_data, colWidths=[(W-4*cm)/3]*3)
ox_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), RED_LIGHT),
("BACKGROUND", (1,0), (1,0), AMBER_LIGHT),
("BACKGROUND", (2,0), (2,0), GREEN_LIGHT),
("BACKGROUND", (0,1), (0,1), RED_LIGHT),
("BACKGROUND", (1,1), (1,1), AMBER_LIGHT),
("BACKGROUND", (2,1), (2,1), GREEN_LIGHT),
("BOX", (0,0), (-1,-1), 0.5, GREY_MID),
("INNERGRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CBD5E1")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "TOP"),
("ALIGN", (0,0), (-1,0), "CENTER"),
]))
story.append(ox_tbl)
story.append(Spacer(1, 0.5*cm))
# --- 3b Uric acid ---
ua_data = [[Paragraph("URIC ACID STONES (5-10%)",
ParagraphStyle("UaHead", fontSize=12, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER))]]
ua_hdr = Table(ua_data, colWidths=[W - 4*cm])
ua_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), AMBER),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(ua_hdr)
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"Three major factors drive uric acid stone formation: <b>low urine pH</b> (principal factor), "
"<b>low urine volume</b>, and <b>elevated urinary uric acid</b>. Unlike calcium stones, uric acid "
"stones can actually <b>dissolve</b> with sustained urinary alkalinization.",
body_style))
ua_bullets = [
"<b>Reduce purines:</b> limit red meat, organ meats, shellfish, anchovies, sardines, and beer",
"<b>Reduce fructose:</b> high-fructose corn syrup and sugar-sweetened drinks raise uric acid production",
"<b>Alkalinize urine:</b> eat citrus fruits and vegetables; target urine pH 6.0-6.5 (potassium citrate pharmacologically)",
"<b>Increase fluid intake</b> to achieve >2 L urine/day",
"Obesity and insulin resistance promote uric acid stones via impaired ammoniagenesis and urinary acidosis",
"A plant-rich, low-purine diet (legumes, whole grains, vegetables) is ideal",
]
for b in ua_bullets:
story.append(Paragraph(f"• {b}", bullet_style))
story.append(Spacer(1, 0.4*cm))
# --- 3c Cystine ---
cy_data = [[Paragraph("CYSTINE STONES (~1%)",
ParagraphStyle("CyHead", fontSize=12, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER))]]
cy_hdr = Table(cy_data, colWidths=[W - 4*cm])
cy_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), GREEN),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(cy_hdr)
story.append(Spacer(1, 0.2*cm))
cy_bullets = [
"<b>Very high fluid intake</b> - target >3 L urine/day (highest requirement of all stone types)",
"<b>Low sodium</b> - reduces cystine excretion (cystine transport is coupled to sodium)",
"<b>Low methionine (animal protein)</b> - methionine is the metabolic precursor of cystine",
"<b>Alkalinize urine to pH >7.0</b> - increases cystine solubility; citrus, vegetables; potassium citrate pharmacologically",
]
for b in cy_bullets:
story.append(Paragraph(f"• {b}", bullet_style))
story.append(Spacer(1, 0.4*cm))
# --- 3d Struvite ---
st_data = [[Paragraph("STRUVITE STONES (INFECTION STONES, ~5%)",
ParagraphStyle("StHead", fontSize=12, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER))]]
st_hdr = Table(st_data, colWidths=[W - 4*cm])
st_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), RED),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(st_hdr)
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"Dietary modification alone is insufficient for struvite stones. These form due to urease-producing "
"organisms (Proteus, Klebsiella, Pseudomonas) that alkalinize urine. Management requires eradication "
"of infection and surgical stone removal. Urinary acidification (ammonium chloride, methionine) is "
"sometimes used adjunctively.",
body_style))
story.append(Spacer(1, 0.5*cm))
# ── SECTION 4: Summary Table ─────────────────────────────────────────────
story.append(PageBreak())
story.append(Paragraph("Summary: Dietary Recommendations by Stone Type", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=8))
summary_data = [
[Paragraph("<b>Stone Type</b>", table_header_style),
Paragraph("<b>Increase</b>", table_header_style),
Paragraph("<b>Restrict</b>", table_header_style),
Paragraph("<b>Target pH</b>", table_header_style)],
[Paragraph("All types", table_cell_style),
Paragraph("Fluids (>2 L urine/day)\nFruits and vegetables", table_cell_style),
Paragraph("Sodium (<2 g/day)\nSugar-sweetened drinks", table_cell_style),
Paragraph("Varies", table_cell_style)],
[Paragraph("Calcium\nOxalate", table_cell_style),
Paragraph("Dietary Ca (1000-1200 mg)\nFruits, vegetables\nLemon juice", table_cell_style),
Paragraph("Animal protein\nOxalate-rich foods\nVitamin C supplements\nCalcium supplements", table_cell_style),
Paragraph("Neutral\n(insensitive to pH)", table_cell_style)],
[Paragraph("Calcium\nPhosphate", table_cell_style),
Paragraph("Fluids\nFruits, vegetables", table_cell_style),
Paragraph("Animal protein\nSodium\nExcess alkali", table_cell_style),
Paragraph("Slightly acid\n(<6.5)", table_cell_style)],
[Paragraph("Uric Acid", table_cell_style),
Paragraph("Fluids\nAlkaline foods\nCitrus", table_cell_style),
Paragraph("Purines (meat, shellfish)\nFructose\nAlcohol/beer", table_cell_style),
Paragraph("Alkaline\n(6.0-6.5)", table_cell_style)],
[Paragraph("Cystine", table_cell_style),
Paragraph("Fluids (>3 L urine/day)\nAlkaline foods", table_cell_style),
Paragraph("Animal protein\nSodium", table_cell_style),
Paragraph("Alkaline\n(>7.0)", table_cell_style)],
]
summary_tbl = Table(summary_data, colWidths=[3*cm, 5.5*cm, 6*cm, 2.5*cm])
summary_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [GREY_LIGHT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#CBD5E1")),
("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), "TOP"),
("ALIGN", (3,1), (3,-1), "CENTER"),
]))
story.append(summary_tbl)
story.append(Spacer(1, 0.5*cm))
# ── SECTION 5: Key Points ────────────────────────────────────────────────
story.append(Paragraph("Key Points for Patient Education", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=8))
key_points = [
("<b>Drink more</b>", "This is the single highest-yield intervention for every stone type. Aim for >2 L urine output/day. Educate patients using their actual 24-hour urine volume."),
("<b>Do NOT cut dietary calcium</b>", "Dietary calcium restriction is harmful - it increases oxalate absorption and stone risk. Maintain 1000-1200 mg/day from food sources."),
("<b>Reduce salt</b>", "Sodium drives calcium into the urine. A low-sodium diet (<2 g/day) reduces urinary calcium excretion and potentiates thiazide therapy."),
("<b>Eat less animal protein</b>", "Animal protein acidifies urine, raises calcium and uric acid excretion, and reduces citrate - the stone inhibitor."),
("<b>Avoid vitamin C supplements</b>", "In calcium oxalate stone formers, vitamin C >1 g/day is converted to oxalate endogenously and increases stone risk."),
("<b>Lifestyle changes must be lifelong</b>", "Stone disease is a chronic condition. Short-term compliance is insufficient; patients must understand this is permanent prevention."),
]
for i, (title, detail) in enumerate(key_points):
num_data = [[
Paragraph(str(i+1), ParagraphStyle(
"Num", fontSize=14, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=18)),
Paragraph(f"{title}<br/>{detail}",
ParagraphStyle("KP", fontSize=10, textColor=GREY_DARK,
fontName="Helvetica", leading=14, leftIndent=4))
]]
num_tbl = Table(num_data, colWidths=[1*cm, W-5.5*cm])
num_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), TEAL),
("BACKGROUND", (1,0), (1,0), TEAL_LIGHT),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (0,0), 4),
("RIGHTPADDING", (0,0), (0,0), 4),
("LEFTPADDING", (1,0), (1,0), 10),
("RIGHTPADDING", (1,0), (1,0), 10),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
]))
story.append(num_tbl)
story.append(Spacer(1, 0.2*cm))
story.append(Spacer(1, 0.5*cm))
# ── SECTION 6: 24-hr Urine Treatment Table ───────────────────────────────
story.append(Paragraph("Dietary Changes Indexed to 24-Hour Urine Abnormalities", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=6))
story.append(Paragraph(
"A 24-hour urine collection (performed on usual diet, at least 6 weeks after a stone event) guides "
"targeted dietary therapy. Two collections are recommended due to day-to-day variability. "
"Key measurements: volume, calcium, oxalate, citrate, uric acid, sodium, potassium, phosphorus, pH, creatinine.",
body_style))
urine_data = [
[Paragraph("<b>Abnormality</b>", table_header_style),
Paragraph("<b>Dietary Change</b>", table_header_style),
Paragraph("<b>Pharmacologic Option</b>", table_header_style)],
[Paragraph("High Ca concentration", table_cell_style),
Paragraph("Avoid excess Ca supplements; maintain dietary Ca; reduce animal protein; reduce Na <3 g/day; reduce sucrose", table_cell_style),
Paragraph("Thiazide diuretic", table_cell_style)],
[Paragraph("High oxalate", table_cell_style),
Paragraph("Avoid high-oxalate foods; maintain adequate dietary Ca; avoid vitamin C supplements", table_cell_style),
Paragraph("Pyridoxine, cholestyramine, Ca/Mg supplements", table_cell_style)],
[Paragraph("High uric acid", table_cell_style),
Paragraph("Reduce purine intake (meat, poultry, fish, shellfish)", table_cell_style),
Paragraph("Allopurinol (xanthine oxidase inhibitor)", table_cell_style)],
[Paragraph("Low citrate", table_cell_style),
Paragraph("Increase fruits and vegetables; lemon juice; reduce animal protein", table_cell_style),
Paragraph("Potassium citrate or potassium bicarbonate", table_cell_style)],
[Paragraph("Low volume", table_cell_style),
Paragraph("Increase total fluid intake to reach >2 L urine/day", table_cell_style),
Paragraph("Not applicable", table_cell_style)],
]
urine_tbl = Table(urine_data, colWidths=[4*cm, 7.5*cm, 5.5*cm])
urine_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TEAL_LIGHT, WHITE]),
("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), "TOP"),
]))
story.append(urine_tbl)
story.append(Spacer(1, 0.5*cm))
# ── SECTION 7: Textbook Images ───────────────────────────────────────────
story.append(PageBreak())
story.append(Paragraph("Illustrations from Medical Literature", h1_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=TEAL, spaceAfter=8))
images_to_fetch = [
(
"https://cdn.orris.care/cdss_images/3de0e068e3d950d5efaee2dc555b2256527591ef3450e2db5a2a637a9c3092cc.png",
"Fig 1. The gut-kidney axis in nephrolithiasis. Antibiotics disrupt the microbiome, altering "
"intestinal and urinary metabolite profiles, which can influence stone formation risk. "
"(Source: NKF Primer on Kidney Diseases 8th Ed.)"
),
(
"https://cdn.orris.care/cdss_images/3201a2026e058cba2d904fee7a0c486973a0cb2319a846a0a848d1e97eae30ed.png",
"Fig 2. Uric acid stone on imaging. Uric acid stones are radiolucent and poorly visible on plain "
"radiographs but detectable on ultrasound and CT, often appearing as filling defects. "
"(Source: Comprehensive Clinical Nephrology 7th Ed.)"
),
]
for url, caption in images_to_fetch:
print(f" Fetching: {url[:60]}...")
img = fetch_image_url(url, max_w=13*cm, max_h=8*cm)
if img:
img_tbl_data = [[img]]
img_tbl = Table(img_tbl_data, colWidths=[W - 4*cm])
img_tbl.setStyle(TableStyle([
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#CBD5E1")),
("BACKGROUND", (0,0), (-1,-1), GREY_LIGHT),
]))
story.append(img_tbl)
story.append(Paragraph(caption, caption_style))
story.append(Spacer(1, 0.5*cm))
else:
story.append(Paragraph(f"[Image unavailable: {caption}]", caption_style))
# ── REFERENCES ──────────────────────────────────────────────────────────
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=0.8, color=GREY_MID))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("References", h2_style))
refs = [
"1. Harrison's Principles of Internal Medicine, 22nd Edition (2025). McGraw-Hill Medical.",
"2. Comprehensive Clinical Nephrology, 7th Edition. Elsevier.",
"3. National Kidney Foundation Primer on Kidney Diseases, 8th Edition. Elsevier.",
"4. Campbell-Walsh-Wein Urology. Elsevier.",
"5. Brenner and Rector's The Kidney, 2-Volume Set. Elsevier.",
"6. Ma Y et al. Risk factors for nephrolithiasis formation: an umbrella review. Int J Surg. 2024;[PMID: 38814276]",
"7. Allam AT et al. A guide to effective prevention and treatment for kidney stones: a systematic review. "
"Naunyn Schmiedebergs Arch Pharmacol. 2026;[PMID: 41116039]",
]
for ref in refs:
story.append(Paragraph(ref,
ParagraphStyle("Ref", fontSize=8.5, textColor=GREY_DARK,
fontName="Helvetica", leading=13, spaceAfter=3, leftIndent=12,
firstLineIndent=-12)))
return story
# ── Generate PDF ─────────────────────────────────────────────────────────────
def main():
W, H = A4
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=1.8*cm, bottomMargin=2*cm,
title="Dietary Guidelines for Renal Calculi",
author="Orris Medical AI",
)
story = build_story()
doc.build(story)
print(f"\nPDF saved to: {OUTPUT}")
if __name__ == "__main__":
main()
cd /home/daytona/workspace/renal-calculi-pdf && python3 generate_pdf.py
cd /home/daytona/workspace/renal-calculi-pdf && python3 -c " import requests, os imgs = [ ('https://cdn.orris.care/cdss_images/3de0e068e3d950d5efaee2dc555b2256527591ef3450e2db5a2a637a9c3092cc.png', 'img1.png'), ('https://cdn.orris.care/cdss_images/3201a2026e058cba2d904fee7a0c486973a0cb2319a846a0a848d1e97eae30ed.png', 'img2.png'), ] for url, fname in imgs: try: r = requests.get(url, timeout=8) r.raise_for_status() with open(fname, 'wb') as f: f.write(r.content) print(f'OK {fname} {len(r.content)} bytes') except Exception as e: print(f'FAIL {fname}: {e}') "
~/renal-calculi-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/renal-calculi-pdf/generate_pdf.py'
cd /home/daytona/workspace/renal-calculi-pdf && python3 generate_pdf.py
ls -lh /home/daytona/workspace/renal-calculi-pdf/Dietary_Guidelines_Renal_Calculi.pdf
Dietary Guidelines Renal Calculi
PDF Document · PDF