From now for topic follow the below structure 1. Introduction 2. History (if relevant) 3. Anatomy 4. Etiology 5. Mechanism (if applicable) 6. Pathology 7. Clinical Features 8. Investigation 9. Radiograph 10. CT 11. MRI 12. Classification 13. Treatment A. Non-operative B. Operative 14. Recent Advances Additional features:include diagram and flow chart
Common peroneal nerve
common peroneal nerve anatomy injury classification treatment
"common peroneal nerve" injury treatment
Anatomical diagram of the common peroneal nerve showing its origin from the sciatic nerve, course around the fibular head, division into deep and superficial peroneal nerve branches, motor and sensory distributions in the lower leg and foot, with labeled muscle groups (tibialis anterior, extensor digitorum longus, extensor hallucis longus, peroneus longus, peroneus brevis) and sensory areas on dorsum of foot
Clinical flowchart for management of common peroneal nerve injury showing: initial assessment with history and examination, then NCS/EMG, then branching into low-energy vs high-energy injury, conservative path including observation, ankle foot orthosis, physiotherapy, with timeline of 3-6 months observation, surgical path including neurolysis, nerve repair/grafting, nerve transfer, tendon transfer for late cases, with decision points based on Seddon/Sunderland classification and recovery outcomes
| Segment | Relations |
|---|---|
| Popliteal fossa | Lies in a fat plane between lateral head of gastrocnemius and biceps femoris |
| Around fibular head | Turns around the biceps femoris tendon and fibular head - most vulnerable point |
| Fibular tunnel | Passes under a fibrous arch at the origin of peroneus longus muscle |
| Proximal leg | Divides into 3 branches |
| Muscle | Action | Branch |
|---|---|---|
| Tibialis anterior | Dorsiflexion + inversion | Deep peroneal |
| Extensor digitorum longus | Toe extension, dorsiflexion | Deep peroneal |
| Extensor hallucis longus | Big toe extension | Deep peroneal |
| Extensor digitorum brevis | Toe extension | Deep peroneal |
| Peroneus longus | Eversion, plantarflexion | Superficial peroneal |
| Peroneus brevis | Eversion | Superficial peroneal |



| Cause | Notes |
|---|---|
| Knee dislocation / multiligament injury | CPN injured in up to 44% of posterolateral corner injuries |
| Fracture of fibular head/neck | ~50% of fibular head fractures have associated CPN injury |
| Direct blow to lateral knee | Classic "car bumper" injury |
| Knee arthroplasty (TKA) | Especially with valgus correction; lithotomy position |
| Laceration / penetrating trauma | Wartime, iatrogenic |
| Cause | Notes |
|---|---|
| Habitual leg crossing | Most common compressive cause |
| Prolonged squatting | Strawberry pickers, yoga (yoga foot drop) |
| Tight plaster cast | Pressure at fibular neck |
| Obstetric stirrups / lithotomy position | Bilateral palsy during prolonged labour (pushing palsy) |
| Prolonged bed rest / immobility | Weight loss + emaciation exacerbates risk |
| Tight knee boots | External compression |
| Finding | Significance |
|---|---|
| Weakness of dorsiflexion | Deep peroneal branch |
| Weakness of eversion | Superficial peroneal branch |
| Preserved inversion | Tibial nerve (L4/L5 mediated) - key differentiator from L5 root |
| Preserved plantar flexion | Tibial nerve intact |
| Sensory loss dorsum of foot | Superficial peroneal |
| Sensory loss 1st web space only | Deep peroneal branch only |
| Tinel's sign at fibular head | Suggests compressive/regenerating lesion |
| Steppage gait | Compensation for foot drop |
| Condition | Key Distinguishing Features |
|---|---|
| CPN palsy | Eversion weak, inversion spared, Tinel at fibular head, NCS abnormal |
| L5 radiculopathy | Inversion AND eversion weak, hip abduction may be weak, back pain, EMG shows paraspinal changes |
| Lumbosacral plexopathy | Broader deficit, hip abduction weak |
| ALS/motor neuron disease | Upper motor neuron signs, widespread fasciculations |
| Tibialis anterior tendon rupture | Palpable gap, normal EMG |
| Study | Finding in CPN Palsy |
|---|---|
| Motor NCS | Slowed conduction velocity across fibular head; reduced CMAP amplitude distal to lesion; localises lesion |
| Sensory NCS | Reduced/absent superficial peroneal SNAP |
| EMG (needle) | Fibrillation potentials, reduced recruitment in tibialis anterior, EHL, EDL, peroneus muscles |
| EMG paraspinals | Normal in CPN palsy - differentiates from L5 radiculopathy |

| Finding | Significance |
|---|---|
| T2 hyperintensity of nerve without enlargement | Neurapraxia (reversible) |
| T2 hyperintensity + nerve enlargement | Axonotmesis, inflammation |
| Nerve discontinuity / neuroma | Neurotmesis (Sunderland grade 5) |
| Intraneural cystic lesion tracking from tibiofibular joint | Intraneural ganglion |
| Denervation changes in anterior compartment (T2 bright muscle) | Subacute/chronic axonal injury |
| Fat replacement in anterior compartment muscles | Chronic denervation (irreversible) |

| Grade | Description | Prognosis |
|---|---|---|
| Neurapraxia | Myelin injury only; axon intact; conduction block | Full recovery in weeks-months without surgery |
| Axonotmesis | Axon disrupted; endoneurium intact | Recovery at 1-3 mm/day; usually good |
| Neurotmesis | Complete nerve division including connective tissue sheaths | No spontaneous recovery; surgery required |
| Grade | Structures Disrupted | Recovery Potential |
|---|---|---|
| 1 | Myelin only (= neurapraxia) | Full; within 12 weeks |
| 2 | Axon discontinuity; endoneurium intact (= axonotmesis) | Excellent; rapid regeneration at 2-3 mm/day |
| 3 | Axon + endoneurium; perineurium intact | Incomplete, unpredictable; ~1 mm/day; intraneural scarring |
| 4 | Axon + endoneurium + perineurium; epineurium intact | No spontaneous recovery; neuroma-in-continuity |
| 5 | Complete nerve disruption (= neurotmesis) | No recovery; end neuroma formation |
| 6 (MacKinnon addition) | Mixed/combined pattern across fascicles | Variable early recovery then plateau |
| Level | Features |
|---|---|
| Above knee (sciatic level) | Combined tibial + peroneal deficits |
| Fibular head (most common) | Classic CPN palsy |
| Deep peroneal only | Dorsiflexion weakness; eversion spared |
| Superficial peroneal only | Eversion weakness; sensory on dorsum |
| Anterior tarsal tunnel | Extensor digitorum brevis + 1st web space |

