~/antibiotic-infusion/generate_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, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.pdfbase import pdfmetrics
OUTPUT = "/home/daytona/workspace/antibiotic-infusion/Antibiotic_Infusion_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=1.5*cm, leftMargin=1.5*cm,
topMargin=1.5*cm, bottomMargin=1.5*cm
)
W, H = A4
styles = getSampleStyleSheet()
# ── Custom styles ──────────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1a3a5c")
MID_BLUE = colors.HexColor("#2e6da4")
LIGHT_BLUE = colors.HexColor("#d6e8f7")
HEADER_BG = colors.HexColor("#1a3a5c")
ROW_ALT = colors.HexColor("#f0f6fc")
WHITE = colors.white
ORANGE = colors.HexColor("#e07b00")
GREEN = colors.HexColor("#1a7a4a")
title_style = ParagraphStyle(
"Title", fontSize=18, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", spaceAfter=2
)
subtitle_style = ParagraphStyle(
"Subtitle", fontSize=9, textColor=colors.HexColor("#cce0f5"),
alignment=TA_CENTER, fontName="Helvetica", spaceAfter=0
)
section_style = ParagraphStyle(
"Section", fontSize=11, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT, spaceAfter=2, spaceBefore=6,
backColor=MID_BLUE, leftIndent=4, rightIndent=4,
borderPad=3
)
note_style = ParagraphStyle(
"Note", fontSize=7.5, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", spaceAfter=4, leftIndent=4
)
footer_style = ParagraphStyle(
"Footer", fontSize=7, textColor=colors.HexColor("#888888"),
alignment=TA_CENTER, fontName="Helvetica"
)
def section_header(text):
return Paragraph(f" {text}", section_style)
def make_table(headers, rows, col_widths):
"""Build a styled table."""
header_row = [Paragraph(f"<b>{h}</b>", ParagraphStyle(
"TH", fontSize=8, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_CENTER, leading=10
)) for h in headers]
body_rows = []
for row in rows:
body_rows.append([
Paragraph(str(cell), ParagraphStyle(
"TD", fontSize=7.8, fontName="Helvetica",
alignment=TA_LEFT, leading=10, leftIndent=2
)) for cell in row
])
data = [header_row] + body_rows
style = TableStyle([
("BACKGROUND", (0, 0), (-1, 0), HEADER_BG),
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, ROW_ALT]),
("GRID", (0, 0), (-1, -1), 0.3, colors.HexColor("#b0c8e0")),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("TOPPADDING", (0, 0), (-1, -1), 3),
("BOTTOMPADDING",(0, 0), (-1, -1), 3),
("LEFTPADDING", (0, 0), (-1, -1), 4),
("RIGHTPADDING", (0, 0), (-1, -1), 4),
("ROUNDEDCORNERS", [3]),
])
return Table(data, colWidths=col_widths, style=style, repeatRows=1)
# ── Content ────────────────────────────────────────────────────────────────────
story = []
# ── Title Banner ──
banner_data = [[
Paragraph("ANTIBIOTIC IV INFUSION REFERENCE", title_style),
Paragraph("Dose · Interval · Infusion Time", subtitle_style)
]]
banner_table = Table(banner_data, colWidths=[W - 3*cm])
banner_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 8),
("ROUNDEDCORNERS", [5]),
]))
story.append(banner_table)
story.append(Spacer(1, 0.25*cm))
src_note = Paragraph(
"Source: Harrison's Principles of Internal Medicine 22E (2025) · Goldman-Cecil Medicine · Lippincott Pharmacology",
ParagraphStyle("src", fontSize=7, textColor=colors.HexColor("#555"), alignment=TA_CENTER,
fontName="Helvetica-Oblique")
)
story.append(src_note)
story.append(Spacer(1, 0.3*cm))
# ── PK Principle Box ──
pk_text = (
"<b>Pharmacodynamic Principle:</b> "
"Beta-lactams are <b>time-dependent</b> killers — extend infusion to maximise time above MIC. "
"Aminoglycosides & fluoroquinolones are <b>concentration-dependent</b> — peak level drives killing."
)
pk_para = Paragraph(pk_text, ParagraphStyle(
"PK", fontSize=8, fontName="Helvetica", textColor=DARK_BLUE,
backColor=colors.HexColor("#fff9e6"), borderColor=ORANGE, borderWidth=0.5,
borderPad=6, spaceAfter=6, leading=12
))
story.append(pk_para)
story.append(Spacer(1, 0.2*cm))
# ─────────────────────────────────────────────────────────────────────────────
# Table column widths (total usable = W - 3cm ≈ 17cm)
C = W - 3*cm # ~17 cm
cw4 = [C*0.26, C*0.22, C*0.20, C*0.32]
cw3 = [C*0.32, C*0.24, C*0.44]
HDR4 = ["Drug", "Dose", "Interval", "Infusion Time / Notes"]
HDR3 = ["Drug", "Dose / Interval", "Infusion Time / Notes"]
# ── 1. PENICILLINS ────────────────────────────────────────────────────────────
story.append(section_header("1. PENICILLINS"))
penicillin_rows = [
["Penicillin G", "2–4 MU IV", "q4h", "15–30 min; continuous infusion for high-dose regimens"],
["Ampicillin", "2 g IV", "q4–6h", "15–30 min"],
["Ampicillin-sulbactam", "3 g IV", "q6h", "15–30 min"],
["Nafcillin / Oxacillin","2 g IV", "q4h", "30–60 min"],
["Piperacillin-tazobactam","3.375–4.5 g IV","q6–8h","⚡ Extended: 4 hours (preferred for P. aeruginosa)"],
]
story.append(make_table(HDR4, penicillin_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 2. CEPHALOSPORINS ─────────────────────────────────────────────────────────
story.append(section_header("2. CEPHALOSPORINS"))
ceph_rows = [
["Cefazolin", "2 g IV", "q8h", "30–60 min"],
["Ceftriaxone", "2 g IV", "q12–24h","30 min"],
["Ceftazidime", "2 g IV", "q8h", "30–60 min; extended 3–4h for resistant organisms"],
["Cefepime", "2 g IV", "q8–12h","30 min (standard); extended 3–4h for resistant organisms"],
["Ceftazidime-avibactam", "2.25 g IV", "q8h", "⚡ 3 hours"],
["Ceftolozane-tazobactam", "3 g IV", "q8h", "1 hour"],
]
story.append(make_table(HDR4, ceph_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 3. CARBAPENEMS ────────────────────────────────────────────────────────────
story.append(section_header("3. CARBAPENEMS"))
carb_rows = [
["Meropenem", "1–2 g IV", "q8h", "⚡ 3 hours (P. aeruginosa/severe); 30 min standard"],
["Imipenem-cilastatin", "0.5–1 g IV", "q6h", "30–60 min"],
["Imipenem-cilastatin-relebactam", "1.25 g IV", "q6h", "30 min"],
["Doripenem", "0.5 g IV", "q8h", "⚡ 1 hour (mandatory)"],
["Ertapenem", "1 g IV", "q24h", "30 min"],
]
story.append(make_table(HDR4, carb_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 4. MONOBACTAM ─────────────────────────────────────────────────────────────
story.append(section_header("4. MONOBACTAM"))
mono_rows = [
["Aztreonam", "1.5–2 g IV", "q6–8h", "30–60 min (use in β-lactam allergy with Gram-negative cover)"],
]
story.append(make_table(HDR4, mono_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 5. GLYCOPEPTIDES ─────────────────────────────────────────────────────────
story.append(section_header("5. GLYCOPEPTIDES"))
glyco_rows = [
["Vancomycin", "15–20 mg/kg IV (actual body weight)", "q8–12h",
"⚠ MINIMUM 60 min (≥1 h); NEVER rapid bolus → Red Man Syndrome, hypotension, cardiac arrest. Adjust to trough: 15–20 µg/mL (Staph); 10–15 µg/mL (Strep/Entero). Surgical prophylaxis: complete within 120 min pre-incision."],
]
story.append(make_table(HDR4, glyco_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 6. LIPOPEPTIDE ────────────────────────────────────────────────────────────
story.append(section_header("6. LIPOPEPTIDE"))
lipo_rows = [
["Daptomycin", "6–10 mg/kg IV (up to 12 mg/kg for enterococcal/VRE IE)", "q24h",
"30 min (IV infusion) OR 2-min IV bolus. Do NOT use in pneumonia — inactivated by surfactant."],
]
story.append(make_table(HDR4, lipo_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 7. AMINOGLYCOSIDES ────────────────────────────────────────────────────────
story.append(section_header("7. AMINOGLYCOSIDES"))
amino_rows = [
["Gentamicin (IE synergy, divided dose)", "1 mg/kg IV", "q8h", "⚡ 20–30 min. Target peak ~3–5 µg/mL, trough ≤1 µg/mL"],
["Gentamicin (streptococcal IE, OD dose)","3 mg/kg IV", "q24h", "30–60 min"],
["Amikacin", "15 mg/kg IV", "q24h", "30–60 min. Target peak 56–64 µg/mL, trough <1 µg/mL"],
]
story.append(make_table(HDR4, amino_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 8. FLUOROQUINOLONES ───────────────────────────────────────────────────────
story.append(section_header("8. FLUOROQUINOLONES"))
fq_rows = [
["Ciprofloxacin (IV)", "400 mg IV", "q8–12h", "⚡ 60 min (surgical prophylaxis: complete within 120 min pre-incision)"],
["Ciprofloxacin (PO)", "500 mg PO", "q12h", "Oral — N/A"],
["Levofloxacin (IV)", "500–750 mg IV", "q24h", "60–90 min"],
]
story.append(make_table(HDR4, fq_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 9. LINCOSAMIDE (CLINDAMYCIN) ─────────────────────────────────────────────
story.append(section_header("9. LINCOSAMIDE — Clindamycin"))
clinda_rows = [
["Clindamycin (IV)", "600–900 mg IV", "q6–8h",
"⚠ 20–60 min; NEVER rapid IV push (hypotension, cardiopulmonary arrest). Max rate: 30 mg/min → 600 mg over ≥20 min, 900 mg over ≥30 min. Add to beta-lactam in necrotising fasciitis/gas gangrene for toxin suppression."],
["Clindamycin (PO)", "300–450 mg PO", "q6–8h", "Oral — N/A"],
]
story.append(make_table(HDR4, clinda_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 10. NITROIMIDAZOLE ────────────────────────────────────────────────────────
story.append(section_header("10. NITROIMIDAZOLE — Metronidazole"))
metro_rows = [
["Metronidazole (loading)", "15 mg/kg IV", "Once", "⚡ Over 1 hour"],
["Metronidazole (maintenance)", "7.5 mg/kg IV", "q6h", "Over 1 hour"],
]
story.append(make_table(HDR4, metro_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 11. OXAZOLIDINONES ────────────────────────────────────────────────────────
story.append(section_header("11. OXAZOLIDINONES"))
oxazo_rows = [
["Linezolid", "600 mg IV or PO", "q12h", "30–120 min (IV); avoid tyramine-rich foods (MAO-I activity)"],
["Tedizolid", "200 mg IV or PO", "q24h", "1 hour (IV)"],
]
story.append(make_table(HDR4, oxazo_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 12. LIPOGLYCOPEPTIDES ─────────────────────────────────────────────────────
story.append(section_header("12. LIPOGLYCOPEPTIDES (Long-acting)"))
lipogly_rows = [
["Dalbavancin", "1500 mg IV single dose", "Once", "30 min"],
["Oritavancin", "1200 mg IV single dose", "Once", "⚡ 3 hours"],
["Telavancin", "7.5–10 mg/kg IV", "q24h", "60 min"],
]
story.append(make_table(HDR4, lipogly_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 13. POLYMYXINS ────────────────────────────────────────────────────────────
story.append(section_header("13. POLYMYXINS"))
poly_rows = [
["Polymyxin B (loading)", "20,000–25,000 IU/kg IV", "Once", "Over 1 hour"],
["Polymyxin B (maintenance)","1.25–1.5 mg/kg IV", "q12h", "60 min"],
["Fosfomycin (IV, MDR)", "8 g IV", "q8h", "⚡ Over 6 hours — always in combination"],
]
story.append(make_table(HDR4, poly_rows, cw4))
story.append(Spacer(1, 0.2*cm))
# ── 14. TETRACYCLINES ─────────────────────────────────────────────────────────
story.append(section_header("14. TETRACYCLINES"))
tetra_rows = [
["Doxycycline (IV)", "100 mg IV", "q12h", "Over 1–4 hours (slowly; phlebitis risk with rapid infusion)"],
["Doxycycline (PO)", "100 mg PO", "q12h", "Oral — N/A; take with food to reduce GI upset"],
]
story.append(make_table(HDR4, tetra_rows, cw4))
story.append(Spacer(1, 0.35*cm))
# ── QUICK SUMMARY TABLE ───────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=MID_BLUE))
story.append(Spacer(1, 0.15*cm))
summary_title = Paragraph("⚡ QUICK INFUSION TIME SUMMARY", ParagraphStyle(
"SumTitle", fontSize=10, fontName="Helvetica-Bold", textColor=DARK_BLUE,
spaceAfter=4
))
story.append(summary_title)
sum_rows = [
["Most beta-lactams (standard)", "30–60 min", "Standard practice for all routine doses"],
["Piperacillin-tazobactam (extended)", "4 hours", "Preferred for P. aeruginosa / resistant organisms"],
["Meropenem (extended)", "3 hours", "Preferred for P. aeruginosa / resistant organisms"],
["Ceftazidime-avibactam", "3 hours", "Always extend — per guidelines"],
["Doripenem", "1 hour", "Fixed infusion time"],
["Vancomycin", "≥60 min", "⚠ NEVER rapid — Red Man Syndrome / arrest"],
["Aminoglycosides", "20–30 min", "Fixed for accurate peak/trough monitoring"],
["Clindamycin", "20–60 min", "⚠ NEVER bolus — max 30 mg/min"],
["Fluoroquinolones", "60 min", "Reduces phlebitis / QT risk"],
["Metronidazole", "60 min", "Both loading and maintenance doses"],
["Oritavancin", "3 hours", "Long single-dose infusion"],
["Fosfomycin IV", "6 hours", "Always in combination for MDR"],
["Polymyxin B (load)", "60 min", "Loading dose only"],
]
sum_hdrs = ["Drug / Class", "Infusion Time", "Key Note"]
sum_cw = [C*0.30, C*0.18, C*0.52]
story.append(make_table(sum_hdrs, sum_rows, sum_cw))
story.append(Spacer(1, 0.3*cm))
# ── Footer ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#b0c8e0")))
story.append(Spacer(1, 0.1*cm))
story.append(Paragraph(
"Sources: Harrison's Principles of Internal Medicine 22E (2025, McGraw Hill) · Goldman-Cecil Medicine · "
"Lippincott Illustrated Reviews: Pharmacology · Campbell-Walsh-Wein Urology 15E | "
"For clinical use, always verify doses against local guidelines and adjust for renal/hepatic function.",
footer_style
))
# ── Build ──────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF created: {OUTPUT}")