Detail about complications of laparoscopic cholecystectomy Flowcharts Illustrative diagrams Give inPDF
mkdir -p /tmp/workspace/lap-chole-pdf && ls /tmp/workspace/lap-chole-pdf/
~/lap-chole-pdf/generate_pdf.py
import urllib.request
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.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether, Image
)
from reportlab.graphics.shapes import (
Drawing, Rect, String, Line, Polygon, Arrow, Circle,
RoundedRect, Path, Group
)
from reportlab.graphics import renderPDF
from reportlab.graphics.charts.flowchart import FlowChart
from reportlab.platypus.flowables import Flowable
import io
import os
# ── Download the Strasberg image ──────────────────────────────────────────────
strasberg_url = "https://cdn.orris.care/cdss_images/03bf417319fcd7615ecc2e50fc17fca8410c7432180cd80e3c027def2f1bfcb7.png"
strasberg_path = "/tmp/workspace/lap-chole-pdf/strasberg.png"
if not os.path.exists(strasberg_path):
urllib.request.urlretrieve(strasberg_url, strasberg_path)
# ── Colour palette ─────────────────────────────────────────────────────────────
C_DARK_BLUE = colors.HexColor("#1a3a5c")
C_MED_BLUE = colors.HexColor("#2e6da4")
C_LIGHT_BLUE = colors.HexColor("#d6eaf8")
C_SKY = colors.HexColor("#e8f4fc")
C_RED = colors.HexColor("#c0392b")
C_ORANGE = colors.HexColor("#e67e22")
C_GREEN = colors.HexColor("#27ae60")
C_YELLOW = colors.HexColor("#f39c12")
C_PURPLE = colors.HexColor("#8e44ad")
C_TEAL = colors.HexColor("#16a085")
C_LIGHT_GREEN = colors.HexColor("#d5f5e3")
C_LIGHT_RED = colors.HexColor("#fadbd8")
C_LIGHT_ORANGE= colors.HexColor("#fdebd0")
C_LIGHT_PURPLE= colors.HexColor("#e8daef")
C_GREY = colors.HexColor("#95a5a6")
C_LIGHT_GREY = colors.HexColor("#f2f3f4")
C_WHITE = colors.white
C_BLACK = colors.black
# ── Document setup ─────────────────────────────────────────────────────────────
OUTPUT = "/tmp/workspace/lap-chole-pdf/Laparoscopic_Cholecystectomy_Complications.pdf"
doc = SimpleDocTemplate(
OUTPUT, pagesize=A4,
leftMargin=1.5*cm, rightMargin=1.5*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Complications of Laparoscopic Cholecystectomy",
author="Medical Reference"
)
styles = getSampleStyleSheet()
# Custom styles
H1 = ParagraphStyle("H1", fontName="Helvetica-Bold", fontSize=22, textColor=C_WHITE,
alignment=TA_CENTER, spaceAfter=4)
H2 = ParagraphStyle("H2", fontName="Helvetica-Bold", fontSize=15, textColor=C_WHITE,
alignment=TA_CENTER, spaceAfter=4)
H3 = ParagraphStyle("H3", fontName="Helvetica-Bold", fontSize=12, textColor=C_DARK_BLUE,
spaceBefore=8, spaceAfter=4)
BODY = ParagraphStyle("BODY", fontName="Helvetica", fontSize=9.5, textColor=C_BLACK,
leading=14, spaceAfter=4, alignment=TA_JUSTIFY)
BULLET = ParagraphStyle("BULLET", fontName="Helvetica", fontSize=9, textColor=C_BLACK,
leading=13, leftIndent=12, spaceAfter=2,
bulletIndent=0, bulletFontName="Helvetica")
CAPTION = ParagraphStyle("CAPTION", fontName="Helvetica-Oblique", fontSize=8,
textColor=C_GREY, alignment=TA_CENTER, spaceAfter=6)
SMALL_BOLD = ParagraphStyle("SMALL_BOLD", fontName="Helvetica-Bold", fontSize=8.5,
textColor=C_DARK_BLUE, spaceAfter=2)
SOURCE = ParagraphStyle("SOURCE", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=C_GREY, spaceAfter=2)
# ── Helper: coloured banner ────────────────────────────────────────────────────
def banner(text, style=H2, bg=C_MED_BLUE, pad=8):
d = Drawing(doc.width, 32)
d.add(RoundedRect(0, 0, doc.width, 32, 6, 6, fillColor=bg, strokeColor=bg))
return [d, Paragraph(text, style)]
def section_header(text, bg=C_MED_BLUE):
tbl = Table([[Paragraph(text, H3)]], colWidths=[doc.width])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TEXTCOLOR", (0,0), (-1,-1), C_WHITE),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [6,6,6,6]),
]))
return tbl
# ── Flowable: Flowchart node helper ───────────────────────────────────────────
class FlowBox(Flowable):
"""A coloured rounded-rect box with centred text for flowcharts."""
def __init__(self, text, width=200, height=30, fill=C_LIGHT_BLUE,
stroke=C_MED_BLUE, font_size=9, text_color=C_DARK_BLUE):
Flowable.__init__(self)
self.text = text
self.width = width
self.height = height
self.fill = fill
self.stroke = stroke
self.font_size = font_size
self.text_color = text_color
def draw(self):
self.canv.setFillColor(self.fill)
self.canv.setStrokeColor(self.stroke)
self.canv.setLineWidth(1.2)
self.canv.roundRect(0, 0, self.width, self.height, 6, fill=1, stroke=1)
self.canv.setFillColor(self.text_color)
self.canv.setFont("Helvetica-Bold", self.font_size)
self.canv.drawCentredString(self.width/2, self.height/2 - self.font_size/3, self.text)
# ══════════════════════════════════════════════════════════════════════════════
# BUILD CONTENT
# ══════════════════════════════════════════════════════════════════════════════
story = []
# ── PAGE 1 : TITLE ─────────────────────────────────────────────────────────────
# Title banner
title_tbl = Table([[Paragraph("COMPLICATIONS OF", H1),
Paragraph("LAPAROSCOPIC CHOLECYSTECTOMY", H1)]],
colWidths=[doc.width])
title_bg = Table([[""]], colWidths=[doc.width], rowHeights=[110])
title_bg.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK_BLUE),
("ROUNDEDCORNERS", [10,10,10,10]),
]))
title_content = Table([
[Paragraph("COMPLICATIONS OF", ParagraphStyle("TC", fontName="Helvetica-Bold", fontSize=20, textColor=C_WHITE, alignment=TA_CENTER))],
[Paragraph("LAPAROSCOPIC CHOLECYSTECTOMY", ParagraphStyle("TC2", fontName="Helvetica-Bold", fontSize=24, textColor=colors.HexColor("#f9ca24"), alignment=TA_CENTER))],
[Paragraph("A Comprehensive Clinical Reference", ParagraphStyle("TC3", fontName="Helvetica-Oblique", fontSize=13, textColor=C_LIGHT_BLUE, alignment=TA_CENTER))],
[Paragraph("Sources: Maingot's Abdominal Operations · Bailey & Love's Surgery · Greenfield's Surgery · Tintinalli's Emergency Medicine",
ParagraphStyle("TC4", fontName="Helvetica", fontSize=8, textColor=C_GREY, alignment=TA_CENTER))],
], colWidths=[doc.width])
title_content.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [10,10,10,10]),
]))
story.append(title_content)
story.append(Spacer(1, 12))
# ── OVERVIEW BOX ──────────────────────────────────────────────────────────────
overview_data = [
[Paragraph("<b>OVERVIEW</b>", SMALL_BOLD)],
[Paragraph(
"Laparoscopic cholecystectomy (LC) is the gold-standard treatment for symptomatic cholelithiasis. "
"Complications occur in <b>10-15% of cases</b>. Serious complications fall into two major areas: "
"<b>access complications</b> and <b>bile duct injuries</b>. "
"Overall mortality is <b><0.3%</b>. The incidence of major bile duct injury is "
"<b>0.4-0.6%</b> (vs 0.1-0.2% for open surgery). "
"Most serious complications are more common early in the surgeon's learning curve.",
BODY)],
]
overview_tbl = Table(overview_data, colWidths=[doc.width])
overview_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("BACKGROUND", (0,1), (-1,-1), C_SKY),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [6,6,6,6]),
]))
story.append(overview_tbl)
story.append(Spacer(1, 14))
# ── MASTER CLASSIFICATION TABLE ───────────────────────────────────────────────
story.append(section_header("CLASSIFICATION OF COMPLICATIONS"))
story.append(Spacer(1, 6))
class_data = [
[Paragraph("<b>Category</b>", SMALL_BOLD), Paragraph("<b>Specific Complications</b>", SMALL_BOLD), Paragraph("<b>Incidence</b>", SMALL_BOLD)],
[Paragraph("Biliary", BODY),
Paragraph("Bile duct injury, bile leak, biliary stricture, retained stones, biliary fistula", BODY),
Paragraph("BDI: 0.4-0.6%\nBile leak: 1-2%", BODY)],
[Paragraph("Vascular / Haemorrhagic", BODY),
Paragraph("Cystic artery bleeding, RHA injury, trocar vessel laceration, abdominal wall haematoma", BODY),
Paragraph("1-2%", BODY)],
[Paragraph("Pneumoperitoneum-related", BODY),
Paragraph("CO2 embolism, vagal reflex, cardiac arrhythmias, hypercarbic acidosis, subcutaneous emphysema, pneumothorax, pneumomediastinum", BODY),
Paragraph("Rare", BODY)],
[Paragraph("Trocar / Access", BODY),
Paragraph("GI tract injury (bowel perforation), GU tract injury, major vessel injury (aorta, IVC, iliac), trocar-site hernia, wound infection", BODY),
Paragraph("0.1-0.4%", BODY)],
[Paragraph("Visceral / Miscellaneous", BODY),
Paragraph("Pancreatitis, splenic injury, retained intraperitoneal stones, intra-abdominal abscess, incisional hernia, biliary cutaneous fistula", BODY),
Paragraph("Varied", BODY)],
[Paragraph("Post-operative General", BODY),
Paragraph("Wound infection, DVT/PE, pulmonary complications, acute MI, urinary retention", BODY),
Paragraph("Varied", BODY)],
]
class_tbl = Table(class_data, colWidths=[3.5*cm, 10.5*cm, 3*cm])
class_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BACKGROUND", (0,1), (-1,1), C_LIGHT_RED),
("BACKGROUND", (0,2), (-1,2), C_LIGHT_ORANGE),
("BACKGROUND", (0,3), (-1,3), C_LIGHT_BLUE),
("BACKGROUND", (0,4), (-1,4), C_LIGHT_PURPLE),
("BACKGROUND", (0,5), (-1,5), C_LIGHT_GREEN),
("BACKGROUND", (0,6), (-1,6), C_LIGHT_GREY),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.5, colors.HexColor("#aab7b8")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
]))
story.append(class_tbl)
story.append(Spacer(1, 8))
story.append(Paragraph("Sources: Maingot's Abdominal Operations; Bailey & Love's Surgery 28e; Tintinalli's Emergency Medicine", SOURCE))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 2 : FLOWCHART — INTRAOPERATIVE DECISION (Bile Duct Injury)
# ══════════════════════════════════════════════════════════════════════════════
def draw_flowchart_page1(story):
story.append(section_header("FLOWCHART 1 — INTRAOPERATIVE RECOGNITION & MANAGEMENT OF BILE DUCT INJURY"))
story.append(Spacer(1, 8))
w = doc.width
def box(text, fill, stroke, width=None, height=34, font_size=9, text_color=C_WHITE, bold=True):
_w = width or w * 0.7
_font = "Helvetica-Bold" if bold else "Helvetica"
tbl = Table([[Paragraph(f"<font name='{_font}' size='{font_size}' color='#{text_color.hexval()[2:]}'>{text}</font>",
ParagraphStyle("fb", alignment=TA_CENTER, leading=12))]],
colWidths=[_w])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), fill),
("BOX", (0,0), (-1,-1), 1.5, stroke),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [8,8,8,8]),
]))
# Centre
return Table([[tbl]], colWidths=[w], style=[
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
def arrow():
d = Drawing(w, 16)
mid = w/2
d.add(Line(mid, 16, mid, 0, strokeColor=C_MED_BLUE, strokeWidth=2))
d.add(Polygon([mid-5, 4, mid+5, 4, mid, 0], fillColor=C_MED_BLUE, strokeColor=C_MED_BLUE))
return d
def diamond(text, fill=C_YELLOW, stroke=C_ORANGE, width=None):
_w = width or w * 0.65
tbl = Table([[Paragraph(f"<font name='Helvetica-Bold' size='9'>◆ {text} ◆</font>",
ParagraphStyle("dm", alignment=TA_CENTER, leading=12))]],
colWidths=[_w])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), fill),
("BOX", (0,0), (-1,-1), 1.5, stroke),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("ROUNDEDCORNERS", [14,14,14,14]),
]))
return Table([[tbl]], colWidths=[w], style=[
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
def two_branch(left_text, right_text, lc=C_GREEN, rc=C_RED):
l = Table([[Paragraph(f"<font name='Helvetica-Bold' size='8.5' color='white'>{left_text}</font>",
ParagraphStyle("lb", alignment=TA_CENTER, leading=11))]],
colWidths=[w*0.44])
l.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),lc),("BOX",(0,0),(-1,-1),1,lc),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("ROUNDEDCORNERS",[6,6,6,6])]))
r = Table([[Paragraph(f"<font name='Helvetica-Bold' size='8.5' color='white'>{right_text}</font>",
ParagraphStyle("rb", alignment=TA_CENTER, leading=11))]],
colWidths=[w*0.44])
r.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),rc),("BOX",(0,0),(-1,-1),1,rc),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("ROUNDEDCORNERS",[6,6,6,6])]))
return Table([[l, "", r]], colWidths=[w*0.44, w*0.12, w*0.44])
story.append(box("INTRAOPERATIVE BILE DUCT INJURY SUSPECTED", C_RED, C_RED))
story.append(arrow())
story.append(diamond("Is injury recognised INTRAOPERATIVELY?"))
story.append(Spacer(1, 4))
story.append(two_branch("YES — Recognised Intraop", "NO — Post-op Presentation"))
story.append(Spacer(1, 4))
# YES branch details
yes_tbl = Table([[
Paragraph("<b>Partial BDI:</b> Immediate primary repair ± T-tube\n"
"<b>Complete transection:</b> End-to-end repair or\n"
"hepaticojejunostomy by hepatobiliary surgeon\n"
"<b>Vascular co-injury:</b> Urgent vascular repair", BULLET)
]], colWidths=[w*0.44])
yes_tbl.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_LIGHT_GREEN),
("BOX",(0,0),(-1,-1),1,C_GREEN),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),
("ROUNDEDCORNERS",[6,6,6,6])]))
no_tbl = Table([[
Paragraph("<b>Bile leak / peritonitis:</b> Presents days 1-5\n"
"<b>Obstructive jaundice:</b> Days to weeks\n"
"<b>Investigations:</b> USS → CT → MRCP/ERCP\n"
"Drain any collections percutaneously", BULLET)
]], colWidths=[w*0.44])
no_tbl.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_LIGHT_RED),
("BOX",(0,0),(-1,-1),1,C_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),
("ROUNDEDCORNERS",[6,6,6,6])]))
story.append(Table([[yes_tbl, "", no_tbl]], colWidths=[w*0.44, w*0.12, w*0.44]))
story.append(arrow())
story.append(diamond("Injury Type / Strasberg Grade?", fill=colors.HexColor("#fef9e7"), stroke=C_ORANGE))
story.append(Spacer(1, 4))
grade_data = [
[Paragraph("<b>Strasberg Grade</b>", SMALL_BOLD),
Paragraph("<b>Description</b>", SMALL_BOLD),
Paragraph("<b>Management</b>", SMALL_BOLD)],
[Paragraph("A", BODY), Paragraph("Bile leak from cystic duct stump or minor duct", BODY),
Paragraph("ERCP + stent / sphincterotomy", BODY)],
[Paragraph("B", BODY), Paragraph("Occluded right posterior sectoral duct", BODY),
Paragraph("Observation or surgical repair", BODY)],
[Paragraph("C", BODY), Paragraph("Bile leak from divided right posterior duct", BODY),
Paragraph("ERCP + stent; repair if persistent", BODY)],
[Paragraph("D", BODY), Paragraph("Lateral injury to main bile duct, no tissue loss", BODY),
Paragraph("Primary repair over T-tube", BODY)],
[Paragraph("E1-E5", BODY), Paragraph("Transection/stricture of main bile duct at various levels", BODY),
Paragraph("Hepaticojejunostomy (Roux-en-Y) by expert HBP surgeon", BODY)],
]
grade_tbl = Table(grade_data, colWidths=[2.0*cm, 8.5*cm, 6.5*cm])
grade_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#d5f5e3")),
("BACKGROUND", (0,2), (-1,2), colors.HexColor("#d5f5e3")),
("BACKGROUND", (0,3), (-1,3), colors.HexColor("#fdebd0")),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#fdebd0")),
("BACKGROUND", (0,5), (-1,5), colors.HexColor("#fadbd8")),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(grade_tbl)
story.append(Spacer(1, 6))
story.append(arrow())
story.append(box("REFER TO SPECIALIST HBP UNIT if complex (E1-E5) or uncertain", C_PURPLE, C_PURPLE))
draw_flowchart_page1(story)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 3 : FLOWCHART 2 — POST-OP MANAGEMENT
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("FLOWCHART 2 — POST-OPERATIVE COMPLICATION ASSESSMENT"))
story.append(Spacer(1, 8))
w = doc.width
def centred_box(text, fill, stroke, col_w=None, height=32, fsize=9, bold=True):
cw = col_w or w * 0.70
fn = "Helvetica-Bold" if bold else "Helvetica"
p = Paragraph(f"<font name='{fn}' size='{fsize}'>{text}</font>",
ParagraphStyle("cb", alignment=TA_CENTER, leading=13,
textColor=C_WHITE))
tbl = Table([[p]], colWidths=[cw])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), fill),
("BOX", (0,0), (-1,-1), 1.5, stroke),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("ROUNDEDCORNERS", [8,8,8,8]),
]))
return Table([[tbl]], colWidths=[w], style=[("ALIGN",(0,0),(-1,-1),"CENTER")])
def small_arrow():
d = Drawing(w, 14)
mid = w/2
d.add(Line(mid, 14, mid, 0, strokeColor=C_MED_BLUE, strokeWidth=1.5))
d.add(Polygon([mid-4, 3, mid+4, 3, mid, 0], fillColor=C_MED_BLUE, strokeColor=C_MED_BLUE))
return d
story.append(centred_box("PATIENT POST LAPAROSCOPIC CHOLECYSTECTOMY with NEW SYMPTOMS", C_DARK_BLUE, C_DARK_BLUE))
story.append(small_arrow())
# Symptom assessment
sym_data = [
[Paragraph("<b>Key Symptoms to Assess</b>", ParagraphStyle("ksa", fontName="Helvetica-Bold", fontSize=9, textColor=C_WHITE, alignment=TA_CENTER))],
[Table([
[Paragraph("• Abdominal pain / peritonism", BULLET),
Paragraph("• Fever / rigors / chills", BULLET)],
[Paragraph("• Jaundice / dark urine", BULLET),
Paragraph("• Nausea / vomiting", BULLET)],
[Paragraph("• Shoulder-tip pain (referred)", BULLET),
Paragraph("• Bile from drain / wound", BULLET)],
], colWidths=[w*0.48, w*0.48])],
]
sym_tbl = Table(sym_data, colWidths=[w])
sym_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), C_MED_BLUE),
("BACKGROUND", (0,1), (0,1), C_SKY),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(sym_tbl)
story.append(small_arrow())
# Three pathways
p_bile = Table([[
Paragraph("<b>BILIARY COMPLICATION?</b>", ParagraphStyle("bc", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_WHITE, alignment=TA_CENTER)),
]], colWidths=[w*0.31])
p_bile.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_RED),("TOPPADDING",(0,0),(-1,-1),6),
("BOTTOMPADDING",(0,0),(-1,-1),6),("BOX",(0,0),(-1,-1),1,C_RED),
("ROUNDEDCORNERS",[6,6,6,6])]))
p_vasc = Table([[
Paragraph("<b>VASCULAR / HAEMORRHAGE?</b>", ParagraphStyle("vc", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_WHITE, alignment=TA_CENTER)),
]], colWidths=[w*0.31])
p_vasc.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_ORANGE),("TOPPADDING",(0,0),(-1,-1),6),
("BOTTOMPADDING",(0,0),(-1,-1),6),("BOX",(0,0),(-1,-1),1,C_ORANGE),
("ROUNDEDCORNERS",[6,6,6,6])]))
p_gen = Table([[
Paragraph("<b>GENERAL SURGICAL?</b>", ParagraphStyle("gc", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_WHITE, alignment=TA_CENTER)),
]], colWidths=[w*0.31])
p_gen.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_TEAL),("TOPPADDING",(0,0),(-1,-1),6),
("BOTTOMPADDING",(0,0),(-1,-1),6),("BOX",(0,0),(-1,-1),1,C_TEAL),
("ROUNDEDCORNERS",[6,6,6,6])]))
story.append(Table([[p_bile, "", p_vasc, "", p_gen]], colWidths=[w*0.31, w*0.02, w*0.31, w*0.04, w*0.31]))
story.append(Spacer(1, 4))
# Details for each pathway
d_bile = Table([[
Paragraph("• Jaundice → USS → MRCP/ERCP\n"
"• Bile peritonitis → CT → percutaneous drain\n"
"• Small leak → observe or ERCP stent\n"
"• Stricture → stent or surgical repair\n"
"• Retained stone → ERCP extraction", BULLET)
]], colWidths=[w*0.31])
d_bile.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_LIGHT_RED),
("BOX",(0,0),(-1,-1),1,C_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("ROUNDEDCORNERS",[4,4,4,4])]))
d_vasc = Table([[
Paragraph("• Active bleed → resuscitate\n"
"• CT angiogram\n"
"• Embolisation or re-operation\n"
"• Trocar wound bleed: pressure /\n"
" tamponade / suture", BULLET)
]], colWidths=[w*0.31])
d_vasc.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),C_LIGHT_ORANGE),
("BOX",(0,0),(-1,-1),1,C_ORANGE),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("ROUNDEDCORNERS",[4,4,4,4])]))
d_gen = Table([[
Paragraph("• Wound infection → antibiotics, open\n"
"• Incisional hernia → repair\n"
"• Abscess → CT-guided drainage\n"
"• Pancreatitis → supportive care\n"
"• DVT/PE → anticoagulation", BULLET)
]], colWidths=[w*0.31])
d_gen.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),colors.HexColor("#d1f2eb")),
("BOX",(0,0),(-1,-1),1,C_TEAL),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
("ROUNDEDCORNERS",[4,4,4,4])]))
story.append(Table([[d_bile, "", d_vasc, "", d_gen]], colWidths=[w*0.31, w*0.02, w*0.31, w*0.04, w*0.31]))
story.append(Spacer(1, 6))
story.append(small_arrow())
story.append(centred_box("MULTIDISCIPLINARY REVIEW: Surgeon + Radiologist + Endoscopist", C_PURPLE, C_PURPLE, col_w=w*0.80))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 4 : DETAILED COMPLICATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("DETAILED COMPLICATIONS — BILIARY SYSTEM", C_RED))
story.append(Spacer(1, 6))
# Bile duct injury
bdi_data = [
[Paragraph("<b>BILE DUCT INJURY (BDI)</b>", SMALL_BOLD),
Paragraph("<b>Details</b>", SMALL_BOLD)],
[Paragraph("Incidence", BODY), Paragraph("0.4-0.6% (LC) vs 0.1-0.2% (open)", BODY)],
[Paragraph("Classic mechanism", BODY),
Paragraph("CBD or RHD mistaken for cystic duct; traction aligns CBD with cystic duct making them appear as one", BODY)],
[Paragraph("Risk factors", BODY),
Paragraph("• Surgeon inexperience (1.7% risk in 1st case → 0.17% by 50th)\n"
"• Aberrant anatomy\n"
"• Dense adhesions / acute inflammation\n"
"• Short cystic duct\n"
"• Large Hartmann's pouch stone\n"
"• Operative bleeding obscuring field", BODY)],
[Paragraph("Recognition", BODY),
Paragraph("Only 15% recognised intraoperatively; remainder present postoperatively as bile leak, biloma, peritonitis, or obstructive jaundice", BODY)],
[Paragraph("Prevention", BODY),
Paragraph("Critical view of safety (CVS): dissect Calot's triangle until only two structures seen entering GB base; consider intraoperative cholangiography", BODY)],
[Paragraph("Management", BODY),
Paragraph("Partial: primary repair ± T-tube\nComplete transection: Roux-en-Y hepaticojejunostomy\nComplex (E1-E5): specialist HBP centre", BODY)],
]
bdi_tbl = Table(bdi_data, colWidths=[4*cm, 13*cm])
bdi_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_RED),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BACKGROUND", (0,1), (-1,1), C_LIGHT_RED),
("BACKGROUND", (0,2), (-1,2), C_WHITE),
("BACKGROUND", (0,3), (-1,3), C_LIGHT_RED),
("BACKGROUND", (0,4), (-1,4), C_WHITE),
("BACKGROUND", (0,5), (-1,5), C_LIGHT_RED),
("BACKGROUND", (0,6), (-1,6), C_WHITE),
("BOX", (0,0), (-1,-1), 1, C_RED),
("INNERGRID", (0,0), (-1,-1), 0.4, colors.HexColor("#f1948a")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(bdi_tbl)
story.append(Spacer(1, 8))
# Bile leak
bl_data = [
[Paragraph("<b>BILE LEAK</b>", SMALL_BOLD), Paragraph("<b>Details</b>", SMALL_BOLD)],
[Paragraph("Sources", BODY), Paragraph("Cystic duct stump, gallbladder fossa ducts (Luschka's ducts), main bile duct", BODY)],
[Paragraph("Presentation", BODY), Paragraph("Abdominal pain, fever, peritonitis (bile peritonitis if no drain); bile from drain", BODY)],
[Paragraph("Investigation", BODY), Paragraph("USS → CT → ERCP (diagnostic and therapeutic)", BODY)],
[Paragraph("Management", BODY), Paragraph("Small leaks: observation (often self-limiting if no distal obstruction)\n"
"Symptomatic: ERCP + sphincterotomy ± stent\n"
"Collections: percutaneous/surgical drainage", BODY)],
]
bl_tbl = Table(bl_data, colWidths=[4*cm, 13*cm])
bl_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_ORANGE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_ORANGE, C_WHITE]),
("BOX", (0,0), (-1,-1), 1, C_ORANGE),
("INNERGRID", (0,0), (-1,-1), 0.4, colors.HexColor("#f0b27a")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(bl_tbl)
story.append(Spacer(1, 8))
# Retained stones
rs_data = [
[Paragraph("<b>RETAINED / SPILLED STONES</b>", SMALL_BOLD), Paragraph("<b>Details</b>", SMALL_BOLD)],
[Paragraph("Spilled stones", BODY), Paragraph("Gallbladder perforation occurs in 10-40% of LC cases; spilled stones in 5-40%", BODY)],
[Paragraph("Consequences", BODY), Paragraph("Intra-abdominal abscess, subcutaneous abscess, biliary-cutaneous fistula, trocar-site abscess (months–years later)", BODY)],
[Paragraph("Retained CBD stones", BODY), Paragraph("Presents with pain, pancreatitis, jaundice post-op; ERCP is treatment of choice", BODY)],
[Paragraph("Prevention", BODY), Paragraph("Retrieve all spilled stones; use retrieval bag for GB extraction", BODY)],
]
rs_tbl = Table(rs_data, colWidths=[4*cm, 13*cm])
rs_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_TEAL),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.HexColor("#d1f2eb"), C_WHITE]),
("BOX", (0,0), (-1,-1), 1, C_TEAL),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(rs_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 5 : VASCULAR + PNEUMOPERITONEUM + TROCAR
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("VASCULAR, PNEUMOPERITONEUM & TROCAR COMPLICATIONS", C_DARK_BLUE))
story.append(Spacer(1, 6))
# Vascular
vasc_data = [
[Paragraph("<b>VASCULAR / HAEMORRHAGIC COMPLICATIONS</b>", SMALL_BOLD), ""],
[Paragraph("Cystic artery bleeding", BODY),
Paragraph("Most common intraop bleed; controlled by haemostatic clips or conversion to open", BODY)],
[Paragraph("Right hepatic artery injury", BODY),
Paragraph("Often co-exists with BDI (close anatomic relationship); may cause hepatic ischaemia; urgent repair needed", BODY)],
[Paragraph("Major vessel injury (aorta, IVC, iliac)", BODY),
Paragraph("During trocar insertion especially with closed technique; do NOT remove trocar — open immediately and isolate vessel", BODY)],
[Paragraph("Abdominal wall vessel (epigastric)", BODY),
Paragraph("Trocar laceration; controlled with Foley catheter tamponade or through-and-through suture", BODY)],
]
vasc_tbl = Table(vasc_data, colWidths=[5*cm, 12*cm])
vasc_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("SPAN", (0,0), (1,0)),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_BLUE, C_WHITE]),
("BOX", (0,0), (-1,-1), 1, C_DARK_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(vasc_tbl)
story.append(Spacer(1, 8))
# Pneumoperitoneum
pneu_data = [
[Paragraph("<b>PNEUMOPERITONEUM-RELATED COMPLICATIONS</b>", SMALL_BOLD), "", ""],
[Paragraph("<b>Complication</b>", SMALL_BOLD), Paragraph("<b>Mechanism</b>", SMALL_BOLD), Paragraph("<b>Management</b>", SMALL_BOLD)],
[Paragraph("CO2 Gas Embolism", BODY), Paragraph("CO2 enters torn vessel during insufflation", BODY),
Paragraph("Deflate pneumoperitoneum; left lateral decubitus; 100% O2; CPR if arrest", BODY)],
[Paragraph("Vaso-vagal Reflex", BODY), Paragraph("Peritoneal stretch → bradycardia", BODY),
Paragraph("Deflate; atropine", BODY)],
[Paragraph("Cardiac Arrhythmias", BODY), Paragraph("Hypercarbia → sympathetic activation", BODY),
Paragraph("Ventilation adjustment; anti-arrhythmics", BODY)],
[Paragraph("Hypercarbic Acidosis", BODY), Paragraph("CO2 absorption especially in long cases", BODY),
Paragraph("Increase ventilation; reduce insufflation pressure", BODY)],
[Paragraph("Subcutaneous Emphysema", BODY), Paragraph("CO2 tracking into subcutaneous tissue", BODY),
Paragraph("Usually self-limiting; monitor", BODY)],
[Paragraph("Pneumothorax / Pneumomediastinum", BODY), Paragraph("CO2 tracking through diaphragmatic defect", BODY),
Paragraph("Chest drain if tension; otherwise conservative", BODY)],
]
pneu_tbl = Table(pneu_data, colWidths=[5*cm, 5.5*cm, 6.5*cm])
pneu_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_MED_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("SPAN", (0,0), (2,0)),
("BACKGROUND", (0,1), (-1,1), C_DARK_BLUE),
("TEXTCOLOR", (0,1), (-1,1), C_WHITE),
("ROWBACKGROUNDS", (0,2), (-1,-1), [C_LIGHT_BLUE, C_WHITE]),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(pneu_tbl)
story.append(Spacer(1, 8))
# Trocar
trocar_data = [
[Paragraph("<b>TROCAR / ACCESS COMPLICATIONS</b>", SMALL_BOLD), ""],
[Paragraph("GI tract perforation (bowel)", BODY),
Paragraph("Most common with blind trocar insertion; Veress needle injury can be managed conservatively; trocar bowel injury requires repair", BODY)],
[Paragraph("GU tract injury (bladder)", BODY),
Paragraph("Seen with prior pelvic surgery; Foley catheter decompression preoperative; intraop repair if recognised", BODY)],
[Paragraph("Trocar-site hernia (port-site hernia)", BODY),
Paragraph("Incidence 1-3%; higher at 10-12mm ports; prevent by closing fascial defects ≥10mm; treat with elective repair", BODY)],
[Paragraph("Wound infection", BODY),
Paragraph("Higher risk with acute cholecystitis, gallbladder perforation, or immunocompromise; treat with antibiotics ± drainage", BODY)],
[Paragraph("Port-site metastasis", BODY),
Paragraph("Rare; reported with unsuspected gallbladder carcinoma; use retrieval bag", BODY)],
]
trocar_tbl = Table(trocar_data, colWidths=[5*cm, 12*cm])
trocar_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_PURPLE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("SPAN", (0,0), (1,0)),
("ROWBACKGROUNDS", (0,1), (-1,-1), [C_LIGHT_PURPLE, C_WHITE]),
("BOX", (0,0), (-1,-1), 1, C_PURPLE),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(trocar_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 6 : STRASBERG CLASSIFICATION + DIAGRAM
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STRASBERG CLASSIFICATION OF BILE DUCT INJURIES", C_DARK_BLUE))
story.append(Spacer(1, 8))
# Embed Strasberg image
if os.path.exists(strasberg_path):
img = Image(strasberg_path, width=doc.width*0.95, height=8*cm)
img.hAlign = "CENTER"
story.append(img)
story.append(Paragraph("Figure: Strasberg Classification of Bile Duct Injuries — Bailey & Love's Surgery 28th Edition", CAPTION))
story.append(Spacer(1, 8))
stras_data = [
[Paragraph("<b>Grade</b>", SMALL_BOLD), Paragraph("<b>Description</b>", SMALL_BOLD),
Paragraph("<b>Anatomical Injury</b>", SMALL_BOLD), Paragraph("<b>Treatment</b>", SMALL_BOLD)],
[Paragraph("A", BODY), Paragraph("Bile leak — minor", BODY),
Paragraph("Cystic duct stump / Luschka duct", BODY), Paragraph("ERCP + sphincterotomy ± stent", BODY)],
[Paragraph("B", BODY), Paragraph("Occlusion — no leak", BODY),
Paragraph("Right posterior sectoral duct occluded", BODY), Paragraph("Observe or surgical repair", BODY)],
[Paragraph("C", BODY), Paragraph("Bile leak — sectoral", BODY),
Paragraph("Right posterior sectoral duct divided + leaking", BODY), Paragraph("ERCP + stent; repair if persistent", BODY)],
[Paragraph("D", BODY), Paragraph("Lateral injury", BODY),
Paragraph("Lateral laceration of main bile duct; no tissue loss", BODY), Paragraph("Primary repair over T-tube", BODY)],
[Paragraph("E1", BODY), Paragraph("Transection — distal", BODY),
Paragraph("Stricture >2 cm from hepatic hilus", BODY), Paragraph("Hepaticojejunostomy", BODY)],
[Paragraph("E2", BODY), Paragraph("Transection — mid", BODY),
Paragraph("Stricture <2 cm from hepatic hilus", BODY), Paragraph("Hepaticojejunostomy", BODY)],
[Paragraph("E3", BODY), Paragraph("Hilar — R+L in continuity", BODY),
Paragraph("Stricture at hilus; R and L hepatic ducts communicate", BODY), Paragraph("Complex Roux-en-Y (specialist)", BODY)],
[Paragraph("E4", BODY), Paragraph("Hilar — R+L separated", BODY),
Paragraph("Stricture with separation of right and left hepatic ducts", BODY), Paragraph("Complex biliary reconstruction", BODY)],
[Paragraph("E5", BODY), Paragraph("Combined injury", BODY),
Paragraph("Right aberrant sectoral duct + main bile duct stricture", BODY), Paragraph("Specialist HBP reconstruction", BODY)],
]
stras_tbl = Table(stras_data, colWidths=[1.4*cm, 3.5*cm, 6.5*cm, 5.6*cm])
stras_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#d5f5e3")),
("BACKGROUND", (0,2), (-1,2), colors.HexColor("#d5f5e3")),
("BACKGROUND", (0,3), (-1,3), colors.HexColor("#fdebd0")),
("BACKGROUND", (0,4), (-1,4), colors.HexColor("#fdebd0")),
("BACKGROUND", (0,5), (-1,5), colors.HexColor("#fadbd8")),
("BACKGROUND", (0,6), (-1,6), colors.HexColor("#fadbd8")),
("BACKGROUND", (0,7), (-1,7), colors.HexColor("#fadbd8")),
("BACKGROUND", (0,8), (-1,8), colors.HexColor("#fadbd8")),
("BACKGROUND", (0,9), (-1,9), colors.HexColor("#fadbd8")),
("BOX", (0,0), (-1,-1), 1, C_MED_BLUE),
("INNERGRID", (0,0), (-1,-1), 0.4, C_GREY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
story.append(stras_tbl)
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 7 : FLOWCHART 3 — CONVERSION TO OPEN
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("FLOWCHART 3 — DIFFICULT CHOLECYSTECTOMY: BAIL-OUT STRATEGIES"))
story.append(Spacer(1, 10))
def flow_box_row(text, fill, stroke, col_w=None, fsize=9.5):
cw = col_w or w * 0.72
p = Paragraph(f"<font name='Helvetica-Bold' size='{fsize}' color='white'>{text}</font>",
ParagraphStyle("fbr", alignment=TA_CENTER, leading=13))
t = Table([[p]], colWidths=[cw])
t.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),fill),("BOX",(0,0),(-1,-1),1.5,stroke),
("TOPPADDING",(0,0),(-1,-1),6),("BOTTOMPADDING",(0,0),(-1,-1),6),
("ROUNDEDCORNERS",[8,8,8,8])]))
return Table([[t]], colWidths=[w], style=[("ALIGN",(0,0),(-1,-1),"CENTER")])
def arr():
d = Drawing(w, 15)
m = w/2
d.add(Line(m, 15, m, 0, strokeColor=C_DARK_BLUE, strokeWidth=2))
d.add(Polygon([m-5, 4, m+5, 4, m, 0], fillColor=C_DARK_BLUE, strokeColor=C_DARK_BLUE))
return d
story.append(flow_box_row("DIFFICULT LAPAROSCOPIC CHOLECYSTECTOMY", C_DARK_BLUE, C_DARK_BLUE, fsize=11))
story.append(arr())
# Warning signs
warn_data = [
[Paragraph("<b>Warning Signs / Triggers for Bail-out</b>", ParagraphStyle("ws", fontName="Helvetica-Bold",
fontSize=9.5, textColor=C_WHITE, alignment=TA_CENTER))],
[Table([
[Paragraph("• Anatomy not clearly defined", BULLET),
Paragraph("• No progress with dissection", BULLET),
Paragraph("• Active uncontrolled bleeding", BULLET)],
[Paragraph("• Dense adhesions / frozen pelvis", BULLET),
Paragraph("• Mirizzi syndrome suspected", BULLET),
Paragraph("• BDI risk is high", BULLET)],
], colWidths=[w*0.33]*3)],
]
warn_tbl = Table(warn_data, colWidths=[w])
warn_tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(0,0), C_RED),
("BACKGROUND",(0,1),(0,1), C_LIGHT_RED),
("BOX",(0,0),(-1,-1),1,C_RED),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),4),
]))
story.append(warn_tbl)
story.append(arr())
# 5 bail-out options
bail_options = [
("1. CONVERT TO OPEN", C_ORANGE, "Choose early — 'by choice' not forced by complication"),
("2. SUBTOTAL CHOLECYSTECTOMY", C_MED_BLUE, "Remove GB fundus/body; leave Hartmann's pouch; safer than risky dissection"),
("3. TUBE CHOLECYSTOSTOMY", C_TEAL, "14Fr Foley catheter; temporary relief; definitive surgery later"),
("4. FUNDUS-FIRST APPROACH", C_PURPLE, "Dissect from fundus down instead of Calot's triangle"),
("5. INTRAOPERATIVE CHOLANGIOGRAPHY", C_GREEN, "Delineate biliary anatomy when in doubt; reduces BDI risk"),
]
for title, fill, desc in bail_options:
opt_tbl = Table([[
Table([[Paragraph(f"<font name='Helvetica-Bold' size='9' color='white'>{title}</font>",
ParagraphStyle("op", alignment=TA_CENTER, leading=12))]],
colWidths=[5.5*cm],
style=[("BACKGROUND",(0,0),(-1,-1),fill),("TOPPADDING",(0,0),(-1,-1),6),
("BOTTOMPADDING",(0,0),(-1,-1),6),("ROUNDEDCORNERS",[6,6,6,6]),
("BOX",(0,0),(-1,-1),1,fill)]),
Paragraph(desc, BODY)
]], colWidths=[5.5*cm, w - 5.5*cm - 0.5*cm])
opt_tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,-1),C_LIGHT_GREY),
("BOX",(0,0),(-1,-1),0.5,C_GREY),
("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING",(0,0),(-1,-1),5),
("VALIGN",(0,0),(-1,-1),"MIDDLE"),
("ROUNDEDCORNERS",[4,4,4,4]),
]))
story.append(opt_tbl)
story.append(Spacer(1, 4))
story.append(arr())
story.append(flow_box_row("DOCUMENT DECISION + INFORM PATIENT POST-OP", C_GREEN, C_GREEN, fsize=10))
story.append(PageBreak())
# ══════════════════════════════════════════════════════════════════════════════
# PAGE 8 : PREVENTION & SUMMARY
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("PREVENTION OF COMPLICATIONS & CLINICAL PEARLS", C_GREEN))
story.append(Spacer(1, 8))
# Critical view of safety
cvs_data = [
[Paragraph("<b>THE CRITICAL VIEW OF SAFETY (CVS)</b>", SMALL_BOLD), ""],
[Paragraph("Requirement 1", BODY),
Paragraph("Hepatocystic triangle is cleared of fat and fibrous tissue", BODY)],
[Paragraph("Requirement 2", BODY),
Paragraph("Lower third of gallbladder is separated from liver bed", BODY)],
[Paragraph("Requirement 3", BODY),
Paragraph("Only TWO structures seen entering the gallbladder (cystic duct + cystic artery)", BODY)],
[Paragraph("Clinical Pearl", BODY),
Paragraph("⚠️ Do NOT clip any structure until CVS is achieved. When in doubt, do NOT clip.", BODY)],
]
cvs_tbl = Table(cvs_data, colWidths=[4.5*cm, 12.5*cm])
cvs_tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,0), C_GREEN),("TEXTCOLOR",(0,0),(-1,0),C_WHITE),
("SPAN",(0,0),(1,0)),
("ROWBACKGROUNDS",(0,1),(-1,-1),[C_LIGHT_GREEN,C_WHITE]),
("BOX",(0,0),(-1,-1),1,C_GREEN),
("INNERGRID",(0,0),(-1,-1),0.4,C_GREY),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(cvs_tbl)
story.append(Spacer(1, 8))
# Key prevention table
prev_data = [
[Paragraph("<b>Complication</b>", SMALL_BOLD), Paragraph("<b>Key Prevention Strategy</b>", SMALL_BOLD)],
[Paragraph("Bile duct injury", BODY), Paragraph("CVS; intraoperative cholangiography; early conversion if anatomy unclear; go fundus-first", BODY)],
[Paragraph("Major vessel injury", BODY), Paragraph("Never aim trocar at spine; use hand as brake; open technique for high-risk patients", BODY)],
[Paragraph("Gallbladder perforation / spilled stones", BODY), Paragraph("Gentle dissection; use retrieval bag; retrieve all spilled stones meticulously", BODY)],
[Paragraph("Port-site hernia", BODY), Paragraph("Close fascial defects at all ≥10mm ports", BODY)],
[Paragraph("CO2 embolism", BODY), Paragraph("Low insufflation pressure; avoid Trendelenburg until peritoneum established", BODY)],
[Paragraph("Pancreatitis", BODY), Paragraph("Intraoperative cholangiography; ERCP for retained stones if suspected", BODY)],
[Paragraph("Wound infection", BODY), Paragraph("Perioperative antibiotics in acute cholecystitis; retrieval bag prevents GB contamination", BODY)],
]
prev_tbl = Table(prev_data, colWidths=[5*cm, 12*cm])
prev_tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,0), C_DARK_BLUE),("TEXTCOLOR",(0,0),(-1,0),C_WHITE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[C_LIGHT_BLUE,C_WHITE]),
("BOX",(0,0),(-1,-1),1,C_DARK_BLUE),
("INNERGRID",(0,0),(-1,-1),0.4,C_GREY),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING",(0,0),(-1,-1),6),("VALIGN",(0,0),(-1,-1),"TOP"),
]))
story.append(prev_tbl)
story.append(Spacer(1, 10))
# Summary box
summ_tbl = Table([[
Paragraph("<b>KEY TAKEAWAYS</b>", ParagraphStyle("kt", fontName="Helvetica-Bold", fontSize=11, textColor=C_WHITE, alignment=TA_CENTER))
],[
Paragraph(
"• Overall mortality <0.3%; complications 10-15% of cases\n"
"• Bile duct injury is the most feared complication: 0.4-0.6% (LC) vs 0.1-0.2% (open)\n"
"• Only 15% of BDIs are recognised intraoperatively\n"
"• Critical View of Safety (CVS) is the single most important preventive measure\n"
"• When in doubt — stop, call for help, convert, or use intraoperative cholangiography\n"
"• Spilled stones must always be retrieved to prevent late abscesses\n"
"• Complex BDI (E1-E5) mandates referral to specialist HBP centre\n"
"• MDT (surgeon + radiologist + endoscopist) is essential for post-op complication management",
BULLET)
]], colWidths=[w])
summ_tbl.setStyle(TableStyle([
("BACKGROUND",(0,0),(-1,0),C_DARK_BLUE),
("BACKGROUND",(0,1),(-1,1),C_SKY),
("BOX",(0,0),(-1,-1),2,C_DARK_BLUE),
("TOPPADDING",(0,0),(-1,-1),8),("BOTTOMPADDING",(0,0),(-1,-1),8),
("LEFTPADDING",(0,0),(-1,-1),10),
("ROUNDEDCORNERS",[8,8,8,8]),
]))
story.append(summ_tbl)
story.append(Spacer(1, 6))
story.append(Paragraph(
"References: Maingot's Abdominal Operations 12e | Bailey & Love's Short Practice of Surgery 28e | "
"Mulholland & Greenfield's Surgery 7e | Tintinalli's Emergency Medicine",
SOURCE))
# ── BUILD ──────────────────────────────────────────────────────────────────────
def on_first_page(canvas, doc):
pass
def on_later_pages(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 7.5)
canvas.setFillColor(C_GREY)
canvas.drawCentredString(A4[0]/2, 1.2*cm, f"Complications of Laparoscopic Cholecystectomy | Page {doc.page}")
canvas.restoreState()
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_later_pages)
print(f"PDF saved to: {OUTPUT}")
cd /tmp/workspace/lap-chole-pdf && python generate_pdf.py 2>&1
~/lap-chole-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/lap-chole-pdf/generate_pdf.py'
cd /tmp/workspace/lap-chole-pdf && python generate_pdf.py 2>&1
python3 -c "import reportlab.graphics.shapes as s; print(dir(s))"
~/lap-chole-pdf/generate_pdf.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/lap-chole-pdf/generate_pdf.py'
cd /tmp/workspace/lap-chole-pdf && sed -i 's/RoundedRect(/Rect(/g' generate_pdf.py && python generate_pdf.py 2>&1
cd /tmp/workspace/lap-chole-pdf && sed -i '/from reportlab.graphics.charts.flowchart import FlowChart/d' generate_pdf.py && python generate_pdf.py 2>&1
ls -lh /tmp/workspace/lap-chole-pdf/Laparoscopic_Cholecystectomy_Complications.pdf
Laparoscopic Cholecystectomy Complications
PDF Document · PDF
Lumbar hernia and lumbar triangle Illustrative diagrams