| Feature | Detail |
|---|---|
| Root value | L4, L5, S1, S2 |
| Origin | Lateral division of sciatic nerve |
| Most vulnerable point | Fibular head/neck |
| Most common injury | Compression/entrapment |
| Key clinical sign | Foot drop + weak eversion, preserved inversion |
| Gold standard investigation | NCS/EMG |
| Critical repair window | < 12 months post-injury |
| Best surgical outcome | 60-70% achieve anti-gravity dorsiflexion (ideal conditions) |
| Late salvage | Tibialis posterior tendon transfer |
Generate a quick reference clinical decision support PDF on CPN injury management
For answer structure given According to Orthopaedics point of view
ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not found"
mkdir -p /home/daytona/workspace/cpn-nerve && python3 -c "import reportlab; print(reportlab.Version)"
~/cpn-nerve/generate_cpn_pdf.py
"""
CPN Injury Management - Quick Reference Clinical Decision Support PDF
Orthopaedic Perspective
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Polygon, Arrow
from reportlab.graphics import renderPDF
from reportlab.lib.colors import HexColor
import io
# ─── COLOUR PALETTE ────────────────────────────────────────────────────────────
NAVY = HexColor("#0D2B55")
TEAL = HexColor("#0A7E8C")
ORANGE = HexColor("#E87722")
LIGHT_BG = HexColor("#F0F4F8")
MID_BLUE = HexColor("#1A5276")
GREEN = HexColor("#1E8449")
RED = HexColor("#C0392B")
YELLOW_BG = HexColor("#FEF9E7")
WHITE = colors.white
GREY = HexColor("#7F8C8D")
LIGHT_GREY= HexColor("#ECF0F1")
BOX_BLUE = HexColor("#D6EAF8")
BOX_GREEN = HexColor("#D5F5E3")
BOX_RED = HexColor("#FADBD8")
BOX_ORANGE= HexColor("#FDEBD0")
W, H = A4 # 595 x 842 pt
# ─── STYLES ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, **kwargs):
return ParagraphStyle(name, **kwargs)
title_style = make_style('Title',
fontName='Helvetica-Bold', fontSize=22, textColor=WHITE,
alignment=TA_CENTER, spaceAfter=4)
subtitle_style = make_style('Subtitle',
fontName='Helvetica', fontSize=11, textColor=HexColor("#BDC3C7"),
alignment=TA_CENTER, spaceAfter=2)
section_style = make_style('Section',
fontName='Helvetica-Bold', fontSize=12, textColor=WHITE,
alignment=TA_LEFT, spaceBefore=6, spaceAfter=4,
leftIndent=8)
body_style = make_style('Body',
fontName='Helvetica', fontSize=9, textColor=HexColor("#2C3E50"),
alignment=TA_LEFT, spaceAfter=3, leading=13)
bold_body = make_style('BoldBody',
fontName='Helvetica-Bold', fontSize=9, textColor=HexColor("#2C3E50"),
alignment=TA_LEFT, spaceAfter=3, leading=13)
small_style = make_style('Small',
fontName='Helvetica', fontSize=8, textColor=HexColor("#2C3E50"),
alignment=TA_LEFT, spaceAfter=2, leading=11)
bullet_style = make_style('Bullet',
fontName='Helvetica', fontSize=8.5, textColor=HexColor("#2C3E50"),
alignment=TA_LEFT, leftIndent=10, spaceAfter=2, leading=12,
bulletIndent=0)
table_header = make_style('TableHeader',
fontName='Helvetica-Bold', fontSize=8.5, textColor=WHITE,
alignment=TA_CENTER)
table_body = make_style('TableBody',
fontName='Helvetica', fontSize=8, textColor=HexColor("#2C3E50"),
alignment=TA_LEFT, leading=11)
caption_style = make_style('Caption',
fontName='Helvetica-Oblique', fontSize=7.5, textColor=GREY,
alignment=TA_CENTER, spaceAfter=3)
flowchart_label = make_style('FlowLabel',
fontName='Helvetica-Bold', fontSize=8, textColor=WHITE,
alignment=TA_CENTER, leading=10)
alert_style = make_style('Alert',
fontName='Helvetica-Bold', fontSize=9, textColor=WHITE,
alignment=TA_CENTER, leading=13)
# ─── HEADER BANNER FLOWABLE ────────────────────────────────────────────────────
class HeaderBanner(Flowable):
def __init__(self, width, height=70):
Flowable.__init__(self)
self.width = width
self.height = height
def draw(self):
c = self.canv
# Main background
c.setFillColor(NAVY)
c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
# Accent bar
c.setFillColor(TEAL)
c.roundRect(0, 0, 6, self.height, 3, fill=1, stroke=0)
# Right accent
c.setFillColor(ORANGE)
c.roundRect(self.width-6, 0, 6, self.height, 3, fill=1, stroke=0)
# Title
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 20)
c.drawCentredString(self.width/2, self.height - 30, "COMMON PERONEAL NERVE INJURY")
# Subtitle
c.setFillColor(HexColor("#BDC3C7"))
c.setFont("Helvetica", 10)
c.drawCentredString(self.width/2, self.height - 46,
"Quick Reference Clinical Decision Support | Orthopaedic Perspective")
# Footer line of header
c.setFillColor(ORANGE)
c.setFont("Helvetica-Bold", 8)
c.drawCentredString(self.width/2, 8, "Campbell's Operative Orthopaedics 15e • Adams & Victor 12e • Harrison's 22e • Localization in Clinical Neurology 8e")
# ─── SECTION HEADER ────────────────────────────────────────────────────────────
class SectionHeader(Flowable):
def __init__(self, text, width, color=TEAL, number=None):
Flowable.__init__(self)
self.text = text
self.width = width
self.color = color
self.number = number
self.height = 22
def draw(self):
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, self.width, self.height, 4, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 11)
label = f" {self.number}. {self.text}" if self.number else f" {self.text}"
c.drawString(10, 6, label)
# ─── COLOURED BOX ──────────────────────────────────────────────────────────────
class ColourBox(Flowable):
def __init__(self, content_items, width, bg_color=BOX_BLUE, border_color=TEAL,
title=None, title_color=TEAL):
Flowable.__init__(self)
self.content_items = content_items
self.width = width
self.bg_color = bg_color
self.border_color = border_color
self.title = title
self.title_color = title_color
# estimate height
lines = sum(1 + item.count('\n') for item in content_items)
self.height = (22 if title else 0) + lines * 13 + 14
def draw(self):
c = self.canv
c.setFillColor(self.bg_color)
c.setStrokeColor(self.border_color)
c.setLineWidth(1.5)
c.roundRect(0, 0, self.width, self.height, 5, fill=1, stroke=1)
y = self.height - 12
if self.title:
c.setFillColor(self.title_color)
c.setFont("Helvetica-Bold", 9)
c.drawString(8, y, self.title)
y -= 13
c.setStrokeColor(self.border_color)
c.setLineWidth(0.5)
c.line(6, y + 2, self.width - 6, y + 2)
y -= 4
c.setFillColor(HexColor("#2C3E50"))
c.setFont("Helvetica", 8.5)
for item in self.content_items:
lines = item.split('\n')
for line in lines:
if line.strip():
c.drawString(10, y, line)
y -= 12
# ─── ALERT BOX ─────────────────────────────────────────────────────────────────
class AlertBox(Flowable):
def __init__(self, text, width, bg=RED, icon="⚠"):
Flowable.__init__(self)
self.text = text
self.width = width
self.bg = bg
self.icon = icon
self.height = 28
def draw(self):
c = self.canv
c.setFillColor(self.bg)
c.roundRect(0, 0, self.width, self.height, 5, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 9.5)
c.drawCentredString(self.width/2, 9, self.text)
# ─── FLOWCHART ─────────────────────────────────────────────────────────────────
class ManagementFlowchart(Flowable):
def __init__(self, width):
Flowable.__init__(self)
self.width = width
self.height = 310
def box(self, c, x, y, w, h, text, bg, text_color=WHITE, fontsize=8, radius=5, bold=False):
c.setFillColor(bg)
c.setStrokeColor(HexColor("#2C3E50"))
c.setLineWidth(0.8)
c.roundRect(x, y, w, h, radius, fill=1, stroke=1)
c.setFillColor(text_color)
font = "Helvetica-Bold" if bold else "Helvetica"
c.setFont(font, fontsize)
lines = text.split('\n')
line_h = fontsize + 2
total_h = len(lines) * line_h
start_y = y + h/2 + total_h/2 - line_h
for line in lines:
c.drawCentredString(x + w/2, start_y, line)
start_y -= line_h
def arrow(self, c, x1, y1, x2, y2, label=""):
c.setStrokeColor(NAVY)
c.setLineWidth(1.2)
c.line(x1, y1, x2, y2)
# arrowhead
c.setFillColor(NAVY)
size = 5
if x1 == x2: # vertical
c.polygon([x2-size/2, y2+size, x2+size/2, y2+size, x2, y2], fill=1, stroke=0)
else: # horizontal
c.polygon([x2-size, y2+size/2, x2-size, y2-size/2, x2, y2], fill=1, stroke=0)
if label:
c.setFillColor(ORANGE)
c.setFont("Helvetica-Bold", 7.5)
mx, my = (x1+x2)/2, (y1+y2)/2
c.drawCentredString(mx, my + 4, label)
def draw(self):
c = self.canv
W = self.width
# Background
c.setFillColor(LIGHT_BG)
c.roundRect(0, 0, W, self.height, 6, fill=1, stroke=0)
# Title
c.setFillColor(NAVY)
c.setFont("Helvetica-Bold", 10)
c.drawCentredString(W/2, self.height - 16, "CPN INJURY — MANAGEMENT ALGORITHM")
# Row Y positions (top to bottom)
y_top = self.height - 45
bw = 140 # box width
bh = 30
cx = W/2
# ── Box 1: Initial Assessment
self.box(c, cx - bw/2, y_top, bw, bh, "INITIAL ASSESSMENT\nHistory + Examination", NAVY, bold=True)
self.arrow(c, cx, y_top, cx, y_top - 18)
# ── Box 2: NCS/EMG
y2 = y_top - 18 - bh
self.box(c, cx - bw/2, y2, bw, bh, "NCS / EMG\n(≥3 weeks post-injury)", TEAL, bold=True)
self.arrow(c, cx, y2, cx, y2 - 18)
# ── Decision diamond — Energy of Injury
y3 = y2 - 18 - 36
dw, dh = 150, 36
# Draw diamond
c.setFillColor(MID_BLUE)
c.setStrokeColor(NAVY)
c.setLineWidth(1)
cx_d, cy_d = cx, y3 + dh/2
c.beginPath()
c.moveTo(cx_d, cy_d + dh/2)
c.lineTo(cx_d + dw/2, cy_d)
c.lineTo(cx_d, cy_d - dh/2)
c.lineTo(cx_d - dw/2, cy_d)
c.closePath()
c.drawPath(c._currentPath if hasattr(c, '_currentPath') else c.beginPath(), fill=1, stroke=1)
# Redraw properly
pts = [cx_d, cy_d + dh/2, cx_d + dw/2, cy_d, cx_d, cy_d - dh/2, cx_d - dw/2, cy_d]
c.setFillColor(MID_BLUE)
c.polygon(pts, fill=1, stroke=1)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 8)
c.drawCentredString(cx_d, cy_d + 4, "Mechanism of Injury?")
c.setFont("Helvetica", 7.5)
c.drawCentredString(cx_d, cy_d - 6, "Energy / Type")
# Left arrow (High energy) — to the left
lx = 80
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(cx_d - dw/2, cy_d, lx + 110, cy_d)
c.polygon([lx+110, cy_d+4, lx+110, cy_d-4, lx+120, cy_d], fill=1, stroke=0)
c.setFillColor(RED); c.setFont("Helvetica-Bold", 7.5)
c.drawCentredString((cx_d - dw/2 + lx + 110)/2, cy_d + 6, "HIGH ENERGY")
# Right arrow (Low energy) — to the right
rx = W - 80
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(cx_d + dw/2, cy_d, rx - 110, cy_d)
c.polygon([rx-120, cy_d+4, rx-120, cy_d-4, rx-110, cy_d], fill=1, stroke=0)
c.setFillColor(GREEN); c.setFont("Helvetica-Bold", 7.5)
c.drawCentredString((cx_d + dw/2 + rx - 110)/2, cy_d + 6, "LOW ENERGY")
# ── LEFT COLUMN: High Energy
bw2 = 130
lbx = 20
y_he = y3 - 14
self.box(c, lbx, y_he - bh, bw2, bh, "Surgical Exploration\nwithin 3 WEEKS", RED)
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(lx + 55, cy_d, lx + 55, y_he)
c.polygon([lx+50, y_he, lx+60, y_he, lx+55, y_he-5], fill=1, stroke=0)
c.setFillColor(NAVY)
y_he2 = y_he - bh - 12
self.box(c, lbx, y_he2 - bh, bw2, bh, "Intraop NAP Recording\n+ Sunderland Grade", ORANGE, text_color=HexColor("#2C3E50"))
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(lbx + bw2/2, y_he - bh, lbx + bw2/2, y_he2)
c.polygon([lbx + bw2/2 - 4, y_he2, lbx + bw2/2 + 4, y_he2, lbx + bw2/2, y_he2 - 5], fill=1, stroke=0)
c.setFillColor(NAVY)
y_he3 = y_he2 - bh - 12
self.box(c, lbx, y_he3 - bh, bw2, bh, "Repair / Graft / Neurolysis\n(see surgical algorithm)", TEAL)
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(lbx + bw2/2, y_he2 - bh, lbx + bw2/2, y_he3)
c.polygon([lbx + bw2/2 - 4, y_he3, lbx + bw2/2 + 4, y_he3, lbx + bw2/2, y_he3 - 5], fill=1, stroke=0)
c.setFillColor(NAVY)
# ── RIGHT COLUMN: Low Energy
rbx = W - 20 - bw2
y_le = y3 - 14
self.box(c, rbx, y_le - bh, bw2, bh, "Conservative Rx\nAFO + Physio", GREEN)
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(rx - 55, cy_d, rx - 55, y_le)
c.polygon([rx-60, y_le, rx-50, y_le, rx-55, y_le-5], fill=1, stroke=0)
c.setFillColor(NAVY)
y_le2 = y_le - bh - 12
# Observation period box
self.box(c, rbx, y_le2 - bh, bw2, bh, "Observe 3–6 months\nSerial NCS/EMG", HexColor("#1A6B3C"))
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(rbx + bw2/2, y_le - bh, rbx + bw2/2, y_le2)
c.polygon([rbx + bw2/2 - 4, y_le2, rbx + bw2/2 + 4, y_le2, rbx + bw2/2, y_le2 - 5], fill=1, stroke=0)
c.setFillColor(NAVY)
# Recovery? diamond
y_le3 = y_le2 - bh - 12
dw2, dh2 = 120, 30
rx_d, ry_d = rbx + bw2/2, y_le3 - dh2/2
pts2 = [rx_d, ry_d + dh2/2, rx_d + dw2/2, ry_d, rx_d, ry_d - dh2/2, rx_d - dw2/2, ry_d]
c.setFillColor(ORANGE)
c.polygon(pts2, fill=1, stroke=1)
c.setFillColor(HexColor("#2C3E50"))
c.setFont("Helvetica-Bold", 7.5)
c.drawCentredString(rx_d, ry_d + 3, "Recovery?")
c.setFont("Helvetica", 7)
c.drawCentredString(rx_d, ry_d - 7, "Clinical + EMG")
c.setStrokeColor(NAVY); c.setLineWidth(1.2)
c.line(rbx + bw2/2, y_le2 - bh, rbx + bw2/2, y_le3 + dh2/2)
# YES arrow going down-right
y_yes = y_le3 - dh2/2 - 12
self.box(c, rbx, y_yes - 22, bw2, 22, "Continue Conservative\n+ Rehabilitation", GREEN)
c.setStrokeColor(GREEN); c.setLineWidth(1.2)
c.line(rx_d, y_le3 - dh2/2, rx_d, y_yes)
c.polygon([rx_d - 4, y_yes, rx_d + 4, y_yes, rx_d, y_yes - 4], fill=1, stroke=0)
c.setFillColor(GREEN); c.setFont("Helvetica-Bold", 7); c.drawString(rx_d + 5, y_le3 - dh2/2 - 7, "YES")
# NO arrow going left toward high energy column
c.setStrokeColor(RED); c.setLineWidth(1.2)
c.line(rx_d - dw2/2, ry_d, cx + 10, ry_d)
c.setFont("Helvetica-Bold", 7); c.setFillColor(RED)
c.drawString(rx_d - dw2/2 + 3, ry_d + 4, "NO → Surgery")
# ── BOTTOM: Outcome boxes
y_bot = 18
bw3 = 80
boxes_data = [
(20, "Grade 1-2\nFull Recovery", GREEN),
(115, "Grade 3\nPartial Recovery", TEAL),
(210, "Grade 4\nNeurolysis ±\nGraft", ORANGE),
(310, "Grade 5\nDirect Repair\nor Graft", RED),
(405, ">12 months\nTendon\nTransfer", MID_BLUE),
]
c.setFillColor(NAVY); c.setFont("Helvetica-Bold", 7.5)
c.drawCentredString(W/2, y_bot + 44, "── Sunderland Grade & Expected Outcome ──")
for bx_off, label, col in boxes_data:
bh3 = 38
self.box(c, bx_off, y_bot, bw3, bh3, label, col, fontsize=7.5)
# ─── BUILD PDF ─────────────────────────────────────────────────────────────────
def build_pdf(path):
doc = SimpleDocTemplate(
path,
pagesize=A4,
leftMargin=15*mm,
rightMargin=15*mm,
topMargin=10*mm,
bottomMargin=10*mm,
title="CPN Injury – Quick Reference (Orthopaedics)",
author="Orris Clinical AI",
subject="Common Peroneal Nerve Injury Management"
)
story = []
usable_W = W - 30*mm # ~535 pt
col_half = (usable_W - 6) / 2
# ── HEADER
story.append(HeaderBanner(usable_W, 72))
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# ROW 1: Anatomy Snapshot | Etiology at a Glance
# ════════════════════════════════════════════════════════════
story.append(SectionHeader("ANATOMY SNAPSHOT", usable_W, TEAL, "1"))
story.append(Spacer(1, 4))
anatomy_data = [
[Paragraph('<b>Feature</b>', table_header), Paragraph('<b>Detail</b>', table_header)],
[Paragraph('Root value', table_body), Paragraph('L4, L5, S1, S2', table_body)],
[Paragraph('Origin', table_body), Paragraph('Lateral division of sciatic nerve (popliteal fossa)', table_body)],
[Paragraph('Most vulnerable point', table_body), Paragraph('Fibular head / neck — superficial, tethered, no muscle cover', table_body)],
[Paragraph('Fibular tunnel', table_body), Paragraph('Fibrous arch at origin of peroneus longus', table_body)],
[Paragraph('Branches', table_body), Paragraph('Deep peroneal, superficial peroneal, recurrent articular, lat. sural cutaneous', table_body)],
[Paragraph('<b>Deep peroneal</b>', bold_body), Paragraph('Tibialis anterior, EHL, EDL, EDB → dorsiflexion + toe extension + 1st web space sensation', table_body)],
[Paragraph('<b>Superficial peroneal</b>', bold_body), Paragraph('Peroneus longus & brevis → eversion + dorsal foot sensation', table_body)],
]
anatomy_table = Table(anatomy_data, colWidths=[col_half*0.38, col_half*1.62])
anatomy_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('ROWBACKGROUNDS', (0,5), (-1,6), [BOX_BLUE, BOX_GREEN]),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('ROUNDEDCORNERS', [4,4,4,4]),
]))
story.append(anatomy_table)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# ROW 2: Etiology + Classification side by side
# ════════════════════════════════════════════════════════════
# LEFT: Etiology
etiol_data = [
[Paragraph('<b>Category</b>', table_header), Paragraph('<b>Common Causes</b>', table_header)],
[Paragraph('Traumatic', table_body), Paragraph('Knee dislocation (44% CPN injury), fibular head # (50% CPN), direct blow, TKA', table_body)],
[Paragraph('Compressive', table_body), Paragraph('Leg crossing, tight cast, prolonged squatting, lithotomy position, bed rest', table_body)],
[Paragraph('SOL', table_body), Paragraph('Intraneural ganglion (tibiofibular joint), Baker cyst, ganglion, lipoma', table_body)],
[Paragraph('Systemic', table_body), Paragraph('Diabetes, vasculitis, emaciation, leprosy', table_body)],
[Paragraph('Iatrogenic', table_body), Paragraph('Lateral decubitus positioning, HTO, poor cast application', table_body)],
]
etiol_table = Table(etiol_data, colWidths=[col_half*0.33, col_half*0.67])
etiol_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ORANGE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
# RIGHT: Classification
class_data = [
[Paragraph('<b>Grade</b>', table_header), Paragraph('<b>Seddon</b>', table_header),
Paragraph('<b>Pathology</b>', table_header), Paragraph('<b>Prognosis</b>', table_header)],
[Paragraph('1', table_body), Paragraph('Neurapraxia', table_body),
Paragraph('Myelin only', table_body), Paragraph('Full; weeks', table_body)],
[Paragraph('2', table_body), Paragraph('Axonotmesis', table_body),
Paragraph('Axon intact endo', table_body), Paragraph('Excellent 2-3mm/d', table_body)],
[Paragraph('3', table_body), Paragraph('Axonotmesis', table_body),
Paragraph('Axon + endo', table_body), Paragraph('Incomplete 1mm/d', table_body)],
[Paragraph('4', table_body), Paragraph('Neurotmesis', table_body),
Paragraph('+ perineurium', table_body), Paragraph('Neuroma-in-cont.', table_body)],
[Paragraph('5', table_body), Paragraph('Neurotmesis', table_body),
Paragraph('Complete', table_body), Paragraph('Surgery essential', table_body)],
[Paragraph('6', table_body), Paragraph('Mixed', table_body),
Paragraph('Variable fascicles', table_body), Paragraph('Variable', table_body)],
]
class_table = Table(class_data, colWidths=[col_half*0.14, col_half*0.28, col_half*0.30, col_half*0.28])
class_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), MID_BLUE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 7.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('TOPPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 4),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BACKGROUND', (0,4), (-1,5), [BOX_RED, BOX_RED]),
]))
# Combine in two-column row
etiol_hdr = SectionHeader("ETIOLOGY", col_half, ORANGE, "2")
class_hdr = SectionHeader("SUNDERLAND CLASSIFICATION", col_half, MID_BLUE, "3")
two_col = Table([[etiol_hdr, Spacer(6, 1), class_hdr]], colWidths=[col_half, 6, col_half])
two_col.setStyle(TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'), ('LEFTPADDING', (0,0),(-1,-1), 0), ('RIGHTPADDING', (0,0),(-1,-1), 0)]))
story.append(two_col)
story.append(Spacer(1, 3))
two_col2 = Table([[etiol_table, Spacer(6, 1), class_table]], colWidths=[col_half, 6, col_half])
two_col2.setStyle(TableStyle([('VALIGN', (0,0), (-1,-1), 'TOP'), ('LEFTPADDING', (0,0),(-1,-1), 0), ('RIGHTPADDING', (0,0),(-1,-1), 0)]))
story.append(two_col2)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# ROW 3: Clinical Features + Differential Diagnosis
# ════════════════════════════════════════════════════════════
story.append(SectionHeader("CLINICAL FEATURES & DIFFERENTIAL DIAGNOSIS", usable_W, MID_BLUE, "4"))
story.append(Spacer(1, 4))
# Clinical features
cf_data = [
[Paragraph('<b>Motor Deficit</b>', bold_body), Paragraph('<b>Sensory Loss</b>', bold_body), Paragraph('<b>Key Gait</b>', bold_body)],
[Paragraph('• Foot drop (dorsiflexion weakness)\n• Weak eversion (peronei)\n• Toe extension weakness\n• Preserved inversion ✓\n• Preserved plantarflexion ✓', table_body),
Paragraph('• Dorsum of foot (sup. peroneal)\n• Lateral leg (lat. sural cut.)\n• 1st web space (deep peroneal)\n• Sole SPARED ✓', table_body),
Paragraph('• Steppage gait\n (high knee lift to\n clear toe from ground)\n• Toe slapping on\n ground contact', table_body)],
]
cf_table = Table(cf_data, colWidths=[col_half*0.75, col_half*0.65, col_half*0.60])
cf_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), MID_BLUE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BACKGROUND', (0,1), (-1,1), BOX_BLUE),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('TOPPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(cf_table)
story.append(Spacer(1, 5))
# Differential
diff_data = [
[Paragraph('<b>Condition</b>', table_header),
Paragraph('<b>Inversion</b>', table_header),
Paragraph('<b>Eversion</b>', table_header),
Paragraph('<b>Back Pain</b>', table_header),
Paragraph('<b>EMG Paraspinals</b>', table_header),
Paragraph('<b>Tinel Fibular Head</b>', table_header)],
[Paragraph('CPN Palsy', table_body), Paragraph('Normal ✓', table_body),
Paragraph('Weak ✗', table_body), Paragraph('Absent', table_body),
Paragraph('Normal', table_body), Paragraph('Present', table_body)],
[Paragraph('L5 Radiculopathy', table_body), Paragraph('Weak ✗', table_body),
Paragraph('Weak ✗', table_body), Paragraph('Often present', table_body),
Paragraph('Abnormal', table_body), Paragraph('Absent', table_body)],
[Paragraph('LS Plexopathy', table_body), Paragraph('Weak ✗', table_body),
Paragraph('Weak ✗', table_body), Paragraph('Variable', table_body),
Paragraph('Normal', table_body), Paragraph('Absent', table_body)],
[Paragraph('Tib. Ant. Tendon Rupture', table_body), Paragraph('Normal ✓', table_body),
Paragraph('Normal ✓', table_body), Paragraph('Absent', table_body),
Paragraph('Normal', table_body), Paragraph('Absent — palpable gap', table_body)],
]
diff_table = Table(diff_data, colWidths=[col_half*0.47, col_half*0.26, col_half*0.26,
col_half*0.33, col_half*0.38, col_half*0.30])
diff_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('BACKGROUND', (0,1), (-1,1), BOX_GREEN),
]))
story.append(diff_table)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# ROW 4: Investigations
# ════════════════════════════════════════════════════════════
story.append(SectionHeader("INVESTIGATIONS", usable_W, HexColor("#6C3483"), "5"))
story.append(Spacer(1, 4))
inv_data = [
[Paragraph('<b>Investigation</b>', table_header), Paragraph('<b>Key Findings / Notes</b>', table_header),
Paragraph('<b>Timing</b>', table_header)],
[Paragraph('NCS – Motor', table_body),
Paragraph('Conduction block across fibular head (>50% CMAP drop) = neurapraxia; Axonal loss = axonotmesis/worse', table_body),
Paragraph('Any time; repeat at 3 months', table_body)],
[Paragraph('NCS – Sensory', table_body),
Paragraph('Superficial peroneal SNAP absent or reduced', table_body),
Paragraph('From day 1', table_body)],
[Paragraph('EMG (Needle)', table_body),
Paragraph('Fibrillations in TA, EHL, EDL, peronei. Normal paraspinals → rules out L5 root', table_body),
Paragraph('≥3 weeks post-injury', table_body)],
[Paragraph('X-Ray Knee/Fibula', table_body),
Paragraph('Fibular head # (50% CPN), knee dislocation, tibial plateau #. Alignment assessment', table_body),
Paragraph('Acute', table_body)],
[Paragraph('MRI Knee / Neurography', table_body),
Paragraph('T2 bright nerve = neurapraxia/axonotmesis. Nerve gap = neurotmesis. Intraneural ganglion → cystic lesion tracking from STFJ', table_body),
Paragraph('Subacute / pre-op', table_body)],
[Paragraph('USS (High-Freq 15-18MHz)', table_body),
Paragraph('Dynamic assessment, ganglion identification, ultrasound-guided aspiration', table_body),
Paragraph('Any time', table_body)],
[Paragraph('Blood (HbA1c, ESR, ANCA)', table_body),
Paragraph('Rule out systemic cause (diabetes, vasculitis)', table_body),
Paragraph('Initial workup', table_body)],
]
inv_table = Table(inv_data, colWidths=[col_half*0.48, col_half*1.22, col_half*0.30])
inv_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), HexColor("#6C3483")),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story.append(inv_table)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# PAGE BREAK
# ════════════════════════════════════════════════════════════
story.append(PageBreak())
story.append(HeaderBanner(usable_W, 40))
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# MANAGEMENT FLOWCHART
# ════════════════════════════════════════════════════════════
story.append(SectionHeader("MANAGEMENT ALGORITHM", usable_W, NAVY, "6"))
story.append(Spacer(1, 4))
story.append(ManagementFlowchart(usable_W))
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# NON-OPERATIVE + OPERATIVE side by side
# ════════════════════════════════════════════════════════════
nonop_hdr = SectionHeader("A. NON-OPERATIVE MANAGEMENT", col_half, GREEN, "7")
op_hdr = SectionHeader("B. OPERATIVE MANAGEMENT", col_half, RED, "8")
hdr_row = Table([[nonop_hdr, Spacer(6,1), op_hdr]], colWidths=[col_half, 6, col_half])
hdr_row.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),('RIGHTPADDING',(0,0),(-1,-1),0)]))
story.append(hdr_row)
story.append(Spacer(1, 3))
nonop_items = [
[Paragraph('<b>Measure</b>', table_header), Paragraph('<b>Details</b>', table_header)],
[Paragraph('Indication', table_body), Paragraph('Grade 1-2 / low-energy / metabolic cause', table_body)],
[Paragraph('AFO (Ankle-Foot Orthosis)', table_body), Paragraph('Rigid/articulated — maintains neutral dorsiflexion during swing. Carbon fibre preferred', table_body)],
[Paragraph('Physiotherapy', table_body), Paragraph('ROM, Achilles stretching (prevent equinus), gait retraining, strengthening intact muscles', table_body)],
[Paragraph('Risk factor removal', table_body), Paragraph('Stop leg crossing, treat DM, weight loss, knee pad', table_body)],
[Paragraph('FES (Peroneal Stimulator)', table_body), Paragraph('WalkAide / Bioness L300 — triggers dorsiflexion; adjunct to AFO', table_body)],
[Paragraph('Observation window', table_body), Paragraph('Grade 1: 4-12 weeks. Grade 2-3: 3-6 months. Serial NCS/EMG', table_body)],
[Paragraph('Failure threshold', table_body), Paragraph('No clinical/EMG recovery at 3 months (axonal loss) → refer for surgery', table_body)],
]
nonop_table = Table(nonop_items, colWidths=[col_half*0.44, col_half*0.56])
nonop_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GREEN),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, BOX_GREEN]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('BACKGROUND', (0,7), (-1,7), BOX_ORANGE),
]))
op_items = [
[Paragraph('<b>Procedure</b>', table_header), Paragraph('<b>Indication / Outcome</b>', table_header)],
[Paragraph('Timing (High energy)', table_body), Paragraph('Explore within 3 WEEKS of laceration/fracture', table_body)],
[Paragraph('Timing (Low energy)', table_body), Paragraph('Surgery if no recovery at 3-6 months', table_body)],
[Paragraph('Neurolysis', table_body), Paragraph('Sunderland 3-4, entrapment, intraneural ganglion. Decompress + articular branch excision + STFJ repair', table_body)],
[Paragraph('Primary Repair\n(Neurorrhaphy)', table_body), Paragraph('Sharp laceration, small gap, tension-free. 60-70% useful dorsiflexion (ideal)', table_body)],
[Paragraph('Nerve Graft\n(Sural / Allograft)', table_body), Paragraph('Gap >2-3 cm. Mean graft 7 cm. 49% >M3 recovery. Worse if >12 cm graft', table_body)],
[Paragraph('Nerve Transfer\n(Tibial → CPN)', table_body), Paragraph('Proximal injury / long gap. 62.9% >M3. End-to-end to distal CPN', table_body)],
[Paragraph('Tendon Transfer\n(Tibialis Post.)', table_body), Paragraph('SALVAGE >12-18 months / irreversible. Bridle procedure — most reliable long-term', table_body)],
]
op_table = Table(op_items, colWidths=[col_half*0.40, col_half*0.60])
op_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), RED),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, BOX_RED]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('BACKGROUND', (0,7), (-1,7), BOX_ORANGE),
]))
content_row = Table([[nonop_table, Spacer(6,1), op_table]], colWidths=[col_half, 6, col_half])
content_row.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),('RIGHTPADDING',(0,0),(-1,-1),0)]))
story.append(content_row)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# CRITICAL TIMELINES + OUTCOMES
# ════════════════════════════════════════════════════════════
story.append(SectionHeader("CRITICAL TIMELINES & SURGICAL OUTCOMES", usable_W, ORANGE, "9"))
story.append(Spacer(1, 4))
timeline_data = [
[Paragraph('<b>Timeline</b>', table_header),
Paragraph('<b>Decision Point</b>', table_header),
Paragraph('<b>Action</b>', table_header)],
[Paragraph('Day 0-21', table_body),
Paragraph('High-energy injury (laceration, fracture-dislocation)', table_body),
Paragraph('Surgical exploration ± primary repair', table_body)],
[Paragraph('3 weeks post-injury', table_body),
Paragraph('EMG now reliable (fibrillations detectable)', table_body),
Paragraph('Baseline EMG / NCS', table_body)],
[Paragraph('3 months', table_body),
Paragraph('Neurapraxia should show clinical/EMG recovery', table_body),
Paragraph('If no recovery → upgrade suspicion → MRI ± surgical referral', table_body)],
[Paragraph('3-6 months', table_body),
Paragraph('Low-energy: failure of conservative Rx', table_body),
Paragraph('Neurolysis / nerve repair', table_body)],
[Paragraph('<12 months', table_body),
Paragraph('CRITICAL WINDOW for nerve repair', table_body),
Paragraph('Nerve repair/graft suboptimal if >12 months post-injury', table_body)],
[Paragraph('>12-18 months', table_body),
Paragraph('Irreversible muscle denervation / failed nerve repair', table_body),
Paragraph('Tibialis posterior tendon transfer (Bridle procedure)', table_body)],
]
tl_table = Table(timeline_data, colWidths=[col_half*0.36, col_half*0.84, col_half*0.80])
tl_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), ORANGE),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8.5),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, LIGHT_BG]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BACKGROUND', (0,5), (-1,5), BOX_RED),
('BACKGROUND', (0,6), (-1,6), BOX_ORANGE),
('FONTNAME', (0,5), (-1,5), 'Helvetica-Bold'),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story.append(tl_table)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# OUTCOMES SUMMARY + RECENT ADVANCES
# ════════════════════════════════════════════════════════════
out_hdr = SectionHeader("OUTCOMES (Evidence-Based)", col_half, TEAL, "10")
adv_hdr = SectionHeader("RECENT ADVANCES", col_half, HexColor("#6C3483"), "11")
out_items = [
[Paragraph('<b>Procedure</b>', table_header), Paragraph('<b>Outcome</b>', table_header)],
[Paragraph('Neurolysis (early, <6m)', table_body), Paragraph('60-70% recovery', table_body)],
[Paragraph('Primary repair (ideal)', table_body), Paragraph('60-70% anti-gravity dorsiflexion', table_body)],
[Paragraph('Nerve grafting (meta-analysis, n=465)', table_body), Paragraph('49% achieve >M3', table_body)],
[Paragraph('Nerve transfer (tibial→CPN)', table_body), Paragraph('62.9% achieve >M3', table_body)],
[Paragraph('Repair >12 months post-injury', table_body), Paragraph('Suboptimal — do NOT repair', table_body)],
[Paragraph('Graft >12 cm', table_body), Paragraph('Poor outcome expected', table_body)],
[Paragraph('Tendon transfer (late)', table_body), Paragraph('Most reliable, durable long-term', table_body)],
]
out_table = Table(out_items, colWidths=[col_half*0.58, col_half*0.42])
out_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, BOX_BLUE]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BACKGROUND', (0,5), (-1,6), BOX_RED),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
adv_items = [
[Paragraph('<b>Advance</b>', table_header), Paragraph('<b>Clinical Relevance</b>', table_header)],
[Paragraph('MR Neurography (3T DTI)', table_body), Paragraph('Fascicular detail, continuity; guides surgical planning', table_body)],
[Paragraph('Nerve Transfers', table_body), Paragraph('Tibial branch → deep CPN; avoids long graft distances', table_body)],
[Paragraph('Implantable FES (ActiGait)', table_body), Paragraph('Closed-loop, gait-triggered stimulation; better than fixed AFO', table_body)],
[Paragraph('Nerve Allografts (Avance)', table_body), Paragraph('5-70 mm gaps; avoids sural harvest morbidity', table_body)],
[Paragraph('Intraneural Ganglion Rx', table_body), Paragraph('Articular branch excision + STFJ capsule repair = low recurrence', table_body)],
[Paragraph('Intraop NAP Recording', table_body), Paragraph('Guides resection vs neurolysis in neuroma-in-continuity', table_body)],
[Paragraph('PRP / Growth Factors', table_body), Paragraph('Emerging — promotes remyelination; experimental', table_body)],
]
adv_table = Table(adv_items, colWidths=[col_half*0.44, col_half*0.56])
adv_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), HexColor("#6C3483")),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, HexColor("#F3E5F5")]),
('GRID', (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
hdr_row2 = Table([[out_hdr, Spacer(6,1), adv_hdr]], colWidths=[col_half, 6, col_half])
hdr_row2.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),('RIGHTPADDING',(0,0),(-1,-1),0)]))
story.append(hdr_row2)
story.append(Spacer(1, 3))
content_row2 = Table([[out_table, Spacer(6,1), adv_table]], colWidths=[col_half, 6, col_half])
content_row2.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('LEFTPADDING',(0,0),(-1,-1),0),('RIGHTPADDING',(0,0),(-1,-1),0)]))
story.append(content_row2)
story.append(Spacer(1, 8))
# ════════════════════════════════════════════════════════════
# QUICK REFERENCE PEARL BOX
# ════════════════════════════════════════════════════════════
pearls_data = [
[
Paragraph('<b>🔑 CLINICAL PEARLS</b>', ParagraphStyle('ph', fontName='Helvetica-Bold', fontSize=9, textColor=NAVY)),
],
[
Paragraph(
'• <b>Preserved inversion</b> = CPN palsy (not L5 root); eversion tests the superficial peroneal nerve specifically<br/>'
'• <b>Wait ≥3 weeks</b> before EMG — fibrillations appear 3 weeks after axonal injury; early normal EMG does not rule out axonal loss<br/>'
'• <b>EMG paraspinals:</b> Normal → CPN palsy | Abnormal → L5 radiculopathy<br/>'
'• <b>Intraneural ganglion</b> suspect when: palpable mass at fibular head + fluctuating weakness with weight-bearing + BMI-associated + no weight loss history<br/>'
'• <b>TKA foot drop:</b> Decompress if not recovering; lithotomy/lateral decubitus positioning is the main iatrogenic cause<br/>'
'• <b>Critical window:</b> Nerve repair >12 months post-injury OR graft >12 cm = predictably poor outcome — proceed to tendon transfer<br/>'
'• <b>Tibialis anterior tendon rupture</b> mimics CPN palsy — look for palpable gap, check EMG (normal in tendon rupture)',
ParagraphStyle('pearl_body', fontName='Helvetica', fontSize=8.5, textColor=HexColor("#2C3E50"), leading=13, leftIndent=5)
)
]
]
pearls_table = Table(pearls_data, colWidths=[usable_W])
pearls_table.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), YELLOW_BG),
('BACKGROUND', (0,1), (-1,1), YELLOW_BG),
('BOX', (0,0), (-1,-1), 1.5, ORANGE),
('LINEBELOW', (0,0), (-1,0), 0.8, ORANGE),
('BOTTOMPADDING', (0,0), (-1,-1), 8),
('TOPPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 10),
('VALIGN', (0,0), (-1,-1), 'TOP'),
]))
story.append(pearls_table)
story.append(Spacer(1, 6))
# ─── FOOTER
footer_data = [[
Paragraph('<font size="7" color="#7F8C8D">Sources: Campbell\'s Operative Orthopaedics 15e (2026) • Adams & Victor\'s Principles of Neurology 12e • Harrison\'s Internal Medicine 22e • Localization in Clinical Neurology 8e • Sabiston Textbook of Surgery 8e • Imaging Anatomy Vol.3 | Generated by Orris Clinical AI — June 2026 | For educational use only</font>', body_style)
]]
footer_table = Table(footer_data, colWidths=[usable_W])
footer_table.setStyle(TableStyle([
('LINEABOVE', (0,0), (-1,0), 0.5, GREY),
('TOPPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 0),
]))
story.append(footer_table)
doc.build(story)
print(f"PDF saved to: {path}")
if __name__ == "__main__":
out_path = "/home/daytona/workspace/cpn-nerve/CPN_Injury_Quick_Reference.pdf"
build_pdf(out_path)
cd /home/daytona/workspace/cpn-nerve && python3 generate_cpn_pdf.py 2>&1
~/cpn-nerve/generate_cpn_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/cpn-nerve/generate_cpn_pdf.py'
cd /home/daytona/workspace/cpn-nerve && python3 generate_cpn_pdf.py 2>&1
~/cpn-nerve/generate_cpn_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/cpn-nerve/generate_cpn_pdf.py'
grep -n "ROWBACKGROUNDS" /home/daytona/workspace/cpn-nerve/generate_cpn_pdf.py
python3 -c " import re with open('/home/daytona/workspace/cpn-nerve/generate_cpn_pdf.py', 'r') as f: content = f.read() # Replace all ROWBACKGROUNDS with just removing them (use single color BACKGROUND for all data rows instead) # Strategy: replace ('ROWBACKGROUNDS', ...) lines with a simple background that won't cause errors # We'll replace each with a comment and add background lines manually # Simple approach: replace all ROWBACKGROUNDS lines with nothing (remove them) content = re.sub(r\"\s*\\('ROWBACKGROUNDS',[^)]+\\),\", '', content) with open('/home/daytona/workspace/cpn-nerve/generate_cpn_pdf.py', 'w') as f: f.write(content) print('Done') "
cd /home/daytona/workspace/cpn-nerve && python3 generate_cpn_pdf.py 2>&1
~/cpn-nerve/generate_cpn_pdf.py
"""
CPN Injury Management - Quick Reference Clinical Decision Support PDF
Orthopaedic Perspective - ReportLab (clean, no ROWBACKGROUNDS)
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.colors import HexColor
# ── Colours
NAVY = HexColor("#0D2B55")
TEAL = HexColor("#0A7E8C")
ORANGE = HexColor("#E87722")
MID_BLUE = HexColor("#1A5276")
GREEN = HexColor("#1E8449")
DARK_GREEN = HexColor("#1A6B3C")
RED = HexColor("#C0392B")
PURPLE = HexColor("#6C3483")
WHITE = colors.white
GREY = HexColor("#7F8C8D")
LIGHT_GREY = HexColor("#ECF0F1")
LIGHT_BG = HexColor("#F0F4F8")
BOX_BLUE = HexColor("#D6EAF8")
BOX_GREEN = HexColor("#D5F5E3")
BOX_RED = HexColor("#FADBD8")
BOX_ORANGE = HexColor("#FDEBD0")
BOX_PURPLE = HexColor("#F3E5F5")
YELLOW_BG = HexColor("#FEF9E7")
W, H = A4 # 595 x 842 pt
# ── Styles
def S(name, **kw):
return ParagraphStyle(name, **kw)
TH = S('TH', fontName='Helvetica-Bold', fontSize=8.5, textColor=WHITE, alignment=TA_CENTER, leading=11)
TB = S('TB', fontName='Helvetica', fontSize=8, textColor=HexColor("#2C3E50"), alignment=TA_LEFT, leading=11)
TBB = S('TBB', fontName='Helvetica-Bold', fontSize=8, textColor=HexColor("#2C3E50"), alignment=TA_LEFT, leading=11)
THs = S('THs', fontName='Helvetica-Bold', fontSize=7.5, textColor=WHITE, alignment=TA_CENTER, leading=10)
TBs = S('TBs', fontName='Helvetica', fontSize=7.5, textColor=HexColor("#2C3E50"), alignment=TA_LEFT, leading=10)
BODY = S('BODY', fontName='Helvetica', fontSize=8.5, textColor=HexColor("#2C3E50"), alignment=TA_LEFT, leading=13)
def alt_bg(table_style_list, n_rows, start=1, c1=WHITE, c2=LIGHT_BG):
"""Append alternating background rows to a style list."""
for i in range(start, n_rows):
col = c1 if (i % 2 == 1) else c2
table_style_list.append(('BACKGROUND', (0, i), (-1, i), col))
# ── Header Banner
class HeaderBanner(Flowable):
def __init__(self, w, h=70):
super().__init__()
self.width, self.height = w, h
def draw(self):
c = self.canv
c.setFillColor(NAVY); c.roundRect(0, 0, self.width, self.height, 8, fill=1, stroke=0)
c.setFillColor(TEAL); c.roundRect(0, 0, 6, self.height, 3, fill=1, stroke=0)
c.setFillColor(ORANGE); c.roundRect(self.width - 6, 0, 6, self.height, 3, fill=1, stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 19)
c.drawCentredString(self.width / 2, self.height - 30, "COMMON PERONEAL NERVE INJURY")
c.setFillColor(HexColor("#BDC3C7")); c.setFont("Helvetica", 10)
c.drawCentredString(self.width / 2, self.height - 46,
"Quick Reference Clinical Decision Support | Orthopaedic Perspective")
c.setFillColor(ORANGE); c.setFont("Helvetica-Bold", 7.5)
c.drawCentredString(self.width / 2, 8,
"Campbell's Operative Orthopaedics 15e (2026) • Adams & Victor 12e • Harrison's 22e • Localization in Clinical Neurology 8e")
class MiniHeader(Flowable):
def __init__(self, w, h=36):
super().__init__()
self.width, self.height = w, h
def draw(self):
c = self.canv
c.setFillColor(NAVY); c.roundRect(0, 0, self.width, self.height, 6, fill=1, stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 13)
c.drawCentredString(self.width / 2, self.height - 20,
"COMMON PERONEAL NERVE — QUICK REFERENCE")
c.setFillColor(HexColor("#BDC3C7")); c.setFont("Helvetica", 8)
c.drawCentredString(self.width / 2, 6, "Page 2 of 2 | Orthopaedic Decision Support")
# ── Section Header
class SecHdr(Flowable):
def __init__(self, text, w, color=TEAL, num=None):
super().__init__()
self.text, self.width, self.color, self.num = text, w, color, num
self.height = 22
def draw(self):
c = self.canv
c.setFillColor(self.color)
c.roundRect(0, 0, self.width, self.height, 4, fill=1, stroke=0)
c.setFillColor(WHITE); c.setFont("Helvetica-Bold", 10.5)
lbl = f" {self.num}. {self.text}" if self.num else f" {self.text}"
c.drawString(10, 6, lbl)
# ── Management Flowchart
class Flowchart(Flowable):
def __init__(self, w):
super().__init__()
self.width, self.height = w, 295
def rbox(self, c, x, y, w, h, lines, bg, fg=WHITE, fs=8, bold=False):
c.setFillColor(bg); c.setStrokeColor(NAVY); c.setLineWidth(0.7)
c.roundRect(x, y, w, h, 4, fill=1, stroke=1)
c.setFillColor(fg); fn = "Helvetica-Bold" if bold else "Helvetica"
c.setFont(fn, fs)
lh = fs + 2
total = len(lines) * lh
sy = y + h / 2 + total / 2 - lh + 1
for ln in lines:
c.drawCentredString(x + w / 2, sy, ln)
sy -= lh
def arr(self, c, x1, y1, x2, y2, col=NAVY):
c.setStrokeColor(col); c.setLineWidth(1.1)
c.line(x1, y1, x2, y2)
sz = 5
c.setFillColor(col)
if x1 == x2:
c.polygon([x2 - sz/2, y2 + sz, x2 + sz/2, y2 + sz, x2, y2], fill=1, stroke=0)
else:
if x2 > x1:
c.polygon([x2 - sz, y2 + sz/2, x2 - sz, y2 - sz/2, x2, y2], fill=1, stroke=0)
else:
c.polygon([x2 + sz, y2 + sz/2, x2 + sz, y2 - sz/2, x2, y2], fill=1, stroke=0)
def diamond(self, c, cx, cy, hw, hh, lines, bg, fg=WHITE, fs=8):
c.setFillColor(bg); c.setStrokeColor(NAVY); c.setLineWidth(0.7)
pts = [cx, cy+hh, cx+hw, cy, cx, cy-hh, cx-hw, cy]
c.polygon(pts, fill=1, stroke=1)
c.setFillColor(fg); c.setFont("Helvetica-Bold", fs)
lh = fs + 2
total = len(lines) * lh
sy = cy + total/2 - lh/2
for ln in lines:
c.drawCentredString(cx, sy, ln)
sy -= lh
def draw(self):
c = self.canv
# background
c.setFillColor(LIGHT_BG); c.roundRect(0, 0, self.width, self.height, 6, fill=1, stroke=0)
c.setFillColor(NAVY); c.setFont("Helvetica-Bold", 9.5)
c.drawCentredString(self.width/2, self.height - 14, "CPN INJURY — ORTHOPAEDIC MANAGEMENT ALGORITHM")
W = self.width
cx = W / 2
bw, bh = 148, 28
# Row 1: Initial Assessment
y1 = self.height - 38
self.rbox(c, cx-bw/2, y1, bw, bh, ["INITIAL ASSESSMENT", "History + Examination"], NAVY, bold=True)
self.arr(c, cx, y1, cx, y1 - 14)
# Row 2: NCS/EMG
y2 = y1 - 14 - bh
self.rbox(c, cx-bw/2, y2, bw, bh, ["NCS / EMG (≥3 weeks post-injury)"], TEAL, bold=True)
self.arr(c, cx, y2, cx, y2 - 14)
# Row 3: Diamond
y3 = y2 - 14 - 35
self.diamond(c, cx, y3, 155, 34, ["MECHANISM?"], MID_BLUE)
# left label
c.setFillColor(RED); c.setFont("Helvetica-Bold", 7.5)
c.drawString(26, y3 + 5, "HIGH ENERGY")
c.setFillColor(DARK_GREEN)
c.drawString(W - 108, y3 + 5, "LOW ENERGY")
# Left branch arrows
self.arr(c, cx - 155, y3, 20 + 130, y3, col=RED)
# Right branch arrows
self.arr(c, cx + 155, y3, W - 20 - 130, y3, col=DARK_GREEN)
lbx = 18
rbx = W - 18 - 130
# HIGH ENERGY path (left column)
y_l1 = y3 - 14
self.arr(c, 18+130/2, y3, 18+130/2, y_l1)
self.rbox(c, lbx, y_l1-bh, 130, bh, ["Surgical Exploration", "within 3 WEEKS"], RED, bold=True)
y_l2 = y_l1 - bh - 12
self.arr(c, lbx+65, y_l1-bh, lbx+65, y_l2)
self.rbox(c, lbx, y_l2-bh+5, 130, bh, ["Intraop NAP Recording", "+ Grade Assessment"], ORANGE, fg=HexColor("#2C3E50"))
y_l3 = y_l2 - bh - 6
self.arr(c, lbx+65, y_l2-bh+5, lbx+65, y_l3)
self.rbox(c, lbx, y_l3-bh, 130, bh, ["Neurolysis / Repair", "/ Graft / Transfer"], TEAL, bold=True)
# LOW ENERGY path (right column)
y_r1 = y3 - 14
self.arr(c, rbx+65, y3, rbx+65, y_r1)
self.rbox(c, rbx, y_r1-bh, 130, bh, ["Conservative Rx", "AFO + Physio"], DARK_GREEN, bold=True)
y_r2 = y_r1 - bh - 12
self.arr(c, rbx+65, y_r1-bh, rbx+65, y_r2)
self.rbox(c, rbx, y_r2-bh+5, 130, bh, ["Observe 3–6 months", "Serial NCS/EMG"], GREEN)
# Recovery diamond
y_r3 = y_r2 - bh - 8
self.arr(c, rbx+65, y_r2-bh+5, rbx+65, y_r3+25)
self.diamond(c, rbx+65, y_r3, 68, 25, ["Recovery?"], ORANGE, fg=HexColor("#2C3E50"), fs=8)
# YES → down
y_r4 = y_r3 - 25 - 8
self.arr(c, rbx+65, y_r3-25, rbx+65, y_r4, col=GREEN)
c.setFillColor(GREEN); c.setFont("Helvetica-Bold", 7)
c.drawString(rbx+68, y_r3-14, "YES")
self.rbox(c, rbx, y_r4-22, 130, 22, ["Continue Conservative Rx"], DARK_GREEN)
# NO → left arrow connecting to surgery
self.arr(c, rbx+65-68, y_r3, lbx+65+20, y_r3, col=RED)
c.setFillColor(RED); c.setFont("Helvetica-Bold", 7)
c.drawString(lbx+80, y_r3+4, "NO → SURGERY")
# Bottom legend row
y_bot = 10
bw3 = 90
legend = [
(10, "Grade 1-2", "Full Recovery", GREEN),
(107, "Grade 3", "Partial / Neurolysis", TEAL),
(214, "Grade 4", "Neurolysis ± Graft", ORANGE),
(317, "Grade 5", "Repair or Graft", RED),
(415, ">12 months","Tendon Transfer", MID_BLUE),
]
c.setFillColor(NAVY); c.setFont("Helvetica-Bold", 7)
c.drawCentredString(W/2, y_bot+50, "─── Sunderland Grade → Expected Treatment ───")
for lx, g, txt, col in legend:
self.rbox(c, lx, y_bot, 88, 38, [g, txt], col, fs=7.5)
def base_ts(header_color, n_data, alt1=WHITE, alt2=LIGHT_BG, extra=None):
"""Build a base TableStyle with alternating rows."""
ts = [
('BACKGROUND', (0, 0), (-1, 0), header_color),
('TEXTCOLOR', (0, 0), (-1, 0), WHITE),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1,-1), 8.5),
('GRID', (0, 0), (-1,-1), 0.4, HexColor("#BDC3C7")),
('BOTTOMPADDING', (0, 0), (-1,-1), 4),
('TOPPADDING', (0, 0), (-1,-1), 4),
('LEFTPADDING', (0, 0), (-1,-1), 6),
('VALIGN', (0, 0), (-1,-1), 'MIDDLE'),
]
for i in range(1, n_data + 1):
ts.append(('BACKGROUND', (0, i), (-1, i), alt1 if i % 2 == 1 else alt2))
if extra:
ts.extend(extra)
return TableStyle(ts)
def build_pdf(path):
doc = SimpleDocTemplate(
path, pagesize=A4,
leftMargin=14*mm, rightMargin=14*mm,
topMargin=10*mm, bottomMargin=10*mm,
title="CPN Injury Quick Reference – Orthopaedics",
author="Orris Clinical AI",
subject="Common Peroneal Nerve Injury Management"
)
story = []
UW = W - 28*mm # usable width ≈ 539 pt
C = (UW - 6) / 2 # half-column ≈ 266 pt
def two_col(left, right, sep=6):
t = Table([[left, Spacer(sep, 1), right]], colWidths=[C, sep, C])
t.setStyle(TableStyle([
('VALIGN', (0,0),(-1,-1), 'TOP'),
('LEFTPADDING', (0,0),(-1,-1), 0),
('RIGHTPADDING', (0,0),(-1,-1), 0),
]))
return t
# ═══════════════════════════════════════ PAGE 1 ═══════════
story.append(HeaderBanner(UW, 70))
story.append(Spacer(1, 7))
# ── 1. Anatomy
story.append(SecHdr("ANATOMY SNAPSHOT", UW, TEAL, "1"))
story.append(Spacer(1, 3))
anat = [
[Paragraph('<b>Feature</b>', TH), Paragraph('<b>Detail</b>', TH)],
[Paragraph('Root value', TB), Paragraph('L4, L5, S1, S2', TB)],
[Paragraph('Origin', TB), Paragraph('Lateral division of sciatic nerve, posterolateral popliteal fossa', TB)],
[Paragraph('Vulnerable point', TB), Paragraph('Fibular head/neck — superficial, tethered, no muscle cover', TB)],
[Paragraph('Fibular tunnel', TB), Paragraph('Fibrous arch at origin of peroneus longus', TB)],
[Paragraph('<b>Deep peroneal</b>', TBB), Paragraph('Tibialis ant., EHL, EDL, EDB → dorsiflexion, toe extension; sensation 1st web space', TB)],
[Paragraph('<b>Superficial peroneal</b>', TBB), Paragraph('Peroneus longus & brevis → eversion; sensation dorsum of foot', TB)],
[Paragraph('Other branches', TB), Paragraph('Recurrent articular (knee), lateral sural cutaneous (upper lat. leg)', TB)],
]
at = Table(anat, colWidths=[C*0.34, C*1.66])
at.setStyle(base_ts(TEAL, 7,
extra=[
('BACKGROUND', (0,5), (-1,5), BOX_BLUE),
('BACKGROUND', (0,6), (-1,6), BOX_GREEN),
]
))
story.append(at)
story.append(Spacer(1, 7))
# ── 2+3: Etiology | Classification
etiol = [
[Paragraph('<b>Category</b>', TH), Paragraph('<b>Common Causes</b>', TH)],
[Paragraph('Traumatic', TB), Paragraph('Knee dislocation (44% CPN), fibular head # (50% CPN), direct blow, TKA', TB)],
[Paragraph('Compressive', TB), Paragraph('Leg crossing, tight cast, squatting, lithotomy, bed rest', TB)],
[Paragraph('Space-occupying', TB), Paragraph('Intraneural ganglion (STFJ), Baker cyst, ganglion, lipoma', TB)],
[Paragraph('Systemic', TB), Paragraph('Diabetes, vasculitis, emaciation, leprosy', TB)],
[Paragraph('Iatrogenic', TB), Paragraph('Lateral decubitus position, HTO, poor cast application', TB)],
]
et = Table(etiol, colWidths=[C*0.34, C*0.66])
et.setStyle(base_ts(ORANGE, 5))
classif = [
[Paragraph('<b>Gr</b>', THs), Paragraph('<b>Seddon</b>', THs), Paragraph('<b>Pathology</b>', THs), Paragraph('<b>Prognosis</b>', THs)],
[Paragraph('1', TBs), Paragraph('Neurapraxia', TBs), Paragraph('Myelin only', TBs), Paragraph('Full; wks–3m', TBs)],
[Paragraph('2', TBs), Paragraph('Axonotmesis', TBs), Paragraph('Axon intact endo', TBs), Paragraph('Excellent 2–3mm/d', TBs)],
[Paragraph('3', TBs), Paragraph('Axonotmesis', TBs), Paragraph('Axon + endo', TBs), Paragraph('Incomplete 1mm/d', TBs)],
[Paragraph('4', TBs), Paragraph('Neurotmesis', TBs), Paragraph('+ perineurium', TBs), Paragraph('Neuroma-in-cont.', TBs)],
[Paragraph('5', TBs), Paragraph('Neurotmesis', TBs), Paragraph('Complete', TBs), Paragraph('Surgery required', TBs)],
[Paragraph('6', TBs), Paragraph('Mixed', TBs), Paragraph('Variable fascicles', TBs), Paragraph('Variable', TBs)],
]
ct = Table(classif, colWidths=[C*0.12, C*0.27, C*0.32, C*0.29])
ct.setStyle(base_ts(MID_BLUE, 6,
extra=[
('FONTSIZE', (0,0),(-1,-1), 7.5),
('BACKGROUND', (0,4),(-1,5), BOX_RED),
]
))
story.append(two_col(SecHdr("ETIOLOGY", C, ORANGE, "2"), SecHdr("SUNDERLAND CLASSIFICATION", C, MID_BLUE, "3")))
story.append(Spacer(1, 3))
story.append(two_col(et, ct))
story.append(Spacer(1, 7))
# ── 4. Clinical Features + Differential
story.append(SecHdr("CLINICAL FEATURES & DIFFERENTIAL DIAGNOSIS", UW, MID_BLUE, "4"))
story.append(Spacer(1, 3))
cf = [
[Paragraph('<b>Motor Deficit</b>', TH), Paragraph('<b>Sensory Loss</b>', TH), Paragraph('<b>Gait / Sign</b>', TH)],
[
Paragraph('• Foot drop (weak dorsiflexion)<br/>• Weak eversion<br/>• Weak toe extension<br/>• Preserved inversion ✓<br/>• Preserved plantarflexion ✓', TB),
Paragraph('• Dorsum of foot (sup. peroneal)<br/>• Lateral upper leg (lat. sural)<br/>• 1st web space (deep peroneal)<br/>• Sole SPARED ✓', TB),
Paragraph('• Steppage (high-stepping) gait<br/>• Toe slapping at heel-strike<br/>• Tinel at fibular head<br/>• NO back pain typically', TB),
],
]
cft = Table(cf, colWidths=[C*0.70, C*0.62, C*0.68])
cft.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), MID_BLUE),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('BACKGROUND', (0,1),(-1,1), BOX_BLUE),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 5),
('BOTTOMPADDING', (0,0),(-1,-1), 5),
('LEFTPADDING', (0,0),(-1,-1), 6),
('VALIGN', (0,0),(-1,-1), 'TOP'),
]))
story.append(cft)
story.append(Spacer(1, 4))
diff = [
[Paragraph('<b>Condition</b>', THs), Paragraph('<b>Inversion</b>', THs),
Paragraph('<b>Eversion</b>', THs), Paragraph('<b>EMG Paraspinals</b>', THs),
Paragraph('<b>Tinel Fib.Head</b>', THs), Paragraph('<b>Back Pain</b>', THs)],
[Paragraph('CPN Palsy', TBs), Paragraph('Normal ✓', TBs),
Paragraph('Weak', TBs), Paragraph('Normal', TBs),
Paragraph('Present', TBs), Paragraph('Absent', TBs)],
[Paragraph('L5 Radiculopathy', TBs), Paragraph('Weak', TBs),
Paragraph('Weak', TBs), Paragraph('Abnormal', TBs),
Paragraph('Absent', TBs), Paragraph('Often present', TBs)],
[Paragraph('LS Plexopathy', TBs), Paragraph('Weak', TBs),
Paragraph('Weak', TBs), Paragraph('Normal', TBs),
Paragraph('Absent', TBs), Paragraph('Variable', TBs)],
[Paragraph('Tib. Ant. Tendon Rupture', TBs), Paragraph('Normal ✓', TBs),
Paragraph('Normal ✓', TBs), Paragraph('Normal', TBs),
Paragraph('Absent — palpable gap', TBs), Paragraph('Absent', TBs)],
]
dt = Table(diff, colWidths=[UW*0.23, UW*0.10, UW*0.10, UW*0.18, UW*0.22, UW*0.17])
ts_diff = [
('BACKGROUND', (0,0),(-1,0), NAVY),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 7.5),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 3),
('BOTTOMPADDING', (0,0),(-1,-1), 3),
('LEFTPADDING', (0,0),(-1,-1), 5),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('BACKGROUND', (0,1),(-1,1), BOX_GREEN),
('BACKGROUND', (0,2),(-1,2), LIGHT_BG),
('BACKGROUND', (0,3),(-1,3), WHITE),
('BACKGROUND', (0,4),(-1,4), LIGHT_BG),
]
dt.setStyle(TableStyle(ts_diff))
story.append(dt)
story.append(Spacer(1, 7))
# ── 5. Investigations
story.append(SecHdr("INVESTIGATIONS", UW, PURPLE, "5"))
story.append(Spacer(1, 3))
inv = [
[Paragraph('<b>Investigation</b>', TH), Paragraph('<b>Key Findings / Notes</b>', TH), Paragraph('<b>Timing</b>', TH)],
[Paragraph('NCS – Motor', TB),
Paragraph('Conduction block (>50% CMAP drop) across fibular head = neurapraxia; reduced amplitude throughout = axonal loss', TB),
Paragraph('Any time; repeat at 3m', TB)],
[Paragraph('NCS – Sensory', TB),
Paragraph('Superficial peroneal SNAP absent or reduced', TB),
Paragraph('From day 1', TB)],
[Paragraph('EMG (Needle)', TB),
Paragraph('Fibrillations in TA, EHL, EDL, peronei after 3 weeks. Normal paraspinals = CPN (not L5 root)', TB),
Paragraph('≥3 weeks post-injury', TB)],
[Paragraph('X-Ray (Knee/Fibula)', TB),
Paragraph('Fibular head # (50% CPN), knee dislocation, tibial plateau #; alignment', TB),
Paragraph('Acute', TB)],
[Paragraph('MRI / MR Neurography', TB),
Paragraph('T2 bright nerve = neurapraxia. Nerve gap = neurotmesis. Intraneural ganglion = cystic lesion from STFJ', TB),
Paragraph('Subacute / pre-op', TB)],
[Paragraph('Ultrasound (15–18 MHz)', TB),
Paragraph('Dynamic nerve assessment, ganglion identification, USS-guided aspiration', TB),
Paragraph('Any time', TB)],
[Paragraph('Bloods (HbA1c, ESR, ANCA)', TB),
Paragraph('Rule out diabetes, vasculitis', TB),
Paragraph('Initial workup', TB)],
]
inv_ts = [
('BACKGROUND', (0,0),(-1,0), PURPLE),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 6),
('VALIGN', (0,0),(-1,-1), 'TOP'),
]
for i in range(1, 8):
inv_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else LIGHT_BG))
it = Table(inv, colWidths=[UW*0.24, UW*0.58, UW*0.18])
it.setStyle(TableStyle(inv_ts))
story.append(it)
story.append(Spacer(1, 7))
# ═══════════════════════════════════════ PAGE 2 ═══════════
story.append(PageBreak())
story.append(MiniHeader(UW, 36))
story.append(Spacer(1, 7))
# ── 6. Flowchart
story.append(SecHdr("MANAGEMENT ALGORITHM", UW, NAVY, "6"))
story.append(Spacer(1, 4))
story.append(Flowchart(UW))
story.append(Spacer(1, 7))
# ── 7 + 8: Non-op | Operative
nonop = [
[Paragraph('<b>Measure</b>', TH), Paragraph('<b>Details</b>', TH)],
[Paragraph('Indication', TB), Paragraph('Grade 1–2 / low-energy / metabolic cause', TB)],
[Paragraph('AFO', TB), Paragraph('Rigid or articulated; carbon fibre preferred; maintains neutral dorsiflexion in swing phase', TB)],
[Paragraph('Physiotherapy', TB), Paragraph('ROM, Achilles stretching (prevent equinus), gait retraining, strengthening', TB)],
[Paragraph('Risk factor removal', TB), Paragraph('Stop leg crossing, treat DM, weight loss, lateral knee pad', TB)],
[Paragraph('FES', TB), Paragraph('WalkAide/Bioness L300 — gait-triggered peroneal stimulation; adjunct to AFO', TB)],
[Paragraph('Observation', TB), Paragraph('Grade 1: 4–12 wks. Grades 2–3: 3–6 months. Serial NCS/EMG to track', TB)],
[Paragraph('<b>Failure threshold</b>', TBB), Paragraph('<b>No clinical/EMG recovery at 3 months (axonal loss) → refer for surgery</b>', TBB)],
]
not_ts = [
('BACKGROUND', (0,0),(-1,0), GREEN),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('VALIGN', (0,0),(-1,-1), 'TOP'),
('BACKGROUND', (0,7),(-1,7), BOX_ORANGE),
]
for i in range(1, 7):
not_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else BOX_GREEN))
nt = Table(nonop, colWidths=[C*0.42, C*0.58])
nt.setStyle(TableStyle(not_ts))
op = [
[Paragraph('<b>Procedure</b>', TH), Paragraph('<b>Indication / Outcome</b>', TH)],
[Paragraph('Timing — High energy', TB), Paragraph('Explore + primary repair within <b>3 WEEKS</b> of laceration / fracture-dislocation', TB)],
[Paragraph('Timing — Low energy', TB), Paragraph('Surgery if no recovery at 3–6 months', TB)],
[Paragraph('Neurolysis', TB), Paragraph('Grade 3–4, entrapment, intraneural ganglion. External ± internal neurolysis + articular branch excision + STFJ capsule repair', TB)],
[Paragraph('Primary Repair', TB), Paragraph('Sharp laceration, small gap, tension-free. <b>60–70% achieve useful dorsiflexion</b> under ideal conditions', TB)],
[Paragraph('Nerve Graft', TB), Paragraph('Gap >2–3 cm. Sural nerve (gold standard) or allograft (5–70 mm). Mean graft 7 cm. <b>49% achieve >M3</b> (n=465, meta-analysis)', TB)],
[Paragraph('Nerve Transfer', TB), Paragraph('Proximal injury / long gap. Tibial branch → distal CPN. <b>62.9% >M3</b> (meta-analysis)', TB)],
[Paragraph('<b>Tendon Transfer (SALVAGE)</b>', TBB), Paragraph('<b>Late (>12–18m) / failed repair. Tibialis posterior transfer (Bridle). Most durable long-term result</b>', TBB)],
]
op_ts = [
('BACKGROUND', (0,0),(-1,0), RED),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('VALIGN', (0,0),(-1,-1), 'TOP'),
('BACKGROUND', (0,7),(-1,7), BOX_ORANGE),
]
for i in range(1, 7):
op_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else BOX_RED))
ot = Table(op, colWidths=[C*0.40, C*0.60])
ot.setStyle(TableStyle(op_ts))
story.append(two_col(SecHdr("A. NON-OPERATIVE MANAGEMENT", C, GREEN, "7"), SecHdr("B. OPERATIVE MANAGEMENT", C, RED, "8")))
story.append(Spacer(1, 3))
story.append(two_col(nt, ot))
story.append(Spacer(1, 7))
# ── 9. Critical Timelines
story.append(SecHdr("CRITICAL TIMELINES & SURGICAL OUTCOMES", UW, ORANGE, "9"))
story.append(Spacer(1, 3))
tl = [
[Paragraph('<b>Timeline</b>', TH), Paragraph('<b>Decision Point</b>', TH), Paragraph('<b>Action</b>', TH)],
[Paragraph('Day 0–21', TB), Paragraph('High-energy (laceration, fracture-dislocation)', TB), Paragraph('Explore ± primary repair', TB)],
[Paragraph('3 weeks', TB), Paragraph('EMG now reliable (fibrillations detectable)', TB), Paragraph('Baseline EMG / NCS', TB)],
[Paragraph('3 months', TB), Paragraph('Neurapraxia should show recovery by now', TB), Paragraph('If no recovery → MRI ± surgical referral', TB)],
[Paragraph('3–6 months', TB), Paragraph('Low-energy failure of conservative Rx', TB), Paragraph('Neurolysis / nerve repair', TB)],
[Paragraph('<b><12 months</b>', TBB), Paragraph('<b>CRITICAL WINDOW for nerve repair</b>', TBB), Paragraph('<b>Surgery beyond 12m = suboptimal result</b>', TBB)],
[Paragraph('>12–18 months', TB), Paragraph('Irreversible denervation / failed nerve repair', TB), Paragraph('Tibialis posterior tendon transfer (Bridle)', TB)],
]
tl_ts = [
('BACKGROUND', (0,0),(-1,0), ORANGE),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8.5),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 6),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('BACKGROUND', (0,5),(-1,5), BOX_RED),
('FONTNAME', (0,5),(-1,5), 'Helvetica-Bold'),
('BACKGROUND', (0,6),(-1,6), BOX_ORANGE),
]
for i in [1,2,3,4]:
tl_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else LIGHT_BG))
tlt = Table(tl, colWidths=[UW*0.17, UW*0.48, UW*0.35])
tlt.setStyle(TableStyle(tl_ts))
story.append(tlt)
story.append(Spacer(1, 7))
# ── 10 + 11: Outcomes | Recent Advances
outcomes = [
[Paragraph('<b>Procedure</b>', TH), Paragraph('<b>Outcome</b>', TH)],
[Paragraph('Neurolysis (early, <6m)', TB), Paragraph('60–70% useful recovery', TB)],
[Paragraph('Primary repair (ideal conditions)', TB), Paragraph('60–70% anti-gravity dorsiflexion', TB)],
[Paragraph('Nerve grafting (meta-analysis, n=465)', TB), Paragraph('<b>49% achieve >M3</b>', TBB)],
[Paragraph('Nerve transfer: tibial → CPN', TB), Paragraph('<b>62.9% achieve >M3</b>', TBB)],
[Paragraph('Repair >12 months post-injury', TB), Paragraph('Poor — do NOT attempt nerve repair', TB)],
[Paragraph('Graft length >12 cm', TB), Paragraph('Suboptimal outcome', TB)],
[Paragraph('Tendon transfer (late salvage)', TB), Paragraph('Most reliable, durable long-term', TB)],
]
out_ts = [
('BACKGROUND', (0,0),(-1,0), TEAL),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('VALIGN', (0,0),(-1,-1), 'MIDDLE'),
('BACKGROUND', (0,5),(-1,6), BOX_RED),
]
for i in [1,2,3,4,7]:
out_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else BOX_BLUE))
outt = Table(outcomes, colWidths=[C*0.60, C*0.40])
outt.setStyle(TableStyle(out_ts))
adv = [
[Paragraph('<b>Advance</b>', TH), Paragraph('<b>Clinical Relevance</b>', TH)],
[Paragraph('MR Neurography (3T DTI)', TB), Paragraph('Fascicular detail + continuity; guides surgical planning', TB)],
[Paragraph('Nerve Transfers', TB), Paragraph('Tibial branch → distal CPN; avoids long regeneration distance', TB)],
[Paragraph('Implantable FES (ActiGait)', TB), Paragraph('Closed-loop gait-triggered stimulation; superior to fixed AFO', TB)],
[Paragraph('Nerve Allografts (Avance)', TB), Paragraph('5–70 mm gaps; no sural harvest morbidity', TB)],
[Paragraph('Intraneural Ganglion Rx', TB), Paragraph('Articular branch excision + STFJ repair = low recurrence', TB)],
[Paragraph('Intraop NAP Recording', TB), Paragraph('Guides resection vs. neurolysis in neuroma-in-continuity', TB)],
[Paragraph('USS-Guided Aspiration', TB), Paragraph('Ganglion cyst aspiration under real-time high-freq USS', TB)],
]
adv_ts = [
('BACKGROUND', (0,0),(-1,0), PURPLE),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('FONTNAME', (0,0),(-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0),(-1,-1), 8),
('GRID', (0,0),(-1,-1), 0.4, HexColor("#BDC3C7")),
('TOPPADDING', (0,0),(-1,-1), 4),
('BOTTOMPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING', (0,0),(-1,-1), 5),
('VALIGN', (0,0),(-1,-1), 'TOP'),
]
for i in range(1, 8):
adv_ts.append(('BACKGROUND', (0,i),(-1,i), WHITE if i%2==1 else BOX_PURPLE))
advt = Table(adv, colWidths=[C*0.45, C*0.55])
advt.setStyle(TableStyle(adv_ts))
story.append(two_col(SecHdr("OUTCOMES (Evidence-Based)", C, TEAL, "10"), SecHdr("RECENT ADVANCES", C, PURPLE, "11")))
story.append(Spacer(1, 3))
story.append(two_col(outt, advt))
story.append(Spacer(1, 7))
# ── Clinical Pearls
pearls = [
[Paragraph('<b>🔑 ORTHOPAEDIC CLINICAL PEARLS</b>',
ParagraphStyle('ph', fontName='Helvetica-Bold', fontSize=9, textColor=NAVY))],
[Paragraph(
'• <b>Preserved inversion</b> differentiates CPN palsy from L5 radiculopathy — inversion (tibialis posterior + tibialis ant.) is L4/L5 via tibial nerve; if inversion weak → suspect L5 root<br/>'
'• <b>Wait ≥3 weeks</b> before EMG — fibrillation potentials take 3 weeks to appear after axonal injury; early normal EMG does NOT exclude axonotmesis<br/>'
'• <b>EMG paraspinals:</b> Normal → CPN palsy | Abnormal → L5 radiculopathy. This single finding is the most reliable differentiator<br/>'
'• <b>Intraneural ganglion:</b> Suspect when there is a palpable mass at fibular head + fluctuating weakness with weight-bearing + no weight loss history. MRI shows cystic lesion tracking from STFJ along articular branch<br/>'
'• <b>TKA foot drop:</b> Most common iatrogenic CPN injury; explore if no recovery; common in valgus-correcting osteotomy and lithotomy positioning<br/>'
'• <b>Critical 12-month rule:</b> Nerve repair/graft >12 months post-injury OR graft >12 cm = predictably suboptimal — proceed to tendon transfer instead<br/>'
'• <b>Tibialis anterior tendon rupture</b> mimics CPN palsy — look for palpable gap at ankle, EMG is normal (no denervation), MRI shows tendon discontinuity',
ParagraphStyle('pb', fontName='Helvetica', fontSize=8.5, textColor=HexColor("#2C3E50"),
leading=13, leftIndent=5, spaceAfter=2)
)],
]
pearl_t = Table(pearls, colWidths=[UW])
pearl_t.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,-1), YELLOW_BG),
('BOX', (0,0),(-1,-1), 1.5, ORANGE),
('LINEBELOW', (0,0),(-1,0), 0.8, ORANGE),
('TOPPADDING', (0,0),(-1,-1), 6),
('BOTTOMPADDING', (0,0),(-1,-1), 7),
('LEFTPADDING', (0,0),(-1,-1), 10),
('VALIGN', (0,0),(-1,-1), 'TOP'),
]))
story.append(pearl_t)
story.append(Spacer(1, 6))
# ── Footer
foot = [[Paragraph(
'<font size="7" color="#7F8C8D">Sources: Campbell\'s Operative Orthopaedics 15e (2026) • Adams & Victor\'s Principles of Neurology 12e • Harrison\'s Internal Medicine 22e • '
'Localization in Clinical Neurology 8e • Sabiston Textbook of Surgery 8e • Imaging Anatomy Vol.3 | '
'Generated by Orris Clinical AI — June 2026 | For educational use only. Verify all clinical decisions against current guidelines.</font>',
BODY)]]
ft = Table(foot, colWidths=[UW])
ft.setStyle(TableStyle([
('LINEABOVE', (0,0),(-1,0), 0.5, GREY),
('TOPPADDING', (0,0),(-1,-1), 4),
('LEFTPADDING',(0,0),(-1,-1), 0),
]))
story.append(ft)
doc.build(story)
print(f"✓ PDF saved: {path}")
if __name__ == "__main__":
build_pdf("/home/daytona/workspace/cpn-nerve/CPN_Injury_Quick_Reference.pdf")