Detail about complications of laparoscopic cholecystectomy Flowcharts Illustrative diagrams Give inPDF

Reading File
Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Reading File
Reading File
Loading Image
Running Command

mkdir -p /tmp/workspace/lap-chole-pdf && ls /tmp/workspace/lap-chole-pdf/

Writing File

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

cd /tmp/workspace/lap-chole-pdf && python generate_pdf.py 2>&1

Editing File

~/lap-chole-pdf/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/tmp/workspace/lap-chole-pdf/generate_pdf.py'
Running Command

cd /tmp/workspace/lap-chole-pdf && python generate_pdf.py 2>&1

Running Command

python3 -c "import reportlab.graphics.shapes as s; print(dir(s))"

Editing File

~/lap-chole-pdf/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/tmp/workspace/lap-chole-pdf/generate_pdf.py'
Running Command

cd /tmp/workspace/lap-chole-pdf && sed -i 's/RoundedRect(/Rect(/g' generate_pdf.py && python generate_pdf.py 2>&1

Running Command

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

Running Command

ls -lh /tmp/workspace/lap-chole-pdf/Laparoscopic_Cholecystectomy_Complications.pdf

Your PDF is ready for download:

Laparoscopic Cholecystectomy Complications

PDF Document · PDF


What's Inside (8 pages, 419 KB)

Page 1 - Title & Overview
  • Colour title banner with source citations
  • Overview box with key statistics (mortality <0.3%, complication rate 10-15%, BDI incidence 0.4-0.6%)
  • Master classification table covering all 6 complication categories with incidence data
Page 2 - Flowchart 1: Intraoperative BDI Recognition & Management
  • Decision diamond: intraop vs post-op recognition
  • Two-branch pathway: intraop repair vs post-op workup
  • Strasberg grade-based management table (A through E5)
  • Terminal box: referral to specialist HBP unit
Page 3 - Flowchart 2: Post-operative Complication Assessment
  • Starting node: new symptoms post-LC
  • Symptom assessment grid
  • Three parallel pathways: Biliary / Vascular / General surgical
  • Each pathway with specific investigation and management steps
  • MDT review box at the base
