~/opd-guide/create_opd_guide.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfgen import canvas
# ─── Color Palette ────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#003366")
TEAL = colors.HexColor("#007B7F")
LIGHT_BG = colors.HexColor("#EAF4F4")
ACCENT = colors.HexColor("#E87722")
LIGHT_ROW = colors.HexColor("#F0F8FF")
ALT_ROW = colors.HexColor("#DDEEFF")
WHITE = colors.white
BLACK = colors.black
GRAY = colors.HexColor("#555555")
LIGHT_GRAY= colors.HexColor("#888888")
PAGE_W, PAGE_H = A4
# ─── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, **kwargs):
return ParagraphStyle(name=name, **kwargs)
title_style = make_style("DocTitle",
fontSize=28, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica-Bold", spaceAfter=6)
subtitle_style = make_style("DocSub",
fontSize=13, textColor=WHITE, alignment=TA_CENTER,
fontName="Helvetica", spaceAfter=4)
chapter_style = make_style("Chapter",
fontSize=16, textColor=WHITE, fontName="Helvetica-Bold",
alignment=TA_LEFT, spaceAfter=2)
heading2_style = make_style("H2",
fontSize=13, textColor=NAVY, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=4)
heading3_style = make_style("H3",
fontSize=11, textColor=TEAL, fontName="Helvetica-Bold",
spaceBefore=6, spaceAfter=3)
body_style = make_style("Body",
fontSize=10, textColor=BLACK, fontName="Helvetica",
leading=15, spaceAfter=4, alignment=TA_JUSTIFY)
bullet_style = make_style("Bullet",
fontSize=10, textColor=BLACK, fontName="Helvetica",
leading=14, spaceAfter=2, leftIndent=14,
bulletIndent=4)
note_style = make_style("Note",
fontSize=9, textColor=GRAY, fontName="Helvetica-Oblique",
leading=12, spaceAfter=4, leftIndent=10)
footer_style = make_style("Footer",
fontSize=8, textColor=LIGHT_GRAY, alignment=TA_CENTER)
# ─── Header / Footer ──────────────────────────────────────────────────────────
def on_page(canvas_obj, doc):
canvas_obj.saveState()
# Top accent line
canvas_obj.setFillColor(TEAL)
canvas_obj.rect(0, PAGE_H - 8*mm, PAGE_W, 4*mm, fill=1, stroke=0)
# Bottom bar
canvas_obj.setFillColor(NAVY)
canvas_obj.rect(0, 0, PAGE_W, 12*mm, fill=1, stroke=0)
canvas_obj.setFillColor(WHITE)
canvas_obj.setFont("Helvetica", 8)
canvas_obj.drawString(15*mm, 4*mm, "OPD OPERATIONAL GUIDE | General / Multi-Specialty")
canvas_obj.drawRightString(PAGE_W - 15*mm, 4*mm,
f"Page {doc.page} | Confidential - All Staff")
canvas_obj.restoreState()
# ─── Chapter banner helper ─────────────────────────────────────────────────────
def chapter_banner(title, subtitle=None):
"""Returns a table that acts as a full-width colored chapter header."""
inner = [Paragraph(title, chapter_style)]
if subtitle:
inner.append(Paragraph(subtitle, make_style("CSub",
fontSize=10, textColor=colors.HexColor("#AADDEE"),
fontName="Helvetica-Oblique")))
t = Table([[inner]], colWidths=[PAGE_W - 40*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("ROUNDEDCORNERS", [4]),
]))
return t
def section_box(content_list, bg=LIGHT_BG):
"""Wraps content in a shaded box."""
t = Table([[content_list]], colWidths=[PAGE_W - 40*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
]))
return t
def make_table(headers, rows, col_widths=None):
data = [[Paragraph(h, make_style("TH",
fontSize=10, fontName="Helvetica-Bold",
textColor=WHITE)) for h in headers]]
for i, row in enumerate(rows):
bg = LIGHT_ROW if i % 2 == 0 else ALT_ROW
data.append([Paragraph(str(c), make_style(f"TC{i}",
fontSize=9, fontName="Helvetica", leading=13)) for c in row])
avail = PAGE_W - 40*mm
cw = col_widths if col_widths else [avail / len(headers)] * len(headers)
t = Table(data, colWidths=cw)
style = TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [LIGHT_ROW, ALT_ROW]),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#AACCCC")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
t.setStyle(style)
return t
# ─── Cover Page ───────────────────────────────────────────────────────────────
def cover_page():
elems = []
# Big colored top block
top_table = Table([[
Paragraph("OPD OPERATIONAL GUIDE", title_style),
Paragraph("General / Multi-Specialty Hospital", subtitle_style),
Paragraph("Edition 2026 | All Staff Reference Manual", subtitle_style),
]], colWidths=[PAGE_W - 40*mm])
top_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 30),
("BOTTOMPADDING", (0,0), (-1,-1), 30),
("LEFTPADDING", (0,0), (-1,-1), 20),
("RIGHTPADDING", (0,0), (-1,-1), 20),
]))
elems.append(top_table)
elems.append(Spacer(1, 14))
# Accent strip
elems.append(HRFlowable(width="100%", thickness=4, color=ACCENT, spaceAfter=14))
# Quick-reference info boxes
info_data = [
["Department", "Purpose", "Issued By", "Review Date"],
["All OPD Departments", "Operational Reference", "Hospital Administration", "July 2026"],
]
elems.append(make_table(info_data[0], [info_data[1]]))
elems.append(Spacer(1, 20))
# About this guide
elems.append(Paragraph("About This Guide", heading2_style))
elems.append(Paragraph(
"This Outpatient Department (OPD) Operational Guide is the definitive reference for all staff "
"working in or supporting OPD services. It covers patient flow, registration, triage, clinical "
"consultation, investigations, pharmacy, documentation, infection control, and emergency response. "
"All staff are expected to read, understand, and comply with the procedures described herein.",
body_style))
elems.append(Spacer(1, 8))
# Scope table
elems.append(Paragraph("Scope of This Document", heading3_style))
scope_rows = [
["Front Desk / Registration", "Patient registration, appointment management, billing"],
["Triage Nurses", "Vitals, triage categorisation, queue management"],
["Medical Officers / Residents", "Consultation, clinical documentation, ordering investigations"],
["Specialist Consultants", "Complex case review, referrals, procedure orders"],
["Pharmacy Staff", "Dispensing, counselling, returns management"],
["Housekeeping / Support", "Cleanliness, equipment handling, waste disposal"],
["OPD Manager / Supervisor", "Operations oversight, staff scheduling, quality monitoring"],
]
elems.append(make_table(["Role", "Primary Responsibilities"], scope_rows,
col_widths=[85*mm, PAGE_W - 40*mm - 85*mm]))
elems.append(PageBreak())
return elems
# ─── Table of Contents ────────────────────────────────────────────────────────
def toc_page():
elems = []
elems.append(chapter_banner("TABLE OF CONTENTS"))
elems.append(Spacer(1, 12))
sections = [
("1", "OPD Overview & Objectives", "3"),
("2", "Patient Registration & Appointment", "4"),
("3", "Triage & Vital Signs Assessment", "5"),
("4", "Consultation Flow & Clinical Standards","6"),
("5", "Investigations & Diagnostic Services", "7"),
("6", "Pharmacy & Prescription Management", "8"),
("7", "Referral & Follow-up Protocols", "9"),
("8", "Infection Prevention & Control", "10"),
("9", "Emergency Response in OPD", "11"),
("10","Documentation & Medical Records", "12"),
("11","Patient Rights & Feedback", "13"),
("12","Staff Conduct & Escalation", "14"),
("13","Key Performance Indicators", "15"),
("14","Quick Reference & Contacts", "16"),
]
toc_data = [[s, t, p] for s, t, p in sections]
elems.append(make_table(["#", "Section", "Page"],
toc_data,
col_widths=[15*mm, PAGE_W - 40*mm - 35*mm, 20*mm]))
elems.append(PageBreak())
return elems
# ─── Section 1: OPD Overview ──────────────────────────────────────────────────
def section_overview():
e = []
e.append(chapter_banner("SECTION 1", "OPD Overview & Objectives"))
e.append(Spacer(1, 10))
e.append(Paragraph("1.1 What is the OPD?", heading2_style))
e.append(Paragraph(
"The Outpatient Department (OPD) is the first point of contact for the majority of patients "
"seeking non-emergency medical care. It provides scheduled consultations, follow-up visits, "
"minor procedures, and allied health services without admission to a hospital ward.",
body_style))
e.append(Paragraph("1.2 Core Objectives", heading2_style))
objectives = [
"Provide timely, safe, and dignified care to every patient.",
"Maintain efficient patient flow to minimise waiting times.",
"Ensure accurate clinical documentation for continuity of care.",
"Promote infection prevention and a clean clinical environment.",
"Facilitate seamless referrals to inpatient and specialist services.",
"Collect patient feedback to drive continuous quality improvement.",
]
for obj in objectives:
e.append(Paragraph(f"• {obj}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("1.3 OPD Operating Hours", heading2_style))
hours_rows = [
["General OPD", "Monday - Saturday", "08:00 - 17:00"],
["Specialist Clinics","Monday - Friday", "09:00 - 15:00"],
["Dental OPD", "Monday - Friday", "09:00 - 14:00"],
["Paediatric OPD", "Monday - Saturday", "08:00 - 16:00"],
["Dermatology / Eye","Tuesday, Thursday, Saturday","10:00 - 14:00"],
["After-Hours Walk-in","Daily", "17:00 - 22:00"],
]
e.append(make_table(["Clinic", "Days", "Hours"], hours_rows,
col_widths=[70*mm, 70*mm, PAGE_W - 40*mm - 140*mm]))
e.append(Paragraph(
"Note: Hours may change on public holidays. Refer to the monthly schedule posted at the OPD notice board.",
note_style))
e.append(PageBreak())
return e
# ─── Section 2: Registration ──────────────────────────────────────────────────
def section_registration():
e = []
e.append(chapter_banner("SECTION 2", "Patient Registration & Appointment"))
e.append(Spacer(1, 10))
e.append(Paragraph("2.1 Walk-in Registration", heading2_style))
steps = [
"Patient approaches the registration counter; staff greets courteously.",
"Verify patient identity using a government-issued photo ID.",
"Search existing records in the Hospital Information System (HIS); create a new record if first visit.",
"Confirm presenting complaint and direct to appropriate specialty counter.",
"Issue a token / queue number and estimated waiting time.",
"Collect registration fee (if applicable) and issue receipt.",
"Guide patient to the waiting area.",
]
for i, s in enumerate(steps, 1):
e.append(Paragraph(f"<b>Step {i}:</b> {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("2.2 Appointment Booking", heading2_style))
appt_rows = [
["In-person", "Registration counter", "Same day / advance"],
["Telephone", "Call centre (Ext. 200)", "Advance booking only"],
["Online", "Hospital patient portal", "Up to 30 days ahead"],
["Mobile App", "Hospital app (iOS / Android)", "Up to 30 days ahead"],
]
e.append(make_table(["Channel", "Method", "Window"], appt_rows))
e.append(Spacer(1, 8))
e.append(Paragraph("2.3 Required Documentation", heading2_style))
docs = [
"Valid government-issued photo ID (Aadhaar / Passport / Driver Licence / Voter ID).",
"Previous OPD card or UHID number (for follow-up patients).",
"Referral letter (if referred from another facility).",
"Previous investigation reports relevant to the presenting complaint.",
"Insurance card / pre-authorisation letter (for insured patients).",
]
for d in docs:
e.append(Paragraph(f"• {d}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("2.4 Cancellation & No-Show Policy", heading2_style))
e.append(Paragraph(
"Patients must cancel appointments at least 4 hours in advance by calling the call centre "
"or using the online portal. Repeated no-shows (3 or more) may require administrative review "
"before rebooking. Cancelled slots should be offered to walk-in or waiting-list patients promptly.",
body_style))
e.append(PageBreak())
return e
# ─── Section 3: Triage ────────────────────────────────────────────────────────
def section_triage():
e = []
e.append(chapter_banner("SECTION 3", "Triage & Vital Signs Assessment"))
e.append(Spacer(1, 10))
e.append(Paragraph("3.1 Purpose of Triage", heading2_style))
e.append(Paragraph(
"Triage ensures patients are seen in order of clinical urgency rather than order of arrival. "
"A trained triage nurse must assess every patient within 5 minutes of registration.",
body_style))
e.append(Paragraph("3.2 Triage Categories", heading2_style))
triage_rows = [
["Priority 1 - IMMEDIATE (Red)", "Life-threatening; act within 0-10 min",
"Chest pain, difficulty breathing, altered consciousness, severe bleeding"],
["Priority 2 - URGENT (Yellow)", "Potentially serious; act within 10-30 min",
"High fever >39 C, moderate pain, fractures, vomiting with dehydration"],
["Priority 3 - ROUTINE (Green)", "Stable; wait is acceptable (30-60 min)",
"Mild symptoms, follow-up visits, routine check-ups"],
["Priority 4 - NON-URGENT (Blue)", "Minor; can wait > 60 min or redirect to GP",
"Minor cuts, insect bites, prescription refills, routine certificates"],
]
e.append(make_table(["Category", "Action Timeline", "Examples"], triage_rows,
col_widths=[58*mm, 42*mm, PAGE_W - 40*mm - 100*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("3.3 Standard Vital Signs Protocol", heading2_style))
vitals = [
("Temperature", "Oral / axillary / tympanic", "Normal 36.1 - 37.2 degC"),
("Pulse Rate", "Radial artery, 60 seconds", "Normal 60 - 100 bpm"),
("Respiratory Rate", "Chest observation, 60 s", "Normal 12 - 20 breaths/min"),
("Blood Pressure", "Upper arm cuff, seated 5 min","Normal < 120/80 mmHg"),
("SpO2", "Pulse oximeter, index finger","Normal > 95%"),
("Blood Glucose (PRN)","Glucometer, fingertip", "Fasting < 100 mg/dL"),
("Weight / Height", "Calibrated scale / stadiometer","Record BMI"),
("Pain Score", "Visual Analogue Scale 0-10", "Document and act >= 7"),
]
e.append(make_table(["Parameter", "Method", "Reference Range"], vitals,
col_widths=[50*mm, 65*mm, PAGE_W - 40*mm - 115*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("3.4 Red-Flag Signs - Immediate Escalation Required", heading2_style))
red_flags = [
"Oxygen saturation < 90% on room air",
"Systolic BP < 90 mmHg or > 180 mmHg with symptoms",
"Heart rate < 40 or > 150 bpm",
"Glasgow Coma Scale (GCS) score < 13",
"Active seizure or post-ictal state",
"Suspected stroke (FAST positive: Face drooping, Arm weakness, Speech difficulty, Time to call)",
"Anaphylaxis (urticaria + hypotension + bronchospasm)",
"Uncontrolled haemorrhage",
]
for rf in red_flags:
e.append(Paragraph(f"⚠ {rf}", make_style("RF",
fontSize=10, textColor=colors.HexColor("#CC0000"),
fontName="Helvetica-Bold", leading=14, leftIndent=12, spaceAfter=3)))
e.append(PageBreak())
return e
# ─── Section 4: Consultation ──────────────────────────────────────────────────
def section_consultation():
e = []
e.append(chapter_banner("SECTION 4", "Consultation Flow & Clinical Standards"))
e.append(Spacer(1, 10))
e.append(Paragraph("4.1 Consultation Room Standards", heading2_style))
standards = [
"Ensure privacy - door/curtain closed before examination.",
"Confirm patient identity (name + date of birth) before beginning.",
"Wash hands / use hand sanitiser before and after every patient contact.",
"Offer a chaperone for intimate examinations; document consent.",
"Explain diagnosis, treatment plan, and next steps in simple language.",
"Provide written instructions or a printed prescription to every patient.",
]
for s in standards:
e.append(Paragraph(f"• {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("4.2 SOAP Note Documentation", heading2_style))
soap_rows = [
["S - Subjective", "Chief complaint, history of present illness, past medical history, drug history, allergies, social history, family history"],
["O - Objective", "Vital signs, physical examination findings, relevant investigations"],
["A - Assessment", "Diagnosis or differential diagnoses with reasoning"],
["P - Plan", "Investigations ordered, treatment prescribed, referrals, follow-up date, patient education"],
]
e.append(make_table(["Component", "Content"], soap_rows,
col_widths=[45*mm, PAGE_W - 40*mm - 45*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("4.3 Prescription Standards", heading2_style))
rx_items = [
"Write legibly or use electronic prescribing module.",
"Include: patient name, UHID, date, drug name (generic preferred), dose, frequency, duration, route.",
"Indicate allergies prominently at the top of the prescription.",
"Sign and stamp with designation and registration number.",
"Avoid abbreviations that may be misread (e.g., use 'micrograms' not 'mcg').",
"High-alert medications require dual check and second physician signature.",
]
for item in rx_items:
e.append(Paragraph(f"• {item}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("4.4 Average Consultation Time Targets", heading2_style))
time_rows = [
["New Patient - General", "15 - 20 minutes"],
["Follow-up - General", "8 - 12 minutes"],
["New Patient - Specialist","20 - 30 minutes"],
["Follow-up - Specialist", "10 - 15 minutes"],
["Minor Procedure", "20 - 45 minutes"],
]
e.append(make_table(["Visit Type", "Target Duration"], time_rows,
col_widths=[100*mm, PAGE_W - 40*mm - 100*mm]))
e.append(PageBreak())
return e
# ─── Section 5: Investigations ────────────────────────────────────────────────
def section_investigations():
e = []
e.append(chapter_banner("SECTION 5", "Investigations & Diagnostic Services"))
e.append(Spacer(1, 10))
e.append(Paragraph("5.1 Ordering Investigations", heading2_style))
steps = [
"Investigations must be ordered through the HIS; paper requests are only for system downtime.",
"Clearly indicate clinical indication on every request form.",
"Urgent requests must be verbally communicated to the lab / radiology team.",
"Inform the patient of expected turnaround times and collection instructions.",
"Review all results on the same day or within 24 hours of reporting.",
"Critical values must be communicated verbally and documented immediately.",
]
for s in steps:
e.append(Paragraph(f"• {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("5.2 Turnaround Time (TAT) Targets", heading2_style))
tat_rows = [
["Routine haematology / biochemistry", "2 - 4 hours"],
["Urgent / STAT tests", "< 1 hour"],
["Microbiology cultures", "24 - 72 hours"],
["Histopathology", "5 - 7 working days"],
["Plain X-ray (reporting)", "1 - 2 hours"],
["Ultrasound abdomen", "Same day (scheduled)"],
["CT scan (routine)", "Same day / next day"],
["ECG", "15 minutes"],
["Pulmonary Function Test", "45 minutes"],
]
e.append(make_table(["Investigation", "Target TAT"], tat_rows,
col_widths=[110*mm, PAGE_W - 40*mm - 110*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("5.3 Critical Value Policy", heading2_style))
e.append(Paragraph(
"When the laboratory or radiology department reports a critical value, they must call the "
"ordering clinician directly within 15 minutes. The clinician must acknowledge, document the "
"call (time, value, action taken) in the HIS, and initiate appropriate management immediately.",
body_style))
critical = [
"Haemoglobin < 6.0 g/dL or > 20 g/dL",
"Platelet count < 50 x 10^9/L or > 1000 x 10^9/L",
"Serum K+ < 2.8 or > 6.5 mEq/L",
"Serum Na+ < 120 or > 160 mEq/L",
"Blood glucose < 50 or > 500 mg/dL",
"INR > 5.0",
"Troponin I positive (any level)",
"SpO2 < 85% on pulse oximetry",
]
for c in critical:
e.append(Paragraph(f"⚠ {c}", make_style("Crit",
fontSize=10, textColor=colors.HexColor("#880000"),
fontName="Helvetica", leading=13, leftIndent=12, spaceAfter=3)))
e.append(PageBreak())
return e
# ─── Section 6: Pharmacy ──────────────────────────────────────────────────────
def section_pharmacy():
e = []
e.append(chapter_banner("SECTION 6", "Pharmacy & Prescription Management"))
e.append(Spacer(1, 10))
e.append(Paragraph("6.1 Prescription Submission & Dispensing", heading2_style))
pharm_steps = [
"Patient submits prescription at the OPD pharmacy counter.",
"Pharmacist verifies patient identity, UHID, and prescription authenticity.",
"Check for drug interactions, allergies, and dose appropriateness.",
"Dispense medications and counsel patient on usage, side effects, and storage.",
"Issue a labelled package with printed instructions for every medication.",
"Record dispensing in the pharmacy management system.",
]
for s in pharm_steps:
e.append(Paragraph(f"• {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("6.2 Controlled Substance Protocol", heading2_style))
e.append(Paragraph(
"Schedule H / H1 / X drugs require a prescription signed and stamped by a registered medical "
"practitioner. Quantity dispensed must match prescription exactly. All controlled substance "
"transactions must be recorded in the controlled drug register and reconciled daily.",
body_style))
e.append(Spacer(1, 8))
e.append(Paragraph("6.3 Medication Return & Disposal", heading2_style))
e.append(Paragraph(
"Patients may return unused, sealed medications within 7 days of purchase with receipt. "
"Returned medications are quarantined and disposed of per biomedical waste rules - they "
"must never be re-dispensed. Document returns in the system.",
body_style))
e.append(Spacer(1, 8))
e.append(Paragraph("6.4 Prescription Error Reporting", heading2_style))
e.append(Paragraph(
"Any prescription ambiguity must be clarified with the prescribing physician before dispensing. "
"Near-miss events and dispensing errors must be reported through the Incident Reporting System "
"within 24 hours. Patient safety is the absolute priority.",
body_style))
e.append(PageBreak())
return e
# ─── Section 7: Referral ──────────────────────────────────────────────────────
def section_referral():
e = []
e.append(chapter_banner("SECTION 7", "Referral & Follow-up Protocols"))
e.append(Spacer(1, 10))
e.append(Paragraph("7.1 Internal Referral", heading2_style))
e.append(Paragraph(
"Refer patients within the hospital using the HIS referral module. The referral should include "
"a clinical summary, reason for referral, and urgency level. The receiving specialist acknowledges "
"within 2 hours for urgent and 24 hours for routine referrals.",
body_style))
e.append(Paragraph("7.2 External Referral", heading2_style))
e.append(Paragraph(
"When referring to an external facility, provide a signed referral letter with: patient demographics, "
"diagnosis, treatment given, investigations, and reason for referral. Ensure patient/family "
"understands the referral and has transport if needed.",
body_style))
e.append(Spacer(1, 6))
e.append(Paragraph("7.3 Referral Urgency Classification", heading2_style))
ref_rows = [
["Routine", "> 24 hours", "Stable, non-urgent condition requiring specialist input"],
["Soon", "Same day", "Worsening but currently stable; specialist review needed today"],
["Urgent", "< 2 hours", "Significant clinical concern; potential for deterioration"],
["Emergency", "Immediate", "Activate emergency team; patient at risk of serious harm"],
]
e.append(make_table(["Level", "Response Time", "Criteria"], ref_rows,
col_widths=[28*mm, 28*mm, PAGE_W - 40*mm - 56*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("7.4 Follow-up Appointments", heading2_style))
followup = [
"Every patient with an ongoing condition should leave OPD with a follow-up appointment date.",
"Follow-up intervals should be guided by the clinical condition and treatment plan.",
"SMS / app reminders should be sent 48 hours before the scheduled appointment.",
"Patients who miss follow-up should be contacted by the care coordinator within 48 hours.",
"Discharge from OPD follow-up must be documented with clear patient instructions.",
]
for f in followup:
e.append(Paragraph(f"• {f}", bullet_style))
e.append(PageBreak())
return e
# ─── Section 8: Infection Control ────────────────────────────────────────────
def section_ipc():
e = []
e.append(chapter_banner("SECTION 8", "Infection Prevention & Control (IPC)"))
e.append(Spacer(1, 10))
e.append(Paragraph("8.1 Hand Hygiene - WHO 5 Moments", heading2_style))
moments = [
"Before touching a patient",
"Before a clean / aseptic procedure",
"After body fluid exposure risk",
"After touching a patient",
"After touching a patient's surroundings",
]
for i, m in enumerate(moments, 1):
e.append(Paragraph(f"<b>{i}.</b> {m}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("8.2 Personal Protective Equipment (PPE)", heading2_style))
ppe_rows = [
["Standard examination", "Gloves (as needed), apron if splash risk"],
["Suspected airborne infection", "N95 respirator, gown, eye protection, gloves"],
["Wound dressing / minor procedure","Sterile gloves, mask, drape"],
["COVID-19 / influenza screening", "Surgical mask, gloves, face shield or goggles"],
]
e.append(make_table(["Situation", "PPE Required"], ppe_rows,
col_widths=[75*mm, PAGE_W - 40*mm - 75*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("8.3 Environmental Cleaning", heading2_style))
cleaning = [
"Consultation rooms: clean between every patient with 70% ethanol or approved disinfectant.",
"Waiting area: wipe high-touch surfaces (chairs, counters) every 2 hours.",
"Terminal cleaning: full room cleaning after any known infectious case.",
"Spills of blood/body fluid: use spill kit; follow written spill management protocol.",
]
for c in cleaning:
e.append(Paragraph(f"• {c}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("8.4 Biomedical Waste Segregation", heading2_style))
waste_rows = [
["Yellow bag", "Anatomical waste, soiled dressings, body fluids"],
["Red bag", "Recyclable contaminated plastic (syringes, IV bags)"],
["Blue / White","Glass, sharps - place directly in sharps container"],
["Black bag", "General non-hazardous solid waste"],
["Sharps bin", "Needles, blades, lancets - never recap needles"],
]
e.append(make_table(["Container", "Contents"], waste_rows,
col_widths=[40*mm, PAGE_W - 40*mm - 40*mm]))
e.append(PageBreak())
return e
# ─── Section 9: Emergency Response ───────────────────────────────────────────
def section_emergency():
e = []
e.append(chapter_banner("SECTION 9", "Emergency Response in OPD"))
e.append(Spacer(1, 10))
e.append(Paragraph("9.1 Cardiac Arrest - OPD Response", heading2_style))
cpr_steps = [
"Call for help loudly; ask someone to call the crash team (Ext. 999) and get the AED.",
"Check for responsiveness - shout and tap shoulders.",
"Open airway (head-tilt chin-lift); look, listen, feel for breathing (max 10 seconds).",
"If no breathing / only gasping: start CPR - 30 compressions : 2 breaths.",
"Compression rate 100-120/min; depth 5-6 cm; allow full chest recoil.",
"Attach AED as soon as available; follow voice prompts.",
"Continue CPR until crash team arrives or patient shows signs of life.",
"Document time of arrest, interventions, and outcome.",
]
for i, s in enumerate(cpr_steps, 1):
e.append(Paragraph(f"<b>{i}.</b> {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("9.2 Anaphylaxis Management", heading2_style))
ana_steps = [
"Identify trigger; remove or stop if possible.",
"Adrenaline (Epinephrine) 0.5 mg (0.5 mL of 1:1000) IM into outer thigh - FIRST-LINE.",
"Call for help (Ext. 999); lay patient flat, legs elevated (unless breathing difficulty).",
"High-flow oxygen 10-15 L/min via non-rebreathing mask.",
"IV access; 500-1000 mL 0.9% saline fluid challenge.",
"Chlorphenamine 10 mg IV and Hydrocortisone 200 mg IV (after adrenaline).",
"Monitor vitals every 5 minutes; repeat adrenaline every 5 min if no improvement.",
"Transfer to Emergency Department.",
]
for i, s in enumerate(ana_steps, 1):
e.append(Paragraph(f"<b>{i}.</b> {s}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("9.3 Emergency Equipment Checklist (Daily)", heading2_style))
eq_rows = [
["AED (Defibrillator)", "Charged and functional"],
["Crash Trolley", "Sealed; check seal intact; reconcile after any use"],
["Oxygen cylinder", "> 75% full; regulator functional"],
["Bag-valve-mask (BVM)", "Present and clean"],
["Adrenaline 1:1000 ampoules","In-date stock; minimum 3 ampoules"],
["IV cannula kit", "Various sizes available"],
["Glucometer + strips", "Calibrated; strip expiry checked"],
]
e.append(make_table(["Equipment", "Daily Check Requirement"], eq_rows,
col_widths=[80*mm, PAGE_W - 40*mm - 80*mm]))
e.append(PageBreak())
return e
# ─── Section 10: Documentation ────────────────────────────────────────────────
def section_documentation():
e = []
e.append(chapter_banner("SECTION 10", "Documentation & Medical Records"))
e.append(Spacer(1, 10))
e.append(Paragraph("10.1 Principles of Medical Documentation", heading2_style))
principles = [
"Every patient encounter must be documented on the same day.",
"Entries must be legible, dated, timed, and signed with designation.",
"Corrections: draw a single line through the error, initial, date, and rewrite - never use whiteout.",
"Late entries: clearly label as 'Late Entry', include original and current date/time.",
"Use standardised clinical terminology; avoid non-approved abbreviations.",
"All consented procedures must have a signed consent form attached.",
"Electronic records are the primary system; paper serves as backup during downtime only.",
]
for p in principles:
e.append(Paragraph(f"• {p}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("10.2 Minimum Dataset per OPD Visit", heading2_style))
dataset_rows = [
["Date and time of visit", "Mandatory"],
["Patient identifiers (name, UHID, DOB)","Mandatory"],
["Presenting complaint", "Mandatory"],
["Vital signs", "Mandatory"],
["Clinical examination findings","Mandatory"],
["Diagnosis / differential", "Mandatory"],
["Investigations ordered", "If applicable"],
["Treatment / prescription", "If applicable"],
["Referral", "If applicable"],
["Follow-up plan", "Mandatory"],
["Patient education given", "Mandatory"],
["Signature & stamp", "Mandatory"],
]
e.append(make_table(["Data Element", "Status"], dataset_rows,
col_widths=[100*mm, PAGE_W - 40*mm - 100*mm]))
e.append(PageBreak())
return e
# ─── Section 11: Patient Rights ───────────────────────────────────────────────
def section_patient_rights():
e = []
e.append(chapter_banner("SECTION 11", "Patient Rights & Feedback"))
e.append(Spacer(1, 10))
e.append(Paragraph("11.1 Patient Rights", heading2_style))
rights = [
"Right to receive respectful, non-discriminatory care irrespective of gender, religion, or economic status.",
"Right to be informed about their diagnosis, treatment options, and prognosis in a language they understand.",
"Right to give or withhold informed consent before any procedure or treatment.",
"Right to privacy and confidentiality of medical information.",
"Right to access their own medical records.",
"Right to refuse treatment (with documented informed refusal).",
"Right to lodge a complaint without fear of retribution.",
"Right to be referred to another provider if a second opinion is sought.",
]
for r in rights:
e.append(Paragraph(f"• {r}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("11.2 Patient Feedback Mechanism", heading2_style))
feedback_rows = [
["Feedback form", "Available at registration desk and exit; deposit in suggestion box"],
["QR code survey", "Displayed in waiting area; instant smartphone access"],
["Online portal", "hospital.example.com/feedback"],
["Call centre", "Ext. 210; Mon-Sat 08:00-17:00"],
["Patient relations desk","Level 1, near main OPD entrance"],
]
e.append(make_table(["Channel", "Details"], feedback_rows,
col_widths=[55*mm, PAGE_W - 40*mm - 55*mm]))
e.append(Spacer(1, 8))
e.append(Paragraph("11.3 Complaint Resolution Timeline", heading2_style))
comp_rows = [
["Verbal complaint at counter","Resolve immediately at point of care"],
["Written complaint", "Acknowledge within 24 hours; resolve within 7 days"],
["Formal grievance", "Acknowledge within 24 hours; resolve within 21 days"],
["Escalation to regulator", "As per applicable law; legal team involvement"],
]
e.append(make_table(["Complaint Type", "Response Timeline"], comp_rows,
col_widths=[80*mm, PAGE_W - 40*mm - 80*mm]))
e.append(PageBreak())
return e
# ─── Section 12: Staff Conduct ────────────────────────────────────────────────
def section_staff_conduct():
e = []
e.append(chapter_banner("SECTION 12", "Staff Conduct & Escalation"))
e.append(Spacer(1, 10))
e.append(Paragraph("12.1 Code of Conduct", heading2_style))
conduct = [
"Maintain professional appearance: clean uniform, name badge visible at all times.",
"Address patients by their preferred name; always introduce yourself.",
"Use plain language; avoid medical jargon when speaking to patients and families.",
"Mobile phone personal use is restricted to break times only in designated areas.",
"Do not discuss patient information in public areas (corridors, lifts, cafeteria).",
"Treat all colleagues with respect; harassment of any kind will not be tolerated.",
"Follow the hospital social media policy - never post patient images or details online.",
]
for c in conduct:
e.append(Paragraph(f"• {c}", bullet_style))
e.append(Spacer(1, 8))
e.append(Paragraph("12.2 Escalation Matrix", heading2_style))
esc_rows = [
["Clinical concern about a patient", "Immediate supervisor / duty doctor / crash team (999)"],
["Staffing shortage", "OPD Supervisor / Nurse Manager"],
["Equipment failure", "Biomedical Engineering (Ext. 310)"],
["IT / HIS downtime", "IT Helpdesk (Ext. 400); activate downtime procedure"],
["Patient aggression / security issue", "Security (Ext. 100); do not engage aggressively"],
["Ethical / legal concern", "Hospital Ethics Committee or Legal Dept."],
["Medication error / near-miss", "Incident Reporting System + immediate supervisor"],
]
e.append(make_table(["Situation", "Escalate To"], esc_rows,
col_widths=[80*mm, PAGE_W - 40*mm - 80*mm]))
e.append(PageBreak())
return e
# ─── Section 13: KPIs ─────────────────────────────────────────────────────────
def section_kpi():
e = []
e.append(chapter_banner("SECTION 13", "Key Performance Indicators (KPIs)"))
e.append(Spacer(1, 10))
e.append(Paragraph(
"The following KPIs are monitored monthly by the OPD Manager and reported to the Hospital "
"Quality Committee. All staff are expected to contribute to meeting these targets.",
body_style))
e.append(Spacer(1, 8))
kpi_rows = [
["Registration to triage time", "< 5 minutes", "95% of patients"],
["Triage to doctor seen (Priority 3)","< 30 minutes","90% of patients"],
["Average OPD turnaround time", "< 90 minutes", "85% of visits"],
["Patient satisfaction score", ">= 4.0 / 5.0","Monthly survey"],
["Prescription error rate", "< 0.1%", "Monthly audit"],
["Investigation critical value notification","< 15 min","100% compliance"],
["Hand hygiene compliance", ">= 85%", "Audit twice monthly"],
["OPD appointment no-show rate", "< 15%", "Monthly report"],
["Documentation completeness", ">= 95%", "Random 10% audit"],
["Complaint resolution within SLA", "100%", "Monthly review"],
]
e.append(make_table(["KPI", "Target", "Monitoring Method"], kpi_rows,
col_widths=[90*mm, 35*mm, PAGE_W - 40*mm - 125*mm]))
e.append(PageBreak())
return e
# ─── Section 14: Quick Reference ─────────────────────────────────────────────
def section_quick_ref():
e = []
e.append(chapter_banner("SECTION 14", "Quick Reference & Key Contacts"))
e.append(Spacer(1, 10))
e.append(Paragraph("14.1 Emergency Extension Numbers", heading2_style))
ext_rows = [
["Crash Team / Code Blue", "999"],
["Security", "100"],
["Fire Emergency", "101"],
["Nursing Supervisor", "150"],
["OPD Manager", "160"],
["Patient Relations", "210"],
["Call Centre / Appointments", "200"],
["Pharmacy", "220"],
["Laboratory", "230"],
["Radiology", "240"],
["Biomedical Engineering", "310"],
["IT Helpdesk", "400"],
["Housekeeping", "450"],
["Ambulance", "108 (national)"],
]
e.append(make_table(["Service", "Extension / Number"], ext_rows,
col_widths=[100*mm, PAGE_W - 40*mm - 100*mm]))
e.append(Spacer(1, 10))
e.append(Paragraph("14.2 Common Acronyms", heading2_style))
acronym_rows = [
["AED", "Automated External Defibrillator"],
["BVM", "Bag-Valve-Mask"],
["CPR", "Cardiopulmonary Resuscitation"],
["GCS", "Glasgow Coma Scale"],
["HIS", "Hospital Information System"],
["IPC", "Infection Prevention and Control"],
["PPE", "Personal Protective Equipment"],
["SOAP", "Subjective, Objective, Assessment, Plan (clinical note format)"],
["STAT", "From Latin statim - immediately"],
["TAT", "Turnaround Time"],
["UHID", "Unique Hospital Identification Number"],
]
e.append(make_table(["Acronym", "Full Form"], acronym_rows,
col_widths=[30*mm, PAGE_W - 40*mm - 30*mm]))
e.append(Spacer(1, 14))
e.append(HRFlowable(width="100%", thickness=2, color=TEAL))
e.append(Spacer(1, 8))
e.append(Paragraph(
"This document is reviewed annually or whenever there is a significant change in policy or regulation. "
"Queries regarding this guide should be directed to the OPD Manager (Ext. 160) or the Quality Department.",
note_style))
e.append(Paragraph(
"OPD Operational Guide | Edition 2026 | All rights reserved - Hospital Administration",
footer_style))
return e
# ─── Build PDF ─────────────────────────────────────────────────────────────────
OUTPUT = "/home/daytona/workspace/opd-guide/OPD_Operational_Guide_2026.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=20*mm, leftMargin=20*mm,
topMargin=20*mm, bottomMargin=18*mm,
title="OPD Operational Guide 2026",
author="Hospital Administration",
subject="Outpatient Department Reference Manual",
)
story = []
story += cover_page()
story += toc_page()
story += section_overview()
story += section_registration()
story += section_triage()
story += section_consultation()
story += section_investigations()
story += section_pharmacy()
story += section_referral()
story += section_ipc()
story += section_emergency()
story += section_documentation()
story += section_patient_rights()
story += section_staff_conduct()
story += section_kpi()
story += section_quick_ref()
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
print(f"PDF created: {OUTPUT}")