~/surgical-nutrition/generate_nutrition_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
# ── Color palette ──────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MID_BLUE = colors.HexColor("#2e6da4")
LIGHT_BLUE = colors.HexColor("#dce9f7")
TEAL = colors.HexColor("#1a7a7a")
LIGHT_TEAL = colors.HexColor("#d4f0ef")
DARK_RED = colors.HexColor("#8b1a1a")
LIGHT_RED = colors.HexColor("#fde8e8")
AMBER = colors.HexColor("#7a5200")
LIGHT_AMBER = colors.HexColor("#fff4cc")
GREEN = colors.HexColor("#1a5e20")
LIGHT_GREEN = colors.HexColor("#d8f0db")
HEADER_BG = colors.HexColor("#0d2740")
WHITE = colors.white
GREY_BG = colors.HexColor("#f4f7fb")
PAGE_W, PAGE_H = A4
LEFT_MARGIN = RIGHT_MARGIN = 1.5*cm
TOP_MARGIN = 1.8*cm
BOT_MARGIN = 1.5*cm
CONTENT_W = PAGE_W - LEFT_MARGIN - RIGHT_MARGIN
def make_pdf(outpath):
doc = SimpleDocTemplate(
outpath,
pagesize=A4,
leftMargin=LEFT_MARGIN, rightMargin=RIGHT_MARGIN,
topMargin=TOP_MARGIN, bottomMargin=BOT_MARGIN,
)
styles = getSampleStyleSheet()
# ── Custom paragraph styles ─────────────────────────────────────────────
title_s = ParagraphStyle("title_s", fontSize=15, leading=18,
textColor=WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold",
spaceAfter=2)
sub_title_s = ParagraphStyle("sub_title_s", fontSize=9.5, leading=12,
textColor=colors.HexColor("#c8ddf7"), alignment=TA_CENTER,
fontName="Helvetica", spaceAfter=0)
sec_hdr_s = ParagraphStyle("sec_hdr_s", fontSize=9.5, leading=11,
textColor=WHITE, fontName="Helvetica-Bold", spaceBefore=2, spaceAfter=1)
cell_s = ParagraphStyle("cell_s", fontSize=7.8, leading=10,
textColor=colors.HexColor("#1a1a1a"), fontName="Helvetica",
spaceAfter=0, spaceBefore=0)
cell_b = ParagraphStyle("cell_b", fontSize=7.8, leading=10,
textColor=DARK_BLUE, fontName="Helvetica-Bold",
spaceAfter=0, spaceBefore=0)
cell_red = ParagraphStyle("cell_red", fontSize=7.8, leading=10,
textColor=DARK_RED, fontName="Helvetica-Bold",
spaceAfter=0, spaceBefore=0)
foot_s = ParagraphStyle("foot_s", fontSize=6.5, leading=8,
textColor=colors.HexColor("#555555"), fontName="Helvetica-Oblique",
spaceBefore=2)
note_s = ParagraphStyle("note_s", fontSize=7.5, leading=9.5,
textColor=GREEN, fontName="Helvetica-Bold",
spaceBefore=2)
story = []
# ═══════════════════════════════════════════════════════════════════════
# HEADER BANNER
# ═══════════════════════════════════════════════════════════════════════
header_data = [[
Paragraph("SURGICAL NUTRITION SUPPORT", title_s),
Paragraph("Indications · Techniques · Complications · TPN Calculation", sub_title_s),
]]
banner = Table([[Paragraph("SURGICAL NUTRITION SUPPORT", title_s)],
[Paragraph("Indications · Techniques · Complications · TPN Calculation for Whipple's / Pancreatic Leak ICU Patient", sub_title_s)]],
colWidths=[CONTENT_W])
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADER_BG),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROUNDEDCORNERS", [4,4,4,4]),
]))
story.append(banner)
story.append(Spacer(1, 4*mm))
# ═══════════════════════════════════════════════════════════════════════
# ROW 1: Indications | Routes & Techniques
# ═══════════════════════════════════════════════════════════════════════
# ── TABLE 1 : Indications ───────────────────────────────────────────────
def section_header(text, bg):
t = Table([[Paragraph(text, sec_hdr_s)]], colWidths=[(CONTENT_W/2)-2*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
]))
return t
def section_header_full(text, bg):
t = Table([[Paragraph(text, sec_hdr_s)]], colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
]))
return t
P = lambda txt, s=cell_s: Paragraph(txt, s)
PB = lambda txt: Paragraph(txt, cell_b)
PR = lambda txt: Paragraph(txt, cell_red)
# Indications table
ind_data = [
[PB("Category"), PB("Indications")],
[P("MALNUTRITION\n(Pre-existing)"),
P("• Weight loss >5% in 1 month or >10% in 6 months\n• BMI <18.5 kg/m² with illness\n• NRS-2002 ≥3 or NUTRIC ≥5")],
[P("GI\nINACCESSIBILITY"),
P("• Intestinal obstruction / ileus >5–7 days\n• Short-bowel syndrome / high-output fistula\n• Severe pancreatitis with intolerance to EN")],
[P("POST-OP\nHIGH RISK"),
P("• Major GI surgery (Whipple's, oesophagectomy)\n• Pancreatic / duodenal leak / fistula\n• Anastomotic leak, prolonged ICU stay")],
[P("CRITICALLY ILL\n(ICU)"),
P("• Cannot tolerate EN (vasopressor dependent, gut ischaemia)\n• EN failing to meet >60% energy needs after 1 week\n• Burns, polytrauma, bone marrow transplant")],
[P("PERIOPERATIVE\nEARLY FEEDING"),
P("• Malnourished patients: start EN within 24–48 h post-op\n• Otherwise healthy: 10 days of partial starvation tolerated\n• Carbohydrate loading ≤2 h before surgery (ERAS)")],
]
ind_col = [(CONTENT_W/2 - 2*mm)*x for x in [0.30, 0.70]]
ind_t = Table(ind_data, colWidths=ind_col, repeatRows=1)
ind_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_BLUE),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), LIGHT_BLUE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#b0c8e8")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
# Routes & Techniques table
rte_data = [
[PB("Route"), PB("Technique / Details")],
[P("ORAL\nSUPPLEMENTS"), P("• 200 kcal + 2 g N / 200 mL carton\n• First-line; add to diet; wean tube feeds")],
[P("NASOGASTRIC\n(NGT/fine-bore)"), P("• Pre-pyloric; Ryle's or fine-bore tube\n• Safe unless high aspiration risk\n• Confirm CXR before use")],
[P("NASOJEJUNAL\n(NJT)"), P("• Post-pyloric; bypasses stomach\n• Preferred in gastroparesis, Whipple's, post-op ileus\n• Reduces aspiration risk")],
[P("GASTROSTOMY\n(PEG / Surg)"), P("• Long-term EN (>4 weeks)\n• Radiological, endoscopic or surgical placement\n• Avoids NGT discomfort")],
[P("JEJUNOSTOMY\n(Feeding)"), P("• Placed intra-op during Whipple / oesophagectomy\n• Direct post-pyloric delivery; bypasses anastomosis\n• Gold standard post-Whipple EN")],
[P("PERIPHERAL\nPN (PPN)"), P("• Dextrose 5–10%, protein 3%\n• Short-term (<14 days); low osmolarity\n• Not for severe malnutrition")],
[P("TOTAL PN\n(TPN / CPN)"), P("• Central vein (SVC via subclavian/IJV/PICC)\n• Dextrose 15–25%, AA 3–5%; all macronutrients\n• Confirmed CXR before commencing")],
]
rte_col = [(CONTENT_W/2 - 2*mm)*x for x in [0.30, 0.70]]
rte_t = Table(rte_data, colWidths=rte_col, repeatRows=1)
rte_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_TEAL),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_TEAL),
("BACKGROUND", (0,4), (-1,4), WHITE),
("BACKGROUND", (0,5), (-1,5), LIGHT_TEAL),
("BACKGROUND", (0,6), (-1,6), WHITE),
("BACKGROUND", (0,7), (-1,7), LIGHT_TEAL),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#a0d4d4")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
# Put both side by side
col_spacer = 4*mm
left_col = [section_header("1. INDICATIONS FOR NUTRITIONAL SUPPORT", DARK_BLUE), ind_t]
right_col = [section_header("2. ROUTES & TECHNIQUES", TEAL), rte_t]
left_items = []
right_items = []
for item in left_col: left_items.append(item)
for item in right_col: right_items.append(item)
two_col = Table(
[[left_col, right_col]],
colWidths=[(CONTENT_W/2)-col_spacer/2, (CONTENT_W/2)-col_spacer/2],
)
two_col.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("INNERGRID", (0,0), (-1,-1), 0, WHITE),
("BOX", (0,0), (-1,-1), 0, WHITE),
]))
story.append(two_col)
story.append(Spacer(1, 4*mm))
# ═══════════════════════════════════════════════════════════════════════
# ROW 2: Complications (EN left, PN right)
# ═══════════════════════════════════════════════════════════════════════
# EN complications
en_data = [
[PB("Category"), PB("Complication")],
[P("TUBE\nRELATED"),
P("• Malposition / misplacement\n• Displacement / blockage / breakage\n• Nasal erosion, sinusitis, oesophagitis")],
[P("GI"),
P("• Diarrhoea (most common, up to 30%)\n• Nausea, vomiting, bloating, cramps\n• Aspiration pneumonia (1–4%)\n• Constipation")],
[P("METABOLIC"),
P("• Refeeding syndrome (hypophosphataemia)\n• Electrolyte disorders (↓K, ↓Mg)\n• Hyperglycaemia\n• Drug interactions via tube")],
[P("INFECTIOUS"),
P("• Aspiration pneumonitis/pneumonia\n• Contamination of feed / equipment\n• Gastrostomy site infection")],
]
en_col = [(CONTENT_W/2 - 2*mm)*x for x in [0.28, 0.72]]
en_t = Table(en_data, colWidths=en_col, repeatRows=1)
en_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), AMBER),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_AMBER),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_AMBER),
("BACKGROUND", (0,4), (-1,4), WHITE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#e8d4a0")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
# PN complications
pn_data = [
[PB("Category"), PB("Complication")],
[P("INSERTION\n(Technical)"),
P("• Pneumothorax (0.5–1% subclavian)\n• Haemothorax, hydrothorax\n• Subclavian artery injury\n• Air / catheter embolism, arrhythmia")],
[P("LINE /\nCATHETER"),
P("• CLA-BSI / line sepsis (up to 15%!)\n• Venous thrombosis, SVC occlusion, PE\n• Line blockage (flush regularly)\n• Fungal infection → uveitis / endocarditis")],
[P("METABOLIC"),
P("• Hyperglycaemia (monitor 6-hrly)\n• Hypoglycaemia (abrupt cessation)\n• Hypophosphataemia / refeeding syndrome\n• ↓K, ↓Mg, ↓Ca during feeding")],
[P("HEPATIC &\nOTHER"),
P("• Hepatic steatosis / cholestasis (long-term)\n• Gallstone formation (bile stasis)\n• CO₂ retention (overfeeding)\n• Intestinal mucosal atrophy / bacterial translocation")],
]
pn_col = [(CONTENT_W/2 - 2*mm)*x for x in [0.28, 0.72]]
pn_t = Table(pn_data, colWidths=pn_col, repeatRows=1)
pn_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_RED),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_RED),
("BACKGROUND", (0,4), (-1,4), WHITE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#e8b0b0")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
two_col2 = Table(
[[
[section_header("3. COMPLICATIONS OF ENTERAL NUTRITION", AMBER), en_t],
[section_header("4. COMPLICATIONS OF PARENTERAL NUTRITION", DARK_RED), pn_t],
]],
colWidths=[(CONTENT_W/2)-col_spacer/2, (CONTENT_W/2)-col_spacer/2],
)
two_col2.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(two_col2)
story.append(Spacer(1, 4*mm))
# ═══════════════════════════════════════════════════════════════════════
# PAGE BREAK → TPN CALCULATION
# ═══════════════════════════════════════════════════════════════════════
story.append(PageBreak())
# Banner page 2
banner2 = Table([[Paragraph("TPN CALCULATION — POST-WHIPPLE'S ICU PATIENT", title_s)],
[Paragraph("60 kg Male · Pancreaticoduodenal Leak · ICU (Critically Ill Hypercatabolic State) | Source: Sabiston / Schwartz / Bailey", sub_title_s)]],
colWidths=[CONTENT_W])
banner2.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADER_BG),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(banner2)
story.append(Spacer(1, 4*mm))
# ── Clinical context ───────────────────────────────────────────────────
ctx_s = ParagraphStyle("ctx_s", fontSize=7.8, leading=10.5,
textColor=DARK_BLUE, fontName="Helvetica",
spaceAfter=0, leftIndent=4)
ctx_bold = ParagraphStyle("ctx_bold", fontSize=7.8, leading=10.5,
textColor=HEADER_BG, fontName="Helvetica-Bold", spaceAfter=0, leftIndent=4)
context_rows = [
[PB("Clinical Scenario"),
P("60 kg male, post-Whipple's (pancreaticoduodenectomy), Day 1–7 ICU with pancreaticoduodenal leak (grade B/C POPF). "
"Haemodynamically stabilising. Severely catabolic / hypermetabolic. EN via feeding jejunostomy preferred if tolerated; "
"TPN if jejunostomy not feasible or leak causing intolerance.")],
]
ctx_t = Table(context_rows, colWidths=[CONTENT_W*0.20, CONTENT_W*0.80])
ctx_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#b0c8e8")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
story.append(ctx_t)
story.append(Spacer(1, 3*mm))
# ── Step 1: Energy ─────────────────────────────────────────────────────
story.append(section_header_full("STEP 1 — ESTIMATE DAILY ENERGY REQUIREMENT", DARK_BLUE))
eng_data = [
[PB("Method"), PB("Formula / Value"), PB("For 60 kg Patient"), PB("Notes")],
[P("Harris-Benedict\n(BEE)"),
P("BEE(M) = 66 + 13.7W + 5H − 6.8A\n(W=kg, H=cm, A=yrs)"),
P("~1500 kcal/day (70 yr, 170 cm)\n~1600 kcal/day (50 yr, 170 cm)"),
P("Multiply by stress factor\n(see below)")],
[P("Simple ICU Rule\n(Hypocaloric)"),
P("< 20 kcal/kg/day (first week ICU)\nTarget 15–20 kcal/kg/day"),
P("900–1200 kcal/day"),
P("Schwartz recommendation;\nreduces hyperglycaemia & infections")],
[P("Full Replacement\n(Day 3–7 onwards)"),
P("25–30 kcal/kg/day (critically ill)"),
P("1500–1800 kcal/day"),
P("Ramp up gradually over 4–7 days;\navoid overfeeding")],
[PR("USED IN THIS CASE"),
PR("Hypocaloric first 48 h: 15 kcal/kg/day\nThen escalate to 25 kcal/kg/day"),
PR("Day 1–2: 900 kcal/day\nDay 3+: 1500 kcal/day"),
PR("Avoids CO₂ retention,\nhyperglycaemia in post-op")],
]
eng_col = [CONTENT_W*x for x in [0.18, 0.30, 0.28, 0.24]]
eng_t = Table(eng_data, colWidths=eng_col, repeatRows=1)
eng_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,4), (-1,4), LIGHT_RED),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#b0c8e8")),
("ROWBACKGROUNDS", (0,1), (-1,3), [LIGHT_BLUE, WHITE, LIGHT_BLUE]),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
story.append(eng_t)
story.append(Spacer(1, 3*mm))
# ── Step 2: Macronutrients ─────────────────────────────────────────────
story.append(section_header_full("STEP 2 — MACRONUTRIENT COMPOSITION (Target: Day 3+, 1500 kcal)", TEAL))
macro_data = [
[PB("Nutrient"), PB("Standard Requirement"), PB("For 60 kg ICU Post-Whipple"), PB("Volume / Concentration"), PB("Kcal")],
[P("DEXTROSE\n(Carbohydrate)"),
P("50–60% of total calories\nMax 4–5 mg/kg/min glucose"),
P("750–900 kcal from dextrose\n= 187–225 g dextrose"),
P("D50% solution:\n375–450 mL\nor D70%: 270–320 mL"),
P("3.4 kcal/g")],
[P("FAT\n(Lipid emulsion)"),
P("20–30% total calories;\nMax 1 g/kg/day (≤100 g/week\nfirst 7 days, soy-based)"),
P("300–450 kcal from fat\n= 33–50 g fat"),
P("20% Intralipid: 165–250 mL\n(2 kcal/mL)"),
P("9 kcal/g\n(emulsion 2 kcal/mL)")],
[P("AMINO ACIDS\n(Protein)"),
P("ICU critically ill:\n1.5–2.0 g/kg/day\nCalorie:N ratio = 100–150:1"),
P("90–120 g protein/day\n= 14.4–19.2 g Nitrogen\n(use 1.5 g/kg = 90 g)"),
P("10% AA solution: 900 mL\nor 15% AA: 600 mL"),
P("4 kcal/g\n(360–480 kcal)")],
]
macro_col = [CONTENT_W*x for x in [0.14, 0.23, 0.24, 0.24, 0.15]]
macro_t = Table(macro_data, colWidths=macro_col, repeatRows=1)
macro_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_TEAL),
("BACKGROUND", (0,2), (-1,2), WHITE),
("BACKGROUND", (0,3), (-1,3), LIGHT_TEAL),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#a0d4d4")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
story.append(macro_t)
story.append(Spacer(1, 3*mm))
# ── Step 3: Electrolytes + Micronutrients ──────────────────────────────
two_col_w = (CONTENT_W - 4*mm) / 2
# Electrolytes
story.append(section_header_full("STEP 3 — ELECTROLYTES, VITAMINS & TRACE ELEMENTS (Daily additions to TPN bag)", GREEN))
elec_data = [
[PB("Component"), PB("Daily Requirement"), PB("Notes / Monitoring")],
[P("Sodium (NaCl)"), P("60–150 mEq/day"), P("Adjust per serum Na; losses from fistula")],
[P("Potassium (KCl)"), P("60–120 mEq/day"), P("↑ requirement during refeeding; essential for +N balance")],
[P("Phosphate"), P("20–30 mmol/day"), P("Critical! Prevents refeeding hypophosphataemia")],
[P("Magnesium"), P("8–20 mEq/day"), P("Monitor daily; ↓ with PN initiation")],
[P("Calcium"), P("10–15 mEq/day"), P("Separate line if with phosphate (precipitate)")],
[P("Multivitamins"), P("1 vial MVI daily"), P("All fat + water-soluble vitamins; add to bag")],
[P("Vitamin K"), P("1 mg/day or 10 mg/week"), P("NOT in standard MVI; add separately weekly")],
[P("Trace elements"), P("1 vial trace elements daily"), P("Zn, Cu, Se, Cr, Mn; check after 28 days on TPN")],
[P("Zinc (extra)"), P("Additional 10–15 mg/day"), P("High output fistula/drain — replace losses")],
[P("Regular Insulin"), P("Per sliding scale / add to\nbag (0.1 U/g dextrose)"),
P("Glucose target: 140–180 mg/dL; check q6h")],
[P("Heparin (optional)"), P("1000 U/L of TPN"), P("Reduces catheter thrombosis risk")],
]
elec_col = [CONTENT_W*x for x in [0.22, 0.33, 0.45]]
elec_t = Table(elec_data, colWidths=elec_col, repeatRows=1)
elec_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREEN),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_GREEN, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#a0d4a0")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
story.append(elec_t)
story.append(Spacer(1, 3*mm))
# ── Step 4: Final TPN Prescription Summary ──────────────────────────────
story.append(section_header_full("STEP 4 — SAMPLE TPN PRESCRIPTION (60 kg, Day 3 Post-Whipple, ICU)", DARK_RED))
rx_data = [
[PB("Component"), PB("Amount"), PB("Source"), PB("Kcal")],
[P("50% Dextrose (D50)"), P("500 mL"), P("Dextrose = 250 g"), P("850 kcal")],
[P("10% Amino Acids"), P("750 mL"), P("Protein = 75 g (1.25 g/kg)"), P("300 kcal")],
[P("20% Lipid Emulsion"), P("250 mL"), P("Fat = 50 g"), P("500 kcal")],
[P("Sodium (as NaCl)"), P("80 mEq"), P("Added to bag"), P("—")],
[P("Potassium (as KCl)"), P("80 mEq"), P("Added to bag"), P("—")],
[P("Phosphate (Na₂HPO₄)"), P("30 mmol"), P("Added to bag"), P("—")],
[P("Magnesium (MgSO₄)"), P("15 mEq"), P("Added to bag"), P("—")],
[P("Calcium gluconate"), P("10 mEq"), P("Add separately or to bag"), P("—")],
[P("Multivitamins (MVI)"), P("1 vial"), P("Added to bag"), P("—")],
[P("Trace elements"), P("1 vial"), P("Added to bag"), P("—")],
[P("Vitamin K"), P("1 mg"), P("Weekly or daily addition"), P("—")],
[P("Regular Insulin"), P("per glucose levels"), P("Sliding scale / add to bag"), P("—")],
[P("TOTAL VOLUME"), P("~1500–2000 mL/24h"), P("Adjust per fluid balance"), P("~1650 kcal")],
[PR("N:Cal Ratio"), PR("~9 g N : 1650 kcal"), PR("Ratio 1:183 (target 1:100–150)"), PR("Increase AA if tolerated")],
]
rx_col = [CONTENT_W*x for x in [0.26, 0.18, 0.33, 0.23]]
rx_t = Table(rx_data, colWidths=rx_col, repeatRows=1)
rx_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,-2), (-1,-2), LIGHT_BLUE),
("BACKGROUND", (0,-1), (-1,-1), LIGHT_RED),
("ROWBACKGROUNDS", (0,1), (-1,-3), [LIGHT_RED, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#e8b0b0")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
story.append(rx_t)
story.append(Spacer(1, 3*mm))
# ── Monitoring & Special Considerations ───────────────────────────────
mon_left = [
[PB("Monitoring — Daily"), PB("Monitoring — Weekly")],
[P("• Serum glucose (q6h)\n• Electrolytes: Na, K, PO₄, Mg, Ca\n"
"• Fluid balance, weight\n• Vital signs, urine output\n• Drain output (fistula losses)"),
P("• LFTs, bilirubin (hepatic steatosis)\n• Renal function (BUN, Cr)\n"
"• Triglycerides (if lipid emulsion)\n• Blood counts (CBC)\n"
"• Phosphate, Mg, Zn after 28 days")],
]
mon_data = Table(mon_left, colWidths=[CONTENT_W*0.50, CONTENT_W*0.50])
mon_data.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("BACKGROUND", (0,1), (-1,1), LIGHT_BLUE),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#b0c8e8")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 5),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
special_rows = [
[PB("Special Considerations: Post-Whipple Pancreatic Leak / POPF")],
[P("• Prefer EN via feeding JEJUNOSTOMY (placed intra-op) over TPN if haemodynamically stable — preserves gut barrier, ↓ infection, ↓ cost")],
[P("• If EN not tolerated due to leak-induced ileus or instability → TPN via central line (SVC)")],
[P("• Fistula / drain losses can cause massive Zn, electrolyte, protein losses → replace aggressively")],
[P("• Avoid hypocaloric for >7 days; escalate protein to 2 g/kg/day as patient improves (recovery phase)")],
[P("• Transition to oral/EN as soon as leak controlled and GI tract functional (use NJT or oral supplements)")],
[P("• Somatostatin analogues (octreotide) ± TPN used to reduce pancreatic secretion and allow fistula closure")],
]
spec_t = Table(special_rows, colWidths=[CONTENT_W])
spec_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_TEAL, WHITE, LIGHT_TEAL, WHITE, LIGHT_TEAL, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#a0d4d4")),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 4),
]))
two_col3 = Table(
[[
[section_header("MONITORING", MID_BLUE), mon_data],
[section_header("SPECIAL CONSIDERATIONS (WHIPPLE'S / POPF)", TEAL), spec_t],
]],
colWidths=[(CONTENT_W/2)-col_spacer/2, (CONTENT_W/2)-col_spacer/2],
)
two_col3.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(two_col3)
story.append(Spacer(1, 3*mm))
# ── Refeeding Syndrome Warning Box ─────────────────────────────────────
ref_data = [
[PR("⚠ REFEEDING SYNDROME RISK — Screen ALL malnourished surgical patients before commencing nutrition")],
[P("Risk factors: BMI <16, weight loss >15% in 3–6 months, starvation >10 days, low K/PO₄/Mg. "
"Start at 10 kcal/kg/day, increase over 4–7 days. Replace PO₄, K, Mg. "
"Give IV thiamine (Pabrinex) before starting feed in high-risk patients.")],
]
ref_t = Table(ref_data, colWidths=[CONTENT_W])
ref_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), LIGHT_RED),
("BACKGROUND", (0,1), (-1,1), colors.HexColor("#fff5f5")),
("GRID", (0,0), (-1,-1), 0.5, DARK_RED),
("TOPPADDING", (0,0),(-1,-1), 3),
("BOTTOMPADDING", (0,0),(-1,-1), 3),
("LEFTPADDING", (0,0),(-1,-1), 6),
("RIGHTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(ref_t)
story.append(Spacer(1, 2*mm))
# ── Footer ─────────────────────────────────────────────────────────────
foot_txt = ("Sources: Sabiston Textbook of Surgery 21e (Ch.34) | Bailey & Love's Short Practice of Surgery 28e (Ch.25) | "
"Schwartz's Principles of Surgery 11e (Ch.3) | ASPEN/SCCM Guidelines 2016/2022. "
"Prepared by Orris AI · For educational use only · Verify all doses against current institutional protocols.")
story.append(Paragraph(foot_txt, foot_s))
doc.build(story)
print(f"PDF written to {outpath}")
make_pdf("/tmp/workspace/surgical-nutrition/Surgical_Nutrition_TPN.pdf")