| Boundary | Structure |
|---|---|
| Anterior | Posterior edge of external oblique muscle |
| Posterior (medial) | Anterior extent of latissimus dorsi muscle |
| Inferior (base) | Iliac crest |
| Floor | Lumbar fascia (internal oblique) |
| Roof | Skin + subcutaneous tissue (superficial) |
| Boundary | Structure |
|---|---|
| Superior | 12th rib + serratus posterior inferior muscle |
| Medial | Erector spinae / sacrospinalis muscles |
| Lateral | Posterior border of internal oblique muscle |
| Floor | Transversalis fascia |
| Roof | Entirely covered by latissimus dorsi muscle |
| Also contains | Quadratus lumborum posteriorly |





| Primary | Secondary (Acquired) | |
|---|---|---|
| Cause | Congenital or spontaneous | Trauma, surgery, infection, poliomyelitis |
| Specific causes | Developmental weakness | Flank/back trauma, iliac crest bone graft donor site, renal surgery, back surgery |
| Feature | Triangle of Petit | Triangle of Grynfeltt |
|---|---|---|
| Position | Inferior | Superior |
| Frequency | More common | Less common (but larger space) |
| Superior border | - | 12th rib + serratus posterior inferior |
| Medial border | Latissimus dorsi | Erector spinae / sacrospinalis |
| Lateral/anterior border | External oblique | Internal oblique (posterior border) |
| Base | Iliac crest | Quadratus lumborum |
| Floor | Lumbar fascia (int. oblique) | Transversalis fascia |
| Roof | Skin | Latissimus dorsi |
| Depth | Superficial | Deep (hidden under latissimus) |