Notes of this
cd /home/daytona/workspace/attachments/87421f72-5b02-46f9-ad66-65e84877df3a/ && …
cd /home/daytona/workspace/attachments/87421f72-5b02-46f9-ad66-65e84877df3a/ && …
cd /home/daytona/workspace/attachments/87421f72-5b02-46f9-ad66-65e84877df3a/ && …
cd /home/daytona/workspace/attachments/87421f72-5b02-46f9-ad66-65e84877df3a/ && …
python3 << 'EOF' import subprocess import os pages = sorted([f for f in os.list…
python3 << 'EOF' with open('/tmp/uro_ocr.txt', 'r') as f: text = f.read() pr…
| Stone Type | Appearance | Crystal Shape | Urine pH | Key Risk Factors |
|---|---|---|---|---|
| Struvite (Magnesium Ammonium Phosphate) | Large, branching staghorn appearance | Rectangular / Coffin-lid | Alkaline | Strongly associated with urea-splitting organisms (e.g., Proteus) |
| Calcium Oxalate | Covered with small projections | Envelope-shaped | Acidic | Hypercalcaemia, Hypercalciuria, Hyperuricosuria |
| Calcium Phosphate | - | - | Alkaline | Hypercalcaemia |
Calcium Oxalate is the most common type of renal calculi.
| Stone Type | Radiopacity | Crystal Shape | Urine pH | Key Notes |
|---|---|---|---|---|
| Cystine | Radiopaque (rare) | Hexagonal | Acidic | Very hard - not easily broken by ESWL |
| Uric Acid | Radiolucent | - | Acidic | Strongly associated with gout |
| Xanthine | Radiolucent | - | - | Notably brick-red in colour |
| Drug-induced | Radiolucent | - | - | - |
| Modality | Notes |
|---|---|
| X-ray KUB | Identifies radiopaque shadows; combined with Micturating Cystogram (MCC) and Post-Void Residual (PVR) urine volume |
| USG KUB | First-line; Limitation: not sensitive for the lower ureter - visual field obscured by overlying bowel gas |
Differential diagnosis note: Always differentiate calculi from a calcified foreign body on imaging.
1. Conservative / Symptom Control
├── Analgesics → Primary acute pain control
├── Antibiotics → Infection prevention and active management
└── Antiulcerants → GI protection against stress
2. ESWL (Extracorporeal Shock Wave Lithotripsy)
3. PCNL (Percutaneous Nephrolithotomy)
4. RIRS (Retrograde Intrarenal Surgery)
5. Pyelolithotomy (open)
6. Nephrectomy → End of line; reserved for severe cases
Pyelolithotomy technique: Requires precise ischaemic technique - clamp the renal artery → remove stone within ischaemic window → unclamp.
Give me in the form of booklet where every page has left adn right side
mkdir -p /home/daytona/workspace/urolithiasis-booklet && ls /home/daytona/worksp…
~/urolithiasis-booklet/build_booklet.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, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
import io
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = HexColor("#1a2a4a")
TEAL = HexColor("#1d7a8c")
TEAL_LIGHT= HexColor("#e8f4f7")
AMBER = HexColor("#e8a020")
AMBER_LT = HexColor("#fff8ee")
RED_BRICK = HexColor("#c0392b")
RED_LT = HexColor("#fdf2f2")
GREY_DARK = HexColor("#444444")
GREY_MID = HexColor("#888888")
GREY_LT = HexColor("#f5f5f5")
WHITE = colors.white
PAGE_W, PAGE_H = A4 # 595 x 842 pt
# ── Styles ───────────────────────────────────────────────────────────────────
base_styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
COVER_TITLE = S("CoverTitle", fontName="Helvetica-Bold", fontSize=30,
textColor=WHITE, alignment=TA_CENTER, leading=38)
COVER_SUB = S("CoverSub", fontName="Helvetica", fontSize=13,
textColor=HexColor("#cce8ef"), alignment=TA_CENTER, leading=18)
COVER_BADGE = S("CoverBadge", fontName="Helvetica-Bold", fontSize=10,
textColor=AMBER, alignment=TA_CENTER)
SECTION_HDR = S("SectionHdr", fontName="Helvetica-Bold", fontSize=11,
textColor=WHITE, alignment=TA_LEFT, leading=15)
BODY = S("Body", fontName="Helvetica", fontSize=8.5,
textColor=GREY_DARK, leading=13, spaceAfter=3)
BODY_BOLD = S("BodyBold", fontName="Helvetica-Bold", fontSize=8.5,
textColor=NAVY, leading=13)
BULLET = S("Bullet", fontName="Helvetica", fontSize=8.5,
textColor=GREY_DARK, leading=13, leftIndent=12,
firstLineIndent=0, spaceAfter=2)
BULLET2 = S("Bullet2", fontName="Helvetica", fontSize=8,
textColor=GREY_DARK, leading=12, leftIndent=24,
firstLineIndent=0, spaceAfter=1)
WARN_TXT = S("WarnTxt", fontName="Helvetica-Bold", fontSize=8.5,
textColor=RED_BRICK, leading=13)
NOTE_TXT = S("NoteTxt", fontName="Helvetica-Oblique", fontSize=8,
textColor=HexColor("#555555"), leading=12)
TABLE_HDR = S("TblHdr", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=11)
TABLE_CELL = S("TblCell", fontName="Helvetica", fontSize=7.5,
textColor=GREY_DARK, leading=11)
TABLE_CELL_B= S("TblCellB", fontName="Helvetica-Bold", fontSize=7.5,
textColor=NAVY, leading=11)
PAGE_NUM = S("PageNum", fontName="Helvetica", fontSize=7.5,
textColor=GREY_MID, alignment=TA_CENTER)
# ── Helper Flowables ──────────────────────────────────────────────────────────
class SectionHeader(Flowable):
"""Coloured banner with white text for a section heading."""
def __init__(self, text, bg=NAVY, width=None, height=18):
super().__init__()
self.text = text
self.bg = bg
self._width = width
self.height = height
def wrap(self, avail_w, avail_h):
self.draw_w = self._width or avail_w
return self.draw_w, self.height + 4
def draw(self):
c = self.canv
c.setFillColor(self.bg)
c.roundRect(0, 0, self.draw_w, self.height, 3, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 9.5)
c.drawString(6, 4.5, self.text)
class ColourBox(Flowable):
"""Shaded box that wraps a list of flowables visually."""
def __init__(self, content_height, width=None, bg=TEAL_LIGHT, radius=4):
super().__init__()
self.content_height = content_height
self._width = width
self.bg = bg
self.radius = radius
def wrap(self, avail_w, avail_h):
self.draw_w = self._width or avail_w
return self.draw_w, self.content_height
def draw(self):
c = self.canv
c.setFillColor(self.bg)
c.roundRect(0, 0, self.draw_w, self.content_height,
self.radius, fill=1, stroke=0)
def bp(text, indent=0):
prefix = " " * indent
sym = "•" if indent == 0 else "–"
style = BULLET if indent == 0 else BULLET2
return Paragraph(f"{prefix}{sym} {text}", style)
def p(text, style=BODY):
return Paragraph(text, style)
def sp(h=4):
return Spacer(1, h)
def hr(color=TEAL, thickness=0.6):
return HRFlowable(width="100%", thickness=thickness, color=color,
spaceAfter=4, spaceBefore=4)
# ── Two-column page layout ────────────────────────────────────────────────────
COL_GAP = 10 * mm
MARGIN = 16 * mm
COL_W = (PAGE_W - 2 * MARGIN - COL_GAP) / 2 # ≈ 250 pt
def make_table_data(headers, rows, col_widths):
"""Build a styled ReportLab table."""
header_row = [Paragraph(h, TABLE_HDR) for h in headers]
data = [header_row]
for row in rows:
data.append([Paragraph(str(c), TABLE_CELL) for c in row])
t = Table(data, colWidths=col_widths)
style = TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("ROWBACKGROUNDS",(0,1),(-1,-1),[GREY_LT, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#cccccc")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING",(0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
t.setStyle(style)
return t
# ── Build content columns ─────────────────────────────────────────────────────
def build_left_col():
"""Returns a list of flowables for the LEFT column of each spread."""
items = []
# ─ Section 1: Aetiology ──────────────────────────────────────
items.append(SectionHeader("1 AETIOLOGY — Drivers of Calculus Formation", NAVY, COL_W))
items.append(sp(5))
cats = [
("Mechanical / Structural", TEAL,
["PUJ (Pelviureteric Junction) obstruction → urinary stasis"]),
("Environmental / Dietary", AMBER,
["Hot climate → excessive sweating → concentrated urine",
"Specific dietary triggers (e.g. coffee)"]),
("Systemic / Metabolic", NAVY,
["Hyperparathyroidism (→ hypercalcaemia)",
"Vitamin A deficiency → desquamation of urinary epithelium"]),
("Infectious / Lifestyle", RED_BRICK,
["Prolonged immobilisation",
"Urease-splitting bacterial infections"]),
]
for title, col, bullets in cats:
items.append(SectionHeader(title, col, COL_W, height=15))
items.append(sp(2))
for b in bullets:
items.append(bp(b))
items.append(sp(4))
items.append(sp(8))
# ─ Section 2: Stone Types (radiopaque) ───────────────────────
items.append(SectionHeader("2 STONE TYPOLOGY — Radiopaque Calculi", NAVY, COL_W))
items.append(sp(5))
items.append(make_table_data(
["Stone Type", "Crystal Shape", "pH", "Key Risk Factors"],
[
["Calcium Oxalate\n(Most common)", "Envelope-shaped", "Acid", "Hypercalcaemia\nHypercalciuria\nHyperuricosuria"],
["Struvite\n(Staghorn)", "Coffin-lid /\nRectangular", "Alkaline", "Urea-splitting\norganisms\n(e.g. Proteus)"],
["Calcium Phosphate", "—", "Alkaline", "Hypercalcaemia"],
],
[COL_W*0.28, COL_W*0.22, COL_W*0.12, COL_W*0.38]
))
items.append(sp(6))
# ─ Section 3: Stone Types (rare / radiolucent) ───────────────
items.append(SectionHeader("3 STONE TYPOLOGY — Rare & Radiolucent Variants", TEAL, COL_W))
items.append(sp(5))
items.append(make_table_data(
["Stone", "Radio-opacity", "pH", "Key Notes"],
[
["Cystine", "Radiopaque\n(rare)", "Acid", "Hexagonal crystals\nHard — NOT broken by ESWL"],
["Uric Acid", "Radiolucent", "Acid", "Associated with Gout"],
["Xanthine", "Radiolucent", "—", "Brick-red colour"],
["Drug-induced", "Radiolucent", "—", "Indinavir, triamterene etc."],
],
[COL_W*0.22, COL_W*0.18, COL_W*0.10, COL_W*0.50]
))
items.append(sp(6))
return items
def build_right_col():
"""Returns a list of flowables for the RIGHT column of each spread."""
items = []
# ─ Section 4: Obstruction / Dietl Crisis ─────────────────────
items.append(SectionHeader("4 OBSTRUCTION MECHANICS & CLINICAL FEATURES", NAVY, COL_W))
items.append(sp(5))
items.append(SectionHeader("Dietl Crisis — Total Pelvic Obstruction", TEAL, COL_W, height=15))
items.append(sp(3))
steps = [
"Urine stops draining from renal pelvis → ureter.",
"Urine accumulates → massive distension of kidney capsule.",
"Patient presents with severe flank pain + swelling.",
"<b>Resolution:</b> Stone shifts → urine flows out → swelling subsides → patient passes large quantities of <b>dilute urine</b>.",
]
for i, s in enumerate(steps, 1):
items.append(p(f"<b>{i}.</b> {s}"))
items.append(sp(6))
items.append(SectionHeader("Physical Examination Signs", TEAL, COL_W, height=15))
items.append(sp(3))
items.append(bp("<b>Renal angle (CVA) tenderness</b>"))
items.append(bp("<b>Bimanual ballottement</b> — specifically in hydronephrosis"))
items.append(sp(8))
# ─ Section 5: Investigations ─────────────────────────────────
items.append(SectionHeader("5 DIAGNOSTIC INVESTIGATION PATHWAY", NAVY, COL_W))
items.append(sp(5))
items.append(p("<b>Step 1 — Baseline Pathology</b>"))
items.append(bp("Urine: RME (Routine Microscopic Examination)"))
items.append(bp("Blood: Sugar, Electrolytes, Creatinine"))
items.append(sp(4))
items.append(p("<b>Step 2 — Primary Imaging</b>"))
items.append(make_table_data(
["Modality", "Notes / Limitations"],
[
["X-ray KUB", "Identifies radiopaque shadows; combine with MCC + PVR"],
["USG KUB", "First-line. NOT sensitive for lower ureter — bowel gas obscures view"],
],
[COL_W*0.30, COL_W*0.70]
))
items.append(sp(5))
items.append(SectionHeader("Investigation of Choice (IOC): NCCT KUB", RED_BRICK, COL_W, height=15))
items.append(sp(3))
items.append(p("<b>Non-Contrast CT Abdomen & Pelvis</b>"))
items.append(sp(2))
items.append(p("⚠ <b>Why NON-contrast?</b>", WARN_TXT))
items.append(p("Contrast dye is nephrotoxic. A patient with renal obstruction is "
"already at risk of AKI — contrast exacerbates this."))
items.append(sp(3))
items.append(p("<b>Secondary option — IVU (Intravenous Urogram):</b>"))
items.append(p("Strictly reserved for cases where serum creatinine is "
"<b>verified as normal</b> (no underlying kidney damage)."))
items.append(sp(5))
items.append(p("<i>Always differentiate calculi from a calcified foreign body on imaging.</i>", NOTE_TXT))
items.append(sp(8))
# ─ Section 6: Management ─────────────────────────────────────
items.append(SectionHeader("6 MANAGEMENT — Surgical Escalation Ladder", NAVY, COL_W))
items.append(sp(5))
ladder = [
("1", "Conservative / Symptom Control",
["Analgesics — primary acute pain control",
"Antibiotics — infection prevention & treatment",
"Antiulcerants — GI protection"]),
("2", "ESWL — Extracorporeal Shock Wave Lithotripsy", []),
("3", "PCNL — Percutaneous Nephrolithotomy", []),
("4", "RIRS — Retrograde Intrarenal Surgery", []),
("5", "Pyelolithotomy (open)\n Technique: clamp renal artery → remove stone → unclamp", []),
("6", "Nephrectomy — end of line; severe/non-salvageable cases only", []),
]
for num, title, subs in ladder:
items.append(p(f"<b>Step {num}:</b> {title}"))
for s in subs:
items.append(bp(s, indent=1))
items.append(sp(3))
items.append(sp(8))
# ─ Section 7: ESWL detail ────────────────────────────────────
items.append(SectionHeader("7 ESWL — Detail", TEAL, COL_W))
items.append(sp(5))
items.append(p("<b>Procedure:</b> Blast-wave dynamics. Ultrasonic waves via "
"<b>Dornier apparatus</b> shatter stone → fragments pass down ureter. "
"Patient must remain <b>perfectly still</b>."))
items.append(sp(5))
items.append(p("<b>Contraindications:</b>"))
contras = [
"Pregnancy",
"Uncontrolled bleeding disorders",
"Stones <b>> 1.5 cm</b> in size",
"Cardiac pacemaker",
"Children (cannot remain still)",
"Obese patients (ultrasound wave delivery impaired)",
"Very hard stones (Cystine or Calcium Oxalate Monohydrate)",
]
for c in contras:
items.append(bp(c))
items.append(sp(5))
items.append(p("<b>Complications:</b>"))
items.append(bp("<b>Pain</b> — most common; often with haematuria"))
items.append(bp("<b>Steinstrasse</b> ('stone street') — fragments from a large "
"shattered stone clog the ureter simultaneously → secondary "
"obstruction requiring further intervention"))
items.append(sp(8))
# ─ Section 8: MET + Ureteric stone ──────────────────────────
items.append(SectionHeader("8 URETERIC STONE — MET & Intervention", NAVY, COL_W))
items.append(sp(5))
items.append(SectionHeader("First-Line: Medical Expulsive Therapy (MET)", TEAL, COL_W, height=15))
items.append(sp(3))
items.append(make_table_data(
["Drug Class", "Example", "Mechanism"],
[
["Alpha-blocker", "Tamsulosin", "Relaxes ureteric smooth muscle"],
["Antispasmodic", "—", "Reduces ureteric spasm"],
["Diuretic", "—", "Promotes urine flow"],
["Antiulcerant", "—", "GI protection"],
],
[COL_W*0.30, COL_W*0.25, COL_W*0.45]
))
items.append(sp(5))
items.append(SectionHeader("Second-Line: Intervention (if MET fails)", AMBER, COL_W, height=15))
items.append(sp(3))
items.append(p("<b>Endoscopic:</b>"))
items.append(bp("Laser Lithotripsy"))
items.append(bp("ICPL (Intracorporeal Pneumatic Lithotripsy)"))
items.append(bp("Dormia Basket — used with ICPL for stone extraction"))
items.append(sp(4))
items.append(p("<b>Surgical:</b>"))
items.append(bp("Ureterolithotomy (open or laparoscopic)"))
items.append(sp(6))
items.append(SectionHeader("DDx — Always Rule Out", RED_BRICK, COL_W, height=15))
items.append(sp(3))
items.append(bp("<b>Musculoskeletal pain</b>"))
items.append(bp("<b>PLID</b> — Prolapsed Lumbar Intervertebral Disc"))
return items
# ── PDF canvas callback (page template) ──────────────────────────────────────
def draw_page(canvas_obj, doc):
canvas_obj.saveState()
w, h = PAGE_W, PAGE_H
# Top banner
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, h - 28, w, 28, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica-Bold", 10)
canvas_obj.drawString(MARGIN, h - 18, "UROLITHIASIS")
canvas_obj.setFont("Helvetica", 8)
canvas_obj.setFillColor(HexColor("#aaccdd"))
canvas_obj.drawRightString(w - MARGIN, h - 18,
"Renal & Ureteric Stones — Clinical Reference")
# Vertical divider between columns
mid_x = MARGIN + COL_W + COL_GAP / 2
canvas_obj.setStrokeColor(HexColor("#cccccc"))
canvas_obj.setLineWidth(0.5)
canvas_obj.line(mid_x, 30, mid_x, h - 32)
# Left label
canvas_obj.setFillColor(TEAL)
canvas_obj.setFont("Helvetica-Bold", 7)
canvas_obj.drawString(MARGIN, h - 36, "◀ LEFT")
# Right label
canvas_obj.drawRightString(w - MARGIN, h - 36, "RIGHT ▶")
# Bottom bar
canvas_obj.setFillColor(GREY_LT)
canvas_obj.rect(0, 0, w, 22, fill=1, stroke=0)
canvas_obj.setFillColor(GREY_MID)
canvas_obj.setFont("Helvetica", 7)
canvas_obj.drawCentredString(w / 2, 7,
f"Page {doc.page} | Urolithiasis Clinical Pathway Dossier")
canvas_obj.restoreState()
def draw_cover(canvas_obj, doc):
"""Full bleed navy cover page."""
canvas_obj.saveState()
w, h = PAGE_W, PAGE_H
# Background gradient effect (two rects)
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, 0, w, h, fill=1, stroke=0)
canvas_obj.setFillColor(HexColor("#0d1f3c"))
canvas_obj.rect(0, 0, w, h / 2, fill=1, stroke=0)
# Decorative teal bar
canvas_obj.setFillColor(TEAL)
canvas_obj.rect(0, h * 0.55, w, 6, fill=1, stroke=0)
# Kidney icon placeholder (circle)
canvas_obj.setFillColor(HexColor("#1d5a6c"))
canvas_obj.circle(w / 2, h * 0.70, 55, fill=1, stroke=0)
canvas_obj.setFillColor(TEAL)
canvas_obj.circle(w / 2, h * 0.70, 50, fill=0, stroke=1)
canvas_obj.setStrokeColor(TEAL)
canvas_obj.setLineWidth(1.5)
# Title
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica-Bold", 28)
canvas_obj.drawCentredString(w / 2, h * 0.48, "UROLITHIASIS")
canvas_obj.setFont("Helvetica-Bold", 14)
canvas_obj.setFillColor(HexColor("#cce8ef"))
canvas_obj.drawCentredString(w / 2, h * 0.43, "Renal & Ureteric Stones")
# Sub-tagline
canvas_obj.setFont("Helvetica", 10)
canvas_obj.setFillColor(HexColor("#99bbcc"))
canvas_obj.drawCentredString(w / 2, h * 0.385,
"Complete Clinical Reference Blueprint")
canvas_obj.drawCentredString(w / 2, h * 0.360,
"Aetiology · Diagnostics · Management")
# Badge
canvas_obj.setFillColor(AMBER)
canvas_obj.roundRect(w/2 - 80, h*0.29, 160, 22, 5, fill=1, stroke=0)
canvas_obj.setFillColor(NAVY)
canvas_obj.setFont("Helvetica-Bold", 9)
canvas_obj.drawCentredString(w / 2, h * 0.298,
"Clinical Pathway Dossier")
# Topics list
topics = ["Stone Types & Typology Matrix",
"Obstruction Mechanics & Dietl Crisis",
"Diagnostic Pathway: USG → NCCT",
"Surgical Escalation Ladder",
"ESWL: Procedure, CI & Complications",
"Medical Expulsive Therapy (MET)",
"DDx: MSK Pain & PLID"]
y = h * 0.22
canvas_obj.setFont("Helvetica", 8.5)
canvas_obj.setFillColor(HexColor("#aaddee"))
for t in topics:
canvas_obj.drawCentredString(w / 2, y, f"✦ {t}")
y -= 14
# Footer
canvas_obj.setFillColor(TEAL)
canvas_obj.rect(0, 0, w, 30, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica", 8)
canvas_obj.drawCentredString(w / 2, 10, "Clinical Notes Booklet | Left–Right Spread Format")
canvas_obj.restoreState()
# ── Assemble the document ─────────────────────────────────────────────────────
OUT = "/home/daytona/workspace/urolithiasis-booklet/Urolithiasis_Booklet.pdf"
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame, NextPageTemplate, PageBreak
# Two frames: left column and right column
left_frame = Frame(
MARGIN, 35,
COL_W, PAGE_H - 35 - 45,
leftPadding=0, rightPadding=0,
topPadding=0, bottomPadding=0,
id="left"
)
right_frame = Frame(
MARGIN + COL_W + COL_GAP, 35,
COL_W, PAGE_H - 35 - 45,
leftPadding=0, rightPadding=0,
topPadding=0, bottomPadding=0,
id="right"
)
# Cover frame
cover_frame = Frame(0, 0, PAGE_W, PAGE_H,
leftPadding=0, rightPadding=0,
topPadding=0, bottomPadding=0,
id="cover")
doc = BaseDocTemplate(
OUT,
pagesize=A4,
rightMargin=0, leftMargin=0, topMargin=0, bottomMargin=0,
)
cover_template = PageTemplate(id="Cover", frames=[cover_frame],
onPage=draw_cover)
spread_template = PageTemplate(id="Spread", frames=[left_frame, right_frame],
onPage=draw_page)
doc.addPageTemplates([cover_template, spread_template])
# ── Story ─────────────────────────────────────────────────────────────────────
story = []
# Cover page (empty — drawn purely on canvas)
story.append(NextPageTemplate("Cover"))
story.append(Spacer(1, PAGE_H)) # fill the cover frame with nothing
# Switch to spread template
story.append(NextPageTemplate("Spread"))
story.append(PageBreak())
# Pour left-column content first; ReportLab will flow right when left overflows
left = build_left_col()
right = build_right_col()
for f in left:
story.append(f)
# Force jump to right column / next page
story.append(PageBreak()) # end of left column → right column
for f in right:
story.append(f)
doc.build(story)
print(f"PDF written → {OUT}")
cd /home/daytona/workspace/urolithiasis-booklet && python3 build_booklet.py 2>&1
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/…
python3 -c " import pdfplumber with pdfplumber.open('/home/daytona/workspace/uro…
Urolithiasis Booklet
PDF Document · PDF
Little changes in pdf... Pyelolithotomy technique: Requires precise ischaemic technique - clamp the renal artery → remove stone within ischaemic window → unclamp.its bot pyelolothotomy technique it was anatrophic nephrolithotomy
Another change was Cy
Another change was add cystine stone under radioluscent table
~/urolithiasis-booklet/build_booklet.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/urolithiasis-booklet/build_booklet.py'
~/urolithiasis-booklet/build_booklet.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/urolithiasis-booklet/build_booklet.py'
~/urolithiasis-booklet/build_booklet.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/urolithiasis-booklet/build_booklet.py'