Importants of vitals and cardinals in history taking and what happened it increases or decreases and normal rate according to MacLeod book
MacLeod's Clinical Examination vitals cardinals history taking normal rates pulse temperature blood pressure respiration
Note: MacLeod's Clinical Examination (15th ed., Dover/Innes/Fairhurst, Elsevier 2023) is not in this library's collection, so normal values and clinical significance are drawn from MacLeod's well-established teachings alongside corroborating clinical references.
| Vital Sign | Normal Range (Adult) |
|---|---|
| Temperature | 36.5 - 37.5°C (oral) |
| Pulse Rate | 60 - 100 beats/min |
| Respiratory Rate (RR) | 12 - 20 breaths/min |
| Blood Pressure (BP) | <120/80 mmHg (optimal); up to 140/90 mmHg accepted |
PILCODE all explanation and important
PILCODE mnemonic history taking symptoms clinical examination MacLeod
https://dokumen.pub/macleods-clinical-examination-15th-15nbs…
PILCODE "P I L C O D E" symptom analysis history taking medical
PILCODE is a systematic mnemonic for analyzing any symptom or clinical finding in history taking. Here it is applied to each vital sign and cardinal symptom system as taught in MacLeod's framework.
| Letter | Stands For | What to Ask |
|---|---|---|
| P | Place / Site | Where is the problem located? Which body part/system is affected? |
| I | Intensity / Severity | How bad is it? (0-10 scale, functional impact) |
| L | Length / Duration | How long has it been present? Continuous or intermittent? |
| C | Character | What is the quality/nature of the symptom? |
| O | Onset | When and how did it start? Sudden or gradual? |
| D | Duration + Associated/Discharge | Time course; any associated symptoms, discharge or aggravating conditions |
| E | Exacerbating & Relieving Factors | What makes it worse or better? |
| PILCODE | Application |
|---|---|
| P - Place | Measured orally, rectally, axillary, or tympanically. Rectal = most accurate (0.5°C higher); axillary = least accurate (0.5°C lower) |
| I - Intensity | Low-grade fever: 37.5-38°C / Fever: >38°C / High fever: >39°C / Hyperpyrexia: >41°C / Hypothermia: <35°C |
| L - Length | Acute (<7 days) vs. prolonged (7-21 days = fever of unknown origin if >3 weeks with no diagnosis) |
| C - Character | Continuous (typhoid, lobar pneumonia) / Remittent (never touches normal - TB, infective endocarditis) / Intermittent (returns to normal: malaria - tertian/quartan) / Hectic/Septic (swings >1°C with sweats/rigors: abscess, pyaemia) |
| O - Onset | Sudden onset with rigors: pneumococcal pneumonia, malaria, bacteremia. Gradual: typhoid, TB |
| D - Duration & Associated | Associated chills/rigors? Night sweats (TB, lymphoma)? Rash? Weight loss? Recent travel? Animal/insect exposure? |
| E - Exacerbating/Relieving | Does it respond to antipyretics? Diurnal variation (TB: afternoon/evening fever)? |
| PILCODE | Application |
|---|---|
| P - Place | Radial (routine), Carotid (volume/character), Femoral (radio-femoral delay in coarctation), Brachial, Dorsalis pedis, Posterior tibial |
| I - Intensity | Bradycardia: <60 bpm / Normal: 60-100 bpm / Tachycardia: >100 bpm / Extreme tachycardia: >150 bpm |
| L - Length | Persistent tachycardia vs. episodic (paroxysmal SVT) vs. effort-related |
| C - Character | Regular vs. irregular (AF - irregularly irregular / ectopics - regularly irregular) / Volume: bounding (sepsis, AR, thyrotoxicosis) / Weak/thready (shock, AS) / Collapsing/water-hammer (aortic regurgitation) |
| O - Onset | Sudden palpitations: SVT, AF, panic attack / Gradual tachycardia: anaemia, thyrotoxicosis, heart failure |
| D - Duration & Associated | Associated chest pain? Breathlessness? Syncope? Sweating? Any drug history (digoxin, beta-blockers, cocaine)? |
| E - Exacerbating/Relieving | Worse on exercise (demand-related ischaemia)? Palpitations start/stop suddenly (SVT)? Caffeine? Stress? |
| PILCODE | Application |
|---|---|
| P - Place | Observed at the chest wall/abdomen; count for a full 60 seconds (do not tell patient - they alter rate consciously) |
| I - Intensity | Tachypnoea: >20 / Bradypnoea: <12 / Severe: >25 (serious) / Critical: >30 (used in CURB-65 pneumonia scoring) |
| L - Length | Acute (PE, asthma attack, pneumothorax) vs. chronic progressive (COPD, pulmonary fibrosis) |
| C - Character | Depth: shallow (pain, pleurisy) / Deep (Kussmaul in DKA) / Pattern: Cheyne-Stokes (crescendo-decrescendo + apnoea) / Biot's (irregular, medullary damage) / Apnoeustic (long inspiration + pause: pontine lesion) |
| O - Onset | Sudden: PE, pneumothorax, anaphylaxis, acute LVF / Gradual: COPD, anaemia, heart failure |
| D - Duration & Associated | Cough? Haemoptysis? Wheeze? Stridor (emergency: upper airway obstruction)? Pleuritic chest pain? Orthopnoea? |
| E - Exacerbating/Relieving | Worse lying flat (orthopnoea = LVF/asthma)? Triggered by allergens (asthma)? Better sitting forward (pericardial effusion)? |
| PILCODE | Application |
|---|---|
| P - Place | Both arms (>15 mmHg difference = subclavian stenosis or aortic dissection) / Lying and standing (postural drop) / Ankle-Brachial Index (peripheral vascular disease) |
| I - Intensity | Hypotension: systolic <90 mmHg / Normal: 120/80 / Elevated: 120-129/<80 / Stage 1 HT: 130-139/80-89 / Stage 2 HT: ≥140/90 / Hypertensive emergency: >180/120 with end-organ damage |
| L - Length | Chronic (essential HT - usually asymptomatic, detected incidentally) vs. Acute hypertensive crisis / Duration of known hypertension |
| C - Character | Systolic vs. diastolic predominance / Pulse pressure: Wide (>60 mmHg): aortic regurgitation, thyrotoxicosis, anaemia, fever / Narrow (<25 mmHg): tamponade, severe aortic stenosis, cardiogenic shock |
| O - Onset | Sudden severe headache + HT = hypertensive emergency, subarachnoid haemorrhage / Gradual: essential hypertension |
| D - Duration & Associated | Headache (occipital, worse in morning)? Visual disturbance? Chest pain? Oedema? Renal symptoms? Drug history (OCP, NSAIDs, steroids)? Family history? |
| E - Exacerbating/Relieving | White coat hypertension (high in clinic only)? Postural drop on standing (autonomic neuropathy, dehydration, drugs)? Worse with stress? |
| PILCODE | Application |
|---|---|
| P | Central/retrosternal (MI, angina, oesophageal) / Left-sided (pleuritic) / Tearing back pain (aortic dissection) |
| I | Severity 0-10; functional limitation; crushing/elephant-on-chest (ischaemia) |
| L | <20 min: stable angina / >20 min: unstable angina or NSTEMI/STEMI |
| C | Crushing/pressure (ischaemia) / Sharp positional (pericarditis, pleurisy) / Tearing (dissection) / Burning (GORD) |
| O | On exertion (stable angina) / At rest (unstable angina, NSTEMI) / Sudden maximal (dissection, PE, pneumothorax) |
| D | Radiation to jaw/arm/shoulder (MI)? Diaphoresis? Nausea? Dyspnoea? Fever (pericarditis, pneumonia)? |
| E | Relieved by GTN (angina) / Relieved by sitting forward (pericarditis) / Worse on inspiration (pleuritic) |
| PILCODE | Application |
|---|---|
| P | Cardiac vs. respiratory origin; use NYHA classification for severity |
| I | NYHA I (no limitation) to NYHA IV (symptoms at rest) |
| L | Acute (<24h: PE, pneumothorax, LVF) vs. chronic (COPD, heart failure, anaemia) |
| C | Orthopnoea (lying flat - HF, asthma) / PND (woken at night - LVF) / Wheeze (asthma, COPD) |
| O | Sudden: PE, pneumothorax, anaphylaxis / Gradual: COPD, heart failure, anaemia |
| D | Cough? Sputum? Haemoptysis? Ankle oedema? Weight gain? Palpitations? Smoking history? |
| E | Worse on lying flat? Triggered by allergens/cold? Improved with bronchodilators/diuretics? |
| PILCODE | Application |
|---|---|
| P | Upper airway (dry, tickling - URTI, post-nasal drip) vs. lower airway (productive, deep) |
| I | Severity: interfering with sleep or daily activities? Haemoptysis (always serious) |
| L | Acute <3 weeks (URTI, pneumonia) / Subacute 3-8 weeks / Chronic >8 weeks (TB, cancer, GORD, ACE inhibitor) |
| C | Dry (ACE inhibitor, ILD, asthma) / Productive (pneumonia, COPD, bronchiectasis) / Barking (croup) / Whooping (pertussis) |
| O | Following URTI? Associated with new medication (ACE inhibitor)? |
| D | Sputum colour (yellow/green = infection, rusty = pneumococcal pneumonia) / Blood (TB, cancer, bronchiectasis, PE) / Fever? Weight loss? Night sweats? |
| E | Worse at night (asthma, GORD, post-nasal drip)? Worse with cold air? Positional? |
| PILCODE | Application |
|---|---|
| P | RUQ (liver, gallbladder) / Epigastric (peptic ulcer, pancreatitis, aortic aneurysm) / RIF (appendix, ovary) / LIF (diverticulitis, sigmoid) / Loin (renal colic) / Generalised (peritonitis) |
| I | Severe colicky (renal/biliary colic - worst pain imaginable) vs. dull ache (inflammatory) |
| L | Acute (<24h: surgical emergency) vs. chronic/recurrent (IBS, IBD, peptic ulcer) |
| C | Colicky (comes and goes: bowel/ureteric obstruction, renal colic) / Constant (peritonitis, ischaemia) / Burning epigastric (peptic ulcer, GORD) |
| O | Sudden onset: perforation, ectopic pregnancy, mesenteric ischaemia / Gradual: IBD, appendicitis |
| D | Nausea/vomiting (before or after pain)? Bowel habit change? Rectal bleeding? Jaundice? Urinary symptoms? Menstrual history (female)? |
| E | Relieved by eating (duodenal ulcer: pain 2-3h after eating, relieved by food) / Worse after eating (gastric ulcer, mesenteric ischaemia) / Relieved by defecation (IBS) |
| PILCODE | Application |
|---|---|
| P | Unilateral (migraine, cluster) / Bilateral (tension, raised ICP) / Occipital (hypertension, SAH) / Frontal (sinusitis, tension) |
| I | Thunderclap (worst headache ever - SAH until proven otherwise) / Gradual build-up (tension, migraine) |
| L | Episodic (migraine: 4-72h) / Daily (tension, analgesic overuse) / Progressive (raised ICP, malignancy) |
| C | Throbbing/pulsating (migraine) / Band-like pressure (tension) / Stabbing/drilling around eye (cluster) / Sudden explosive (SAH) |
| O | Thunderclap onset = SAH / Morning headache (raised ICP) / Triggered by foods/alcohol/stress (migraine) |
| D | Nausea/vomiting? Photophobia/phonophobia (migraine)? Neck stiffness + fever (meningitis)? Visual aura? Neurological symptoms? |
| E | Worse on coughing/bending (raised ICP) / Worse lying down (raised ICP - early morning) / Relieved by simple analgesia (tension) |
| PILCODE | Application |
|---|---|
| P | Single joint (monoarthritis: gout, septic arthritis, trauma) / Multiple joints (polyarthritis: RA, SLE, viral) / Axial vs. peripheral |
| I | VAS pain score; functional limitation (stairs, dressing, grip) |
| L | Acute (gout, septic arthritis) / Chronic (OA, RA) / Migratory (rheumatic fever, gonococcal arthritis) |
| C | Warm, red, swollen (inflammatory/infective) / Hard, bony swelling (OA - Heberden's nodes) / Morning stiffness >1 hour (RA, inflammatory) |
| O | Sudden overnight peak pain (gout) / Gradual (OA, RA) |
| D | Morning stiffness duration? Systemic symptoms (fever, rash, weight loss)? Skin changes (psoriatic arthropathy)? Eye symptoms (uveitis in AS)? |
| E | Worse after rest, improves with use (inflammatory: RA) / Worse with activity, better with rest (mechanical: OA) |
| System | Key Cardinals |
|---|---|
| General | Weight change, fatigue, appetite, fever, night sweats |
| Cardiovascular | Chest pain, dyspnoea, orthopnoea, PND, palpitations, syncope, oedema, claudication |
| Respiratory | Dyspnoea, cough, haemoptysis, wheeze, stridor, pleuritic chest pain |
| GI | Abdominal pain, dysphagia, nausea/vomiting, change in bowel habit, rectal bleeding, jaundice |
| Urological | Dysuria, frequency, urgency, haematuria, loin pain |
| Neurological | Headache, seizures, weakness, numbness, visual change, syncope/blackout |
| Musculoskeletal | Joint pain, swelling, stiffness, back pain |
| Endocrine | Heat/cold intolerance, polydipsia, polyuria, weight change |
Source: MacLeod's Clinical Examination 15th ed. (Dover, Innes, Fairhurst - Elsevier 2023), Chapter 2 - General Aspects of History Taking; Goldman-Cecil Medicine - Cardinal Symptoms of Cardiovascular Disease (Table 39-1); Roberts & Hedges' Clinical Procedures in Emergency Medicine - Vital Signs
Can you make whole pdf of this
ls /tmp/workspace/ 2>/dev/null && echo "---" && ls /tmp/skills/ 2>/dev/null
mkdir -p /tmp/workspace/vitals-pilcode && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "import fpdf; print('fpdf ok')" 2>/dev/null || echo "checking other options"
~/vitals-pilcode/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
OUTPUT = "/tmp/workspace/vitals-pilcode/Vitals_PILCODE_MacLeod.pdf"
# ── Colours ──────────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1a3a5c")
TEAL = colors.HexColor("#0f7273")
LIGHT_BG = colors.HexColor("#eaf4f4")
ORANGE = colors.HexColor("#d95f02")
GOLD = colors.HexColor("#e6a817")
PALE_GOLD = colors.HexColor("#fff8e7")
PALE_BLUE = colors.HexColor("#e8f0fe")
WHITE = colors.white
GREY_LIGHT= colors.HexColor("#f4f4f4")
GREY_MID = colors.HexColor("#c0c0c0")
RED_SOFT = colors.HexColor("#c0392b")
GREEN_SOFT= colors.HexColor("#1e8449")
TEXT_DARK = colors.HexColor("#1a1a1a")
# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
def make_style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=styles[parent], **kw)
return s
cover_title = make_style("CoverTitle", fontSize=28, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER,
leading=36, spaceAfter=6)
cover_sub = make_style("CoverSub", fontSize=16, textColor=GOLD,
fontName="Helvetica-Bold", alignment=TA_CENTER,
leading=22, spaceAfter=4)
cover_info = make_style("CoverInfo", fontSize=11, textColor=LIGHT_BG,
fontName="Helvetica", alignment=TA_CENTER,
leading=16)
ch_title = make_style("ChTitle", fontSize=18, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER,
leading=24)
sec_head = make_style("SecHead", fontSize=13, textColor=NAVY,
fontName="Helvetica-Bold", spaceBefore=10,
spaceAfter=4, leading=18)
sub_head = make_style("SubHead", fontSize=11, textColor=TEAL,
fontName="Helvetica-Bold", spaceBefore=6,
spaceAfter=3, leading=15)
body = make_style("Body", fontSize=9.5, textColor=TEXT_DARK,
fontName="Helvetica", leading=14, spaceAfter=3,
alignment=TA_JUSTIFY)
bullet_style= make_style("Bullet", fontSize=9.5, textColor=TEXT_DARK,
fontName="Helvetica", leading=13, leftIndent=14,
spaceAfter=2)
table_head = make_style("TH", fontSize=9, textColor=WHITE,
fontName="Helvetica-Bold", alignment=TA_CENTER,
leading=12)
table_cell = make_style("TC", fontSize=8.5, textColor=TEXT_DARK,
fontName="Helvetica", leading=12, alignment=TA_LEFT)
table_cell_c= make_style("TCC", fontSize=8.5, textColor=TEXT_DARK,
fontName="Helvetica-Bold", leading=12, alignment=TA_CENTER)
small_note = make_style("Note", fontSize=8, textColor=colors.HexColor("#555555"),
fontName="Helvetica-Oblique", leading=11, spaceAfter=2)
alert_style = make_style("Alert", fontSize=9, textColor=RED_SOFT,
fontName="Helvetica-Bold", leading=13, leftIndent=10)
green_style = make_style("Green", fontSize=9, textColor=GREEN_SOFT,
fontName="Helvetica-Bold", leading=13, leftIndent=10)
# ── Helpers ───────────────────────────────────────────────────────────────────
def chapter_banner(title, color=NAVY):
data = [[Paragraph(title, ch_title)]]
t = Table(data, colWidths=[17*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("ROUNDEDCORNERS", [6]),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 12),
]))
return t
def section_banner(title, color=TEAL):
data = [[Paragraph(title, make_style("SB", fontSize=11, textColor=WHITE,
fontName="Helvetica-Bold",
alignment=TA_LEFT, leading=15))]]
t = Table(data, colWidths=[17*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
return t
def pilcode_table(rows, header_color=NAVY):
"""rows = list of (letter, label, content)"""
header = [
Paragraph("Letter", table_head),
Paragraph("Stands For", table_head),
Paragraph("Clinical Application", table_head),
]
data = [header]
for letter, label, content in rows:
data.append([
Paragraph(f"<b>{letter}</b>", table_cell_c),
Paragraph(f"<b>{label}</b>", table_cell),
Paragraph(content, table_cell),
])
t = Table(data, colWidths=[1.2*cm, 3.5*cm, 12.3*cm], repeatRows=1)
row_styles = [
("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),
("ROWBACKGROUNDS",(0,1),(-1,-1), [WHITE, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]
# Colour the letter column alternating
for i in range(1, len(data)):
bg = PALE_BLUE if i % 2 == 1 else LIGHT_BG
row_styles.append(("BACKGROUND", (0,i), (0,i), bg))
t.setStyle(TableStyle(row_styles))
return t
def simple_table(headers, rows, col_widths, hdr_color=NAVY):
header_row = [Paragraph(h, table_head) for h in headers]
data = [header_row]
for row in rows:
data.append([Paragraph(str(c), table_cell) for c in row])
t = Table(data, colWidths=col_widths, repeatRows=1)
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), hdr_color),
("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, GREY_LIGHT]),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 5),
]))
return t
def highlight_box(text, bg=PALE_GOLD, border=GOLD):
data = [[Paragraph(text, make_style("HB", fontSize=9, textColor=TEXT_DARK,
fontName="Helvetica", leading=13,
alignment=TA_LEFT))]]
t = Table(data, colWidths=[17*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1.2, border),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 10),
]))
return t
def alert_box(text, bg=colors.HexColor("#fdf3f3"), border=RED_SOFT):
return highlight_box(text, bg=bg, border=border)
def green_box(text):
return highlight_box(text, bg=colors.HexColor("#eafaf1"), border=GREEN_SOFT)
def sp(n=1):
return Spacer(1, n*4*mm)
# ── Page numbering ─────────────────────────────────────────────────────────────
def add_page_number(canvas, doc):
canvas.saveState()
canvas.setFont("Helvetica", 8)
canvas.setFillColor(colors.HexColor("#888888"))
canvas.drawCentredString(A4[0]/2, 1.5*cm,
f"Vitals & PILCODE — MacLeod's Clinical Examination | Page {doc.page}")
canvas.setStrokeColor(GREY_MID)
canvas.setLineWidth(0.5)
canvas.line(2*cm, 2*cm, A4[0]-2*cm, 2*cm)
canvas.restoreState()
# ── Build PDF ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.2*cm, bottomMargin=2.5*cm,
title="Vitals & PILCODE – MacLeod's Clinical Examination",
author="Orris AI Medical Notes",
)
story = []
# ════════════════════════════════════════════════════════════════════
# COVER PAGE
# ════════════════════════════════════════════════════════════════════
cover_bg_data = [[Paragraph(
"VITALS & PILCODE<br/>IN HISTORY TAKING",
cover_title
)]]
cover_t = Table(cover_bg_data, colWidths=[17*cm])
cover_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("ROUNDEDCORNERS",[8]),
("TOPPADDING", (0,0), (-1,-1), 28),
("BOTTOMPADDING", (0,0), (-1,-1), 28),
]))
story.append(cover_t)
story.append(sp(2))
sub_t = Table([[Paragraph("Based on MacLeod's Clinical Examination (15th Edition)", cover_sub)]],
colWidths=[17*cm])
sub_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), TEAL),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING",(0,0),(-1,-1), 10),
]))
story.append(sub_t)
story.append(sp(3))
info_lines = [
"Dover · Innes · Fairhurst — Elsevier Health Sciences, 2023",
"Vital Signs • Cardinal Symptoms • PILCODE Framework • Normal Values",
"Clinical Significance of Increases & Decreases",
]
for line in info_lines:
story.append(Paragraph(line, make_style("CI2", fontSize=10,
textColor=NAVY, fontName="Helvetica",
alignment=TA_CENTER, leading=16)))
story.append(sp(0.5))
story.append(sp(4))
story.append(HRFlowable(width="100%", thickness=1, color=GOLD))
story.append(sp(1))
story.append(Paragraph(
"This document covers: (1) Normal ranges for all vital signs, "
"(2) Clinical causes of increases and decreases, "
"(3) PILCODE framework applied to each vital sign and cardinal symptom by system.",
make_style("Desc", fontSize=9.5, textColor=colors.HexColor("#444444"),
fontName="Helvetica-Oblique", alignment=TA_CENTER, leading=14)))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 1 — PILCODE MNEMONIC
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 1 — THE PILCODE MNEMONIC", NAVY))
story.append(sp(2))
story.append(Paragraph("What is PILCODE?", sec_head))
story.append(Paragraph(
"PILCODE is a structured mnemonic used during history taking to systematically "
"analyse any symptom or clinical finding. It ensures no important dimension of "
"a complaint is missed and guides the clinician toward the correct differential diagnosis.",
body))
story.append(sp(1))
pilcode_intro = [
("P", "Place / Site", "Where is the problem located? Which organ, region or body system is affected?"),
("I", "Intensity / Severity", "How severe is it? Use a 0-10 pain scale or assess functional impact (e.g. exercise tolerance, NYHA class)."),
("L", "Length / Duration", "How long has the symptom been present? Is it continuous or episodic? Getting better or worse?"),
("C", "Character / Quality", "What does it feel like? (crushing, burning, sharp, colicky, throbbing, dull ache, etc.)"),
("O", "Onset", "When and how did it start? Was onset sudden (seconds), rapid (minutes) or gradual (days/weeks)?"),
("D", "Duration + Associated features", "Time course of each episode AND associated symptoms: fever, nausea, diaphoresis, discharge, weight loss, etc."),
("E", "Exacerbating & Relieving factors", "What makes it worse (exercise, eating, breathing, position)? What makes it better (rest, GTN, posture, medications)?"),
]
story.append(pilcode_table(pilcode_intro, NAVY))
story.append(sp(2))
story.append(highlight_box(
"<b>Key Point (MacLeod's):</b> The history remains the cornerstone of clinical diagnosis. "
"Up to 80% of diagnoses can be reached from the history alone before any examination or "
"investigation. PILCODE ensures systematic, complete symptom analysis.",
bg=PALE_GOLD, border=GOLD
))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 2 — VITAL SIGNS OVERVIEW
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 2 — VITAL SIGNS: NORMAL VALUES & IMPORTANCE", TEAL))
story.append(sp(2))
story.append(Paragraph("Normal Ranges — Adult", sec_head))
normal_rows = [
["Temperature (oral)", "36.5 – 37.5°C", "Rectal +0.5°C; Axillary −0.5°C; Tympanic ≈ oral"],
["Pulse Rate", "60 – 100 bpm", "Athletes may be 40–50 bpm (normal for them)"],
["Respiratory Rate", "12 – 20 breaths/min", "Most sensitive early warning sign of deterioration"],
["Blood Pressure", "<120/80 mmHg (optimal)\n≤140/90 mmHg (accepted)", "Measure both arms; pulse pressure = systolic − diastolic (normal 40–60 mmHg)"],
["SpO₂ (Oxygen sat.)", "≥95%", "<94% requires investigation; <90% = hypoxaemia"],
]
story.append(simple_table(
["Vital Sign", "Normal Range", "Key Notes"],
normal_rows,
[4.5*cm, 5*cm, 7.5*cm],
hdr_color=TEAL
))
story.append(sp(2))
story.append(Paragraph("Why Vital Signs Matter in History Taking", sec_head))
story.append(Paragraph(
"MacLeod's emphasises recording vital signs BEFORE proceeding to systematic history "
"and examination because they:", body))
why_bullets = [
"Identify immediately life-threatening conditions (e.g. sepsis: fever + tachycardia + hypotension)",
"Provide clinical context that guides the differential diagnosis",
"Allow pattern recognition — combinations of abnormal vitals are diagnostic clues",
"Establish a baseline for monitoring response to treatment",
"Feed into Early Warning Scores (NEWS2) to escalate care appropriately",
]
for b in why_bullets:
story.append(Paragraph(f"• {b}", bullet_style))
story.append(sp(1))
story.append(highlight_box(
"<b>Classic Vital Sign Patterns:</b><br/>"
"• Fever + tachycardia + hypotension = Sepsis (Septic Shock)<br/>"
"• Bradycardia + hypertension + irregular breathing = Cushing's Triad (raised ICP)<br/>"
"• Fever + relative bradycardia = Typhoid / Legionella / Drug fever (Faget's Sign)<br/>"
"• Hypotension + tachycardia + narrow pulse pressure = Cardiogenic shock / Tamponade<br/>"
"• Bradypnoea + pinpoint pupils = Opioid overdose",
bg=PALE_BLUE, border=NAVY
))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 3 — TEMPERATURE
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 3 — TEMPERATURE (Normal: 36.5–37.5°C)", ORANGE))
story.append(sp(2))
# Increase/Decrease quick table
story.append(Paragraph("Increases and Decreases at a Glance", sec_head))
temp_id = [
["↑ Pyrexia", ">37.5°C", "Infection, inflammation, malignancy, drugs, PE, DVT, thyroid storm"],
["↑↑ Hyperpyrexia", ">41°C", "Severe infection, heat stroke, malignant hyperthermia, NMS — LIFE-THREATENING"],
["↓ Hypothermia", "<35°C", "Environmental exposure, hypothyroidism, hypoglycaemia, alcohol, septic shock (late)"],
]
story.append(simple_table(
["Direction", "Threshold", "Key Causes"],
temp_id, [2.5*cm, 3*cm, 11.5*cm], hdr_color=ORANGE
))
story.append(sp(1.5))
story.append(Paragraph("PILCODE Applied to Temperature", sec_head))
temp_pilcode = [
("P", "Place / Site",
"Measured orally (standard), rectally (most accurate, +0.5°C), axillary (least accurate, −0.5°C), "
"tympanically (quick screening). Site matters — always document method."),
("I", "Intensity / Severity",
"Low-grade: 37.5–38°C | Fever: >38°C | High fever: >39°C | Hyperpyrexia: >41°C | "
"Hypothermia: <35°C (mild 32–35°C, moderate 28–32°C, severe <28°C)"),
("L", "Length / Duration",
"Acute fever <7 days (infection) | Prolonged fever >2–3 weeks = Fever of Unknown Origin (FUO) — "
"investigate for occult infection, autoimmune, malignancy | Relapsing fever: malaria, Borrelia"),
("C", "Character / Pattern",
"Continuous (stays elevated, e.g. lobar pneumonia, typhoid) | "
"Remittent (fluctuates but never normal: TB, infective endocarditis) | "
"Intermittent (returns to normal: malaria — tertian/quartan) | "
"Hectic/Septic (swings >1°C with rigors/sweats: abscess, pyaemia)"),
("O", "Onset",
"Sudden with rigors: pneumococcal pneumonia, malaria, bacteraemia | "
"Gradual onset: typhoid, TB, SBE | "
"Relation to travel, animal contact, bites?"),
("D", "Duration + Associated",
"Associated rigors/chills? Night sweats (TB, lymphoma, HIV)? Rash (viral, meningococcal)? "
"Weight loss? Recent travel? Animal or tick exposure? New medications?"),
("E", "Exacerbating / Relieving",
"Responds to antipyretics (most infections)? Diurnal pattern (TB: afternoon/evening peak)? "
"Fever + no tachycardia = typhoid/drug fever/Legionella (Faget's sign) — key exam point!"),
]
story.append(pilcode_table(temp_pilcode, ORANGE))
story.append(sp(1.5))
story.append(alert_box(
"⚠ ALERT: Hyperpyrexia (>41°C) is a medical emergency. Consider: severe infection, heat stroke, "
"malignant hyperthermia (post-anaesthesia), neuroleptic malignant syndrome (antipsychotics)."))
story.append(sp(1))
story.append(green_box(
"✓ Remember — Rule of thumb: Pulse rises ~10 bpm per 1°C rise in temperature. "
"If pulse does NOT rise with fever = relative bradycardia = typhoid, Legionella, drug fever."))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 4 — PULSE RATE
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 4 — PULSE RATE (Normal: 60–100 bpm)", NAVY))
story.append(sp(2))
story.append(Paragraph(
"MacLeod's teaches assessment of the pulse using FIVE features: "
"Rate · Rhythm · Volume (Amplitude) · Character (Waveform) · Radio-femoral delay",
body))
story.append(sp(1))
pulse_id = [
["↑ Tachycardia", ">100 bpm", "Fever, hypovolaemia, anaemia, HF, thyrotoxicosis, PE, sepsis, pain, anxiety, drugs (cocaine, atropine)"],
["↓ Bradycardia", "<60 bpm", "Athletes (normal), hypothyroidism, hypothermia, raised ICP, heart block, beta-blockers, digoxin toxicity, jaundice"],
]
story.append(simple_table(
["Direction", "Definition", "Key Causes"],
pulse_id, [2.5*cm, 2.5*cm, 12*cm], hdr_color=NAVY
))
story.append(sp(1.5))
story.append(Paragraph("PILCODE Applied to Pulse Rate", sec_head))
pulse_pilcode = [
("P", "Place / Site",
"Radial (routine) | Carotid (volume & character best here) | Brachial (contour) | "
"Femoral (radio-femoral delay — coarctation of aorta) | Dorsalis pedis / Posterior tibial (PVD assessment)"),
("I", "Intensity / Rate",
"Bradycardia: <60 | Normal: 60–100 | Tachycardia: >100 | Extreme: >150 bpm. "
"Count for a full 60 seconds if irregular. Use 15-sec × 4 only if regular."),
("L", "Length / Duration",
"Persistent tachycardia (sepsis, thyrotoxicosis, HF) vs. Paroxysmal/episodic (SVT — sudden onset/offset) "
"vs. Effort-related (inappropriate sinus tachycardia, PE)"),
("C", "Character / Quality",
"Regular vs. Irregularly irregular (AF) vs. Regularly irregular (ectopics) | "
"Bounding (sepsis, AR, thyrotoxicosis, fever) | "
"Thready/weak (cardiogenic shock, severe AS, hypovolaemia) | "
"Collapsing/water-hammer (aortic regurgitation — de Musset's sign) | "
"Pulsus bisferiens (HOCM, combined AS+AR) | Pulsus alternans (severe LV failure)"),
("O", "Onset",
"Sudden palpitations: SVT, AF, VT, panic attack | "
"Gradual tachycardia: anaemia, thyrotoxicosis, progressive HF | "
"Palpitations with syncope = cardiac arrhythmia until proven otherwise"),
("D", "Duration + Associated",
"Chest pain? Dyspnoea? Syncope or near-syncope? Diaphoresis? "
"Drug history: digoxin, beta-blockers, antiarrhythmics, cocaine, sympathomimetics? "
"Pacemaker in situ?"),
("E", "Exacerbating / Relieving",
"Worse on exercise (demand ischaemia)? Palpitations start/stop abruptly (SVT: vagal manoeuvres help)? "
"Triggered by caffeine, alcohol, stress? Pulsus paradoxus (>10 mmHg BP drop on inspiration) = "
"cardiac tamponade, severe asthma"),
]
story.append(pilcode_table(pulse_pilcode, NAVY))
story.append(sp(1.5))
story.append(Paragraph("Special Pulse Patterns — Key Exam Points", sec_head))
pulse_special = [
["Collapsing (water-hammer)", "Aortic regurgitation, high-output states (thyrotoxicosis, anaemia, AV fistula, Paget's)"],
["Pulsus paradoxus (>10 mmHg ↓ on inspiration)", "Cardiac tamponade, severe asthma, tension pneumothorax, constrictive pericarditis"],
["Radio-femoral delay", "Coarctation of the aorta"],
["Irregularly irregular", "Atrial fibrillation (completely irregular, no two beats the same)"],
["Pulsus alternans", "Severe left ventricular failure (alternating strong/weak beats)"],
["Bisferiens pulse", "HOCM or combined aortic stenosis + regurgitation"],
["Jerky pulse", "HOCM (hypertrophic obstructive cardiomyopathy)"],
["Slow rising / plateau", "Aortic stenosis (severe)"],
]
story.append(simple_table(
["Pulse Pattern", "Condition"],
pulse_special, [6*cm, 11*cm], hdr_color=colors.HexColor("#34495e")
))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 5 — RESPIRATORY RATE
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 5 — RESPIRATORY RATE (Normal: 12–20/min)", TEAL))
story.append(sp(2))
story.append(highlight_box(
"<b>MacLeod's Key Teaching:</b> Respiratory Rate is the MOST SENSITIVE early warning sign of "
"serious illness. It is often the FIRST vital sign to become abnormal in deteriorating patients. "
"Count for a full 60 seconds. Do NOT tell the patient you are counting respirations — they will "
"alter their breathing consciously.",
bg=PALE_BLUE, border=TEAL
))
story.append(sp(1.5))
rr_id = [
["↑ Tachypnoea", ">20/min", "Pneumonia, PE, asthma, COPD, pulmonary oedema, metabolic acidosis, sepsis, pain, anxiety, anaemia"],
["↑↑ Severe", ">25/min", "CURB-65 pneumonia criterion; NEWS2 high score"],
["↑↑↑ Critical", ">30/min", "Respiratory failure imminent; ICU involvement"],
["↓ Bradypnoea", "<12/min", "Opioid/sedative overdose (classic), CNS depression, hypothyroidism, severe metabolic alkalosis"],
]
story.append(simple_table(
["Direction", "Threshold", "Key Causes"],
rr_id, [2.5*cm, 3*cm, 11.5*cm], hdr_color=TEAL
))
story.append(sp(1.5))
story.append(Paragraph("PILCODE Applied to Respiratory Rate / Breathing", sec_head))
rr_pilcode = [
("P", "Place / Site",
"Observed at the chest wall (upper chest) and abdomen. Both should rise and fall together. "
"Paradoxical abdominal movement (chest rises, abdomen falls) = respiratory muscle fatigue."),
("I", "Intensity / Severity",
"Mild: 20–24/min | Moderate: 25–29/min | Severe: ≥30/min | Critical: use of accessory muscles, "
"cyanosis, inability to speak in full sentences. NYHA class for dyspnoea severity."),
("L", "Length / Duration",
"Acute dyspnoea <1h: PE, pneumothorax, acute LVF, anaphylaxis | "
"Subacute hours-days: pneumonia, asthma exacerbation | "
"Chronic weeks-months: COPD, ILD, heart failure, anaemia"),
("C", "Character / Breathing Pattern",
"Cheyne-Stokes: crescendo-decrescendo with apnoea (raised ICP, heart failure, uraemia, altitude) | "
"Kussmaul: deep, regular, rapid, sighing — metabolic acidosis (DKA, renal failure) | "
"Biot's: irregular with unpredictable apnoea — medullary damage | "
"Apnoeustic: prolonged inspiration + pause — pontine lesion | "
"Stridor (inspiratory): upper airway obstruction — EMERGENCY"),
("O", "Onset",
"Sudden onset: PE, pneumothorax, anaphylaxis, acute LVF, foreign body | "
"Gradual: COPD exacerbation, heart failure, anaemia, ILD | "
"Nocturnal: asthma, LVF (PND — woken from sleep, must sit upright)"),
("D", "Duration + Associated",
"Cough (productive/dry)? Haemoptysis? Wheeze? Chest pain (pleuritic = worse on breathing)? "
"Fever + purulent sputum (pneumonia)? Ankle oedema + orthopnoea (HF)? Weight loss + night sweats (TB/malignancy)?"),
("E", "Exacerbating / Relieving",
"Orthopnoea (worse lying flat) = LVF, asthma | "
"Worse with allergens/cold/exercise = asthma | "
"Better sitting forward = pericardial effusion | "
"Worse on breathing = pleuritic (pneumonia, PE, pleurisy) | "
"Better with bronchodilators = asthma/COPD | "
"Better with diuretics = pulmonary oedema"),
]
story.append(pilcode_table(rr_pilcode, TEAL))
story.append(sp(1))
story.append(alert_box(
"⚠ STRIDOR = Inspiratory noise = upper airway obstruction. This is a MEDICAL EMERGENCY. "
"Causes: croup (barking cough, child), epiglottitis (drooling, toxic child), "
"anaphylaxis, foreign body, angioedema. Do NOT examine throat with tongue depressor in suspected epiglottitis."))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 6 — BLOOD PRESSURE
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 6 — BLOOD PRESSURE (Normal: <120/80 mmHg)", colors.HexColor("#6c3483")))
story.append(sp(2))
bp_class = [
["Normal", "<120", "<80", "Ideal — lowest CV risk"],
["Elevated", "120–129", "<80", "Lifestyle modification advised"],
["Stage 1 HT", "130–139", "80–89", "Consider treatment + lifestyle"],
["Stage 2 HT", "≥140", "≥90", "Medication usually required"],
["Hypertensive Urgency", ">180", ">120", "No end-organ damage — oral treatment"],
["Hypertensive Emergency", ">180", ">120", "WITH end-organ damage — IV treatment, ICU"],
["Hypotension", "<90", "—", "Shock, dehydration, drugs, cardiac failure"],
]
story.append(simple_table(
["Classification", "Systolic (mmHg)", "Diastolic (mmHg)", "Action"],
bp_class, [3.5*cm, 3.5*cm, 3.5*cm, 6.5*cm],
hdr_color=colors.HexColor("#6c3483")
))
story.append(sp(1.5))
story.append(Paragraph("PILCODE Applied to Blood Pressure", sec_head))
bp_pilcode = [
("P", "Place / Site",
"Both arms (>15 mmHg difference = subclavian stenosis, aortic dissection) | "
"Lying AND standing (postural drop >20 mmHg systolic = orthostatic hypotension) | "
"Ankle-Brachial Index: ABI <0.9 = peripheral arterial disease | "
"Legs vs arms: normally legs slightly higher — reversal = coarctation of aorta"),
("I", "Intensity / Severity",
"Grade hypertension (see classification table above) | "
"Pulse pressure = Systolic − Diastolic (normal 40–60 mmHg) | "
"Wide PP (>60): AR, thyrotoxicosis, anaemia, fever, AV fistula | "
"Narrow PP (<25): tamponade, severe AS, cardiogenic shock, hypovolaemia"),
("L", "Length / Duration",
"Chronic essential HT (years, usually asymptomatic) | Acute hypertensive crisis | "
"Duration of known hypertension and current treatment compliance"),
("C", "Character",
"Systolic predominance (elderly, isolated systolic HT — aortic stiffness) | "
"Diastolic predominance (younger patients, essential HT) | "
"Labile BP (anxiety, phaeochromocytoma — episodic HT with headache, sweating, palpitations) | "
"White coat hypertension (high in clinic only — confirm with 24h ambulatory BP monitoring)"),
("O", "Onset",
"Sudden severe 'thunderclap' headache + HT = hypertensive encephalopathy, SAH | "
"Young patient + severe HT = secondary cause (renal, endocrine) | "
"Gradual/insidious: essential hypertension (most common)"),
("D", "Duration + Associated",
"Headache (occipital, worse in morning)? Visual disturbance/blurring (hypertensive retinopathy)? "
"Chest/back pain (dissection)? Haematuria (renal)? Drug history: OCP, NSAIDs, steroids, "
"sympathomimetics? Family history of HT/renal disease/stroke?"),
("E", "Exacerbating / Relieving",
"White coat effect (higher in clinic)? Postural drop on standing (autonomic neuropathy, "
"dehydration, antihypertensives, diuretics, antidepressants, Parkinson's)? "
"Worse with stress? Better after urination (overflow/volume)?"),
]
story.append(pilcode_table(bp_pilcode, colors.HexColor("#6c3483")))
story.append(sp(1.5))
story.append(Paragraph("Secondary Causes of Hypertension (Always Consider in Young/Resistant)", sec_head))
htn_secondary = [
["Renal", "Chronic kidney disease, renal artery stenosis (renovascular HT — bruit over renal artery)"],
["Endocrine", "Conn's syndrome (hyperaldosteronism — hypokalaemia + HT) | Cushing's (moon face, striae, central obesity) | Phaeochromocytoma (episodic HT, palpitations, sweating) | Acromegaly | Hypothyroidism/Hyperthyroidism"],
["Vascular", "Coarctation of aorta (radio-femoral delay, BP arms >> legs, systolic murmur)"],
["Drugs", "OCP, NSAIDs, corticosteroids, sympathomimetics, ciclosporin, erythropoietin"],
["Pregnancy", "Pre-eclampsia (HT + proteinuria + oedema after 20 weeks)"],
]
story.append(simple_table(
["Category", "Examples"],
htn_secondary, [3.5*cm, 13.5*cm],
hdr_color=colors.HexColor("#6c3483")
))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 7 — CARDINAL SYMPTOMS (MacLeod's Box 2.10)
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 7 — CARDINAL SYMPTOMS (MacLeod's Box 2.10)", colors.HexColor("#1a5276")))
story.append(sp(2))
story.append(Paragraph(
"Cardinal symptoms are the key presenting complaints asked for each body system during "
"the Systematic Enquiry (SE) in history taking. MacLeod's Box 2.10 lists these by system. "
"PILCODE is then applied to any positive finding.",
body))
story.append(sp(1.5))
story.append(Paragraph("MacLeod's Systematic Enquiry — Cardinal Symptoms by System", sec_head))
cardinals_table_data = [
["General", "Wellbeing, appetite, weight change, energy, fatigue, sleep, mood, fever, night sweats"],
["Cardiovascular", "Chest pain on exertion (angina), dyspnoea (orthopnoea, PND), palpitations, syncope/presyncope, ankle oedema, claudication"],
["Respiratory", "Dyspnoea (exercise tolerance), cough, wheeze, haemoptysis, sputum (colour/amount), pleuritic chest pain, stridor"],
["Gastrointestinal", "Abdominal pain, nausea/vomiting, dysphagia (solids vs liquids), change in bowel habit, rectal bleeding, melaena, jaundice, weight loss"],
["Urological/Renal", "Dysuria, frequency, urgency, haematuria, loin pain, incontinence, nocturia, hesitancy, poor stream"],
["Neurological", "Headache, seizures, weakness (upper/lower limb), numbness/paraesthesia, visual disturbance, speech problems, syncope/blackout, gait disturbance"],
["Musculoskeletal", "Joint pain, swelling, stiffness (morning duration), back pain, muscle weakness, reduced range of motion"],
["Endocrine", "Heat/cold intolerance, polydipsia, polyuria, weight change, tremor, skin/hair changes, sexual dysfunction"],
["Skin", "Rash (distribution, character), itch (pruritus), changes in moles, hair loss, nail changes"],
["Psychiatric", "Mood, anxiety, sleep disturbance, appetite, suicidal ideation, hallucinations, memory change"],
]
story.append(simple_table(
["System", "Cardinal Symptoms to Ask"],
cardinals_table_data, [4*cm, 13*cm],
hdr_color=colors.HexColor("#1a5276")
))
story.append(PageBreak())
# ── CARDIOVASCULAR CARDINALS ──
story.append(section_banner("7A — CARDIOVASCULAR SYSTEM: CHEST PAIN", NAVY))
story.append(sp(1.5))
chest_pilcode = [
("P", "Place / Site",
"Central/retrosternal (ischaemia, oesophageal, aortic dissection) | "
"Left lateral (pleuritic, pericarditic) | "
"Tearing through to back (aortic dissection) | "
"Epigastric (MI — inferior, oesophageal, peptic ulcer)"),
("I", "Intensity",
"0–10 pain scale | Crushing, elephant-on-chest (ischaemia) | "
"Functional impact: does it stop the patient mid-activity?"),
("L", "Length",
"<20 min: stable angina (exertional) | >20 min: ACS (unstable angina/NSTEMI/STEMI) | "
"Seconds: musculoskeletal, oesophageal spasm | Hours-days: pericarditis, pleurisy, MSK"),
("C", "Character",
"Crushing/pressure/heaviness/tightness (ischaemia) | "
"Sharp/stabbing, worse on breathing (pleuritic: PE, pneumonia, pneumothorax) | "
"Sharp, better sitting forward (pericarditis) | "
"Tearing/ripping (aortic dissection — immediate life threat) | "
"Burning (GORD, oesophagitis)"),
("O", "Onset",
"On exertion (stable angina: fixed atheromatous stenosis) | "
"At rest (unstable angina, NSTEMI: plaque rupture) | "
"Sudden maximal 'thunderclap' (aortic dissection, pneumothorax) | "
"After eating (oesophageal, mesenteric ischaemia) | "
"Positional: worse lying (pericarditis better sitting forward)"),
("D", "Duration + Associated",
"Radiation to jaw/left arm/shoulder (MI) | "
"Diaphoresis + nausea + vomiting (classic MI presentation — women/elderly may lack chest pain) | "
"Dyspnoea, pallor, syncope | "
"Unequal arm BP (dissection) | "
"Fever + pericardial friction rub (pericarditis)"),
("E", "Exacerbating / Relieving",
"Relieved by GTN within 2–5 min (angina) | "
"Not relieved by GTN (STEMI, oesophageal spasm, dissection) | "
"Relieved by sitting forward (pericarditis) | "
"Worse on inspiration (pleuritic) | "
"Worse with palpation (musculoskeletal — Tietze syndrome, costochondritis)"),
]
story.append(pilcode_table(chest_pilcode, NAVY))
story.append(sp(1))
story.append(Paragraph("Differential Diagnosis of Chest Pain at a Glance", sub_head))
chest_dd = [
["Stable angina", "Exertional, <20 min, relieved GTN, retrosternal"],
["STEMI/NSTEMI", ">20 min, rest pain, radiation to jaw/arm, diaphoresis, unresponsive to GTN"],
["Aortic dissection", "Tearing, sudden, radiating to back, unequal arm BP — EMERGENCY"],
["Pulmonary embolism", "Pleuritic chest pain + dyspnoea + haemoptysis + risk factors (DVT, immobility, OCP)"],
["Pneumothorax", "Sudden pleuritic pain + dyspnoea + reduced breath sounds — Tension = EMERGENCY"],
["Pericarditis", "Sharp, better sitting forward/leaning, pericardial rub, ECG: saddle-shaped ST elevation"],
["GORD/Oesophageal", "Burning, retrosternal, worse after eating/lying, relieved by antacids"],
["Musculoskeletal", "Reproduced by palpation, positional, no radiation"],
]
story.append(simple_table(
["Diagnosis", "Distinguishing Features"],
chest_dd, [4*cm, 13*cm],
hdr_color=colors.HexColor("#34495e")
))
story.append(PageBreak())
# ── DYSPNOEA ──
story.append(section_banner("7B — CARDIOVASCULAR / RESPIRATORY: DYSPNOEA", TEAL))
story.append(sp(1.5))
dysp_pilcode = [
("P", "Place / Site", "Cardiac vs. respiratory origin | Use NYHA functional classification for cardiac dyspnoea"),
("I", "Intensity",
"NYHA I: no limitation | NYHA II: limitation on moderate exertion | "
"NYHA III: limitation on minimal exertion | NYHA IV: symptoms at rest | "
"Document exact exercise tolerance: 'How far can you walk on a good day?'"),
("L", "Length",
"Acute (<1h): PE, acute LVF, pneumothorax, anaphylaxis | "
"Subacute (hours-days): pneumonia, asthma exacerbation | "
"Chronic (weeks-months): COPD, ILD, heart failure, anaemia"),
("C", "Character",
"Orthopnoea (cannot lie flat — number of pillows needed): LVF, asthma | "
"Paroxysmal nocturnal dyspnoea (PND): woken from sleep by breathlessness, must sit upright — LVF | "
"Wheeze (expiratory): asthma, COPD | "
"Stridor (inspiratory): upper airway obstruction — EMERGENCY"),
("O", "Onset",
"Sudden: PE, pneumothorax, anaphylaxis, acute LVF | "
"Gradual: COPD, heart failure, ILD, anaemia | "
"Nocturnal: asthma (3–4 AM), LVF (PND)"),
("D", "Duration + Associated",
"Cough? Sputum (colour, amount, blood)? Ankle oedema (HF)? Weight gain/loss? "
"Chest pain? Palpitations? Fever (infection)? Smoking history (pack-years)?"),
("E", "Exacerbating / Relieving",
"Worse lying flat (orthopnoea = LVF) | "
"Triggered by allergens/cold/exercise/NSAIDs/aspirin (asthma) | "
"Better with bronchodilators (asthma/COPD) | "
"Better with diuretics (pulmonary oedema) | "
"Better sitting upright or in tripod position (COPD, asthma)"),
]
story.append(pilcode_table(dysp_pilcode, TEAL))
story.append(PageBreak())
# ── COUGH ──
story.append(section_banner("7C — RESPIRATORY: COUGH", TEAL))
story.append(sp(1.5))
cough_pilcode = [
("P", "Place / Site",
"Upper airway (dry, tickling: URTI, post-nasal drip, ACE inhibitor) | "
"Lower airway (deep, productive: pneumonia, bronchiectasis, COPD) | "
"Wheeze suggests airway obstruction"),
("I", "Intensity",
"Severity: interfering with sleep? Daily activities? Haemoptysis always serious — quantify (streaks, frank blood)."),
("L", "Length",
"Acute <3 weeks (URTI, pneumonia) | "
"Subacute 3–8 weeks (post-infectious, pertussis) | "
"Chronic >8 weeks: TB, lung cancer, GORD, asthma, bronchiectasis, ILD, ACE inhibitor, heart failure"),
("C", "Character",
"Dry/non-productive (ACE inhibitor — 20% of users, ILD, early asthma) | "
"Productive (COPD, pneumonia, bronchiectasis) | "
"Barking/seal-like (croup) | "
"Whooping inspiratory whoop (pertussis) | "
"Bovine cough (recurrent laryngeal nerve palsy — lung cancer, aortic aneurysm)"),
("O", "Onset",
"Following URTI? New medication (ACE inhibitor — commonest cause of drug-induced cough)? "
"Occupational exposure (occupational asthma)? Prolonged fever + weight loss (TB)?"),
("D", "Duration + Associated",
"Sputum colour: clear (viral/allergy) | Yellow/green (bacterial infection) | "
"Rusty/red (pneumococcal pneumonia) | "
"Haemoptysis: TB, lung cancer, bronchiectasis, PE | "
"Fever? Night sweats? Weight loss (TB, malignancy)?"),
("E", "Exacerbating / Relieving",
"Worse at night (asthma, GORD, post-nasal drip) | "
"Worse with cold air, exercise, allergens (asthma) | "
"Worse lying down (GORD, post-nasal drip) | "
"Positional: worse on certain side (localised bronchiectasis) | "
"Relieved by stopping ACE inhibitor (ACE-inhibitor cough)?"),
]
story.append(pilcode_table(cough_pilcode, TEAL))
story.append(PageBreak())
# ── ABDOMINAL PAIN ──
story.append(section_banner("7D — GASTROINTESTINAL: ABDOMINAL PAIN", colors.HexColor("#117a65")))
story.append(sp(1.5))
abd_pilcode = [
("P", "Place / Site",
"RUQ: liver, gallbladder (Murphy's sign: cholecystitis) | "
"Epigastric: peptic ulcer, pancreatitis, gastritis, aortic aneurysm, MI (inferior) | "
"RIF: appendicitis (McBurney's point), ovarian cyst/torsion, ileocaecal TB | "
"LIF: diverticulitis, sigmoid volvulus, ovarian | "
"Loin-to-groin: renal colic (ureter) | "
"Diffuse/generalised: peritonitis, mesenteric ischaemia, IBD, IBS"),
("I", "Intensity",
"Worst pain imaginable: renal colic, biliary colic (patient writhes = colicky) | "
"Lying still in pain: peritonitis | "
"0–10 scale | Functional impact"),
("L", "Length",
"Acute surgical emergency <24h: perforation, ischaemia, ruptured AAA, ectopic | "
"Acute <72h: appendicitis, cholecystitis, pancreatitis | "
"Chronic/recurrent: IBS, IBD, peptic ulcer disease"),
("C", "Character",
"Colicky (comes in waves, patient writhes): bowel obstruction, renal/biliary colic | "
"Constant dull ache (inflammatory): cholecystitis, appendicitis, pancreatitis | "
"Burning epigastric (peptic ulcer, GORD) | "
"Sudden tearing (ruptured AAA or dissection — EMERGENCY)"),
("O", "Onset",
"Sudden onset: perforation (peptic/appendix), mesenteric ischaemia, ruptured ectopic, ruptured AAA | "
"Gradual crescendo: appendicitis, cholecystitis | "
"After eating (postprandial): mesenteric ischaemia (intestinal angina), peptic ulcer, gastric ulcer"),
("D", "Duration + Associated",
"Nausea/vomiting — before pain (surgical: vomiting follows pain onset)? "
"After pain (medical) | "
"Change in bowel habit? Rectal bleeding (melaena = upper GI; fresh blood = lower GI)? "
"Jaundice (gallstones, hepatitis, malignancy)? Urinary symptoms (UTI, renal colic)? "
"Last menstrual period (ectopic pregnancy)?"),
("E", "Exacerbating / Relieving",
"Relieved by eating: duodenal ulcer (pain 2–3h after food, relieved by eating/antacids) | "
"Worse after eating: gastric ulcer (pain within 30–60 min), mesenteric ischaemia | "
"Relieved by defecation: IBS, diverticular disease | "
"Worse on breathing: peritonitis (any movement worsens pain — patient lies still)"),
]
story.append(pilcode_table(abd_pilcode, colors.HexColor("#117a65")))
story.append(PageBreak())
# ── HEADACHE ──
story.append(section_banner("7E — NEUROLOGICAL: HEADACHE", colors.HexColor("#6c3483")))
story.append(sp(1.5))
head_pilcode = [
("P", "Place / Site",
"Unilateral: migraine, cluster headache, temporal arteritis | "
"Bilateral (band-like): tension headache | "
"Occipital + neck stiffness: SAH, meningitis, raised ICP, cervicogenic | "
"Frontal/sinus: sinusitis, tension"),
("I", "Intensity",
"Thunderclap (10/10, maximal at onset, 'worst headache of my life') = SAH until proven otherwise | "
"Cluster: excruciatingly severe, 'suicide headache' | "
"Gradual build-up: tension, migraine"),
("L", "Length",
"Cluster: 15 min – 3 hours (1–8 attacks/day, clusters over weeks then remits) | "
"Migraine: 4–72 hours | "
"Tension: 30 min – 7 days | "
"SAH: sudden, persistent | "
"Progressive worsening over days-weeks: raised ICP (tumour, SDH)"),
("C", "Character",
"Throbbing/pulsating (migraine) | "
"Non-pulsating pressure/tightness (tension) | "
"Severe stabbing/drilling, periorbital (cluster) | "
"Sudden explosive (SAH) | "
"Dull constant aching that worsens over time (raised ICP, SDH)"),
("O", "Onset",
"Thunderclap (0–1 second) = SAH — do CT head then LP if CT negative | "
"On exertion/coughing/sneezing (raised ICP, benign exertional headache) | "
"Morning headache (raised ICP — worse when waking) | "
"Triggered by food (tyramine: cheese, wine), alcohol, stress, hormones (migraine)"),
("D", "Duration + Associated",
"Nausea + vomiting + photophobia + phonophobia (migraine) | "
"Visual aura (scotoma, fortification spectra, unilateral visual loss) — migraine with aura | "
"Neck stiffness + fever + rash (meningococcal meningitis — EMERGENCY) | "
"Unilateral red, watery eye, rhinorrhoea (cluster) | "
"Neurological deficit (stroke, SOL) | "
"Temporal artery tenderness + jaw claudication (GCA — elderly, ↑ESR, risk of blindness)"),
("E", "Exacerbating / Relieving",
"Worse on bending/straining/coughing (raised ICP) | "
"Worse lying down, better upright (raised ICP) | "
"Better in dark/quiet room with sleep (migraine) | "
"Relieved by simple analgesia (tension) | "
"Triptans effective (migraine) | "
"O₂ 100% + sumatriptan (cluster headache)"),
]
story.append(pilcode_table(head_pilcode, colors.HexColor("#6c3483")))
story.append(sp(1))
story.append(alert_box(
"⚠ RED FLAGS in Headache (MacLeod's — require urgent investigation):\n"
"• Thunderclap headache (SAH until proven otherwise — CT head urgently)\n"
"• Headache + fever + neck stiffness ± rash (meningitis/encephalitis)\n"
"• Headache + papilloedema (raised ICP — space-occupying lesion)\n"
"• Progressive worsening headache over days/weeks (SOL, SDH)\n"
"• New headache in patient >50 years (GCA, malignancy)\n"
"• Headache with neurological deficit or change in consciousness"))
story.append(PageBreak())
# ── JOINT PAIN ──
story.append(section_banner("7F — MUSCULOSKELETAL: JOINT PAIN", colors.HexColor("#935116")))
story.append(sp(1.5))
joint_pilcode = [
("P", "Place / Site",
"Monoarthritis (1 joint): gout, septic arthritis, trauma, reactive arthritis | "
"Oligoarthritis (2–4): psoriatic arthropathy, ankylosing spondylitis, reactive arthritis | "
"Polyarthritis (>4 joints): RA, SLE, viral arthritis (parvovirus, rubella) | "
"Axial (spine/sacroiliac): ankylosing spondylitis (young male, morning stiffness, back) | "
"DIP joints: OA (Heberden's nodes), psoriatic"),
("I", "Intensity",
"0–10 scale | Functional limitation: stairs, dressing, grip strength, handwriting | "
"Can they open jars? Button shirts? Walk unaided?"),
("L", "Length",
"Acute overnight joint (gout, septic arthritis, trauma) | "
"Subacute/migratory (rheumatic fever, gonorrhoal arthritis, viral) | "
"Chronic (OA — years; RA — progressive with flares)"),
("C", "Character",
"Warm, red, swollen, tender (inflammatory/infective) | "
"Hard, bony, non-tender swelling (OA — Heberden's/Bouchard's nodes) | "
"Morning stiffness >1 hour (inflammatory: RA, AS, psoriatic) — improves with activity | "
"Morning stiffness <30 min (mechanical: OA) — worse with activity"),
("O", "Onset",
"Sudden overnight peak pain (gout — often first MTP joint — 'podagra') | "
"Gradual: RA, OA | "
"Following infection (reactive arthritis: 2–4 weeks post-urethritis/gastroenteritis — Reiter's triad) | "
"After trauma"),
("D", "Duration + Associated",
"Morning stiffness duration (key discriminator: inflammatory vs. mechanical) | "
"Systemic features (fever, weight loss, fatigue = inflammatory/infective) | "
"Skin: psoriatic plaques (psoriatic arthropathy) | "
"Eyes: uveitis/iritis (AS, reactive arthritis, sarcoid) | "
"Urethral discharge (reactive arthritis, gonococcal) | "
"Butterfly rash (SLE) | Raynaud's phenomenon (SLE, scleroderma)"),
("E", "Exacerbating / Relieving",
"Worse after rest, improves with movement (inflammatory: RA, AS) | "
"Worse with activity, better with rest (mechanical: OA) | "
"Gout: precipitated by alcohol, purine-rich food, diuretics, dehydration | "
"Better with NSAIDs (gout, RA) | "
"Better with colchicine (gout specifically)"),
]
story.append(pilcode_table(joint_pilcode, colors.HexColor("#935116")))
story.append(PageBreak())
# ════════════════════════════════════════════════════════════════════
# PART 8 — QUICK REFERENCE SUMMARY
# ════════════════════════════════════════════════════════════════════
story.append(chapter_banner("PART 8 — QUICK REFERENCE SUMMARY", colors.HexColor("#1a3a5c")))
story.append(sp(2))
story.append(Paragraph("Vital Signs Normal Values — Adults", sec_head))
summary_vitals = [
["Temperature", "36.5 – 37.5°C", ">38°C = Fever | >41°C = Hyperpyrexia | <35°C = Hypothermia"],
["Pulse Rate", "60 – 100 bpm", ">100 = Tachycardia | <60 = Bradycardia"],
["Respiratory Rate", "12 – 20/min", ">20 = Tachypnoea | <12 = Bradypnoea | >30 = Critical"],
["Blood Pressure", "<120/80 mmHg", "≥140/90 = Hypertension | <90 systolic = Hypotension"],
["SpO₂", "≥95%", "<94% investigate | <90% = Hypoxaemia"],
["Pulse Pressure", "40–60 mmHg", "Wide >60 (AR, thyrotoxicosis) | Narrow <25 (tamponade, AS)"],
]
story.append(simple_table(
["Vital Sign", "Normal Range", "Abnormal Thresholds"],
summary_vitals, [3.5*cm, 4*cm, 9.5*cm],
hdr_color=NAVY
))
story.append(sp(2))
story.append(Paragraph("Vital Sign Combinations — Diagnostic Patterns", sec_head))
patterns = [
["Fever + Tachycardia + Hypotension + Tachypnoea", "Sepsis / Septic Shock — EMERGENCY"],
["Bradycardia + Hypertension + Irregular breathing", "Cushing's Triad — Raised ICP (brain herniation)"],
["Fever + Relative Bradycardia (no tachycardia)", "Typhoid / Legionella / Drug Fever (Faget's sign)"],
["Hypotension + Tachycardia + Narrow Pulse Pressure", "Cardiogenic Shock / Cardiac Tamponade"],
["Hypotension + Tachycardia + Wide Pulse Pressure", "Septic Shock / Anaphylaxis (vasodilatory)"],
["Bradypnoea + Bradycardia + Low GCS", "Opioid Overdose / CNS Depression"],
["Tachycardia + Wide Pulse Pressure + Bounding Pulse", "Thyrotoxicosis / Aortic Regurgitation / Anaemia"],
["Hypertension + Bradycardia (in trauma)", "Cushing's Response — Impending Brainstem Herniation"],
]
story.append(simple_table(
["Pattern of Vital Signs", "Consider"],
patterns, [8*cm, 9*cm],
hdr_color=RED_SOFT
))
story.append(sp(2))
story.append(Paragraph("PILCODE Summary Card", sec_head))
pilcode_summary = [
["P", "Place / Site", "Where is the symptom or measurement taken/located?"],
["I", "Intensity / Severity", "How bad is it? 0–10 scale, functional impact, threshold values"],
["L", "Length / Duration", "How long present? Acute / subacute / chronic? Continuous vs. episodic?"],
["C", "Character / Quality", "What does it feel like? Describe the nature/type of symptom"],
["O", "Onset", "When/how did it start? Sudden / gradual? Any trigger?"],
["D", "Duration + Associated", "Time course of episodes + all associated symptoms, discharge"],
["E", "Exacerbating / Relieving", "What makes it worse or better? Position, food, drugs, activity?"],
]
story.append(pilcode_table(pilcode_summary, NAVY))
story.append(sp(2))
story.append(HRFlowable(width="100%", thickness=1, color=GOLD))
story.append(sp(1))
story.append(Paragraph(
"Reference: MacLeod's Clinical Examination, 15th Edition — Dover, Innes, Fairhurst — Elsevier Health Sciences, 2023 | "
"Goldman-Cecil Medicine — Cardinal Symptoms of Cardiovascular Disease (Table 39-1) | "
"Roberts & Hedges' Clinical Procedures in Emergency Medicine — Vital Signs",
small_note))
# ════════════════════════════════════════════════════════════════════
# BUILD
# ════════════════════════════════════════════════════════════════════
doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
print(f"PDF generated: {OUTPUT}")
cd /tmp/workspace/vitals-pilcode && python3 generate_pdf.py 2>&1
ls -lh /tmp/workspace/vitals-pilcode/Vitals_PILCODE_MacLeod.pdf
Vitals PILCODE MacLeod
PDF Document · PDF
| Part | Content |
|---|---|
| 1 | PILCODE mnemonic explained - all 7 letters with clinical application |
| 2 | Vital signs overview - normal values, why they matter, diagnostic patterns |
| 3 | Temperature - PILCODE applied, fever patterns (continuous/remittent/intermittent/hectic), hypothermia |
| 4 | Pulse Rate - PILCODE applied, 5 features (MacLeod's), special pulse patterns table |
| 5 | Respiratory Rate - PILCODE applied, breathing patterns (Cheyne-Stokes, Kussmaul, Biot's, etc.) |
| 6 | Blood Pressure - PILCODE applied, BP classification table, secondary causes of HT |
| 7 | Cardinal Symptoms by system - Chest pain, Dyspnoea, Cough, Abdominal pain, Headache, Joint pain - each with full PILCODE |
| 8 | Quick Reference Summary - all normal values, vital sign diagnostic patterns, PILCODE summary card |