Who pain ladder
WHO pain ladder three steps analgesic ladder 2024

| Step | Pain Level | Drugs Used |
|---|---|---|
| Step 1 | Mild pain | Non-opioids: Paracetamol (acetaminophen), NSAIDs (e.g. aspirin, ibuprofen, diclofenac) ± adjuvants |
| Step 2 | Mild-to-moderate pain (Step 1 fails) | Weak opioids: Codeine, Tramadol + non-opioid ± adjuvants |
| Step 3 | Moderate-to-severe pain (Step 2 fails) | Strong opioids: Morphine, Oxycodone, Hydromorphone, Fentanyl + non-opioid ± adjuvants |
Give me in pdf
mkdir -p /home/daytona/workspace/who-pain-ladder && ls /home/daytona/workspace/who-pain-ladder/
~/who-pain-ladder/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
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import Image as RLImage
import urllib.request
import os
OUTPUT = "/home/daytona/workspace/who-pain-ladder/WHO_Pain_Ladder.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=2*cm, leftMargin=2*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm,
title="WHO Analgesic (Pain) Ladder",
author="Orris Medical Reference"
)
W, H = A4
styles = getSampleStyleSheet()
# ── Custom styles ────────────────────────────────────────────────────────────
BLUE = colors.HexColor("#1a3a5c")
ORANGE = colors.HexColor("#e07b2a")
LIGHT = colors.HexColor("#fdf3e7")
STEP1C = colors.HexColor("#fff3e0")
STEP2C = colors.HexColor("#ffe0b2")
STEP3C = colors.HexColor("#e07b2a")
HEADBG = colors.HexColor("#1a3a5c")
title_style = ParagraphStyle(
"Title", parent=styles["Title"],
textColor=colors.white, fontSize=22, leading=28,
alignment=TA_CENTER, spaceAfter=4
)
subtitle_style = ParagraphStyle(
"Sub", parent=styles["Normal"],
textColor=colors.HexColor("#bbcfe0"), fontSize=10,
alignment=TA_CENTER, spaceAfter=2
)
section_style = ParagraphStyle(
"Section", parent=styles["Heading2"],
textColor=BLUE, fontSize=13, leading=16,
spaceBefore=14, spaceAfter=4,
borderPad=2
)
body_style = ParagraphStyle(
"Body", parent=styles["Normal"],
fontSize=10, leading=14, alignment=TA_JUSTIFY,
textColor=colors.HexColor("#2c2c2c"), spaceAfter=4
)
bullet_style = ParagraphStyle(
"Bullet", parent=styles["Normal"],
fontSize=10, leading=13,
leftIndent=16, bulletIndent=4,
textColor=colors.HexColor("#2c2c2c"), spaceAfter=3
)
table_header_style = ParagraphStyle(
"TH", parent=styles["Normal"],
fontSize=10, leading=13, textColor=colors.white,
alignment=TA_CENTER, fontName="Helvetica-Bold"
)
table_cell_style = ParagraphStyle(
"TC", parent=styles["Normal"],
fontSize=9, leading=12, textColor=colors.HexColor("#2c2c2c"),
alignment=TA_LEFT
)
footer_style = ParagraphStyle(
"Footer", parent=styles["Normal"],
fontSize=7.5, textColor=colors.grey,
alignment=TA_CENTER
)
principle_label = ParagraphStyle(
"PL", parent=styles["Normal"],
fontSize=10, fontName="Helvetica-Bold",
textColor=BLUE, spaceAfter=1
)
story = []
# ── HEADER BANNER ────────────────────────────────────────────────────────────
header_data = [[
Paragraph("WHO Analgesic (Pain) Ladder", title_style),
]]
header_sub = [[
Paragraph("World Health Organization | Introduced 1986 | Cancer & Chronic Pain Management", subtitle_style),
]]
header_table = Table([[
Paragraph("WHO Analgesic (Pain) Ladder", title_style),
]], colWidths=[W - 4*cm])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADBG),
("ROUNDEDCORNERS", [8]),
("TOPPADDING", (0,0), (-1,-1), 16),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
sub_table = Table([[
Paragraph("World Health Organization | Introduced 1986 | Cancer & Chronic Pain Management", subtitle_style),
]], colWidths=[W - 4*cm])
sub_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), HEADBG),
("ROUNDEDCORNERS", [8]),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 16),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
]))
story.append(header_table)
story.append(sub_table)
story.append(Spacer(1, 0.4*cm))
# ── OVERVIEW ─────────────────────────────────────────────────────────────────
story.append(Paragraph("Overview", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=6))
story.append(Paragraph(
"The WHO Pain Ladder is a stepwise framework for managing pain — originally developed for cancer pain "
"and now widely applied across chronic pain conditions. It advocates selecting analgesics based on "
"<b>pain intensity</b>, not diagnosis, escalating through three steps until pain is controlled. "
"Adjuvant medications may be added at any step.",
body_style
))
story.append(Spacer(1, 0.3*cm))
# ── LADDER VISUAL (table) ─────────────────────────────────────────────────────
story.append(Paragraph("The Three Steps", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=8))
step_header = [
Paragraph("Step", table_header_style),
Paragraph("Pain Level", table_header_style),
Paragraph("Drugs", table_header_style),
Paragraph("Examples", table_header_style),
]
step1 = [
Paragraph("<b>Step 1</b>", ParagraphStyle("s1", parent=styles["Normal"], fontSize=11,
fontName="Helvetica-Bold", textColor=colors.HexColor("#6d3a00"), alignment=TA_CENTER)),
Paragraph("Mild pain\n(1–3 / 10)", table_cell_style),
Paragraph("<b>Non-opioids</b>\n± Adjuvants", table_cell_style),
Paragraph("Paracetamol, Aspirin, NSAIDs\n(ibuprofen, diclofenac, naproxen)", table_cell_style),
]
step2 = [
Paragraph("<b>Step 2</b>", ParagraphStyle("s2", parent=styles["Normal"], fontSize=11,
fontName="Helvetica-Bold", textColor=colors.HexColor("#7d3b00"), alignment=TA_CENTER)),
Paragraph("Mild-to-moderate\npain (4–6 / 10)\nStep 1 fails", table_cell_style),
Paragraph("<b>Weak opioid</b>\n+ Non-opioid\n± Adjuvants", table_cell_style),
Paragraph("Codeine, Tramadol\n+ Paracetamol / NSAID", table_cell_style),
]
step3 = [
Paragraph("<b>Step 3</b>", ParagraphStyle("s3", parent=styles["Normal"], fontSize=11,
fontName="Helvetica-Bold", textColor=colors.white, alignment=TA_CENTER)),
Paragraph("<font color='white'>Moderate-to-severe\npain (7–10 / 10)\nStep 2 fails</font>", table_cell_style),
Paragraph("<font color='white'><b>Strong opioid</b>\n+ Non-opioid\n± Adjuvants</font>", table_cell_style),
Paragraph("<font color='white'>Morphine*, Oxycodone,\nHydromorphone, Fentanyl\n+ Paracetamol / NSAID\n(*drug of choice)</font>", table_cell_style),
]
ladder_table = Table(
[step_header, step1, step2, step3],
colWidths=[2.2*cm, 3.8*cm, 4.5*cm, 6.3*cm]
)
ladder_table.setStyle(TableStyle([
# Header
("BACKGROUND", (0,0), (-1,0), HEADBG),
("TEXTCOLOR", (0,0), (-1,0), colors.white),
("ALIGN", (0,0), (-1,0), "CENTER"),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 10),
# Step rows background
("BACKGROUND", (0,1), (-1,1), STEP1C),
("BACKGROUND", (0,2), (-1,2), STEP2C),
("BACKGROUND", (0,3), (-1,3), STEP3C),
# Grid
("GRID", (0,0), (-1,-1), 0.5, colors.white),
("ROWBACKGROUNDS",(0,1), (-1,-1), [STEP1C, STEP2C, STEP3C]),
# Padding
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(ladder_table)
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph(
"<i>Note: Move <b>up</b> the ladder if pain is uncontrolled — do not switch sideways to another drug at the same step.</i>",
ParagraphStyle("note", parent=styles["Normal"], fontSize=8.5, textColor=colors.grey, spaceAfter=4)
))
story.append(Spacer(1, 0.3*cm))
# ── ADJUVANTS ─────────────────────────────────────────────────────────────────
story.append(Paragraph("Adjuvant Medications (any step)", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=6))
adj_data = [
[Paragraph("<b>Class</b>", table_header_style), Paragraph("<b>Examples</b>", table_header_style), Paragraph("<b>Use</b>", table_header_style)],
["Tricyclic antidepressants", "Amitriptyline, Nortriptyline", "Neuropathic pain"],
["Anticonvulsants", "Gabapentin, Pregabalin, Carbamazepine", "Neuropathic pain"],
["Corticosteroids", "Dexamethasone, Prednisolone", "Bone pain, raised ICP, inflammation"],
["Muscle relaxants", "Baclofen, Diazepam", "Muscle spasm pain"],
["Bisphosphonates", "Zoledronic acid, Pamidronate", "Bone metastasis pain"],
]
adj_table = Table(adj_data, colWidths=[4.5*cm, 6.0*cm, 6.3*cm])
adj_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), HEADBG),
("TEXTCOLOR", (0,0), (-1,0), colors.white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.HexColor("#f5f9fd"), colors.white]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#c0d0e0")),
("FONTSIZE", (0,1), (-1,-1), 9),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 7),
("RIGHTPADDING", (0,0), (-1,-1), 7),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(adj_table)
story.append(Spacer(1, 0.35*cm))
# ── 5 PRINCIPLES ──────────────────────────────────────────────────────────────
story.append(Paragraph("WHO's 5 Principles of Analgesic Administration", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=6))
principles = [
("1. By Mouth", "Oral route preferred whenever feasible — simple, cheap, and easily adjusted."),
("2. By the Clock", "Give on a regular schedule (not PRN) to maintain continuous analgesia and prevent pain breakthrough."),
("3. By the Ladder", "Select the analgesic step based on pain intensity; escalate if uncontrolled."),
("4. For the Individual", "Titrate doses to each patient — there is no standard dose for opioids."),
("5. Attention to Detail", "Reassess pain relief and monitor for adverse effects vigilantly after every change."),
]
pr_data = [[
Paragraph(f"<b>{p[0]}</b>", ParagraphStyle("pl", parent=styles["Normal"], fontSize=10,
fontName="Helvetica-Bold", textColor=BLUE, alignment=TA_CENTER)),
Paragraph(p[1], table_cell_style)
] for p in principles]
pr_table = Table(pr_data, colWidths=[4.5*cm, 12.3*cm])
pr_table.setStyle(TableStyle([
("ROWBACKGROUNDS", (0,0), (-1,-1), [colors.HexColor("#eaf2fb"), colors.white]),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#c0d0e0")),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(pr_table)
story.append(Spacer(1, 0.35*cm))
# ── STEP 4 (extended) ─────────────────────────────────────────────────────────
story.append(Paragraph("Step 4 — Interventional Pain Management (Modified Ladder)", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=6))
story.append(Paragraph(
"A fourth step is widely recognised for <b>refractory or intractable pain</b> not controlled by maximal "
"pharmacotherapy. Options include:",
body_style
))
step4_items = [
"Spinal cord stimulation (SCS)",
"Nerve blocks (regional anaesthesia)",
"Intrathecal / epidural drug delivery systems",
"Neurolytic procedures (chemical or surgical nerve ablation)",
"Transcutaneous electrical nerve stimulation (TENS)",
]
for item in step4_items:
story.append(Paragraph(f"\u2022 {item}", bullet_style))
story.append(Spacer(1, 0.3*cm))
# ── CLINICAL PEARLS ───────────────────────────────────────────────────────────
story.append(Paragraph("Clinical Pearls", section_style))
story.append(HRFlowable(width="100%", thickness=1.5, color=ORANGE, spaceAfter=6))
pearls = [
("<b>Morphine</b> is the opioid of choice for moderate-to-severe cancer pain (Step 3)."),
("<b>Breakthrough pain</b>: add short-acting opioid (immediate-release morphine / fentanyl) on top of regular background dose."),
("<b>Oral morphine dosing</b>: start every 4 hours, then convert to slow-release (MST) every 12 hours once stable dose established."),
("<b>Opioid-induced neurotoxicity</b>: myoclonus, hallucinations, confusion from metabolite accumulation — consider dose reduction or opioid rotation."),
("<b>Opioid rotation</b>: switch opioids if adequate analgesia with unacceptable side effects. Use equianalgesic dose tables."),
("<b>Pethidine (meperidine)</b>: no longer recommended for chronic cancer pain — neurotoxic metabolite (norpethidine) accumulates."),
("<b>Constipation</b>: near-universal with opioids — always prescribe a laxative prophylactically."),
("<b>Nausea</b> is common initially but usually resolves; treat with antiemetics."),
("Addiction vs dependence: <b>dependence</b> is a physiological opioid response; <b>addiction</b> is a psychosocial phenomenon — distinguish carefully."),
]
for pearl in pearls:
story.append(Paragraph(f"\u2022 {pearl}", bullet_style))
story.append(Spacer(1, 0.5*cm))
# ── FOOTER ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.grey, spaceAfter=4))
story.append(Paragraph(
"Sources: Cummings Otolaryngology Head & Neck Surgery | Bailey & Love's Short Practice of Surgery, 28th Ed. | "
"Scott-Brown's Otorhinolaryngology | WHO Cancer Pain Relief and Palliative Care, 1996",
footer_style
))
story.append(Paragraph(
"Generated by Orris Medical Reference | June 2026",
footer_style
))
doc.build(story)
print(f"PDF created: {OUTPUT}")
python /home/daytona/workspace/who-pain-ladder/generate_pdf.py
WHO Pain Ladder
PDF Document · PDF