~/insulin-ref/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import PageTemplate, Frame
from reportlab.pdfgen import canvas as pdfcanvas
OUTPUT = "/home/daytona/workspace/insulin-ref/Insulin_Quick_Reference.pdf"
# ── colour palette ──────────────────────────────────────────────────────────
C_NAVY = colors.HexColor("#1a3a5c")
C_TEAL = colors.HexColor("#0d7377")
C_LTBL = colors.HexColor("#e8f4f8")
C_AMBER = colors.HexColor("#f5a623")
C_AMBER2 = colors.HexColor("#fff3cd")
C_RED = colors.HexColor("#c0392b")
C_LRED = colors.HexColor("#fde8e8")
C_GREEN = colors.HexColor("#1a7a4a")
C_LGRN = colors.HexColor("#e8f8ee")
C_GREY = colors.HexColor("#f2f4f7")
C_DKGRY = colors.HexColor("#4a4a4a")
C_WHITE = colors.white
W, H = A4 # 595 x 842 pt
# ── styles ──────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
sTitle = S("sTitle", fontName="Helvetica-Bold", fontSize=20,
textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=2)
sSub = S("sSub", fontName="Helvetica", fontSize=10,
textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=0)
sH1 = S("sH1", fontName="Helvetica-Bold", fontSize=11,
textColor=C_WHITE, alignment=TA_LEFT, spaceBefore=0, spaceAfter=0)
sH2 = S("sH2", fontName="Helvetica-Bold", fontSize=9,
textColor=C_NAVY, alignment=TA_LEFT, spaceBefore=4, spaceAfter=2)
sBody = S("sBody", fontName="Helvetica", fontSize=8,
textColor=C_DKGRY, leading=11, spaceBefore=1, spaceAfter=1)
sBold = S("sBold", fontName="Helvetica-Bold", fontSize=8,
textColor=C_DKGRY, leading=11)
sSmall = S("sSmall", fontName="Helvetica", fontSize=7,
textColor=C_DKGRY, leading=10)
sNote = S("sNote", fontName="Helvetica-Oblique", fontSize=7,
textColor=colors.HexColor("#666666"), leading=10)
sCtr = S("sCtr", fontName="Helvetica-Bold", fontSize=8,
textColor=C_DKGRY, alignment=TA_CENTER)
sWht = S("sWht", fontName="Helvetica-Bold", fontSize=8,
textColor=C_WHITE, alignment=TA_CENTER)
sFormula = S("sFormula", fontName="Helvetica-Bold", fontSize=9,
textColor=C_TEAL, alignment=TA_CENTER, spaceBefore=3, spaceAfter=3)
sAlert = S("sAlert", fontName="Helvetica-Bold", fontSize=8,
textColor=C_RED, alignment=TA_LEFT)
sGreen = S("sGreen", fontName="Helvetica-Bold", fontSize=8,
textColor=C_GREEN, alignment=TA_LEFT)
# ── helpers ──────────────────────────────────────────────────────────────────
def section_header(title):
"""Teal band with white text."""
t = Table([[Paragraph(title, sH1)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_TEAL),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [3, 3, 3, 3]),
]))
return t
def navy_header(title):
"""Navy band."""
t = Table([[Paragraph(title, sH1)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]))
return t
def amber_box(text):
t = Table([[Paragraph(text, sBold)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_AMBER2),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 1, C_AMBER),
]))
return t
def red_box(text):
t = Table([[Paragraph(text, sAlert)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LRED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 1, C_RED),
]))
return t
def green_box(text):
t = Table([[Paragraph(text, sGreen)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LGRN),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 1, C_GREEN),
]))
return t
def formula_box(text):
t = Table([[Paragraph(text, sFormula)]], colWidths=[W - 30*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LTBL),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 1.5, C_TEAL),
]))
return t
def std_table(data, col_widths, header_rows=1, zebra=True):
t = Table(data, colWidths=col_widths, repeatRows=header_rows)
style = [
("BACKGROUND", (0,0), (-1, header_rows-1), C_NAVY),
("TEXTCOLOR", (0,0), (-1, header_rows-1), C_WHITE),
("FONTNAME", (0,0), (-1, header_rows-1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
if zebra:
for i in range(header_rows, len(data)):
if i % 2 == 0:
style.append(("BACKGROUND", (0,i), (-1,i), C_GREY))
t.setStyle(TableStyle(style))
return t
# ── page background callback ─────────────────────────────────────────────────
def page_bg(canvas, doc):
canvas.saveState()
# header band
canvas.setFillColor(C_NAVY)
canvas.rect(0, H - 28*mm, W, 28*mm, fill=1, stroke=0)
# footer
canvas.setFillColor(colors.HexColor("#e8edf2"))
canvas.rect(0, 0, W, 10*mm, fill=1, stroke=0)
canvas.setFillColor(C_NAVY)
canvas.setFont("Helvetica", 7)
canvas.drawCentredString(W/2, 3.5*mm,
"Insulin Quick-Reference Card | For clinical use by trained healthcare professionals | "
"Always verify doses before administering | Page %d" % doc.page)
canvas.restoreState()
# ── document ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=32*mm,
bottomMargin=14*mm,
leftMargin=15*mm,
rightMargin=15*mm,
title="Insulin Quick-Reference Card",
author="Orris Medical AI",
)
story = []
sp = lambda n=3: Spacer(1, n)
# ═══════════════════════════════════════════════════════════════════
# PAGE-HEADER OVERLAY (drawn via page_bg; add title content here)
# We place the title as first "story" elements that sit visually
# inside the nav band via top-margin alignment.
# ═══════════════════════════════════════════════════════════════════
# Title block (drawn on top of navy band via negative space trick)
# ReportLab doesn't do overlays easily, so we embed title in the
# first table styled with a navy background that bleeds to margin.
title_data = [[
Paragraph("INSULIN QUICK-REFERENCE CARD", sTitle),
Paragraph("Dosing Formulas · Types · Sliding Scale · DKA Protocol", sSub),
]]
# Two-row header block
header_block = Table([
[Paragraph("INSULIN QUICK-REFERENCE CARD", sTitle)],
[Paragraph("Dosing Formulas · Insulin Types · Sliding Scales · DKA/HHS Protocol · Glucose Targets", sSub)],
], colWidths=[W - 30*mm])
header_block.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
]))
story.append(header_block)
story.append(sp(6))
# ═══════════════════════════════════════════════════════════════════
# SECTION 1 — DIAGNOSIS & HbA1c
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("1. DIAGNOSIS OF DIABETES (ADA 2024)"))
story.append(sp(4))
diag_data = [
[Paragraph("<b>Test</b>", sWht),
Paragraph("<b>Normal</b>", sWht),
Paragraph("<b>Prediabetes</b>", sWht),
Paragraph("<b>DIABETES</b>", sWht)],
["Fasting Plasma Glucose (8 h fast)",
"< 100 mg/dL", "100–125 mg/dL",
Paragraph("<b>≥ 126 mg/dL</b>", sBold)],
["2-h OGTT (75 g glucose)",
"< 140 mg/dL", "140–199 mg/dL",
Paragraph("<b>≥ 200 mg/dL</b>", sBold)],
["HbA1c",
"< 5.7%", "5.7–6.4%",
Paragraph("<b>≥ 6.5%</b>", sBold)],
["Random Glucose + symptoms",
"—", "—",
Paragraph("<b>≥ 200 mg/dL</b>", sBold)],
]
story.append(std_table(diag_data, [70*mm, 35*mm, 38*mm, 35*mm]))
story.append(sp(4))
story.append(Paragraph(
"Confirm on a <b>separate day</b> (except symptomatic hyperglycaemic crisis). "
"HbA1c unreliable in haemolytic anaemia, haemoglobinopathies, or recent transfusion.",
sNote))
story.append(sp(4))
# HbA1c ↔ Average Glucose
story.append(Paragraph("HbA1c → Estimated Average Glucose (eAG)", sH2))
hba1c_data = [
[Paragraph("<b>HbA1c %</b>", sWht),
Paragraph("<b>eAG mg/dL</b>", sWht),
Paragraph("<b>Interpretation</b>", sWht),
Paragraph("<b>HbA1c %</b>", sWht),
Paragraph("<b>eAG mg/dL</b>", sWht),
Paragraph("<b>Interpretation</b>", sWht)],
["5.7", "117", "Prediabetes threshold",
"8.0", "183", "Suboptimal"],
["6.0", "126", "Upper prediabetes",
"9.0", "212", "Poor control"],
["6.5", "140", "Diagnosis threshold",
"10.0", "240", "Very poor"],
[Paragraph("<b>7.0</b>", sBold), Paragraph("<b>154</b>", sBold),
Paragraph("<b>Treatment TARGET</b>", sBold),
"12.0+", "298+", "DKA / HHS risk"],
]
story.append(std_table(hba1c_data,
[18*mm, 22*mm, 37*mm, 18*mm, 22*mm, 37*mm]))
story.append(sp(6))
# ═══════════════════════════════════════════════════════════════════
# SECTION 2 — INSULIN TYPES
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("2. INSULIN TYPES AT A GLANCE"))
story.append(sp(4))
ins_data = [
[Paragraph("<b>Type</b>", sWht),
Paragraph("<b>Examples</b>", sWht),
Paragraph("<b>Onset</b>", sWht),
Paragraph("<b>Peak</b>", sWht),
Paragraph("<b>Duration</b>", sWht),
Paragraph("<b>Clinical Use</b>", sWht)],
[Paragraph("<b>Rapid-acting</b>", sBold),
"Lispro, Aspart, Glulisine",
"5–15 min", "30–90 min", "3–5 h",
"Meal bolus; give with/just before meal"],
[Paragraph("<b>Short-acting</b>", sBold),
"Regular (Actrapid)",
"30–60 min", "2–4 h", "6–8 h",
"Sliding scale SC; IV infusion DKA/ICU"],
[Paragraph("<b>Intermediate</b>", sBold),
"NPH (Insulatard)",
"1–2 h", "4–8 h", "12–16 h",
"BD regimen; steroid-induced hyperglycaemia"],
[Paragraph("<b>Long-acting (Basal)</b>", sBold),
"Glargine (Lantus), Detemir",
"2–4 h", "No peak", "20–24 h",
"Once-daily basal; Glargine → same time daily"],
[Paragraph("<b>Ultra-long</b>", sBold),
"Degludec (Tresiba)",
"2 h", "No peak", "> 42 h",
"Flexible timing; lowest hypoglycaemia risk"],
[Paragraph("<b>Pre-mixed</b>", sBold),
"30/70, Novomix 30",
"Variable", "Dual", "12–16 h",
"BD convenience; less flexible; avoid T1DM"],
]
story.append(std_table(ins_data,
[28*mm, 35*mm, 16*mm, 16*mm, 16*mm, 49*mm]))
story.append(sp(4))
story.append(amber_box(
"⚠ NEVER mix Glargine with other insulins. "
"Rapid-acting analogue is PREFERRED over Regular for meal-time bolus (faster, safer)."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 3 — DOSING FORMULAS
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("3. INSULIN DOSING FORMULAS"))
story.append(sp(5))
story.append(Paragraph("Step 1 — Total Daily Dose (TDD)", sH2))
story.append(formula_box(
"TDD = 0.4–0.5 units/kg/day "
"│ Obese/Stress: 0.6–0.8 u/kg "
"│ Elderly/CKD/Thin: 0.2–0.3 u/kg"))
story.append(sp(3))
tdd_data = [
[Paragraph("<b>Weight (kg)</b>", sWht),
Paragraph("<b>Conservative TDD (0.4)</b>", sWht),
Paragraph("<b>Standard TDD (0.5)</b>", sWht),
Paragraph("<b>Aggressive TDD (0.6)</b>", sWht),
Paragraph("<b>Use case</b>", sWht)],
["50 kg", "20 units", "25 units", "30 units", "Elderly / low risk"],
["60 kg", "24 units", "30 units", "36 units", "Thin / CKD"],
["70 kg", "28 units", "35 units", "42 units", Paragraph("<b>Standard adult</b>", sBold)],
["80 kg", "32 units", "40 units", "48 units", "Typical adult"],
["90 kg", "36 units", "45 units", "54 units", "Overweight"],
["100 kg","40 units", "50 units", "60 units", "Obese / steroids"],
]
story.append(std_table(tdd_data, [28*mm, 38*mm, 38*mm, 38*mm, 33*mm]))
story.append(sp(6))
story.append(Paragraph("Step 2 — Split TDD into Basal + Bolus", sH2))
story.append(formula_box(
"BASAL = 50% of TDD → Glargine SC once daily (bedtime) │ "
"BOLUS = 50% of TDD ÷ 3 → Rapid-acting SC with each meal"))
story.append(sp(3))
example_data = [
[Paragraph("<b>Example: 70 kg patient, TDD = 35 units</b>", sWht), "", ""],
[Paragraph("<b>Component</b>", sWht),
Paragraph("<b>Dose</b>", sWht),
Paragraph("<b>Timing</b>", sWht)],
["Glargine (Basal)", "17–18 units SC", "10 PM daily (fixed time)"],
["Aspart / Lispro (Breakfast)", "5–6 units SC", "Within 15 min before meal"],
["Aspart / Lispro (Lunch)", "5–6 units SC", "Within 15 min before meal"],
["Aspart / Lispro (Dinner)", "5–6 units SC", "Within 15 min before meal"],
]
example_t = Table(example_data, colWidths=[55*mm, 40*mm, 65*mm])
example_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_TEAL),
("SPAN", (0,0), (-1,0)),
("BACKGROUND", (0,1), (-1,1), C_NAVY),
("TEXTCOLOR", (0,0), (-1,1), C_WHITE),
("FONTNAME", (0,0), (-1,1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("BACKGROUND", (0,3), (-1,3), C_GREY),
("BACKGROUND", (0,5), (-1,5), C_GREY),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(example_t)
story.append(sp(6))
story.append(Paragraph("Step 3 — Insulin Sensitivity Factor (ISF) & Correction Dose", sH2))
story.append(formula_box(
"ISF = 1800 ÷ TDD (Regular insulin) │ 1700 ÷ TDD (Rapid-acting)"))
story.append(sp(3))
story.append(formula_box(
"CORRECTION DOSE = (Actual Glucose − Target Glucose) ÷ ISF"))
story.append(sp(3))
isf_data = [
[Paragraph("<b>TDD (units)</b>", sWht),
Paragraph("<b>ISF (mg/dL per unit)</b>", sWht),
Paragraph("<b>Example: Glucose 280, Target 120</b>", sWht),
Paragraph("<b>Correction Dose</b>", sWht)],
["20", "90", "(280-120) ÷ 90 = 1.8", "→ 2 units"],
["28", "64", "(280-120) ÷ 64 = 2.5", "→ 2–3 units"],
["35", "51", "(280-120) ÷ 51 = 3.1", "→ 3 units"],
["50", "36", "(280-120) ÷ 36 = 4.4", "→ 4 units"],
["70", "26", "(280-120) ÷ 26 = 6.2", "→ 6 units"],
]
story.append(std_table(isf_data, [28*mm, 40*mm, 70*mm, 30*mm]))
story.append(sp(4))
story.append(Paragraph("Step 4 — Insulin-to-Carbohydrate Ratio (ICR)", sH2))
story.append(formula_box(
"ICR = 500 ÷ TDD → 1 unit covers X grams of carbohydrate"))
story.append(sp(3))
story.append(Paragraph(
"Example: TDD = 35 → ICR = 500 ÷ 35 = 14 g/unit. "
"If patient eats 56 g carbs: 56 ÷ 14 = <b>4 units pre-meal bolus.</b>", sBody))
story.append(sp(4))
story.append(Paragraph("Glargine Titration — '3-3-3' Rule", sH2))
story.append(green_box(
"✓ Check fasting glucose for 3 days. "
"If fasting > 130 mg/dL on 3 consecutive mornings → INCREASE Glargine by 2 units. "
"If fasting < 70 mg/dL → DECREASE Glargine by 10–20%. "
"Target fasting: 80–130 mg/dL."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 4 — SLIDING SCALE
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("4. INPATIENT SLIDING SCALE (Supplemental / Correction Insulin)"))
story.append(sp(4))
story.append(Paragraph(
"Use RAPID-ACTING (Aspart / Lispro) or REGULAR (Actrapid) SC. "
"Sliding scale alone is inadequate — always combine with scheduled BASAL insulin.", sNote))
story.append(sp(3))
# Two-column layout: Low scale | High scale
low_scale = [
[Paragraph("<b>LOW SCALE</b> (Elderly / CKD / Thin / Hypoglycaemia-prone)", sWht), ""],
[Paragraph("<b>Glucose (mg/dL)</b>", sWht),
Paragraph("<b>Dose (SC)</b>", sWht)],
[Paragraph("< 70", sAlert),
Paragraph("HOLD all insulin → Treat hypoglycaemia", sAlert)],
["70–150", "0 units"],
["151–200", "1 unit"],
["201–250", "2 units"],
["251–300", "3 units"],
["301–350", "4 units"],
["351–400", "5 units"],
[Paragraph("> 400", sAlert), Paragraph("6 units + CALL DOCTOR", sAlert)],
]
high_scale = [
[Paragraph("<b>HIGH SCALE</b> (Obese / Steroids / ICU / High insulin resistance)", sWht), ""],
[Paragraph("<b>Glucose (mg/dL)</b>", sWht),
Paragraph("<b>Dose (SC)</b>", sWht)],
[Paragraph("< 70", sAlert),
Paragraph("HOLD all insulin → Treat hypoglycaemia", sAlert)],
["70–150", "0 units"],
["151–200", "4 units"],
["201–250", "6 units"],
["251–300", "8 units"],
["301–350", "10 units"],
["351–400", "12 units"],
[Paragraph("> 400", sAlert), Paragraph("14 units + CALL DOCTOR", sAlert)],
]
def scale_table(data):
t = Table(data, colWidths=[38*mm, 42*mm])
style = [
("BACKGROUND", (0,0), (-1,0), C_TEAL),
("SPAN", (0,0), (-1,0)),
("BACKGROUND", (0,1), (-1,1), C_NAVY),
("TEXTCOLOR", (0,0), (-1,1), C_WHITE),
("FONTNAME", (0,0), (-1,1), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("BACKGROUND", (0,2), (-1,2), C_LRED),
("BACKGROUND", (0,-1),(-1,-1),C_LRED),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS", (0,3), (-1,-2), [C_WHITE, C_GREY]),
]
t.setStyle(TableStyle(style))
return t
dual_scales = Table(
[[scale_table(low_scale), Spacer(6, 1), scale_table(high_scale)]],
colWidths=[82*mm, 6*mm, 82*mm]
)
story.append(dual_scales)
story.append(sp(4))
story.append(amber_box(
"STANDARD SCALE (General Ward): Add 2 units to Low Scale values above for most adult patients. "
"Reassess scale intensity daily. Goal: 70–180 mg/dL."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 5 — GLUCOSE TARGETS
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("5. GLUCOSE TARGETS BY CLINICAL SETTING"))
story.append(sp(4))
tgt_data = [
[Paragraph("<b>Setting</b>", sWht),
Paragraph("<b>Pre-meal Target</b>", sWht),
Paragraph("<b>Post-meal / Random</b>", sWht),
Paragraph("<b>HbA1c Target</b>", sWht),
Paragraph("<b>Key Notes</b>", sWht)],
["OPD — Young/Healthy T2DM",
"80–130 mg/dL", "< 180 mg/dL", Paragraph("<b>< 7.0%</b>", sBold),
"Strict; no hypoglycaemia"],
["OPD — Elderly / Frail / CVD",
"100–150 mg/dL", "< 200 mg/dL", "< 8.0–8.5%",
"Avoid hypoglycaemia (fall risk)"],
[Paragraph("<b>Pregnancy (GDM)</b>", sBold),
"< 95 mg/dL", "1 h: < 140 / 2 h: < 120", "< 6.0–6.5%",
"Tightest targets; monitor closely"],
["Non-ICU Ward",
"100–140 mg/dL", "140–180 mg/dL", "Per pre-admission",
"Hold metformin during acute illness"],
[Paragraph("<b>ICU (General / Post-op)</b>", sBold),
"—", Paragraph("<b>140–180 mg/dL</b>", sBold), "—",
"NICE-SUGAR: tight control (80-110) → ↑ mortality"],
["ICU — Cardiac Surgery",
"—", "140–180 mg/dL", "—",
"Insulin infusion preferred; avoid < 110"],
["DKA — Active phase",
"—", "250–300 (don't drop fast)", "—",
"When < 250 → add D5 to IVF; keep insulin running"],
]
story.append(std_table(tgt_data, [38*mm, 28*mm, 36*mm, 22*mm, 46*mm]))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 6 — DKA PROTOCOL
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("6. DKA / HHS EMERGENCY PROTOCOL"))
story.append(sp(4))
# DKA vs HHS comparison
comp_data = [
[Paragraph("<b>Feature</b>", sWht),
Paragraph("<b>DKA</b>", sWht),
Paragraph("<b>HHS</b>", sWht)],
["Typical patient", "T1DM (any age)", "T2DM — elderly"],
["Glucose",
Paragraph("<b>250–600 mg/dL</b>", sBold),
Paragraph("<b>> 600 mg/dL</b>", sBold)],
["pH",
Paragraph("<b>< 7.3 (acidotic)</b>", sAlert),
"> 7.3 (near normal)"],
["HCO3", "< 18 mEq/L", "Normal (> 18)"],
["Ketones", "+++ (blood > 3 mmol/L)", "Absent / trace"],
["Osmolality", "< 320 mOsm/kg", Paragraph("<b>> 320 mOsm/kg</b>", sBold)],
["Anion gap", "> 12 (HAGMA)", "Normal"],
["Primary Rx",
Paragraph("<b>Insulin + Fluids</b>", sBold),
Paragraph("<b>FLUIDS first, then insulin</b>", sBold)],
["Mortality", "< 5%", Paragraph("<b>8–25%</b>", sAlert)],
]
story.append(std_table(comp_data, [38*mm, 62*mm, 62*mm]))
story.append(sp(5))
story.append(Paragraph("DKA Management — Phase Sequence", sH2))
phases_data = [
[Paragraph("<b>Phase</b>", sWht),
Paragraph("<b>Action</b>", sWht),
Paragraph("<b>Detail / Dose</b>", sWht)],
[Paragraph("<b>1 — FLUIDS</b>\n(start immediately)", sBold),
"0.9% Normal Saline IV",
"Hour 1–2: 1 L/h → Hours 3–4: 500 mL/h → Hours 5–12: 250 mL/h\n"
"Total 24 h: 5–8 L. When glucose < 250: switch to D5% in 0.45% NaCl"],
[Paragraph("<b>2 — POTASSIUM</b>\n(before insulin!)", sBold),
"KCl replacement",
"K+ < 3.5: Give KCl 20–40 mEq/h IV for 1–2 h BEFORE starting insulin\n"
"K+ 3.5–5.5: Add KCl 20 mEq/L to each IV bag\n"
"K+ > 5.5: Start insulin; recheck K+ in 1 h. Target K+: 3.5–5.0"],
[Paragraph("<b>3 — INSULIN</b>\n(after 1 L fluid + K+>3.5)", sBold),
"Regular Insulin IV infusion",
"Bolus: 0.1 u/kg IV push, THEN\n"
"Infusion: 0.1 u/kg/h (50 units in 50 mL NS = 1 u/mL via syringe pump)\n"
"Target: glucose falls 50–75 mg/dL/h\n"
"When glucose < 250: reduce to 0.02–0.05 u/kg/h; add dextrose to IVF"],
[Paragraph("<b>4 — BICARBONATE</b>\n(only if severe)", sBold),
"NaHCO3 100 mEq in 400 mL\nsterile water over 2 h",
"ONLY if pH < 7.1. Risk: hypokalaemia, cerebral oedema.\n"
"Do NOT give routinely. Recheck pH after 2 h."],
[Paragraph("<b>5 — TRANSITION\nto SC Insulin</b>", sBold),
"Resolution criteria (ALL 3):",
"Glucose < 200 AND HCO3 ≥ 18 AND pH > 7.3 AND Anion gap ≤ 12\n"
"Give Glargine SC (80% prior TDD) AT LEAST 2–4 h BEFORE stopping IV insulin"],
]
phases_t = Table(phases_data, colWidths=[33*mm, 45*mm, 88*mm])
phases_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_NAVY),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("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), 5),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("BACKGROUND", (0,1), (0,1), C_LTBL),
("BACKGROUND", (0,2), (0,2), C_AMBER2),
("BACKGROUND", (0,3), (0,3), C_LRED),
("BACKGROUND", (0,4), (0,4), C_AMBER2),
("BACKGROUND", (0,5), (0,5), C_LGRN),
]))
story.append(phases_t)
story.append(sp(5))
# DKA monitoring table
story.append(Paragraph("DKA Monitoring Frequency", sH2))
mon_data = [
[Paragraph("<b>Parameter</b>", sWht),
Paragraph("<b>Frequency</b>", sWht),
Paragraph("<b>Target</b>", sWht)],
["Capillary glucose", "Every 1 h", "Fall 50–75 mg/dL/h; < 200 for transition"],
["Serum K+", "Every 2 h", "3.5–5.0 mEq/L (CRITICAL)"],
["VBG / ABG", "Every 2 h", "pH > 7.3; HCO3 > 18"],
["Anion gap", "Every 4 h", "≤ 12 mEq/L (DKA resolved)"],
["Urine output", "Continuous (IDC)", "> 0.5 mL/kg/h"],
["ECG", "Continuous", "Watch for hypokalaemia changes (flat T, U waves)"],
["Neuro / GCS", "Every 1 h", "GCS improving; watch for cerebral oedema"],
]
story.append(std_table(mon_data, [40*mm, 38*mm, 88*mm]))
story.append(sp(4))
story.append(red_box(
"⚠ CRITICAL RULE: Check K+ BEFORE starting insulin. "
"If K+ < 3.5 → HOLD insulin → replace K+ first. "
"Insulin drives K+ into cells → fatal hypokalaemia if not corrected first."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 7 — SPECIAL SITUATIONS
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("7. SPECIAL CLINICAL SITUATIONS"))
story.append(sp(4))
spec_data = [
[Paragraph("<b>Situation</b>", sWht),
Paragraph("<b>Insulin Adjustment</b>", sWht),
Paragraph("<b>Key Caution</b>", sWht)],
["NPO / Fasting (pre-op)",
"Give 50–75% of basal only; HOLD all bolus insulin",
"Resume bolus only when patient eating"],
["Renal failure (eGFR < 30)",
"Reduce TDD by 25–50%; monitor closely q4 h",
"Insulin cleared more slowly → accumulates → hypoglycaemia"],
["Liver failure",
"Reduce TDD by 25%; hypoglycaemia common",
"Reduced gluconeogenesis → unpredictable glucose"],
["Steroid-induced (prednisolone OD)",
"NPH morning or ↑ afternoon bolus; peak 4–8 h post-dose",
"Glucose rises in afternoon; fasting glucose may be normal"],
["Sick day (T1DM)",
"NEVER stop insulin; check ketones q4 h if glucose > 300",
"Even if not eating: reduce bolus; maintain basal fully"],
["ICU → SC transition",
"80% of 24 h IV insulin used = SC TDD; basal 50% + bolus 50%",
"Give SC 2–4 h BEFORE stopping IV infusion (lag time!)"],
["Pregnancy (GDM/T1DM)",
"Insulin requirements ↑ 2–3× in T3; monitor more frequently",
"Avoid oral agents except Metformin (insulin preferred)"],
["Post-bariatric surgery",
"Significant reduction often possible; may need to ↓ TDD 50–80%",
"Risk of late dumping / reactive hypoglycaemia; check glucose 1 h post-meal"],
]
story.append(std_table(spec_data, [40*mm, 65*mm, 60*mm]))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 8 — T2DM DRUG LADDER
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("8. T2DM ORAL DRUG LADDER (ADA 2024 Algorithm)"))
story.append(sp(4))
ladder_data = [
[Paragraph("<b>Step</b>", sWht),
Paragraph("<b>Drug / Class</b>", sWht),
Paragraph("<b>Dose</b>", sWht),
Paragraph("<b>HbA1c ↓</b>", sWht),
Paragraph("<b>Weight</b>", sWht),
Paragraph("<b>Hypo Risk</b>", sWht),
Paragraph("<b>Key Indication / Caution</b>", sWht)],
[Paragraph("<b>1</b>", sBold), "Metformin",
"500–2000 mg BID", "1.0–1.5%", "Neutral", "None",
"First-line always; avoid eGFR < 30, contrast, acute illness"],
[Paragraph("<b>2A</b>\n(CVD)", sBold),
"GLP-1 RA (Semaglutide, Liraglutide)",
"Sema: 0.25→1 mg SC/wk", "1.5–2.0%",
Paragraph("<b>↓↓ 5–15 kg</b>", sGreen), "None",
"CV mortality ↓ (LEADER, SUSTAIN-6); avoid MEN2/thyroid Ca"],
[Paragraph("<b>2B</b>\n(HF/CKD)", sBold),
"SGLT-2i (Empagliflozin, Dapagliflozin)",
"Empa 10–25 mg OD", "0.7–1.0%",
Paragraph("<b>↓ 2–3 kg</b>", sGreen), "None",
"HF hospitalisation ↓, CKD progression ↓; avoid eGFR < 30"],
[Paragraph("<b>2C</b>\n(Cost)", sBold),
"Sulfonylurea (Gliclazide MR)",
"30–120 mg OD morning", "1.5–2.0%",
Paragraph("<b>↑ 2 kg</b>", sAlert), Paragraph("<b>HIGH</b>", sAlert),
"Cheapest; avoid glibenclamide in elderly/CKD"],
[Paragraph("<b>2D</b>\n(Safe)", sBold),
"DPP-4i (Sitagliptin, Vildagliptin)",
"Sita 100 mg OD", "0.5–0.8%", "Neutral", "None",
"Well tolerated; dose-reduce in CKD; avoid saxagliptin in HF"],
[Paragraph("<b>3</b>", sBold),
"Add 3rd agent or Basal Insulin",
"Glargine 0.1–0.2 u/kg hs", "3–4%+",
Paragraph("<b>↑</b>", sAlert), Paragraph("<b>YES</b>", sAlert),
"If HbA1c > 9%: start basal insulin + continue Metformin"],
]
story.append(std_table(ladder_data,
[14*mm, 36*mm, 28*mm, 16*mm, 16*mm, 16*mm, 50*mm]))
story.append(sp(4))
story.append(amber_box(
"STOP Sulfonylurea when starting insulin (double hypoglycaemia risk). "
"CONTINUE Metformin with insulin (reduces insulin dose by ~30%). "
"HOLD SGLT-2i 3–4 days before major surgery (euglycaemic DKA risk)."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 9 — HYPOGLYCAEMIA MANAGEMENT
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("9. HYPOGLYCAEMIA RECOGNITION & MANAGEMENT"))
story.append(sp(4))
hypo_data = [
[Paragraph("<b>Severity</b>", sWht),
Paragraph("<b>Glucose</b>", sWht),
Paragraph("<b>Symptoms</b>", sWht),
Paragraph("<b>Treatment</b>", sWht)],
[Paragraph("<b>Level 1 (Alert)</b>", sBold),
"54–70 mg/dL",
"Sweating, tremor, anxiety, palpitations, hunger",
"15–20 g fast carbs PO (4 glucose tabs / 150 mL juice / 3 tsp sugar)\n"
"Recheck in 15 min; repeat if still < 70"],
[Paragraph("<b>Level 2 (Significant)</b>", sBold),
"< 54 mg/dL",
"Confusion, slurred speech, altered behaviour",
"Same as Level 1 if conscious\n"
"If impaired: Glucagon 1 mg IM/SC or D50W 25 mL IV"],
[Paragraph("<b>Level 3 (Severe)</b>", sBold),
"Any (with unconsciousness)",
"Seizure, unresponsive, coma",
"D50W 50 mL IV push IMMEDIATELY\n"
"Then D10% 100 mL/h infusion until glucose > 100 mg/dL\n"
"Glucagon 1 mg IM if no IV access\n"
"Recheck glucose q15 min × 4"],
]
hypo_t = Table(hypo_data, colWidths=[28*mm, 22*mm, 52*mm, 66*mm])
hypo_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), C_NAVY),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7.5),
("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), 5),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#cccccc")),
("BACKGROUND", (0,1), (-1,1), C_AMBER2),
("BACKGROUND", (0,2), (-1,2), colors.HexColor("#ffe4cc")),
("BACKGROUND", (0,3), (-1,3), C_LRED),
]))
story.append(hypo_t)
story.append(sp(4))
story.append(green_box(
"RULE OF 15: 15 g carbs → wait 15 min → recheck. If still < 70 → repeat. "
"After recovery: give 15–20 g long-acting carbs (bread / biscuits / milk). "
"Identify and address cause: excess insulin dose, missed meal, exercise, alcohol, renal failure."))
story.append(sp(8))
# ═══════════════════════════════════════════════════════════════════
# SECTION 10 — CLINICAL PEARLS
# ═══════════════════════════════════════════════════════════════════
story.append(section_header("10. CLINICAL PEARLS — MASTER LIST"))
story.append(sp(4))
pearls = [
("TDD Starting Dose",
"0.4 u/kg (conservative) to 0.5 u/kg (standard) to 0.6–0.8 u/kg (obese/ICU). "
"ALWAYS start low → titrate up every 3 days based on fasting glucose."),
("HbA1c & Anaemia",
"HbA1c is UNRELIABLE in haemolytic anaemia, iron-deficiency, haemoglobinopathies, "
"and after recent blood transfusion. Use fructosamine or eAG instead."),
("DKA: K+ before Insulin",
"ALWAYS check K+ before starting insulin. If K+ < 3.5 → replace first. "
"Insulin shifts K+ intracellularly → fatal arrhythmia if uncorrected."),
("DKA: Insulin Stops Ketogenesis",
"Goal of insulin in DKA is to STOP ketone production, NOT primarily to drop glucose. "
"When glucose < 250: add dextrose and KEEP insulin running until anion gap closes."),
("IV → SC Insulin Transition",
"Give SC Glargine (0.3–0.5 u/kg) at least 2–4 hours BEFORE stopping IV infusion. "
"SC insulin takes 2–4 h to become active. Skipping overlap = rebound DKA."),
("Metformin Rules",
"HOLD metformin: eGFR < 30, acute illness, contrast CT (hold 48 h before and after), "
"major surgery, heart failure (class III–IV), hepatic failure."),
("SGLT-2i Safety",
"HOLD 3–4 days before major surgery (euglycaemic DKA risk). Causes genital infections (~10%): "
"counsel hygiene. Contraindicated eGFR < 30. Benefits: HF hospitalisation ↓, CKD progression ↓."),
("ICU Glucose Target",
"Target 140–180 mg/dL in ICU (NICE-SUGAR trial, 2009). Tight control (80–110 mg/dL) "
"significantly INCREASES mortality in ICU patients. Avoid hypoglycaemia at all costs."),
("Steroid-Induced Hyperglycaemia",
"Glucose peaks 4–8 h post-dose. If on once-daily prednisolone in morning: "
"use NPH (Insulatard) in morning OR increase afternoon/dinner bolus. Fasting glucose may appear normal."),
("Never Stop Insulin — T1DM Sick Day",
"Even if T1DM patient is vomiting and not eating: NEVER stop insulin (may reduce to 50–75%). "
"Absence of insulin → DKA even without eating. Check ketones q4 h if glucose > 300."),
("GLP-1 Agonist Initiation",
"Start semaglutide SC at 0.25 mg/week × 4 weeks, then 0.5 mg/week. "
"Slow titration prevents nausea. Oral semaglutide: take 30 min before first food/drink of day "
"with ≤ 120 mL plain water ONLY."),
("Glargine Administration",
"Give at the SAME time each day (bedtime preferred). Cannot be mixed with any other insulin. "
"Clear solution (NOT milky — milky = NPH). Inject SC; do not inject IV."),
]
pearl_rows = []
for title, text in pearls:
pearl_rows.append([
Paragraph(f"<b>{title}</b>", sBold),
Paragraph(text, sBody)
])
pearl_t = Table(pearl_rows, colWidths=[40*mm, 125*mm])
pearl_style = [
("FONTSIZE", (0,0), (-1,-1), 7.5),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#dddddd")),
("BACKGROUND", (0,0), (0,-1), C_LTBL),
]
for i in range(0, len(pearl_rows), 2):
pearl_style.append(("BACKGROUND", (1,i), (1,i), C_WHITE))
if i+1 < len(pearl_rows):
pearl_style.append(("BACKGROUND", (1,i+1), (1,i+1), C_GREY))
pearl_t.setStyle(TableStyle(pearl_style))
story.append(pearl_t)
story.append(sp(6))
story.append(Paragraph(
"Sources: Rosen's Emergency Medicine · Miller's Anesthesia 10e · Harrison's Principles of "
"Internal Medicine 22e · Creasy & Resnik's Maternal-Fetal Medicine · "
"ADA Standards of Care 2024 · NICE-SUGAR Trial (NEJM 2009)",
sNote))
# ── build ────────────────────────────────────────────────────────────────────
doc.build(story, onFirstPage=page_bg, onLaterPages=page_bg)
print(f"PDF written to {OUTPUT}")