Page 4 - Detailed Biliary Complications
  • Bile Duct Injury: mechanism, risk factors (including learning curve data - 1.7% risk at first case → 0.17% by 50th), recognition, prevention, management
  • Bile Leak: sources (cystic stump, Luschka's ducts), investigation, management
  • Retained/Spilled Stones: frequency, consequences, prevention
Page 5 - Vascular, Pneumoperitoneum & Trocar Complications
  • Vascular: cystic artery, RHA, major vessel (aorta/IVC/iliac), abdominal wall
  • Pneumoperitoneum: CO2 embolism, vagal reflex, arrhythmias, hypercarbic acidosis, subcutaneous emphysema, pneumothorax
  • Trocar/Access: bowel perforation, GU injury, port-site hernia, wound infection, port-site metastasis
Page 6 - Strasberg Classification with Textbook Diagram
  • Embedded schematic image from Bailey & Love's Surgery 28th Edition
  • Full A-E5 classification table with anatomical description and treatment for each grade
Page 7 - Flowchart 3: Difficult Cholecystectomy - Bail-out Strategies
  • Warning signs triggering bail-out decision
  • 5 bail-out options: Convert to open / Subtotal cholecystectomy / Tube cholecystostomy / Fundus-first / IOC
  • Documentation reminder
Page 8 - Prevention & Key Takeaways
  • Critical View of Safety (CVS) - all 3 requirements
  • Prevention table per complication category
  • Summary box with 8 key clinical pearls

Sources consulted:
  • Maingot's Abdominal Operations, p.1025
  • Bailey & Love's Short Practice of Surgery 28e (Strasberg classification, complication rates)
  • Mulholland & Greenfield's Surgery 7e (BDI incidence 0.3-0.6%)
  • Tintinalli's Emergency Medicine (complication tables)

Lumbar hernia and lumbar triangle Illustrative diagrams

Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Loading Image
Loading Image
Loading Image
Reading File
Loading Image
Loading Image
Reading File
Reading File
I now have all the content and images. Let me build a comprehensive, richly illustrated response:

Lumbar Hernia and Lumbar Triangles

A lumbar hernia is a protrusion through a defect in the posterior abdominal wall in the lumbar region. It is rare, occurring through one of two anatomical weak points - the Triangle of Petit (inferior) or the Triangle of Grynfeltt (superior).

Anatomy of the Lumbar Region

The lumbar region is bounded by:
  • Superiorly - 12th rib
  • Inferiorly - iliac crest
  • Posteriorly - erector spinae muscles
  • Anteriorly - vertical line from anterior tip of 12th rib to iliac crest

Textbook Anatomy Diagram - Both Lumbar Triangles

(Maingot's Abdominal Operations, p. 168)
Lumbar hernia anatomy — superior and inferior lumbar triangles showing hernia sacs (arrows)

Detailed Anatomy Illustration - Both Triangles Labelled

(Current Surgical Therapy 14e - FIG. 5)
Pertinent anatomy for superior and inferior lumbar hernias — 12th rib, latissimus dorsi, erector spinae, internal oblique, external oblique, quadratus lumborum, iliac crest, both triangles labelled
This is the key diagram - it shows both triangles in their muscular context with all boundaries labelled.

The Two Lumbar Triangles

1. Inferior Lumbar Triangle of Petit (More Common)

BoundaryStructure
AnteriorPosterior edge of external oblique muscle
Posterior (medial)Anterior extent of latissimus dorsi muscle
Inferior (base)Iliac crest
FloorLumbar fascia (internal oblique)
RoofSkin + subcutaneous tissue (superficial)
  • This is the more common site for primary lumbar hernias
  • The triangle may be absent when the lower border of latissimus dorsi overlaps the external oblique
  • Located superficially in the posterior abdominal wall

2. Superior Lumbar Triangle of Grynfeltt (Less Common but Larger)

BoundaryStructure
Superior12th rib + serratus posterior inferior muscle
MedialErector spinae / sacrospinalis muscles
LateralPosterior border of internal oblique muscle
FloorTransversalis fascia
RoofEntirely covered by latissimus dorsi muscle
Also containsQuadratus lumborum posteriorly
  • Deeper than Petit's triangle
  • Covered posteriorly by latissimus dorsi (so the hernia is hidden beneath it)
  • Has transversalis fascia as its floor
  • Though less common, it is actually the larger of the two spaces

Clinical Photos from Bailey & Love's Surgery 28e

Inferior lumbar hernia (Triangle of Petit) — Clinical photograph:
Inferior lumbar hernia containing caecum, appendix and small bowel — Bailey & Love's Surgery
Figure 64.23 — Inferior lumbar hernia containing caecum, appendix and small bowel.

Left superior lumbar hernia (Triangle of Grynfeltt) — Clinical photograph + CT:
Superior lumbar hernia clinical appearance (a) and CT scan (b) — Bailey & Love
CT scan of right-sided superior lumbar hernia emerging below 12th rib at level of right kidney
Figure 64.24(b) — CT scan of a right-sided superior lumbar hernia. Emerging just below the 12th rib, it is level with the right kidney and right lobe of liver.

CT Imaging of Lumbar Hernia

CT scan showing lumbar hernia at Triangle of Petit (Current Surgical Therapy 14e):
CT cross-section: lumbar hernia at right-sided triangle of Petit with blue arrow indicating defect
FIG. 4 — Lumbar hernia on the patient's right side at the triangle of Petit.

CT showing interparietal lumbar hernia with disrupted muscle layers:
CT scan showing disrupted medial edges of internal oblique and transversus abdominis with intact external oblique muscle
FIG. 7 — Lumbar hernia CT scan showing a disrupted internal oblique and transversus abdominis with an intact external oblique (interparietal type).

Lumbar Hernia - Clinical Details

Types

PrimarySecondary (Acquired)
CauseCongenital or spontaneousTrauma, surgery, infection, poliomyelitis
Specific causesDevelopmental weaknessFlank/back trauma, iliac crest bone graft donor site, renal surgery, back surgery

Epidemiology

  • Rare - fewer than 300 cases reported in literature
  • Most present in adults >50 years old
  • 2/3 in males
  • Left side more common
  • Bilateral lumbar hernias reported

Presentation

  • Soft, reducible swelling in lower posterior abdomen / flank
  • Vague dullness or focal pain in the flank / lower back, worsened with movement
  • Swelling increases with straining or Valsalva manoeuvre
  • Contents may include: retroperitoneal fat (most common), colon, kidney, small bowel

Why Strangulation is Rare

At least two of the three boundaries of each triangle are soft and muscular - this prevents a rigid constricting ring from forming. However, hernias do progressively enlarge over time and can overhang the iliac crest.

Differential Diagnosis (Bailey & Love)

  • Lipoma
  • Incisional hernia (from prior renal operation)
  • Cold (tuberculous) abscess
  • Pseudohernia - due to subcostal nerve injury during kidney operation causing denervation / flank muscle atrophy

Investigations

  • USS - first-line; confirms reducibility
  • CT scan - gold standard; defines defect, contents, muscle layer disruption; distinguishes true hernia from pseudohernia/eventration

Treatment

Surgery is recommended regardless of symptoms because:
  1. Hernias progressively enlarge
  2. Risk of incarceration
  3. Bony boundaries (ribs, iliac crest) make primary tissue closure difficult - mesh reinforcement is required

Surgical Approach

Open repair:
  • Modified lateral decubitus position; kidney rest to open the lumbar space
  • Oblique skin incision over hernia
  • Latissimus dorsi may need takedown for superior triangle hernias
  • Sac identified, opened, contents reduced, sac inverted or excised
  • Small defect with healthy tissue: primary suture repair (nylon/polypropylene)
  • Large defect or weak tissues: prosthetic mesh placed between peritoneum and abdominal wall, fixed with non-absorbable sutures
Laparoscopic repair (TAPP approach):
  • Gaining popularity especially for small hernias
  • Patient in semilateral position; ports placed away from defect
  • Peritoneum incised above hernia, dissected back; defect exposed
  • Contents reduced; mesh fixed with ample overlap (3-5 cm in each direction)
  • Retroperitoneoscopic approach: balloon dissector in lateral retroperitoneal space; avoids entering peritoneal cavity

Quick Comparison Summary

FeatureTriangle of PetitTriangle of Grynfeltt
PositionInferiorSuperior
FrequencyMore commonLess common (but larger space)
Superior border-12th rib + serratus posterior inferior
Medial borderLatissimus dorsiErector spinae / sacrospinalis
Lateral/anterior borderExternal obliqueInternal oblique (posterior border)
BaseIliac crestQuadratus lumborum
FloorLumbar fascia (int. oblique)Transversalis fascia
RoofSkinLatissimus dorsi
DepthSuperficialDeep (hidden under latissimus)

Sources:
  • Maingot's Abdominal Operations, p. 168
  • Bailey & Love's Short Practice of Surgery 28e, Figures 64.23-64.24
  • Current Surgical Therapy 14e, Figs. 4-7
This is a shared conversation. Sign in to Orris to start your own chat.