This is for medicine posting. I am 2nd year MBBS Student Can you tell me the significance of asking these things for medicine history taking with definition and what things to rule out. You can take long time to collect the information
cd /tmp/workspace/attachments/d674b850-1b3c-4cd5-a4fb-32ebcf3bfe0d/ && python3 -c " import pdfplumber with pdfplumber.open('PDF.pdf') as pdf: for i, page in enumerate(pdf.pages): text = page.extract_text() print(f'=== PAGE {i+1} ===') print(text) print() "
| Sub-question | Significance |
|---|---|
| Site | Visceral pain is poorly localised; parietal pain is well localised. Site points to the organ involved |
| Type | Colicky = hollow viscus obstruction (bowel, biliary, ureteric); Burning = peptic ulcer; Constant dull = solid organ capsule stretch |
| Onset | Sudden = perforation, torsion, vascular event; Gradual = inflammatory, neoplastic |
| Radiation | Biliary colic → right shoulder; Pancreatitis → back; Renal colic → groin |
| Aggravating/Relieving | Eating relieves duodenal ulcer; worsens gastric ulcer; fatty food worsens biliary colic |
| Episodes | Recurrent episodic = peptic ulcer, biliary colic, irritable bowel |
| Item Asked | Significance |
|---|---|
| H/O TB | TB can cause intestinal TB (ileocaecal most common), TB peritonitis, mesenteric lymphadenopathy - mimics Crohn's disease |
| H/O DM | Diabetic gastroparesis (vomiting, bloating), increased susceptibility to infections, NAFLD |
| H/O HTN | Mesenteric ischaemia, increased cardiovascular risk; ACE inhibitors can cause diarrhoea |
| H/O Jaundice | Previous hepatitis B/C → chronic liver disease/cirrhosis; Gallstone disease recurrence |
| H/O Bronchial Asthma | ASA/NSAIDs for asthma-related symptoms can cause peptic ulcers; Alpha-1 antitrypsin deficiency causes both emphysema and liver cirrhosis |
| H/O Epilepsy | Antiepileptic drugs (valproate, carbamazepine) → hepatotoxicity, DILI |
| H/O Blood Transfusion | Risk of Hepatitis B, Hepatitis C, HIV - all causing liver disease |
| H/O Drug Intake | NSAIDs → peptic ulcer, GI bleed; Antibiotics → C. difficile colitis; Steroids → peptic ulcer |
| Item | Significance |
|---|---|
| Diet | Vegetarian deficiency (B12 → megaloblastic anaemia); High fat diet → gallstones, NAFLD; Low fibre → constipation, diverticular disease, colorectal cancer |
| Loss of weight and appetite | Red flag = malignancy (GI cancer, lymphoma); also TB, hyperthyroidism, uncontrolled DM |
| Bowel and bladder habits | Baseline normal → to compare current change; Change in bowel habits in >40yr = colorectal carcinoma till proven otherwise |
| Sleep disturbances | Nocturnal symptoms (diarrhoea, pain) = organic > functional; Symptoms only in day = may be functional |
| Smoking | Increases risk of peptic ulcer (decreases prostaglandin), gastric cancer, IBD (paradoxically protective against UC but worsens Crohn's) |
| Alcohol | Alcoholic hepatitis, cirrhosis, pancreatitis, varices, Mallory-Weiss tear, thiamine deficiency (Wernicke's) |
| Exposure to endemic areas | Amoebic liver abscess, hepatitis A/E (faeco-oral), typhoid, echinococcal cyst |
| Pattern | Typical Causes |
|---|---|
| Proximal weakness (combing hair, squatting) | Myopathy (muscular dystrophy, polymyositis, steroid myopathy, thyroid myopathy) |
| Distal weakness (buttoning, gripping chappals) | Peripheral neuropathy (polyneuropathy - diabetes, GBS, CMT disease) |
| Proximal + Distal | ALS (amyotrophic lateral sclerosis), mixed neuropathy |
| CN | Symptom asked | Significance and what it rules out |
|---|---|---|
| I (Olfactory) | Disturbance of smell | Anosmia: anterior cranial fossa fracture, meningioma, Parkinson's (early), olfactory groove meningioma |
| II (Optic) | Visual disturbance | Optic neuritis (MS - unilateral, pain on movement), retinal detachment, glaucoma, raised ICP (papilloedema - bilateral blurring), pituitary adenoma (bitemporal hemianopia) |
| III, IV, VI | Diplopia, squint, ptosis | Oculomotor palsy (CN III - ptosis, dilated pupil, "down and out" eye): posterior communicating artery aneurysm, uncal herniation, DM; CN VI palsy: raised ICP (false localizing sign), pontine lesion; Myasthenia gravis (fatigable ptosis) |
| V (Trigeminal) | Difficulty eating, facial sensory loss | Trigeminal neuralgia (lancinating unilateral facial pain, trigger zones), acoustic neuroma (CPA angle tumour compresses V), pontine lesion, Wallenberg's syndrome |
| VII (Facial) | Drooling, angle of mouth deviation, inability to close eye, taste loss | UMN facial palsy (spares forehead - frontalis has bilateral cortical supply) = stroke; LMN facial palsy (entire face including forehead) = Bell's palsy (idiopathic, HSV), Ramsay Hunt syndrome (herpes zoster - painful ear vesicles), parotid tumour |
| VIII (Vestibulocochlear) | Hearing loss, vertigo, tinnitus | Hearing loss + vertigo: Meniere's disease (episodic, tinnitus, low-frequency hearing loss, fullness in ear); Acoustic neuroma (unilateral sensorineural hearing loss); Labyrinthitis; Aminoglycoside toxicity (cochlear damage) |
| IX, X (Glossopharyngeal + Vagus) | Nasal regurgitation, nasal twang speech, dysphagia | Bulbar palsy (LMN - flaccid, nasal speech, absent gag) vs. Pseudobulbar palsy (UMN - spastic, brisk jaw jerk, emotional lability); Causes: MND, brainstem stroke, GBS, diphtheria |
| XI (Spinal Accessory) | Difficulty turning head, shoulder weakness | Neck dissection surgery injury; posterior fossa tumour; C-spine pathology |
| XII (Hypoglossal) | Difficulty making food bolus, dysarthria | Tongue wasting/fasciculation = LMN (MND, medullary lesion); Tongue deviates to side of lesion (LMN) or opposite side (UMN) |
| Pattern asked | Localization |
|---|---|
| Hemi-anaesthesia (one half of body) | Contralateral thalamus or cortex (hemispheric/thalamic lesion - stroke, space-occupying lesion) |
| Glove and stocking | Peripheral neuropathy (diabetes, alcohol, B12 deficiency, GBS) - length-dependent, distal |
| Dermatomal distribution (root pains) | Nerve root compression (radiculopathy) - disc prolapse, HNP (herniated nucleus pulposus), spondylosis |
| Band-like sensation over torso | Spinal cord lesion at the corresponding dermatome - spinal cord compression, transverse myelitis |
| Wash basin attacks (sudden fall on washing face with cold water) | Vertebrobasilar insufficiency or cervical spondylosis affecting vertebral arteries |
| Difficulty walking in dark (positive Romberg's) | Posterior column disease (dorsal columns carry vibration + JPS) - Subacute combined degeneration (B12 deficiency), tabes dorsalis (neurosyphilis), Friedreich's ataxia |
| Symptom asked | Clinical significance |
|---|---|
| Apraxia (in cerebellar context - dyspraxia) | Inability to perform coordinated purposive movements |
| Difficulty taking food to mouth | Intention tremor / dysmetria of UL - tested by finger-nose test |
| Difficulty buttoning | Dysdiadochokinesia (inability to perform rapid alternating movements) |
| Swaying while walking / reeling sensation | Cerebellar gait (wide-based, staggering, cannot tandem walk) |
| Symptom | Mechanism | Lesion |
|---|---|---|
| Retention / Hesitancy | Detrusor-sphincter dyssynergia, or UMN lesion causing spastic bladder initially presenting as retention | Acute spinal cord lesion (spinal shock), BPH (non-neurological in males) |
| Precipitancy / Urge incontinence | Uninhibited detrusor contractions | UMN lesion (stroke, MS, spinal cord disease) - hyperreflexic bladder |
| Incontinence / dribbling | Overflow incontinence (atonic bladder) | LMN/cauda equina lesion - lost detrusor tone |
| Item | Significance |
|---|---|
| HTN | Most common cause of stroke (haemorrhagic and ischaemic), vascular dementia, hypertensive encephalopathy |
| DM | Diabetic peripheral neuropathy (glove-stocking), autonomic neuropathy, lacunar stroke, mononeuropathy multiplex |
| TB | TB meningitis (most dangerous - thick exudate at base of brain, cranial nerve palsies, hydrocephalus), tuberculoma |
| HIV | HIV encephalopathy, cryptococcal meningitis, PML (JC virus), cerebral toxoplasmosis, CNS lymphoma |
| Neurosyphilis | Tabes dorsalis (posterior column loss), GPI (general paresis of insane), meningovascular syphilis |
| Dog bite / Anti-rabies vaccination | Rabies (hydrophobia, aerophobia, encephalitis) or post-vaccine ADEM |
| Malignancy / Chemotherapy | Brain metastases (lung, breast, colon, melanoma most common), chemo-induced peripheral neuropathy (vincristine, cisplatin), radiation-induced CNS damage |
| TIA | Transient Ischaemic Attack = warning sign before full stroke; asks about previous focal neuro deficits <24 hours duration |
| Epilepsy | Drug interactions; post-ictal weakness (Todd's paralysis mimics stroke) |
| Sub-question | Significance |
|---|---|
| Duration | Acute <3 weeks = viral URTI, pneumonia; Subacute 3-8 weeks = post-infectious, pertussis; Chronic >8 weeks = important - must investigate thoroughly |
| Onset | Sudden = foreign body (especially in children), pulmonary embolism |
| Character | Barking cough = croup (laryngotracheobronchitis, children), epiglottitis; Whooping cough = Bordetella pertussis (inspiratory whoop); Bovine cough (no explosive quality) = recurrent laryngeal nerve palsy |
| Productive or dry | Productive = infection, bronchiectasis, lung abscess; Dry = ILD (interstitial lung disease), early malignancy, ACE inhibitor-induced (bradykinin accumulation - chronic dry cough in 5-20%) |
| Aggravating factors | Lying flat (postnasal drip, GERD); Cold air (asthma); Exercise (asthma, cardiac) |
| Diurnal variation | Morning cough + copious sputum = bronchiectasis, chronic bronchitis; Nocturnal cough = asthma, GERD, cardiac (pulmonary oedema) |
| Seasonal variation | Seasonal worsening = asthma (allergen trigger), COPD exacerbations in winter |
| Character | Significance |
|---|---|
| Quantity | Copious (>200 mL/day) = bronchiectasis, lung abscess |
| Colour: Yellow/green | Infection (neutrophils + bacteria - purulent) - pneumonia, acute exacerbation COPD |
| Colour: Rusty/blood-tinged | Pneumococcal pneumonia ("rusty sputum" - classic) |
| Colour: Pink frothy | Pulmonary oedema (cardiac failure) |
| Colour: Black/tarry | Coal dust (anthracosis), smoke inhalation |
| Colour: Red currant jelly | Klebsiella pneumonia |
| Smell: Foul/offensive | Anaerobic infection - lung abscess, empyema, aspiration pneumonia |
| Consistency: Three layers on standing | Bronchiectasis (frothy top, mucoid middle, purulent bottom) |
| Blood-stained | Haemoptysis (see below) |
| Postural variation | Bronchiectasis (sputum increases on lying down in certain positions, gravity-dependent drainage) |
| mMRC Grade | Description |
|---|---|
| 0 | No dyspnoea except strenuous exercise |
| 1 | Dyspnoea on hurrying or walking up a slope |
| 2 | Walks slower than contemporaries or stops for breath after 15 min on flat |
| 3 | Stops for breath after 100 m or a few minutes on flat |
| 4 | Too breathless to leave house, on dressing/undressing |
| Term | Definition | Cause |
|---|---|---|
| Orthopnoea | Dyspnoea on lying flat, relieved by sitting up; quantify by number of pillows | Left heart failure (LVF) - increased venous return when supine raises LVEDP; Also bilateral pleural effusion, diaphragmatic palsy |
| PND (Paroxysmal Nocturnal Dyspnoea) | Sudden dyspnoea waking patient from sleep, 1-2 hrs after going to bed, relieved by sitting up/standing | LVF (nocturnal fluid redistribution + reduced adrenergic tone) |
| Platypnoea | Dyspnoea worse on sitting up, relieved by lying down (opposite of orthopnoea) | Hepatopulmonary syndrome (liver disease + intrapulmonary shunts), ASD (orthodeoxia), right-to-left shunts |
| Trepopnoea | Dyspnoea on lying on one side only | Unilateral pleural effusion, asymmetric lung disease |
| Type | Character | Cause |
|---|---|---|
| Pleuritic | Sharp, stabbing, unilateral, worsened by deep breathing, coughing | Pleuritis (pneumonia, TB, SLE), pulmonary embolism/infarction, pneumothorax |
| Dull constant | Heavy, non-positional | Malignant pleural disease, rib metastases |
| Central burning | Retrosternal | Tracheitis, oesophageal (GERD) |
| Musculoskeletal | Localised, worsened by movement/palpation | Costochondritis (Tietze syndrome), rib fracture |
| Item | Significance |
|---|---|
| H/O TB | Old TB cavities → aspergilloma, bronchiectasis, post-TB fibrosis, recurrence; Milliary TB |
| H/O Bronchial Asthma / Allergies | Baseline, severity, trigger identification, previous hospitalisations/ICU admissions |
| Recurrent RTI / Whooping Cough / Measles | Childhood lung infections → bronchiectasis (post-measles, post-pertussis), Williams-Campbell syndrome |
| H/O Penicillin injections | Penicillin allergy - anaphylaxis risk; treatment of RHD prophylaxis |
| ATT (Anti-TB Treatment) | Previous TB treatment - compliance, drug resistance (MDR-TB), hepatotoxicity from ATT |
| ART | HIV - opportunistic infections (PCP, TB, CMV pneumonia, Kaposi sarcoma) |
| Occupation | Disease |
|---|---|
| Mining (coal) | Pneumoconiosis / CWP (Coal Worker's Pneumoconiosis) |
| Silica exposure (sand-blasting, quarrying, granite) | Silicosis |
| Asbestos (shipbuilding, insulation) | Asbestosis, Mesothelioma, pleural plaques |
| Farming (mouldy hay) | Hypersensitivity Pneumonitis (Farmer's Lung - Thermophilic actinomycetes) |
| Animal husbandry (birds) | Bird-Breeder's Lung (pigeon/budgerigar - HP), Psittacosis, Q fever |
| Cotton textile | Byssinosis |
| Welder | Siderosis, manganism (neurological) |
| Class | Description |
|---|---|
| Class I | No symptoms during ordinary activity |
| Class II | Slight limitation of physical activity; comfortable at rest; ordinary activity causes symptoms |
| Class III | Marked limitation of physical activity; comfortable at rest; less-than-ordinary activity causes symptoms |
| Class IV | Symptoms at rest; unable to carry on any activity without discomfort |
| Character asked | Significance |
|---|---|
| Regular vs. Irregular | Regular and fast = SVT, atrial flutter, sinus tachycardia; Irregular = atrial fibrillation (irregularly irregular), ectopics (regularly irregular) |
| At rest vs. exertion | At rest = arrhythmia (VT, SVT); Exertional = HOCM (outflow obstruction on exertion), anaemia, thyrotoxicosis |
| Associated SOB | Suggests cardiac cause with haemodynamic compromise |
| Sudden start and stop | SVT (AV nodal re-entrant tachycardia - AVNRT) - "paroxysmal" |
| Relation to exertion | Significance |
|---|---|
| Exertional syncope | HOCM (hypertrophic obstructive cardiomyopathy), severe aortic stenosis, pulmonary hypertension - outflow obstruction worsens on exertion; RED FLAG - can be pre-VF |
| Post-exertional | Post-exercise vasodilation |
| Postural (orthostatic) | Autonomic failure (DM, Parkinson's, MSA), hypovolaemia, drugs (antihypertensives) |
| Situational | Cough syncope, micturition syncope, Valsalva |
| Reflex (vasovagal) | Prodrome of nausea/sweating/pallor, triggered by pain/fear/emotional stress - benign |
| Question | Significance |
|---|---|
| Time of day variation | Cardiac/venous oedema: worse in evening (all-day dependency), better in morning (feet elevated during sleep); Renal: worse in morning (periorbital + pedal) |
| Bilateral vs. Unilateral | Bilateral = systemic (cardiac, hepatic, renal, hypoalbuminaemic); Unilateral = local (DVT, lymphoedema, cellulitis) |
| Pitting vs. Non-pitting | Pitting = low oncotic pressure (hypoalbuminaemia) or raised hydrostatic pressure; Non-pitting = lymphoedema (after cancer surgery), myxoedema (hypothyroidism) |
| Sacral oedema | Specifically in CVS posting = right heart failure (RHF) - dependent oedema shifts to sacrum when patient is recumbent |
| Oliguria | Cardiac output falls → renal underperfusion → oliguria |
| Fatigue and right hypochondric pain | Hepatic congestion from RHF → tender hepatomegaly (right hypochondric pain) |
| History item | What it suggests |
|---|---|
| Failure of cry after birth | Severe cyanotic CHD requiring immediate resuscitation (TGA, critical PS, critical AS) |
| Failure to thrive | Large left-to-right shunt (VSD, PDA, AV canal) - high pulmonary flow, fatigue on feeding, FTT |
| Recurrent RTI | Large L→R shunt → pulmonary plethora → recurrent respiratory infections |
| Squatting | Fallot's tetralogy (squatting increases systemic vascular resistance → reduces R→L shunt → improves SpO2); Classic for cyanotic CHD |
| Cyanotic spells (Tet spells) | Hypercyanotic spells in Tetralogy - treated with knee-chest position, morphine, propranolol |
| Consanguinity | Autosomal recessive CHDs; chromosomal anomalies (Down syndrome - AV canal, VSD; Turner - coarctation; Marfan - aortic root) |
| Item | Significance |
|---|---|
| Smoking | Major risk factor for atherosclerosis (IHD, peripheral vascular disease, stroke), endothelial dysfunction; Nicotine staining of fingers = clinical marker on examination |
| Alcohol | Alcoholic cardiomyopathy (dilated cardiomyopathy), AF, hypertension; Binge drinking = "Holiday Heart Syndrome" (AF after binge) |
| Diet (Veg/Mixed) | High saturated fat diet → dyslipidaemia → CAD; Vegetarian → lower cholesterol but possible B12 deficiency |
| Loss of weight / appetite | Cardiac cachexia (severe CCF - catabolic state); Malignancy; TB |
| Item | Why asked |
|---|---|
| CHD (Congenital Heart Disease) | Familial CHDs, chromosomal syndromes |
| IHD (Ischaemic Heart Disease) | Strong family history = premature CAD (first-degree male relative <55yr, female <65yr) is an independent risk factor |
| HTN | Essential hypertension is polygenic, runs in families |
| DM | Type 2 DM has strong familial component, drives cardiovascular risk |
| SCD (Sudden Cardiac Death) | HCM (AD), Long QT syndrome (AD), Brugada syndrome - familial arrhythmogenic conditions |
| Item | Significance |
|---|---|
| Menarche / Cycle details | Oral contraceptive pills (OCP) → increased risk of venous thromboembolism, stroke, hypertension |
| Dysmenorrhoea / Amenorrhoea | Endometriosis may occasionally mimic cardiac symptoms; PCOS → metabolic syndrome → CVD risk |
| Number of pregnancies / Complications | Peripartum cardiomyopathy (last month of pregnancy to 5 months postpartum - dilated CMP); Pre-eclampsia → future hypertension/CVD risk |
| Recent delivery | Peripartum CMP, DVT/PE postpartum (hypercoagulable state) |
| Item | Significance |
|---|---|
| Right hypochondric pain / Appetite | Hepatic congestion from RHF - congestive hepatopathy; Anorexia in CCF (hepatic congestion, gut wall oedema) |
| Fatigue | Reduced cardiac output → poor perfusion of skeletal muscles; also in anaemia, thyroid disease |
| Cyanotic spells | Cyanotic CHD; Hypercyanotic spells (Tet spells) |
| Oliguria | Reduced renal perfusion from low cardiac output (cardiorenal syndrome) |
| Question | What it tells you |
|---|---|
| Duration | Acute vs. Subacute vs. Chronic → narrows the differential list dramatically |
| Onset | Sudden (vascular, obstructive, mechanical) vs. Gradual (inflammatory, neoplastic, metabolic) |
| Progression | Getting worse (malignant, obstructive) vs. Intermittent/episodic (functional, vascular) vs. Static (old fixed lesion) vs. Improving (infective/inflammatory with treatment) |
| Symptom | Red Flag Feature | Likely Diagnosis |
|---|---|---|
| Dysphagia | Progressive solids → liquids, weight loss, age >50 | Carcinoma oesophagus |
| Abdominal pain | Sudden onset, board-like rigidity | Perforation peritonitis |
| Haematemesis | Massive, known liver disease, cirrhosis | Variceal bleed (emergency) |
| Chest pain | Tearing, maximal at onset, back radiation | Aortic dissection |
| Chest pain | >20 min, not relieved by nitrates, diaphoresis | STEMI |
| Syncope | Exertional, young person/athlete | HOCM, AS, Arrhythmia |
| Haemoptysis | Smoker >40 years, weight loss | Lung carcinoma |
| Weakness + bladder/bowel incontinence | Saddle anaesthesia | Cauda equina syndrome (emergency) |
| Headache | "Worst headache of life", sudden onset | Subarachnoid haemorrhage |
| Breathlessness | Sudden, pleuritic chest pain, tachycardia | Pulmonary embolism |
Generate a quick reference PDF with differential diagnosis flowcharts for common presenting symptoms
ls /tmp/workspace/ && mkdir -p /tmp/workspace/ddx-flowcharts
~/ddx-flowcharts/generate_pdf.py
"""
DDx Flowchart PDF Generator
Medicine Posting - Quick Reference for 2nd Year MBBS
Covers: GIT, CNS, Respiratory, CVS
Style: Decision-tree (Yes/No branches)
"""
import math
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle, HRFlowable
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Polygon, Circle
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Group
from reportlab.platypus.flowables import Flowable
# ── Colour palette ─────────────────────────────────────────────────────────────
C_HEADER_BG = colors.HexColor("#1a3a5c") # deep navy
C_HEADER_FG = colors.white
C_SYSTEM_GIT = colors.HexColor("#e8f5e9") # pale green
C_SYSTEM_CNS = colors.HexColor("#fce4ec") # pale pink
C_SYSTEM_RS = colors.HexColor("#e3f2fd") # pale blue
C_SYSTEM_CVS = colors.HexColor("#fff8e1") # pale amber
C_ROOT = colors.HexColor("#1a3a5c") # dark navy - root node
C_QUESTION = colors.HexColor("#1565c0") # blue - decision node
C_YES_PATH = colors.HexColor("#2e7d32") # green - yes branch node
C_NO_PATH = colors.HexColor("#b71c1c") # red - no branch / further Q
C_DIAGNOSIS = colors.HexColor("#4a148c") # purple - final diagnosis
C_REDBOX = colors.HexColor("#c62828") # red flag box
C_KEYFACT = colors.HexColor("#f57f17") # orange - key fact
C_YES_LINE = colors.HexColor("#388e3c")
C_NO_LINE = colors.HexColor("#d32f2f")
C_ARROW = colors.HexColor("#37474f")
W, H = A4 # 595 x 842 pts
# ── Custom Flowable: Decision Tree Drawing ─────────────────────────────────────
class FlowchartFlowable(Flowable):
"""
Renders a decision-tree as a ReportLab Drawing.
tree_data: list of node dicts with layout info.
"""
def __init__(self, nodes, width=None, height=None):
Flowable.__init__(self)
self.nodes = nodes
self.chart_width = width or (W - 40*mm)
self.chart_height = height or 220*mm
def wrap(self, aw, ah):
return self.chart_width, self.chart_height
def draw(self):
d = Drawing(self.chart_width, self.chart_height)
for node in self.nodes:
self._draw_node(d, node)
for node in self.nodes:
self._draw_connections(d, node)
d.drawOn(self.canv, 0, 0)
def _draw_node(self, d, n):
x, y = n['x'], n['y']
w, h = n.get('w', 110), n.get('h', 26)
kind = n.get('kind', 'question')
# Box fill colour
fill_map = {
'root': colors.HexColor("#1a3a5c"),
'question': colors.HexColor("#1565c0"),
'yes': colors.HexColor("#2e7d32"),
'no': colors.HexColor("#c62828"),
'diag': colors.HexColor("#4a148c"),
'redflag': colors.HexColor("#b71c1c"),
'key': colors.HexColor("#e65100"),
}
text_col = colors.white
fill_col = fill_map.get(kind, colors.HexColor("#1565c0"))
# Rounded rect
r = Rect(x - w/2, y - h/2, w, h,
rx=6, ry=6,
fillColor=fill_col,
strokeColor=colors.white,
strokeWidth=0.8)
d.add(r)
label = n.get('label', '')
font_size = n.get('fs', 7)
lines = self._wrap_text(label, w - 8, font_size)
line_h = font_size * 1.3
total_text_h = len(lines) * line_h
start_y = y + total_text_h/2 - line_h * 0.75
for i, line in enumerate(lines):
s = String(x, start_y - i * line_h, line,
fontName='Helvetica-Bold' if kind in ('root', 'diag', 'redflag') else 'Helvetica',
fontSize=font_size,
fillColor=text_col,
textAnchor='middle')
d.add(s)
def _draw_connections(self, d, n):
for conn in n.get('connections', []):
x1, y1 = n['x'], n['y'] - n.get('h', 26)/2
tx_node = conn['to_node']
x2, y2_top = tx_node['x'], tx_node['y'] + tx_node.get('h', 26)/2
label = conn.get('label', '')
is_yes = label.upper().startswith('Y')
lc = C_YES_LINE if is_yes else C_NO_LINE
# Draw elbow line: down from source, across, up to target
mid_y = (y1 + y2_top) / 2
line1 = Line(x1, y1, x1, mid_y, strokeColor=lc, strokeWidth=1.2)
line2 = Line(x1, mid_y, x2, mid_y, strokeColor=lc, strokeWidth=1.2)
line3 = Line(x2, mid_y, x2, y2_top, strokeColor=lc, strokeWidth=1.2)
# Arrow head at destination
arrow = Polygon([x2, y2_top,
x2 - 3, y2_top + 5,
x2 + 3, y2_top + 5],
fillColor=lc, strokeColor=lc, strokeWidth=0)
d.add(line1); d.add(line2); d.add(line3); d.add(arrow)
if label:
lx = (x1 + x2) / 2 + (5 if x2 > x1 else -5)
ly = mid_y + 2
s = String(lx, ly, label,
fontName='Helvetica-Bold',
fontSize=6,
fillColor=lc,
textAnchor='middle')
d.add(s)
@staticmethod
def _wrap_text(text, max_w, font_size):
"""Very simple word-wrap for labels."""
words = text.split()
lines, cur = [], []
char_w = font_size * 0.55
for w in words:
test = ' '.join(cur + [w])
if len(test) * char_w <= max_w:
cur.append(w)
else:
if cur:
lines.append(' '.join(cur))
cur = [w]
if cur:
lines.append(' '.join(cur))
return lines or ['']
# ── Styles ─────────────────────────────────────────────────────────────────────
def make_styles():
ss = getSampleStyleSheet()
def add(name, **kw):
if name not in ss:
ss.add(ParagraphStyle(name=name, **kw))
return ss[name]
add('SystemTitle',
fontName='Helvetica-Bold', fontSize=18,
textColor=C_HEADER_FG, alignment=TA_CENTER,
spaceAfter=4, spaceBefore=0)
add('SympTitle',
fontName='Helvetica-Bold', fontSize=13,
textColor=C_HEADER_BG, alignment=TA_LEFT,
spaceAfter=3, spaceBefore=10)
add('SubHead',
fontName='Helvetica-Bold', fontSize=9,
textColor=C_HEADER_BG, alignment=TA_LEFT,
spaceAfter=2, spaceBefore=4)
add('Body8',
fontName='Helvetica', fontSize=8,
textColor=colors.HexColor("#212121"),
leading=11, spaceAfter=2)
add('RedFlag',
fontName='Helvetica-Bold', fontSize=8,
textColor=C_REDBOX,
leading=11, spaceAfter=2)
add('SmallCenter',
fontName='Helvetica', fontSize=7,
textColor=colors.grey,
alignment=TA_CENTER, spaceAfter=0)
add('Cover_Title',
fontName='Helvetica-Bold', fontSize=28,
textColor=C_HEADER_BG, alignment=TA_CENTER,
spaceAfter=6)
add('Cover_Sub',
fontName='Helvetica', fontSize=13,
textColor=colors.HexColor("#455a64"),
alignment=TA_CENTER, spaceAfter=4)
add('Cover_Tiny',
fontName='Helvetica', fontSize=9,
textColor=colors.grey,
alignment=TA_CENTER, spaceAfter=2)
return ss
# ── Node builder helpers ───────────────────────────────────────────────────────
def node(nid, x, y, label, kind='question', w=110, h=24, fs=7):
return {'id': nid, 'x': x, 'y': y, 'label': label,
'kind': kind, 'w': w, 'h': h, 'fs': fs, 'connections': []}
def connect(src, dst, label=''):
src['connections'].append({'label': label, 'to_node': dst})
# ══════════════════════════════════════════════════════════════════════════════
# FLOWCHART DATA DEFINITIONS
# Each function returns (nodes_list, title, colour, height_pts)
# ══════════════════════════════════════════════════════════════════════════════
def chart_abdominal_pain():
"""Abdominal Pain DDx decision tree"""
nodes = []
CW = 520 # canvas width pts
CH = 310 # canvas height pts
root = node('root', 260, 290, 'ABDOMINAL PAIN', 'root', w=160, h=28, fs=10)
# Branch 1: Sudden vs gradual
q1 = node('q1', 260, 255, 'Sudden onset?', 'question', w=130, h=22, fs=8)
# Sudden YES → sub-branch
q2 = node('q2', 100, 220, 'Board-like rigidity?', 'question', w=120, h=22, fs=7)
d_perf= node('d_perf',100, 185, 'PERFORATION PERITONITIS', 'diag', w=135, h=22, fs=7)
q3 = node('q3', 100, 150, 'Tearing, radiates to back?', 'question', w=130, h=22, fs=7)
d_ao = node('d_ao', 100, 115, 'AORTIC DISSECTION / ANEURYSM', 'redflag', w=145, h=22, fs=7)
d_isc = node('d_isc', 245, 150, 'MESENTERIC ISCHAEMIA', 'diag', w=130, h=22, fs=7)
# Sudden NO → gradual/colicky
q4 = node('q4', 420, 230, 'Colicky character?', 'question', w=120, h=22, fs=7)
q5 = node('q5', 330, 195, 'RIF + fever + tenderness?', 'question', w=135, h=22, fs=7)
d_app = node('d_app', 330, 160, 'ACUTE APPENDICITIS', 'diag', w=125, h=22, fs=7)
q6 = node('q6', 420, 163, 'Post-fatty meal, RUQ?', 'question', w=130, h=22, fs=7)
d_bil = node('d_bil', 420, 128, 'BILIARY COLIC / CHOLECYSTITIS', 'diag', w=145, h=22, fs=7)
q7 = node('q7', 420, 95, 'Radiates to back, alcohol?', 'question', w=140, h=22, fs=7)
d_pan = node('d_pan', 420, 60, 'ACUTE PANCREATITIS', 'diag', w=125, h=22, fs=7)
d_ren = node('d_ren', 260, 95, 'RENAL / URETERIC COLIC', 'diag', w=130, h=22, fs=7)
# Chronic/gradual YES path
q8 = node('q8', 260, 195, 'Meal-related + epigastric?', 'question', w=135, h=22, fs=7)
d_pud = node('d_pud', 140, 160, 'PEPTIC ULCER DISEASE', 'diag', w=125, h=22, fs=7)
d_ibs = node('d_ibs', 260, 162, 'IBS / FUNCTIONAL PAIN', 'diag', w=125, h=22, fs=7)
nodes = [root, q1, q2, d_perf, q3, d_ao, d_isc, q4, q5, d_app, q6, d_bil, q7, d_pan, d_ren, q8, d_pud, d_ibs]
connect(root, q1)
connect(q1, q2, 'YES'); connect(q2, d_perf, 'YES')
connect(q2, q3, 'NO'); connect(q3, d_ao, 'YES'); connect(q3, d_isc, 'NO')
connect(q1, q4, 'NO'); connect(q4, q5, 'YES'); connect(q5, d_app, 'YES')
connect(q4, q6, 'NO'); connect(q6, d_bil, 'YES')
connect(q6, q7, 'NO'); connect(q7, d_pan, 'YES'); connect(q7, d_ren, 'NO')
connect(q5, q8, 'NO'); connect(q8, d_pud, 'YES'); connect(q8, d_ibs, 'NO')
return nodes, 'Abdominal Pain', C_SYSTEM_GIT, CH
def chart_jaundice():
nodes = []
CH = 290
root = node('root', 260, 275, 'JAUNDICE', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 245, 'Urine dark + stool pale?', 'question', w=145, h=22, fs=7)
# YES = conjugated → post-hepatic or hepatocellular
q2 = node('q2', 130, 213, 'Painful jaundice?', 'question', w=120, h=22, fs=7)
d_chol = node('d_chol', 130, 180, 'CHOLEDOCHOLITHIASIS / CHOLANGITIS', 'diag', w=155, h=22, fs=7)
q3 = node('q3', 310, 213, 'Painless + weight loss?', 'question', w=135, h=22, fs=7)
d_ca = node('d_ca', 310, 180, 'Ca HEAD OF PANCREAS (Courvoisier)', 'diag', w=155, h=22, fs=7)
d_hep = node('d_hep', 430, 213, 'HEPATOCELLULAR (Hepatitis / DILI / ALD)', 'diag', w=150, h=22, fs=6)
# NO = urine normal / dark only urobilinogen → pre-hepatic
q4 = node('q4', 260, 155, 'Anaemia + splenomegaly?', 'question', w=135, h=22, fs=7)
d_haem = node('d_haem', 260, 122, 'HAEMOLYTIC JAUNDICE', 'diag', w=130, h=22, fs=7)
q5 = node('q5', 420, 155, 'Young + KF ring + liver disease?', 'question', w=145, h=22, fs=7)
d_wil = node('d_wil', 420, 122, "WILSON'S DISEASE", 'diag', w=120, h=22, fs=7)
d_gil = node('d_gil', 420, 89, 'GILBERT / CRIGLER-NAJJAR', 'diag', w=135, h=22, fs=7)
nodes = [root, q1, q2, d_chol, q3, d_ca, d_hep, q4, d_haem, q5, d_wil, d_gil]
connect(root, q1)
connect(q1, q2, 'YES'); connect(q2, d_chol, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_ca, 'YES'); connect(q3, d_hep, 'NO')
connect(q1, q4, 'NO'); connect(q4, d_haem, 'YES'); connect(q4, q5, 'NO')
connect(q5, d_wil, 'YES'); connect(q5, d_gil, 'NO')
return nodes, 'Jaundice', C_SYSTEM_GIT, CH
def chart_haematemesis():
CH = 280
root = node('root', 260, 265, 'HAEMATEMESIS', 'root', w=150, h=28, fs=10)
q1 = node('q1', 260, 235, 'Known cirrhosis / portal HTN?', 'question', w=150, h=22, fs=7)
d_var = node('d_var', 130, 200, 'OESOPHAGEAL VARICES', 'redflag', w=135, h=22, fs=7)
q2 = node('q2', 390, 200, 'NSAID / alcohol use?', 'question', w=125, h=22, fs=7)
d_pud = node('d_pud', 260, 200, 'PEPTIC ULCER / EROSIVE GASTRITIS', 'diag', w=145, h=22, fs=7)
q3 = node('q3', 390, 168, 'Preceded by forceful vomiting?', 'question', w=150, h=22, fs=7)
d_mw = node('d_mw', 390, 135, 'MALLORY-WEISS TEAR', 'diag', w=130, h=22, fs=7)
q4 = node('q4', 390, 103, 'Weight loss + dysphagia?', 'question', w=135, h=22, fs=7)
d_ca = node('d_ca', 390, 72, 'GASTRIC CARCINOMA', 'diag', w=120, h=22, fs=7)
d_dieu = node('d_dieu', 220, 103, "DIEULAFOY'S / AORTO-ENTERIC", 'diag', w=145, h=22, fs=7)
nodes = [root, q1, d_var, q2, d_pud, q3, d_mw, q4, d_ca, d_dieu]
connect(root, q1)
connect(q1, d_var, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_pud, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_mw, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_ca, 'YES'); connect(q4, d_dieu, 'NO')
return nodes, 'Haematemesis', C_SYSTEM_GIT, CH
def chart_diarrhoea():
CH = 280
root = node('root', 260, 265, 'DIARRHOEA', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 235, 'Acute (<2 weeks)?', 'question', w=130, h=22, fs=7)
q2 = node('q2', 130, 202, 'Blood + mucus in stool?', 'question', w=130, h=22, fs=7)
d_bac = node('d_bac', 130, 169, 'BACILLARY DYSENTERY (Shigella)', 'diag', w=145, h=22, fs=7)
d_amo = node('d_amo', 130, 137, 'AMOEBIC COLITIS', 'diag', w=120, h=22, fs=7)
d_ent = node('d_ent', 280, 202, 'VIRAL / ETEC GASTROENTERITIS', 'diag', w=145, h=22, fs=7)
# Chronic
q3 = node('q3', 430, 202, 'Chronic (>4 weeks)?', 'question', w=130, h=22, fs=7)
q4 = node('q4', 430, 169, 'Blood + nocturnal symptoms?', 'question', w=140, h=22, fs=7)
d_ibd = node('d_ibd', 430, 137, 'IBD (UC / CROHN\'S)', 'diag', w=120, h=22, fs=7)
q5 = node('q5', 430, 105, 'Steatorrhoea + weight loss?', 'question', w=140, h=22, fs=7)
d_mal = node('d_mal', 430, 73, 'MALABSORPTION (Coeliac / EPI)', 'diag', w=145, h=22, fs=7)
d_ibs = node('d_ibs', 270, 105, 'IBS / MICROSCOPIC COLITIS', 'diag', w=135, h=22, fs=7)
nodes = [root, q1, q2, d_bac, d_amo, d_ent, q3, q4, d_ibd, q5, d_mal, d_ibs]
connect(root, q1)
connect(q1, q2, 'YES'); connect(q2, d_bac, 'YES'); connect(q2, d_ent, 'NO')
connect(q1, q3, 'NO'); connect(q3, q4, 'YES'); connect(q4, d_ibd, 'YES')
connect(q4, q5, 'NO'); connect(q5, d_mal, 'YES'); connect(q5, d_ibs, 'NO')
connect(q2, d_amo, 'NO') # second branch after enteric
return nodes, 'Diarrhoea', C_SYSTEM_GIT, CH
def chart_dysphagia():
CH = 260
root = node('root', 260, 248, 'DYSPHAGIA', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 218, 'Solids AND liquids from onset?', 'question', w=150, h=22, fs=7)
# YES = motility
q2 = node('q2', 120, 186, 'Regurgitation of old food?', 'question', w=140, h=22, fs=7)
d_ach = node('d_ach', 120, 154, 'ACHALASIA CARDIA', 'diag', w=120, h=22, fs=7)
d_des = node('d_des', 120, 122, 'DIFFUSE OESOPHAGEAL SPASM', 'diag', w=140, h=22, fs=7)
# NO = structural / progressive
q3 = node('q3', 400, 186, 'Progressive solids → liquids?', 'question', w=145, h=22, fs=7)
d_car = node('d_car', 400, 154, 'Ca OESOPHAGUS', 'redflag', w=120, h=22, fs=7)
q4 = node('q4', 400, 122, 'H/O GERD + heartburn?', 'question', w=130, h=22, fs=7)
d_stri = node('d_stri', 400, 90, 'PEPTIC STRICTURE', 'diag', w=120, h=22, fs=7)
d_pv = node('d_pv', 240, 122, 'PLUMMER-VINSON / EXT COMPRESSION', 'diag', w=155, h=22, fs=6)
nodes = [root, q1, q2, d_ach, d_des, q3, d_car, q4, d_stri, d_pv]
connect(root, q1)
connect(q1, q2, 'YES'); connect(q2, d_ach, 'YES'); connect(q2, d_des, 'NO')
connect(q1, q3, 'NO'); connect(q3, d_car, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_stri, 'YES'); connect(q4, d_pv, 'NO')
return nodes, 'Dysphagia', C_SYSTEM_GIT, CH
def chart_weakness():
CH = 310
root = node('root', 260, 296, 'LIMB WEAKNESS', 'root', w=150, h=28, fs=10)
q1 = node('q1', 260, 266, 'Sudden onset?', 'question', w=130, h=22, fs=7)
d_str = node('d_str', 120, 235, 'STROKE / TIA', 'redflag', w=115, h=22, fs=7)
q2 = node('q2', 380, 235, 'Progressive onset?', 'question', w=130, h=22, fs=7)
# UMN vs LMN
q3 = node('q3', 380, 204, 'UMN signs (spasticity, brisk reflexes)?', 'question', w=155, h=22, fs=6)
q4 = node('q4', 210, 204, 'LMN signs (wasting, fasciculations)?', 'question', w=155, h=22, fs=6)
d_mnd = node('d_mnd', 120, 172, 'MND / ALS (both UMN+LMN)', 'diag', w=135, h=22, fs=7)
d_rad = node('d_rad', 260, 172, 'RADICULOPATHY / GBS', 'diag', w=125, h=22, fs=7)
q5 = node('q5', 420, 172, 'Spinal cord level (band + bladder)?', 'question', w=150, h=22, fs=6)
d_sc = node('d_sc', 420, 140, 'SPINAL CORD COMPRESSION', 'redflag', w=140, h=22, fs=7)
d_ms = node('d_ms', 420, 108, 'MULTIPLE SCLEROSIS (relapsing)', 'diag', w=145, h=22, fs=7)
# Proximal weakness
q6 = node('q6', 120, 140, 'Proximal > distal?', 'question', w=125, h=22, fs=7)
d_myo = node('d_myo', 120, 108, 'MYOPATHY / POLYMYOSITIS', 'diag', w=130, h=22, fs=7)
d_mg = node('d_mg', 120, 76, 'MYASTHENIA GRAVIS (fatigable)', 'diag', w=135, h=22, fs=7)
nodes = [root, q1, d_str, q2, q3, q4, d_mnd, d_rad, q5, d_sc, d_ms, q6, d_myo, d_mg]
connect(root, q1)
connect(q1, d_str, 'YES'); connect(q1, q2, 'NO')
connect(q2, q3, 'YES'); connect(q2, q4, 'NO')
connect(q3, d_mnd, 'YES'); connect(q3, q5, 'NO')
connect(q4, q6, 'YES'); connect(q4, d_rad, 'NO')
connect(q5, d_sc, 'YES'); connect(q5, d_ms, 'NO')
connect(q6, d_myo, 'YES'); connect(q6, d_mg, 'NO')
return nodes, 'Limb Weakness', C_SYSTEM_CNS, CH
def chart_headache():
CH = 300
root = node('root', 260, 285, 'HEADACHE', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 255, 'Thunderclap - worst ever?', 'question', w=150, h=22, fs=7)
d_sah = node('d_sah', 120, 222, 'SUBARACHNOID HAEMORRHAGE', 'redflag', w=150, h=22, fs=7)
q2 = node('q2', 380, 222, 'Fever + neck stiffness?', 'question', w=140, h=22, fs=7)
d_men = node('d_men', 380, 190, 'MENINGITIS / ENCEPHALITIS', 'redflag', w=145, h=22, fs=7)
q3 = node('q3', 260, 190, 'Progressive + vomiting + papilloedema?', 'question', w=160, h=22, fs=6)
d_soc = node('d_soc', 120, 158, 'SPACE-OCCUPYING LESION / Raised ICP', 'redflag', w=155, h=22, fs=7)
q4 = node('q4', 380, 158, 'Unilateral, pulsatile, photophobia?', 'question', w=155, h=22, fs=7)
d_mig = node('d_mig', 380, 126, 'MIGRAINE', 'diag', w=110, h=22, fs=7)
q5 = node('q5', 380, 94, 'Temporal, jaw claudication >50yr?', 'question', w=155, h=22, fs=7)
d_gca = node('d_gca', 380, 62, 'GIANT CELL ARTERITIS', 'redflag', w=130, h=22, fs=7)
d_ten = node('d_ten', 220, 94, 'TENSION / CLUSTER HEADACHE', 'diag', w=145, h=22, fs=7)
nodes = [root, q1, d_sah, q2, d_men, q3, d_soc, q4, d_mig, q5, d_gca, d_ten]
connect(root, q1)
connect(q1, d_sah, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_men, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_soc, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_mig, 'YES'); connect(q4, q5, 'NO')
connect(q5, d_gca, 'YES'); connect(q5, d_ten, 'NO')
return nodes, 'Headache', C_SYSTEM_CNS, CH
def chart_cough():
CH = 290
root = node('root', 260, 276, 'COUGH', 'root', w=130, h=28, fs=10)
q1 = node('q1', 260, 246, 'Duration > 8 weeks (Chronic)?', 'question', w=155, h=22, fs=7)
# Acute
q2 = node('q2', 110, 214, 'Fever + productive sputum?', 'question', w=140, h=22, fs=7)
d_pne = node('d_pne', 110, 182, 'PNEUMONIA', 'diag', w=110, h=22, fs=7)
d_urt = node('d_urt', 110, 150, 'VIRAL URTI / ACUTE BRONCHITIS', 'diag', w=145, h=22, fs=7)
# Chronic
q3 = node('q3', 420, 214, 'Smoker > 10 pack-years?', 'question', w=145, h=22, fs=7)
d_cop = node('d_cop', 420, 182, 'COPD / CHRONIC BRONCHITIS', 'diag', w=140, h=22, fs=7)
q4 = node('q4', 420, 150, 'Episodic + wheeze?', 'question', w=130, h=22, fs=7)
d_ast = node('d_ast', 420, 118, 'BRONCHIAL ASTHMA', 'diag', w=125, h=22, fs=7)
q5 = node('q5', 420, 86, 'Fever + night sweats + wt loss?', 'question', w=155, h=22, fs=7)
d_tb = node('d_tb', 420, 54, 'PULMONARY TUBERCULOSIS', 'redflag', w=140, h=22, fs=7)
q6 = node('q6', 260, 150, 'On ACE inhibitor?', 'question', w=130, h=22, fs=7)
d_ace = node('d_ace', 260, 118, 'ACE INHIBITOR COUGH', 'diag', w=130, h=22, fs=7)
d_ild = node('d_ild', 260, 86, 'ILD / MALIGNANCY', 'diag', w=120, h=22, fs=7)
nodes = [root, q1, q2, d_pne, d_urt, q3, d_cop, q4, d_ast, q5, d_tb, q6, d_ace, d_ild]
connect(root, q1)
connect(q1, q2, 'NO'); connect(q2, d_pne, 'YES'); connect(q2, d_urt, 'NO')
connect(q1, q3, 'YES'); connect(q3, d_cop, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_ast, 'YES'); connect(q4, q5, 'NO')
connect(q5, d_tb, 'YES'); connect(q5, q6, 'NO')
connect(q6, d_ace, 'YES'); connect(q6, d_ild, 'NO')
return nodes, 'Cough', C_SYSTEM_RS, CH
def chart_dyspnoea():
CH = 300
root = node('root', 260, 285, 'BREATHLESSNESS', 'root', w=155, h=28, fs=10)
q1 = node('q1', 260, 255, 'Sudden onset?', 'question', w=130, h=22, fs=7)
# Sudden
q2 = node('q2', 120, 222, 'Pleuritic pain + tachycardia?', 'question', w=150, h=22, fs=7)
d_pe = node('d_pe', 120, 190, 'PULMONARY EMBOLISM', 'redflag', w=135, h=22, fs=7)
q3 = node('q3', 120, 158, 'Absent breath sounds + hyper-resonance?', 'question', w=160, h=22, fs=6)
d_pnx = node('d_pnx', 120, 126, 'PNEUMOTHORAX', 'redflag', w=120, h=22, fs=7)
d_af = node('d_af', 260, 222, 'ACUTE LVF / AF', 'redflag', w=115, h=22, fs=7)
# Chronic/progressive
q4 = node('q4', 420, 222, 'Progressive over weeks-months?', 'question', w=150, h=22, fs=7)
q5 = node('q5', 420, 190, 'Orthopnoea + PND?', 'question', w=130, h=22, fs=7)
d_lvf = node('d_lvf', 420, 158, 'CARDIAC FAILURE (LVF)', 'diag', w=130, h=22, fs=7)
q6 = node('q6', 420, 126, 'Wheeze + obstructive?', 'question', w=130, h=22, fs=7)
d_cop = node('d_cop', 420, 94, 'ASTHMA / COPD', 'diag', w=115, h=22, fs=7)
d_pf = node('d_pf', 260, 126, 'ILD / PULMONARY FIBROSIS', 'diag', w=135, h=22, fs=7)
nodes = [root, q1, q2, d_pe, q3, d_pnx, d_af, q4, q5, d_lvf, q6, d_cop, d_pf]
connect(root, q1)
connect(q1, q2, 'YES'); connect(q2, d_pe, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_pnx, 'YES'); connect(q3, d_af, 'NO')
connect(q1, q4, 'NO'); connect(q4, q5, 'YES'); connect(q5, d_lvf, 'YES')
connect(q5, q6, 'NO'); connect(q6, d_cop, 'YES'); connect(q6, d_pf, 'NO')
return nodes, 'Breathlessness', C_SYSTEM_RS, CH
def chart_haemoptysis():
CH = 270
root = node('root', 260, 258, 'HAEMOPTYSIS', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 228, 'Smoker >40 yrs + weight loss?', 'question', w=155, h=22, fs=7)
d_ca = node('d_ca', 120, 196, 'LUNG CARCINOMA', 'redflag', w=120, h=22, fs=7)
q2 = node('q2', 380, 196, 'Fever + night sweats + cough?', 'question', w=150, h=22, fs=7)
d_tb = node('d_tb', 380, 164, 'PULMONARY TUBERCULOSIS', 'redflag', w=145, h=22, fs=7)
q3 = node('q3', 380, 132, 'Copious purulent sputum + clubbing?', 'question', w=158, h=22, fs=6)
d_bron = node('d_bron', 380, 100, 'BRONCHIECTASIS / ASPERGILLOMA', 'diag', w=150, h=22, fs=7)
q4 = node('q4', 380, 68, 'Sudden dyspnoea + pleuritic pain?', 'question', w=155, h=22, fs=7)
d_pe = node('d_pe', 380, 36, 'PE + INFARCTION', 'redflag', w=115, h=22, fs=7)
d_ms = node('d_ms', 220, 100, 'MITRAL STENOSIS (MS)', 'diag', w=125, h=22, fs=7)
nodes = [root, q1, d_ca, q2, d_tb, q3, d_bron, q4, d_pe, d_ms]
connect(root, q1)
connect(q1, d_ca, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_tb, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_bron, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_pe, 'YES'); connect(q4, d_ms, 'NO')
return nodes, 'Haemoptysis', C_SYSTEM_RS, CH
def chart_chest_pain():
CH = 310
root = node('root', 260, 298, 'CHEST PAIN', 'root', w=140, h=28, fs=10)
q1 = node('q1', 260, 268, 'Tearing + maximal at onset + back radiation?', 'question', w=175, h=22, fs=6)
d_ao = node('d_ao', 110, 235, 'AORTIC DISSECTION', 'redflag', w=130, h=22, fs=7)
q2 = node('q2', 380, 235, 'Central, crushing >20 min, diaphoresis?', 'question', w=160, h=22, fs=6)
d_mi = node('d_mi', 380, 202, 'STEMI / NSTEMI / ACS', 'redflag', w=120, h=22, fs=7)
q3 = node('q3', 380, 170, 'Exertional, <20 min, relieved by GTN?', 'question', w=160, h=22, fs=6)
d_ang = node('d_ang', 380, 138, 'STABLE ANGINA', 'diag', w=115, h=22, fs=7)
q4 = node('q4', 380, 106, 'Pleuritic + sudden SOB?', 'question', w=145, h=22, fs=7)
d_pe = node('d_pe', 380, 74, 'PULMONARY EMBOLISM', 'redflag', w=135, h=22, fs=7)
q5 = node('q5', 380, 42, 'Sharp + relieved leaning forward?', 'question', w=155, h=22, fs=7)
d_per = node('d_per', 200, 74, 'PERICARDITIS', 'diag', w=115, h=22, fs=7)
d_gerd = node('d_gerd', 200, 42, 'GERD / MUSCULOSKELETAL', 'diag', w=135, h=22, fs=7)
nodes = [root, q1, d_ao, q2, d_mi, q3, d_ang, q4, d_pe, q5, d_per, d_gerd]
connect(root, q1)
connect(q1, d_ao, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_mi, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_ang, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_pe, 'YES'); connect(q4, q5, 'NO')
connect(q5, d_per, 'YES'); connect(q5, d_gerd, 'NO')
return nodes, 'Chest Pain', C_SYSTEM_CVS, CH
def chart_palpitations():
CH = 280
root = node('root', 260, 265, 'PALPITATIONS', 'root', w=150, h=28, fs=10)
q1 = node('q1', 260, 235, 'Irregular rhythm?', 'question', w=135, h=22, fs=7)
d_af = node('d_af', 120, 202, 'ATRIAL FIBRILLATION', 'diag', w=130, h=22, fs=7)
q2 = node('q2', 380, 202, 'Regular + sudden start/stop?', 'question', w=150, h=22, fs=7)
d_svt = node('d_svt', 380, 170, 'SVT / AVNRT', 'diag', w=110, h=22, fs=7)
q3 = node('q3', 380, 138, 'Exertional + syncope?', 'question', w=140, h=22, fs=7)
d_vt = node('d_vt', 380, 106, 'VT / HOCM', 'redflag', w=110, h=22, fs=7)
q4 = node('q4', 380, 74, 'Weight loss + tremor + exophthalmos?', 'question', w=160, h=22, fs=6)
d_thy = node('d_thy', 380, 42, 'THYROTOXICOSIS', 'diag', w=120, h=22, fs=7)
d_anx = node('d_anx', 220, 74, 'ANXIETY / ANAEMIA / ECTOPICS', 'diag', w=145, h=22, fs=7)
nodes = [root, q1, d_af, q2, d_svt, q3, d_vt, q4, d_thy, d_anx]
connect(root, q1)
connect(q1, d_af, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_svt, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_vt, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_thy, 'YES'); connect(q4, d_anx, 'NO')
return nodes, 'Palpitations', C_SYSTEM_CVS, CH
def chart_syncope():
CH = 285
root = node('root', 260, 272, 'SYNCOPE (LOC)', 'root', w=145, h=28, fs=10)
q1 = node('q1', 260, 242, 'Exertional?', 'question', w=125, h=22, fs=7)
d_hoc = node('d_hoc', 120, 210, 'HOCM / SEVERE AS / PHT', 'redflag', w=140, h=22, fs=7)
q2 = node('q2', 380, 210, 'Prodrome (nausea/diaphoresis)?', 'question', w=155, h=22, fs=7)
d_vvs = node('d_vvs', 380, 178, 'VASOVAGAL SYNCOPE (benign)', 'diag', w=145, h=22, fs=7)
q3 = node('q3', 380, 146, 'Postural (standing → faint)?', 'question', w=145, h=22, fs=7)
d_oh = node('d_oh', 380, 114, 'ORTHOSTATIC HYPOTENSION', 'diag', w=140, h=22, fs=7)
q4 = node('q4', 380, 82, 'Sudden, no prodrome (Stokes-Adams)?', 'question', w=158, h=22, fs=6)
d_chb = node('d_chb', 380, 50, 'COMPLETE HEART BLOCK / ARRHYTHMIA', 'redflag', w=155, h=22, fs=7)
d_sei = node('d_sei', 210, 82, 'SEIZURE (with post-ictal confusion)', 'diag', w=150, h=22, fs=7)
nodes = [root, q1, d_hoc, q2, d_vvs, q3, d_oh, q4, d_chb, d_sei]
connect(root, q1)
connect(q1, d_hoc, 'YES'); connect(q1, q2, 'NO')
connect(q2, d_vvs, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_oh, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_chb, 'YES'); connect(q4, d_sei, 'NO')
return nodes, 'Syncope', C_SYSTEM_CVS, CH
def chart_oedema():
CH = 280
root = node('root', 260, 265, 'PEDAL OEDEMA', 'root', w=145, h=28, fs=10)
q1 = node('q1', 260, 235, 'Bilateral pitting?', 'question', w=135, h=22, fs=7)
# Unilateral
d_dvt = node('d_dvt', 110, 202, 'DVT / CELLULITIS / LYMPHOEDEMA', 'diag', w=155, h=22, fs=7)
# Bilateral
q2 = node('q2', 390, 202, 'JVP raised + hepatomegaly?', 'question', w=150, h=22, fs=7)
d_rhf = node('d_rhf', 390, 170, 'RIGHT HEART FAILURE / CCF', 'diag', w=140, h=22, fs=7)
q3 = node('q3', 390, 138, 'Massive proteinuria (frothy urine)?', 'question', w=158, h=22, fs=7)
d_ns = node('d_ns', 390, 106, 'NEPHROTIC SYNDROME', 'diag', w=130, h=22, fs=7)
q4 = node('q4', 390, 74, 'Cirrhosis signs + ascites?', 'question', w=145, h=22, fs=7)
d_cir = node('d_cir', 390, 42, 'LIVER CIRRHOSIS (portal HTN + hypoalb)', 'diag', w=160, h=22, fs=6)
d_misc = node('d_misc', 220, 74, 'HYPOALBUMINAEMIA / DRUG / HYPOTHYROIDISM', 'diag', w=160, h=22, fs=6)
nodes = [root, q1, d_dvt, q2, d_rhf, q3, d_ns, q4, d_cir, d_misc]
connect(root, q1)
connect(q1, d_dvt, 'NO'); connect(q1, q2, 'YES')
connect(q2, d_rhf, 'YES'); connect(q2, q3, 'NO')
connect(q3, d_ns, 'YES'); connect(q3, q4, 'NO')
connect(q4, d_cir, 'YES'); connect(q4, d_misc, 'NO')
return nodes, 'Pedal Oedema', C_SYSTEM_CVS, CH
# ══════════════════════════════════════════════════════════════════════════════
# PAGE BUILDERS
# ══════════════════════════════════════════════════════════════════════════════
def system_header_table(title, subtitle, bg_color, ss):
data = [[Paragraph(f'<b>{title}</b>', ss['SystemTitle']),
Paragraph(subtitle, ss['SmallCenter'])]]
t = Table(data, colWidths=[W - 40*mm, None])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg_color),
('TEXTCOLOR', (0,0), (-1,-1), C_HEADER_FG),
('ROUNDEDCORNERS', [6]),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING',(0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
]))
return t
def legend_table(ss):
items = [
("■ ROOT", "#1a3a5c", "Starting symptom"),
("■ QUESTION", "#1565c0", "Decision point (Yes/No)"),
("■ YES path →", "#2e7d32", "Positive finding leads here"),
("■ DIAGNOSIS", "#4a148c", "Final diagnosis reached"),
("■ RED FLAG", "#c62828", "Emergency / urgent diagnosis"),
]
rows = []
for sym, col, desc in items:
rows.append([
Paragraph(f'<font color="{col}"><b>{sym}</b></font>', ss['Body8']),
Paragraph(desc, ss['Body8']),
])
t = Table(rows, colWidths=[60*mm, 80*mm])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), colors.HexColor("#f5f5f5")),
('GRID', (0,0), (-1,-1), 0.3, colors.HexColor("#bdbdbd")),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
return t
def make_chart_section(chart_func, ss):
"""Returns a list of flowables for one symptom chart."""
nodes, title, bg, chart_h = chart_func()
elems = []
# Symptom title bar
title_data = [[Paragraph(f'<b>{title}</b>', ss['SympTitle'])]]
tt = Table(title_data, colWidths=[W - 40*mm])
tt.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING',(0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 8),
('RIGHTPADDING', (0,0), (-1,-1), 8),
('BOX', (0,0), (-1,-1), 0.5, colors.HexColor("#90a4ae")),
('ROUNDEDCORNERS', [4]),
]))
elems.append(tt)
elems.append(Spacer(1, 3*mm))
# The flowchart drawing
fc = FlowchartFlowable(nodes, width=W - 40*mm, height=chart_h)
elems.append(fc)
elems.append(Spacer(1, 4*mm))
elems.append(HRFlowable(width="100%", thickness=0.5,
color=colors.HexColor("#cfd8dc")))
elems.append(Spacer(1, 3*mm))
return elems
# ── Red-flag summary table ─────────────────────────────────────────────────────
def red_flag_table(ss):
data = [
[Paragraph('<b>Symptom</b>', ss['SubHead']),
Paragraph('<b>Red Flag Feature</b>', ss['SubHead']),
Paragraph('<b>Emergency Diagnosis</b>', ss['SubHead'])],
['Abdominal Pain', 'Board-like rigidity, sudden onset', 'Perforation Peritonitis'],
['Abdominal Pain', 'Tearing, radiates to back', 'Aortic Dissection'],
['Haematemesis', 'Massive bleed + known cirrhosis', 'Variceal Bleed'],
['Jaundice', 'Painless + weight loss + Courvoisier', 'Ca Head of Pancreas'],
['Dysphagia', 'Progressive solids→liquids, weight loss', 'Ca Oesophagus'],
['Weakness', 'Saddle anaesthesia + bladder/bowel', 'Cauda Equina Syndrome'],
['Headache', 'Worst headache of life (thunderclap)', 'Subarachnoid Haemorrhage'],
['Headache', 'Fever + neck stiffness + Kernig', 'Bacterial Meningitis'],
['Cough/Haemoptysis', 'Smoker >40yr + weight loss', 'Lung Carcinoma'],
['Breathlessness', 'Sudden + pleuritic + tachycardia', 'Pulmonary Embolism'],
['Breathlessness', 'Sudden + absent breath sounds', 'Tension Pneumothorax'],
['Chest Pain', 'Tearing, maximal at onset, back rad', 'Aortic Dissection'],
['Chest Pain', 'Crushing >20 min, diaphoresis', 'STEMI / ACS'],
['Syncope', 'Exertional in young person/athlete', 'HOCM / Severe AS / VT'],
['Syncope', 'Sudden, no prodrome, Stokes-Adams', 'Complete Heart Block'],
['Fever + Rash', 'Petechial/purpuric rash + meningism', 'Meningococcal Septicaemia'],
]
col_w = [(W-40*mm) * f for f in [0.20, 0.43, 0.37]]
t = Table(data, colWidths=col_w, repeatRows=1)
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), C_HEADER_BG),
('TEXTCOLOR', (0,0), (-1,0), colors.white),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 8),
('ROWBACKGROUNDS', (0,1), (-1,-1),
[colors.HexColor("#ffebee"), colors.white]),
('GRID', (0,0), (-1,-1), 0.4, colors.HexColor("#e0e0e0")),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('TEXTCOLOR', (2,1), (2,-1), C_REDBOX),
('FONTNAME', (2,1), (2,-1), 'Helvetica-Bold'),
]))
return t
# ══════════════════════════════════════════════════════════════════════════════
# MAIN BUILD
# ══════════════════════════════════════════════════════════════════════════════
def build_pdf(out_path):
ss = make_styles()
doc = SimpleDocTemplate(
out_path,
pagesize=A4,
leftMargin=20*mm, rightMargin=20*mm,
topMargin=18*mm, bottomMargin=18*mm,
)
story = []
# ── COVER ──────────────────────────────────────────────────────────────────
story.append(Spacer(1, 30*mm))
story.append(Paragraph('DDx FLOWCHARTS', ss['Cover_Title']))
story.append(Paragraph('Quick Reference for Medicine Posting', ss['Cover_Sub']))
story.append(Spacer(1, 6*mm))
story.append(HRFlowable(width="60%", thickness=2, color=C_HEADER_BG))
story.append(Spacer(1, 6*mm))
story.append(Paragraph('2nd Year MBBS | History Taking & Differential Diagnosis', ss['Cover_Sub']))
story.append(Spacer(1, 4*mm))
story.append(Paragraph(
'Covers: Gastrointestinal · Neurological · Respiratory · Cardiovascular Systems',
ss['Cover_Tiny']))
story.append(Spacer(1, 10*mm))
# Legend
story.append(Paragraph('<b>How to read the flowcharts</b>', ss['SubHead']))
story.append(Spacer(1, 2*mm))
story.append(legend_table(ss))
story.append(Spacer(1, 6*mm))
story.append(Paragraph(
'At each QUESTION node, follow the GREEN arrow for YES and RED arrow for NO '
'until you reach a DIAGNOSIS (purple) or RED FLAG (urgent/emergency) node.',
ss['Body8']))
story.append(Spacer(1, 8*mm))
# Cover index
index_items = [
('GIT System', 'Abdominal Pain · Jaundice · Haematemesis · Diarrhoea · Dysphagia'),
('CNS System', 'Limb Weakness · Headache'),
('Respiratory', 'Cough · Breathlessness · Haemoptysis'),
('CVS System', 'Chest Pain · Palpitations · Syncope · Pedal Oedema'),
('Summary', 'Red Flag Symptoms Quick Reference Table'),
]
for sys_name, items in index_items:
story.append(Paragraph(
f'<b><font color="#1a3a5c">{sys_name}</font></b> — {items}',
ss['Body8']))
story.append(Spacer(1, 2*mm))
story.append(PageBreak())
# ── GIT ────────────────────────────────────────────────────────────────────
git_title = system_header_table(
'1. GASTROINTESTINAL SYSTEM', 'Abdominal Pain · Jaundice · Haematemesis · Diarrhoea · Dysphagia',
C_HEADER_BG, ss)
story.append(git_title)
story.append(Spacer(1, 5*mm))
for fn in [chart_abdominal_pain, chart_jaundice, chart_haematemesis, chart_diarrhoea, chart_dysphagia]:
story += make_chart_section(fn, ss)
story.append(PageBreak())
# ── CNS ────────────────────────────────────────────────────────────────────
cns_title = system_header_table(
'2. CENTRAL NERVOUS SYSTEM', 'Limb Weakness · Headache',
C_HEADER_BG, ss)
story.append(cns_title)
story.append(Spacer(1, 5*mm))
for fn in [chart_weakness, chart_headache]:
story += make_chart_section(fn, ss)
story.append(PageBreak())
# ── RESPIRATORY ────────────────────────────────────────────────────────────
rs_title = system_header_table(
'3. RESPIRATORY SYSTEM', 'Cough · Breathlessness · Haemoptysis',
C_HEADER_BG, ss)
story.append(rs_title)
story.append(Spacer(1, 5*mm))
for fn in [chart_cough, chart_dyspnoea, chart_haemoptysis]:
story += make_chart_section(fn, ss)
story.append(PageBreak())
# ── CVS ────────────────────────────────────────────────────────────────────
cvs_title = system_header_table(
'4. CARDIOVASCULAR SYSTEM', 'Chest Pain · Palpitations · Syncope · Pedal Oedema',
C_HEADER_BG, ss)
story.append(cvs_title)
story.append(Spacer(1, 5*mm))
for fn in [chart_chest_pain, chart_palpitations, chart_syncope, chart_oedema]:
story += make_chart_section(fn, ss)
story.append(PageBreak())
# ── RED FLAG SUMMARY ───────────────────────────────────────────────────────
rf_header = system_header_table(
'RED FLAG QUICK REFERENCE', 'Never miss these — Emergency Diagnoses by Symptom',
C_REDBOX, ss)
story.append(rf_header)
story.append(Spacer(1, 5*mm))
story.append(red_flag_table(ss))
story.append(Spacer(1, 8*mm))
story.append(Paragraph(
'<b>Remember:</b> History alone leads to the correct diagnosis in ~80% of cases. '
'A red flag symptom = STOP and rule out the emergency first.',
ss['Body8']))
# Build
doc.build(story)
print(f"PDF written to: {out_path}")
if __name__ == '__main__':
build_pdf('/tmp/workspace/ddx-flowcharts/DDx_Flowcharts_Medicine_Posting.pdf')
cd /tmp/workspace/ddx-flowcharts && python3 generate_pdf.py 2>&1
ls -lh /tmp/workspace/ddx-flowcharts/DDx_Flowcharts_Medicine_Posting.pdf && python3 -c " from pypdf import PdfReader r = PdfReader('/tmp/workspace/ddx-flowcharts/DDx_Flowcharts_Medicine_Posting.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): t = p.extract_text() print(f' Page {i+1}: {t[:80].strip()[:60]}...' if t else f' Page {i+1}: (graphic only)') "
DDx Flowcharts Medicine Posting
PDF Document · PDF
| Page(s) | Content |
|---|---|
| 1 | Cover + How to read + Index + Colour legend |
| 2-4 | GIT System - Abdominal Pain, Jaundice, Haematemesis, Diarrhoea, Dysphagia |
| 5-6 | CNS System - Limb Weakness, Headache |
| 7-8 | Respiratory - Cough, Breathlessness, Haemoptysis |
| 9-11 | CVS System - Chest Pain, Palpitations, Syncope, Pedal Oedema |
| 12 | Red Flag Quick Reference Table - 17 emergency diagnoses by symptom |
Make a checklist PDF of normal vs abnormal examination findings
~/ddx-flowcharts/generate_checklist.py
"""
Normal vs Abnormal Examination Findings Checklist PDF
Medicine Posting - 2nd Year MBBS
All 4 Systems: GIT, CNS, Respiratory, CVS
Layout: Finding | Normal | Abnormal | Clinical Significance
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, PageBreak,
Table, TableStyle, HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
W, H = A4
# ── Colours ────────────────────────────────────────────────────────────────────
C_NAVY = colors.HexColor("#1a3a5c")
C_WHITE = colors.white
C_NORMAL = colors.HexColor("#e8f5e9") # light green
C_ABNORMAL = colors.HexColor("#ffebee") # light red
C_SIG = colors.HexColor("#fff8e1") # light amber
C_SECTION = colors.HexColor("#e3f2fd") # light blue - section subheading
C_NORMAL_TXT= colors.HexColor("#1b5e20") # dark green
C_ABNORM_TXT= colors.HexColor("#b71c1c") # dark red
C_SIG_TXT = colors.HexColor("#e65100") # dark orange
# System accent colours
SYS_GIT = colors.HexColor("#2e7d32")
SYS_CNS = colors.HexColor("#6a1b9a")
SYS_RS = colors.HexColor("#1565c0")
SYS_CVS = colors.HexColor("#c62828")
# ── Styles ─────────────────────────────────────────────────────────────────────
def make_styles():
ss = getSampleStyleSheet()
def add(name, **kw):
if name not in ss:
ss.add(ParagraphStyle(name=name, **kw))
return ss[name]
add('CoverTitle', fontName='Helvetica-Bold', fontSize=26,
textColor=C_NAVY, alignment=TA_CENTER, spaceAfter=6)
add('CoverSub', fontName='Helvetica', fontSize=12,
textColor=colors.HexColor("#455a64"), alignment=TA_CENTER, spaceAfter=4)
add('CoverTiny', fontName='Helvetica', fontSize=8,
textColor=colors.grey, alignment=TA_CENTER, spaceAfter=2)
add('SysHeader', fontName='Helvetica-Bold', fontSize=14,
textColor=C_WHITE, alignment=TA_LEFT, spaceAfter=0, leading=18)
add('SecHead', fontName='Helvetica-Bold', fontSize=9,
textColor=C_NAVY, alignment=TA_LEFT, spaceAfter=0, leading=12)
add('ColHead', fontName='Helvetica-Bold', fontSize=8,
textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=0, leading=10)
add('Cell', fontName='Helvetica', fontSize=7.5,
textColor=colors.HexColor("#212121"), leading=10, spaceAfter=0)
add('CellBold', fontName='Helvetica-Bold', fontSize=7.5,
textColor=C_NAVY, leading=10, spaceAfter=0)
add('CellNorm', fontName='Helvetica', fontSize=7.5,
textColor=C_NORMAL_TXT, leading=10, spaceAfter=0)
add('CellAbn', fontName='Helvetica', fontSize=7.5,
textColor=C_ABNORM_TXT, leading=10, spaceAfter=0)
add('CellSig', fontName='Helvetica', fontSize=7,
textColor=colors.HexColor("#4a148c"), leading=9.5, spaceAfter=0)
add('Body8', fontName='Helvetica', fontSize=8,
textColor=colors.HexColor("#212121"), leading=11, spaceAfter=2)
add('LegendHead', fontName='Helvetica-Bold', fontSize=9,
textColor=C_NAVY, spaceAfter=2)
return ss
SS = make_styles()
# ── Column widths (A4 usable = W - 40mm = ~515pt) ─────────────────────────────
TW = W - 40*mm # total table width ≈ 515 pt
CW = [TW*0.22, TW*0.19, TW*0.30, TW*0.29] # Finding | Normal | Abnormal | Significance
# ── Helper builders ────────────────────────────────────────────────────────────
def p(text, style='Cell'):
return Paragraph(text, SS[style])
def sys_header_block(title, subtitle, accent):
"""Full-width coloured header for each system."""
data = [[
Paragraph(f'<b>{title}</b>', SS['SysHeader']),
Paragraph(subtitle, SS['CoverTiny'])
]]
t = Table(data, colWidths=[TW * 0.6, TW * 0.4])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), accent),
('TOPPADDING', (0,0), (-1,-1), 7),
('BOTTOMPADDING', (0,0), (-1,-1), 7),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TEXTCOLOR', (0,0), (-1,-1), C_WHITE),
]))
return t
def col_header_row():
return [
p('<b>FINDING / TEST</b>', 'ColHead'),
p('<b>NORMAL</b>', 'ColHead'),
p('<b>ABNORMAL</b>', 'ColHead'),
p('<b>CLINICAL SIGNIFICANCE</b>', 'ColHead'),
]
def section_row(title):
"""A merged section sub-heading row."""
return [p(f' {title}', 'SecHead'), '', '', '']
def row(finding, normal, abnormal, significance):
return [
p(finding, 'CellBold'),
p(normal, 'CellNorm'),
p(abnormal,'CellAbn'),
p(significance, 'CellSig'),
]
def make_table(data_rows):
"""Assemble a styled table with column header + data rows."""
all_rows = [col_header_row()] + data_rows
t = Table(all_rows, colWidths=CW, repeatRows=1)
# Base style
style = [
# Header row
('BACKGROUND', (0,0), (-1,0), C_NAVY),
('TEXTCOLOR', (0,0), (-1,0), C_WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,0), 8),
('ALIGN', (0,0), (-1,0), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 4),
('RIGHTPADDING', (0,0), (-1,-1), 4),
('GRID', (0,0), (-1,-1), 0.35, colors.HexColor("#bdbdbd")),
('ROWBACKGROUNDS',(0,1), (-1,-1), [C_WHITE, colors.HexColor("#f9f9f9")]),
]
# Section rows: merge + highlight
for i, r in enumerate(data_rows, start=1):
if r[1] == '' and r[2] == '' and r[3] == '':
style += [
('SPAN', (0,i), (3,i)),
('BACKGROUND', (0,i), (3,i), C_SECTION),
('FONTNAME', (0,i), (3,i), 'Helvetica-Bold'),
]
t.setStyle(TableStyle(style))
return t
# ══════════════════════════════════════════════════════════════════════════════
# DATA: GIT SYSTEM
# ══════════════════════════════════════════════════════════════════════════════
def git_general_data():
return [
section_row('GENERAL EXAMINATION'),
row('Built & Nourishment',
'Normal BMI (18.5-24.9)\nAdequate subcutaneous fat\nNormal mid-arm circumference',
'Cachexia / wasting\nObesity (BMI >30)\nLow waist-hip ratio',
'Cachexia → malignancy, TB, cirrhosis, IBD\nObesity → NAFLD, gallstones, DM'),
row('Pallor',
'Pink conjunctivae\nPink tongue and palms',
'Pale conjunctivae\nPale tongue / palmar creases',
'GI bleed (haematemesis, melaena)\nMalabsorption (Fe, B12, folate)\nAncylostomiasis'),
row('Icterus (Jaundice)',
'White sclerae\nNormal skin colour',
'Yellow sclerae (earliest sign)\nYellow skin, mucous membranes',
'Pre-hepatic / Hepatic / Post-hepatic jaundice\nSclerae go yellow first (high elastin content)'),
row('Cyanosis',
'Pink lips and tongue (central)\nWarm pink periphery',
'Central: bluish tongue / lips\nPeripheral: bluish fingertips',
'Central cyanosis with GI → hepatopulmonary syndrome\nAlpha-1 AT deficiency (emphysema + cirrhosis)'),
row('Clubbing',
'Normal nail fold angle <160°\nNo fluctuance',
'Loss of nail fold angle >180°\nFluctuation of nail bed\nDrum-stick appearance',
'GI causes: Crohn\'s, UC, cirrhosis, coeliac\nSchamroth sign: diamond gap lost'),
row('Lymphadenopathy',
'Not palpable',
'Palpable nodes: soft/hard, tender/non-tender\nLeft supraclavicular (Virchow\'s node)',
'Virchow\'s node (Troisier\'s sign) → GI malignancy\nHard non-tender → metastatic carcinoma'),
row('Pedal Oedema',
'No pitting on pressure',
'Pitting oedema: bilateral/unilateral\nDepth graded 1+ to 4+',
'Bilateral: liver cirrhosis (hypoalb + portal HTN)\nNephrotic, cardiac, nutritional'),
section_row('SIGNS OF LIVER FAILURE'),
row('Spider Naevi',
'Absent (may have 1-5 in normal individuals)',
'>5 spider naevi, especially upper trunk',
'Portal hypertension / cirrhosis\nOestrogen excess (liver cannot metabolise)\nAlso in pregnancy'),
row('Palmar Erythema',
'Normal palm colour',
'Erythema of thenar/hypothenar eminences\n(spares palm centre)',
'Liver cirrhosis, RA, thyrotoxicosis, pregnancy\nVasodilation from elevated oestrogen'),
row('Gynaecomastia',
'No breast tissue in males',
'Palpable glandular breast tissue in males',
'Cirrhosis (↑ oestrogen)\nAlso: spironolactone, digoxin, cimetidine'),
row('Leukonychia (White Nails)',
'Pink nail beds',
'Diffuse white discolouration of nails',
'Hypoalbuminaemia (cirrhosis, nephrotic)\nMuehrcke\'s lines = parallel white bands'),
row('Dupuytren\'s Contracture',
'Full finger extension',
'Flexion contracture of ring/little finger\nThickened palmar fascia',
'Alcoholic liver disease\nAlso: DM, trauma, epileptic drugs'),
row('Flapping Tremor (Asterixis)',
'No flapping on outstretched hands',
'Coarse, irregular flapping when wrists extended',
'Hepatic encephalopathy (NH3 accumulation)\nAlso: CO2 retention, uraemia'),
row('Parotid Enlargement',
'Non-palpable',
'Bilateral painless parotid enlargement',
'Alcoholic liver disease (nutritional deficiency)\nAlso: mumps, Sjogren\'s, bulimia'),
row('Testicular Atrophy',
'Normal testicular size',
'Reduced testicular volume',
'Cirrhosis → hypothalamo-pituitary axis suppression'),
row('Alopecia (in liver disease)',
'Normal scalp hair',
'Diffuse hair loss',
'Zinc / nutritional deficiency in cirrhosis'),
section_row('ABDOMINAL INSPECTION'),
row('Shape of Abdomen',
'Scaphoid (slightly concave) or flat in lying position',
'Distended uniformly → ascites, obstruction\nLocalised bulge → hernia, mass, organomegaly\nEverted umbilicus → ascites',
'Uniform distension with full flanks → ascites\nCentral distension → intestinal obstruction'),
row('Skin over Abdomen',
'Normal colour, no dilated veins\nNo scars or sinuses',
'Caput medusae (dilated veins radiating from umbilicus)\nStretched shiny skin → ascites\nStriae',
'Caput medusae → portal HTN (flow away from umbilicus)\nSVC obstruction: flow downward\nIVC obstruction: flow upward'),
row('Umbilicus',
'Central, inverted',
'Everted → raised intra-abdominal pressure (ascites, mass)\nShifted → organomegaly, mass',
'Everted umbilicus with distension → ascites / large mass'),
row('Visible Peristalsis',
'Not visible',
'Visible gastric peristalsis (left to right)\nLadder pattern (intestinal)',
'Gastric outlet obstruction (pyloric stenosis)\nIntestinal obstruction'),
row('Visible Pulsations',
'Epigastric pulsation may be normal in thin individuals',
'Prominent, visible epigastric pulsation',
'Aortic aneurysm\nRight ventricular hypertrophy (transmitted pulsation)'),
section_row('ABDOMINAL PALPATION'),
row('Tenderness',
'No tenderness on palpation',
'Localised: RIF (appendicitis), RUQ (cholecystitis), epigastric (PUD)\nGuarding: voluntary/involuntary muscle contraction\nRigidity: board-like, involuntary',
'Guarding → peritoneal irritation\nRigidity → peritonitis\nRebound tenderness (Blumberg) → peritoneal involvement'),
row('Liver',
'Not palpable below costal margin OR just palpable at end of deep inspiration\nUpper border: 5th ICS (right mid-clavicular line)',
'Palpable >2 cm below costal margin\nRCML or crossed midline\nTender / pulsatile / nodular / hard',
'Tender hepatomegaly: hepatitis, CCF (congestive), liver abscess\nPulsatile liver: tricuspid regurgitation\nNodular hard: cirrhosis, metastases'),
row('Spleen',
'Not palpable',
'Palpable below left costal margin\nNotched edge, moves to RIF on inspiration\nCannot insunate fingers between mass and costal margin',
'Mild: 1-3 cm → infection (EBV, malaria, typhoid), CCF\nModerate: 3-8 cm → haemolysis, cirrhosis\nMassive: >8 cm → CML, Kala-azar, myelofibrosis'),
row('Kidney',
'Not palpable normally (right may be just palpable in thin individuals)',
'Bimanually palpable, ballotable\nBand of colonic resonance anterior to mass',
'Renal cell carcinoma, polycystic kidneys, hydronephrosis\nBand of resonance distinguishes kidney from spleen'),
row('Murphy\'s Sign',
'Negative: no arrest of inspiration on deep pressure RUQ',
'Positive: arrest of inspiration on pressure over gallbladder',
'Acute cholecystitis (sensitivity 65%, specificity 87%)'),
row('McBurney\'s Tenderness',
'No tenderness at McBurney\'s point (1/3 from ASIS to umbilicus)',
'Maximum tenderness at McBurney\'s point',
'Acute appendicitis\nRovsing\'s sign: pain at RIF on palpating LIF'),
section_row('ABDOMINAL PERCUSSION'),
row('Liver Span',
'Normal liver span: 8-12 cm in right MCL by percussion',
'<8 cm: small liver (cirrhosis end-stage, fulminant failure)\n>12 cm: hepatomegaly',
'Dull percussion over 5th ICS to lower costal margin = liver'),
row('Shifting Dullness',
'Absent: flanks remain resonant on turning',
'Present: dullness shifts to dependent flank on turning patient',
'Ascites: needs >500 mL fluid to elicit shifting dullness\nSensitivity ~83%'),
row('Fluid Thrill',
'Absent',
'Present: transmitted wave felt on opposite flank',
'Tense ascites: large volume\nMore specific than shifting dullness\nAssistant\'s hand on mid-abdomen to prevent fat wave'),
row('Traube\'s Space',
'Resonant (air in gastric fundus and splenic flexure)',
'Dull',
'Splenomegaly obliterates Traube\'s space (9th-11th rib, left side)\nAlso dull: left pleural effusion, full stomach'),
section_row('ABDOMINAL AUSCULTATION'),
row('Bowel Sounds',
'3-5 sounds/minute, small bowel\nHigh-pitched, gurgling',
'Increased, high-pitched (tinkling)\nHyperactive\nAbsent (silent abdomen)',
'Tinkling: early intestinal obstruction\nHyperactive: diarrhoea, gastroenteritis\nAbsent: paralytic ileus, peritonitis'),
row('Venous Hum',
'Absent',
'Soft hum in epigastrium',
'Portal hypertension: Cruveilhier-Baumgarten syndrome\nRecanalized umbilical vein'),
row('Bruit',
'Absent',
'Hepatic bruit: hepatoma (systolic)\nSplenic bruit: haemangioma\nRenal bruit: renal artery stenosis\nAortic bruit: aneurysm',
'Hepatoma bruit → hepatocellular carcinoma\nRenal bruit → renovascular hypertension'),
]
# ══════════════════════════════════════════════════════════════════════════════
# DATA: CNS SYSTEM
# ══════════════════════════════════════════════════════════════════════════════
def cns_general_data():
return [
section_row('HIGHER MENTAL FUNCTIONS'),
row('Consciousness & Orientation',
'Alert, oriented to Time/Place/Person\nGCS 15/15',
'Confused, disoriented\nDrowsy / stuporous / comatose\nGCS <15',
'Reduced GCS → brain injury, metabolic (hepatic, uraemic, DKA), structural\nAcute confusional state → meningitis, encephalitis, drugs'),
row('Memory',
'Immediate: recalls 3 words at 1 min\nRecent: recalls events of past days\nRemote: recalls distant events\nAll intact',
'Immediate loss → frontal lobe\nRecent loss: earliest in Alzheimer\'s\nRemote loss: late dementia\nKorsakoff: recent loss + confabulation',
'Hippocampus: recent memory\nFrontal lobe: working memory\nKorsakoff\'s → thiamine deficiency (Wernicke\'s)'),
row('Speech',
'Fluent, normal comprehension\nNormal naming, repetition\nNormal writing and reading',
'Broca\'s aphasia: non-fluent, comprehension intact\nWernicke\'s: fluent, comprehension impaired, jargon speech\nDysarthria: slurred but language content normal',
'Broca\'s area: posterior inferior frontal gyrus\nWernicke\'s: posterior superior temporal gyrus\nDysarthria: cerebellar, bulbar palsy, pseudobulbar'),
section_row('CRANIAL NERVES'),
row('CN I - Olfactory',
'Identifies familiar odours bilaterally',
'Anosmia (unilateral / bilateral)',
'Unilateral: olfactory groove meningioma, anterior fossa fracture\nBilateral: Parkinson\'s (early), COVID-19, Kallmann\'s'),
row('CN II - Optic',
'Visual acuity 6/6 or 20/20\nNormal colour vision\nFull visual fields\nSharp disc margins on fundoscopy',
'Reduced acuity / field defect\nPapilloedema (blurred disc margins)\nOptic atrophy (pale disc)',
'Papilloedema → raised ICP (bilateral)\nOptic neuritis → MS (unilateral, painful)\nBitemporal hemianopia → pituitary adenoma'),
row('CN III, IV, VI - Oculomotor',
'Full and conjugate extraocular movements\nPupils equal, round, reactive to light (PERLA)\nNo ptosis or diplopia\nNo nystagmus',
'Ptosis, dilated pupil, "down and out" eye → CN III palsy\nCN VI: medial deviation, diplopia on lateral gaze\nHorner\'s: ptosis + miosis + anhidrosis',
'CN III: posterior communicating artery aneurysm, tentorial herniation, DM (pupil-sparing)\nHorner\'s → pancoast tumour, carotid dissection'),
row('CN V - Trigeminal',
'Normal facial sensation (all 3 divisions)\nNormal muscles of mastication, jaw deviates to midline',
'Facial sensory loss in dermatomal distribution\nJaw deviation on opening (towards side of lesion - LMN)',
'Trigeminal neuralgia: lancinating, trigger zones\nAcoustic neuroma compresses V at CPA\nJaw deviates to side of LMN lesion (weak pterygoid)'),
row('CN VII - Facial',
'Symmetrical facial expressions\nForehead wrinkles bilaterally on raising eyebrows\nTaste intact anterior 2/3 tongue',
'LMN: entire face involved (forehead + lower)\nUMN: lower face only (forehead spared)\nLoss of taste anterior 2/3',
'LMN facial palsy: Bell\'s palsy (HSV), Ramsay Hunt (VZV), parotid tumour, acoustic neuroma\nUMN: stroke (contralateral)'),
row('CN VIII - Vestibulocochlear',
'Hears whispered voice at 60 cm\nRinne: AC > BC (positive)\nWeber: no lateralisation',
'Rinne negative (BC > AC) → conductive loss\nWeber lateralises: to deaf ear (conductive) or to good ear (SNHL)\nNystagmus (vestibular component)',
'SNHL: acoustic neuroma, aminoglycoside toxicity, Meniere\'s\nConductive: otosclerosis, otitis media\nMeniere\'s: episodic vertigo + tinnitus + low-freq SNHL'),
row('CN IX, X - Glossopharyngeal/Vagus',
'Normal gag reflex\nUvula midline\nNormal palatal movement\nNormal swallowing\nNormal voice',
'Absent gag (IX afferent, X efferent)\nUvula deviates away from lesion side (UMN) or towards lesion side (LMN)\nNasal voice, nasal regurgitation',
'Bulbar palsy (LMN): MND, GBS, diphtheria, syringobulbia\nPseudoBulbar (UMN): stroke, MS, MND → brisk gag, dysarthria + dysphagia'),
row('CN XI - Spinal Accessory',
'Turns head against resistance (SCM)\nShrugs shoulder against resistance (trapezius)',
'Weakness of head turning or shoulder shrugging',
'Neck dissection injury\nForamen magnum / posterior fossa tumour'),
row('CN XII - Hypoglossal',
'Tongue midline, no wasting\nNormal tongue movements',
'LMN: tongue deviates to side of lesion, wasting, fasciculations\nUMN: spastic tongue, deviates away',
'LMN: medullary infarct, hypoglossal canal neuroma, MND\nBilateral: motor neurone disease, pseudobulbar palsy'),
section_row('MOTOR SYSTEM'),
row('Muscle Tone',
'Normal tone: gentle resistance throughout range of movement',
'Hypotonia: floppy, pendular reflexes (LMN, cerebellar)\nSpasticity: velocity-dependent, clasp-knife (UMN)\nRigidity: constant resistance throughout range\n - Lead-pipe: Parkinson\'s\n - Cogwheel: Parkinson\'s + tremor',
'Spasticity → UMN (corticospinal)\nLead-pipe rigidity → Parkinson\'s (extrapyramidal)\nClasp-knife phenomenon: spinal cord injury'),
row('Muscle Power (MRC Scale)',
'MRC Grade 5: full power against resistance in all muscle groups\nSymmetrical',
'Grade 0: no movement\nGrade 1: flicker\nGrade 2: movement only with gravity eliminated\nGrade 3: moves against gravity\nGrade 4: reduced power against resistance\nGrade 5-: just under normal',
'Proximal weakness (4, 3) → myopathy\nDistal weakness → neuropathy\nHemi pattern → UMN (stroke, SOL)\nPara pattern → spinal cord'),
row('Deep Tendon Reflexes',
'Grade 2+ (normal) in all limbs\nSymmetrical\nBiceps C5-6, Triceps C7, Knee L3-4, Ankle S1-2',
'Hyperreflexia (3+ or 4+) → UMN lesion\nHyporeflexia / areflexia → LMN, peripheral neuropathy, GBS\nSpread of reflex: corticospinal lesion\nClonus (>5 beats) → UMN',
'UMN signs: ↑ tone + ↑ reflexes + ↑ plantar\nLMN signs: ↓ tone + ↓ reflexes + ↓ power + wasting + fasciculations'),
row('Plantar Response',
'Flexor plantar: toes curl downward on stroking outer sole',
'Extensor plantar (Babinski sign): big toe extends, other toes fan out',
'Positive Babinski → UMN lesion (corticospinal tract)\nNormal in infants <18 months\nElicited by stroking outer sole lateral to medial'),
row('Superficial Reflexes',
'Abdominal reflex: contraction of abdominal muscles\nCremasteric reflex present in males\nAnal reflex: anal wink on perianal scratch',
'Absent abdominal reflex: UMN lesion (MS, cord lesion)\nAbsent cremasteric: UMN or inguinal surgery\nAbsent anal reflex: cauda equina lesion',
'Abdominal reflex lost in 50% of MS patients\nLoss of anal reflex → cauda equina emergency'),
section_row('SENSORY SYSTEM'),
row('Touch / Pain / Temperature',
'Normal touch, pain, temperature perception\nSymmetrical in all dermatomes',
'Loss in dermatomal pattern → nerve root\nGlove-stocking loss → peripheral neuropathy\nHemisensory loss → thalamic/cortical\nSplit sensory loss → syringomyelia',
'Syringomyelia: suspended sensory loss (cape distribution), pain + temperature lost, touch preserved\nBrown-Sequard: ipsilateral motor + proprioception, contralateral pain + temperature'),
row('Vibration Sense',
'Feels vibration of 128 Hz tuning fork at malleoli, patella, ASIS, sternum',
'Loss at feet: peripheral neuropathy, dorsal column disease',
'Dorsal columns carry vibration + joint position sense (ipsilateral)\nDiabetic neuropathy, B12 deficiency, tabes dorsalis'),
row('Joint Position Sense (JPS)',
'Correctly identifies up/down movements of toes/fingers',
'Loss of JPS: positive Romberg\'s sign\nPseudoathetosis (writhing movements of hands with eyes closed)',
'Posterior column disease (B12, tabes, SACD)\nRomberg positive: ataxia worse in dark, vestibular or cerebellar'),
section_row('CEREBELLAR SIGNS'),
row('Nystagmus',
'Absent at rest and on gaze in all directions',
'Horizontal nystagmus: fast phase away from lesion side\nVertical nystagmus: brainstem\nBidirectional: drugs (phenytoin)',
'Cerebellar nystagmus: fast phase to side of lesion (ipsilateral)\nBrainstem: vertical or direction-changing nystagmus'),
row('Dysmetria / Intention Tremor',
'Smooth, accurate finger-nose-finger test\nHeel-shin test smooth',
'Past-pointing on FNF (intention tremor, overshooting)\nHeel-shin: zig-zag path',
'Ipsilateral cerebellar lesion → ipsilateral dysmetria\nIncrease as finger approaches target = intention tremor'),
row('Dysdiadochokinesia',
'Rapid alternating movements smooth and regular',
'Irregular, clumsy rapid alternating movements',
'Cerebellar lesion (ipsilateral)\nTest: rapid pronation-supination of hands'),
row('Rebound Phenomenon',
'Arm returns smoothly to position after resistance released',
'Overshoot on release of resistance',
'Cerebellar disease: lack of check reflex\nTest: pull arm against resistance, then suddenly release'),
row('Gait',
'Narrow-based, coordinated, normal heel-toe walking\nAble to tandem walk (heel-to-toe in straight line)',
'Wide-based, ataxic, staggering gait → cerebellar\nSpastic, scissor gait → UMN bilateral\nSteppage gait → foot drop (LMN / peroneal nerve)\nParkinsonian: shuffling, festinant, reduced arm swing\nSensory ataxia: stamping, worse in dark',
'Wide-based cerebellar gait: "drunk walk"\nParkinsonian gait: en-bloc turning, freezing, propulsion\nWaddling gait: proximal myopathy'),
section_row('MENINGEAL SIGNS'),
row('Neck Stiffness',
'Full range of passive neck flexion without resistance',
'Resistance to passive neck flexion\nNuchal rigidity',
'Meningitis (bacterial, viral, TB, fungal)\nSAH (blood in CSF causes meningism)\nNot present in encephalitis alone'),
row('Kernig\'s Sign',
'Negative: can extend knee when hip flexed 90°',
'Positive: pain and resistance to knee extension when hip at 90°',
'Meningeal irritation → sensitivity ~27%, specificity ~95% for meningitis'),
row('Brudzinski\'s Sign',
'Negative: passive neck flexion does not flex hips',
'Positive: involuntary hip and knee flexion on passive neck flexion',
'Meningeal irritation (meningitis, SAH)\nMore sensitive than Kernig\'s in adults'),
]
# ══════════════════════════════════════════════════════════════════════════════
# DATA: RESPIRATORY SYSTEM
# ══════════════════════════════════════════════════════════════════════════════
def rs_general_data():
return [
section_row('GENERAL & VITAL SIGNS'),
row('Respiratory Rate',
'Adult: 12-18 breaths/minute\nRegular rhythm\nThoracoabdominal pattern',
'Tachypnoea (>20/min): respiratory failure, fever, PE, metabolic acidosis\nBradypnoea (<12/min): CNS depression, opioid\nCheyne-Stokes: alternating hyperpnoea-apnoea\nKussmaul: deep, regular, rapid - DKA',
'Tachypnoea is the most sensitive indicator of respiratory distress\nKussmaul breathing → metabolic acidosis (compensatory)'),
row('Cyanosis',
'Peripheral SpO2 >95%\nPink lips and tongue',
'Central cyanosis: bluish tongue (SpO2 <85%)\nPeripheral: cold blue periphery with normal tongue',
'Central cyanosis: V/Q mismatch, right-to-left shunt, hypoventilation\nPeripheral: vasoconstriction, cardiac failure, Raynaud\'s'),
row('Clubbing',
'Nail fold angle <160°, no fluctuation',
'Increased nail fold angle, fluctuant nail bed\nCurved nails (watch-glass)\nDrum-stick fingers',
'RS causes: lung carcinoma, mesothelioma, ILD, bronchiectasis, lung abscess, empyema\nNOT in COPD/asthma/PE'),
row('Jugular Venous Pressure',
'<4 cm above sternal angle at 45° (normal JVP)\nInspiratory fall in JVP (Kussmaul negative)',
'Raised JVP: distended veins at 45°\nKussmaul\'s sign: JVP rises on inspiration',
'Raised JVP in RS → cor pulmonale (right heart failure from lung disease)\nKussmaul\'s sign → constrictive pericarditis, severe RHF'),
section_row('UPPER RESPIRATORY TRACT'),
row('Nasal / Oral Cavity',
'Clear nostrils, no DNS\nNormal tonsils, no discharge\nClean oral hygiene',
'Flaring alae nasi: accessory muscle use in respiratory distress\nDeviated nasal septum\nPost-nasal drip, tonsillar enlargement\nOral candidiasis → HIV/immunosuppression',
'Flaring alae nasi: significant respiratory distress\nPost-nasal drip → chronic cough'),
row('Trachea',
'Midline position on inspection and palpation',
'Tracheal deviation:\nTowards lesion: collapse, fibrosis, pneumonectomy\nAway from lesion: large effusion, tension pneumothorax\nTracheal tug: severe dyspnoea',
'Tracheal shift is crucial to determine the pathological side\nTracheal tug (Campbell\'s sign) → severe airflow obstruction'),
section_row('CHEST INSPECTION'),
row('Shape of Chest',
'Elliptical, AP < transverse diameter\nSymmetrical\nNormal subcostal angle',
'Barrel chest: AP ≥ transverse, horizontal ribs, ↑ subcostal angle\nPectus excavatum (funnel chest): depressed sternum\nPectus carinatum (pigeon chest): protruded sternum\nKyphoscoliosis: restrictive lung disease',
'Barrel chest → emphysema/COPD (hyperinflation)\nKyphoscoliosis → restrictive defect (reduces lung volume)'),
row('Chest Wall Movements',
'Equal and symmetrical in all zones\nNormal expansion: 5-7 cm circumference increase',
'Reduced on one side → pathology on that side (effusion, collapse, consolidation, pneumothorax)\nParadoxical breathing → flail chest\nAccessory muscle use: SCM, scalenes in distress',
'Side with REDUCED movement = PATHOLOGICAL side\nParadoxical: segment moves in on inspiration = flail chest'),
row('Intercostal Spaces',
'Normal, symmetrical',
'Bulging ICS: large pleural effusion, tension pneumothorax\nSunken / crowded ICS: collapse or old fibrosis',
'Bulging ICS with trachea away → large effusion / tension pneumothorax'),
section_row('CHEST PALPATION'),
row('Chest Expansion (Palpation)',
'Equal bilateral expansion of 5-7 cm\nSymmetrical finger movement on inspiration',
'Reduced unilateral expansion: consolidation, collapse, effusion, pneumothorax, fibrosis',
'The side with REDUCED expansion = diseased side'),
row('Tactile Vocal Fremitus (TVF)',
'Moderate, equal vibration felt bilaterally\nDecrease from apex to base normally',
'Increased TVF: consolidation (sound conducted better through solid lung)\nDecreased / absent: pleural effusion (fluid blocks), pneumothorax (air), collapse',
'TVF and VR (vocal resonance on auscultation) always change together\nIncreased = consolidation; Decreased = fluid/air/collapse'),
row('Apex Beat (in chest exam)',
'Felt in 5th ICS, mid-clavicular line, left side',
'Displaced to right: left-sided push lesion (left effusion, left pneumothorax)\nDisplaced to left: left-sided pull or right-sided push',
'Cor pulmonale may have RV heave (left parasternal)\nDeviation helps confirm tracheal shift side'),
section_row('CHEST PERCUSSION'),
row('Percussion Note',
'Resonant over all lung fields\nDull over liver (5th ICS right)\nCardiac dullness (left 2nd-5th ICS)',
'Dull: consolidation (lobar pneumonia), pleural effusion, collapse\nStony dull: pleural effusion (differentiated from consolidation)\nHyper-resonant: pneumothorax, emphysema',
'Stony dull = pleural fluid (stone-like percussion note)\nHyper-resonant = air (pneumothorax)\nLobar pneumonia: dull + bronchial breathing + crackles + ↑ VR'),
row('Upper Border of Liver Dullness',
'5th ICS in right mid-clavicular line',
'Raised: right lower lobe collapse / consolidation pushing liver up\nLowered: emphysema (hyperinflation), pneumothorax, interposition',
'In COPD: liver pushed down, resonance extends below 5th ICS\nKronig\'s isthmus: band of resonance over apex (narrowed in TB)'),
row('Shifting Dullness / Tidal Percussion',
'Absent shifting dullness (no pleural fluid)',
'Present shifting dullness → pleural effusion',
'Tidal percussion: dullness rises with deep inspiration = confirmatory of effusion'),
section_row('CHEST AUSCULTATION'),
row('Breath Sounds',
'Vesicular breath sounds: soft, rustling, inspiratory > expiratory, no pause between phases',
'Bronchial breathing: loud, harsh, equal I:E, pause between phases, high-pitched (tubular)\nReduced/absent breath sounds: effusion, pneumothorax, collapse\nBronchovesicular: over manubrium, right apex normally',
'Bronchial breathing → consolidation, large cavity (TB), upper lobe collapse (central)\nAbsent BS → pleural effusion, large pneumothorax'),
row('Added Sounds - Crackles (Crepitations)',
'Absent',
'Fine crackles: dry, high-pitched, end-inspiratory (Velcro-like)\nCoarse crackles: bubbling, low-pitched\nPost-tussive crackles: appear after coughing\nCreeky crackles: pleural rub (can sound like crackles)',
'Fine crackles (basal): pulmonary fibrosis (ILD), pulmonary oedema\nCoarse crackles: bronchiectasis, resolving pneumonia\nPost-tussive: bronchiectasis (clear with coughing)'),
row('Added Sounds - Rhonchi / Wheeze',
'Absent',
'Expiratory wheeze: polyphonic (asthma, COPD)\nMonophonic wheeze: single obstructed bronchus (carcinoma, FB)\nInspiratory stridor: upper airway obstruction',
'Polyphonic = multiple airways affected (asthma)\nMonophonic = single site (carcinoma, foreign body, mucus plug)\nStridor → laryngeal / tracheal obstruction → emergency'),
row('Vocal Resonance (VR)',
'Normal: muffled, low-pitched voice transmission\nSymmetrical bilaterally',
'Increased VR: consolidation\nDecreased VR: effusion, pneumothorax\nWhispering pectoriloquy: "99" whispered heard clearly through consolidation\nAegophony: bleating / nasal quality at upper border of effusion\nBronchophony: "99" loudly transmitted through consolidation',
'Aegophony: E-to-A change (sounds like "ay" instead of "eee") at effusion upper border\nWhispering pectoriloquy: most specific sign of consolidation'),
row('Pleural Rub',
'Absent',
'Creaking, leathery sound, heard in both inspiration and expiration\nLocalised, does not clear with coughing',
'Pleuritis: pneumonia, PE + infarction, TB, malignancy, SLE\nAlways implies parietal + visceral pleura friction (inflammation)'),
]
# ══════════════════════════════════════════════════════════════════════════════
# DATA: CVS SYSTEM
# ══════════════════════════════════════════════════════════════════════════════
def cvs_general_data():
return [
section_row('GENERAL EXAMINATION - CVS SPECIFIC'),
row('Cyanosis',
'Pink lips and tongue\nWarm pink periphery\nSpO2 >95%',
'Central: tongue + lips blue\nPeripheral: cold blue extremities, warm trunk\nDifferential cyanosis: blue feet, pink hands (PDA with Eisenmenger)',
'Differential cyanosis → PDA with reversed (R-to-L) shunt: lower body from PA (deoxygenated), upper from aorta\nCentral → Eisenmenger\'s, fallot, severe LVF'),
row('Clubbing',
'No clubbing (normal nail fold angle <160°)',
'Bilateral pandigital clubbing: all 4 limbs\nBilateral fingers only: some CHD\nUnilateral right hand: subclavian involvement',
'CVS causes: cyanotic CHD (Fallot, Eisenmenger), infective endocarditis, bacterial\nNot in non-cyanotic CHD unless IE develops'),
row('Pedal Oedema',
'No pitting on tibial pressure\nNo sacral oedema',
'Pitting: bilateral, graded 1+-4+\nSacral (bedridden patients)\nAscites with bilateral LL oedema',
'Bilateral pedal oedema with raised JVP + hepatomegaly = RHF/CCF\nSacral oedema = specific to bedbound CCF patients'),
row('Xanthelasma / Xanthomas',
'No lipid deposits around eyes or tendons',
'Xanthelasma: yellow plaques at medial canthus of eyelids\nTendon xanthomas: Achilles, extensor tendons\nArcus senilis: white ring at corneal periphery',
'Xanthelasma → hypercholesterolaemia (familial or secondary)\nArcus senilis >45yr → premature atherosclerosis\nAll are markers of dyslipidaemia → increased CAD risk'),
row('Marfan\'s Habitus',
'Normal arm span ≤ height\nUpper segment = Lower segment\nNormal thumb/wrist sign',
'Arm span > height\nLower segment > upper segment\nPositive Steinberg thumb sign (thumb protrudes beyond ulnar border)\nPositive Walker-Murdoch wrist sign',
'Marfan\'s → aortic root dilatation, aortic regurgitation, MVP\nHigh risk of aortic dissection'),
section_row('VITAL SIGNS - CVS'),
row('Pulse - Rate',
'60-100/min (resting adult)',
'Tachycardia >100: AF, SVT, VT, fever, anaemia, thyrotoxicosis, CCF\nBradycardia <60: complete heart block, sinus node disease, athletes, hypothyroid, beta-blockers',
'Sudden tachycardia at rest → arrhythmia\nPulse deficit (apex > radial pulse) → AF'),
row('Pulse - Rhythm',
'Regular',
'Irregularly irregular: AF (no two intervals equal)\nRegularly irregular: ectopics, Wenckebach (2nd degree AVB)\nPulsus bigeminus: every second beat is ectopic',
'AF: most common sustained arrhythmia\nApex > radial pulse = pulse deficit = AF\nWenckebach: progressively longer PR → dropped beat'),
row('Pulse - Volume',
'Normal volume: moderate, symmetrical',
'High volume (bounding): AR, PDA, fever, thyrotoxicosis, hyperdynamic states\nLow volume (thready): shock, cardiac tamponade, severe AS\nPulsus paradoxus: volume falls >10 mmHg on inspiration',
'Bounding pulse → aortic regurgitation (AR)\nThready pulse → cardiogenic / hypovolaemic shock\nPulsus paradoxus → tamponade, severe asthma, constrictive pericarditis'),
row('Pulse - Character',
'Normal (dicrotic notch not palpable)',
'Slow-rising (pulsus parvus et tardus): severe AS\nCollapsing (water-hammer): AR, PDA, AV fistula\nBisferiens pulse: two peaks per beat - AR + AS or HOCM\nDicrotic: bigeminy appearance - dilated cardiomyopathy, fever',
'Water-hammer pulse: felt at radial artery raised above heart level\nQuincke\'s pulsation (nail bed) → AR\nDuroziez\'s sign (femoral bruit) → AR'),
row('Blood Pressure',
'Systolic 90-140 mmHg\nDiastolic 60-90 mmHg\nPulse pressure 30-40 mmHg\nNo significant radio-radial or radio-femoral delay',
'Hypertension (HTN): >140/90 mmHg\nHypotension: <90/60 mmHg\nWidened PP (>60 mmHg): AR, PDA, fever, thyrotoxicosis\nNarrow PP (<25 mmHg): AS, cardiac tamponade\nRadio-femoral delay: coarctation of aorta',
'Wide pulse pressure + bounding pulse → AR, PDA\nNarrow PP + slow-rising pulse → AS\nRadio-femoral delay: coarctation (difference in upper vs lower limb BP)\nHill\'s sign: popliteal > brachial BP by >20 mmHg → significant AR'),
section_row('JVP ASSESSMENT'),
row('Jugular Venous Pressure (JVP)',
'<4 cm above sternal angle at 45°\nPulsatile (vs carotid which is not compressible)\nABD: Abdomino-jugular reflux (hepatojugular) negative',
'Raised JVP: RHF, CCF, constrictive pericarditis, tamponade, SVC obstruction\nKussmaul\'s sign (JVP rises on inspiration): RCM, constrictive pericarditis\nSVC obstruction: non-pulsatile, no ABD response, prominent collaterals',
'Hepatojugular reflux: apply pressure to liver → JVP rises and stays raised for >10 sec = RHF\nDistinguish JVP from carotid: JVP has dual pulsation (a, v waves), falls with inspiration, obliterated by compression'),
row('JVP Waveforms',
'a wave: before S1 (atrial contraction)\nv wave: before S2 (venous filling)\nNormal a > v wave',
'Giant a wave: TS, RVH, pulmonary stenosis, complete heart block (cannon a)\nAbsent a wave: atrial fibrillation\nGiant v wave: tricuspid regurgitation\nX descent obliterated: cardiac tamponade\nM/W pattern with prominent x and y descents: constrictive pericarditis',
'Cannon a wave: atrium contracts against closed tricuspid → CHB, nodal rhythm\nGiant v wave + pulsatile liver → tricuspid regurgitation'),
section_row('PRECORDIAL EXAMINATION'),
row('Inspection of Precordium',
'No visible pulsations except possible apical impulse in thin individuals\nNo scars, sinuses, deformities',
'Precordial bulge: childhood cardiac enlargement (chronic large heart)\nVisible apex beat displaced\nParasternal pulsation: RV enlargement\nAortic pulsation in 2nd R ICS: aneurysm / unfolded aorta\nPulmonary pulsation in 2nd L ICS: pulmonary hypertension',
'Precordial bulge → long-standing childhood cardiomegaly\nParasternal heave → right ventricular hypertrophy (cor pulmonale, MS with PHT)'),
row('Apex Beat',
'Normal: 5th ICS, mid-clavicular line (left)\nNormal character: brief, localised tapping impulse',
'Displaced outward/downward → cardiomegaly (LVH/LVD)\nHeaving (forceful, sustained): LVH (AS, HTN)\nHyperdynamic (forceful, brief, displaces hand): LVD/volume overload (AR, MR)\nTapping: palpable S1 → Mitral Stenosis\nDiffuse: LV dilatation\nAbsent: obesity, emphysema, pericardial effusion, dextrocardia',
'Heaving → pressure overload (AS, HTN) = LVH\nHyperdynamic / thrusting → volume overload (AR, MR, VSD) = LV dilatation\nTapping apex = palpable loud S1 → always think MITRAL STENOSIS'),
row('Parasternal Heave',
'Absent: no pulsation felt at left sternal border',
'Left parasternal heave: right ventricular hypertrophy / enlargement',
'RVH → pulmonary hypertension (from MS, COPD), pulmonary stenosis\nGrade parasternal heave 1-3+'),
row('Thrills',
'Absent',
'Systolic thrill at aortic area: severe AS\nSystolic thrill at pulmonary area: PS\nSystolic thrill at lower sternal border: VSD\nDiastolic thrill at apex: severe MS',
'Thrill = palpable murmur (grade 4 or above)\nAortic thrill → severe AS\nDiastolic thrill at apex → severe MS (rough turbulent flow)'),
section_row('AUSCULTATION - HEART SOUNDS'),
row('First Heart Sound (S1)',
'S1: closure of mitral (M1) and tricuspid (T1) valves\nLoud at apex, heard in all areas\nNormal loudness',
'Loud S1: Mitral Stenosis (rheumatic)\nSoft S1: MR, LBBB, prolonged PR interval, poor LV function\nVariable S1: AF, complete heart block\nSplitting of S1: RBBB, Ebstein\'s anomaly',
'Loud S1 → MITRAL STENOSIS (leaflets held open in diastole, snap shut)\nSoft S1 → MR, LVF, first degree AVB\nM1 before T1 (mitral closes first in systole)'),
row('Second Heart Sound (S2)',
'A2 before P2 (aortic closes before pulmonary)\nSingle at aortic area\nPhysiological splitting on inspiration (A2-P2 gap increases)',
'Loud P2: pulmonary hypertension (PHT)\nSingle S2: severe AS (A2 inaudible), Fallot\'s (absent P2)\nFixed splitting: ASD (RA volume equalises throughout respiratory cycle)\nWidened splitting: RBBB, PS\nReversed (paradoxical) splitting: LBBB, severe AS',
'Fixed split S2 → ASD (pathognomonic)\nLoud P2 → pulmonary hypertension (PHT)\nReversed split: A2 comes after P2, splitting narrows on inspiration (LBBB)'),
row('Third Heart Sound (S3)',
'Normal in children and young adults (<30 yr)\nAbnormal in adults >40yr',
'Pathological S3 in adult: low-pitched, early diastolic, at apex with bell\nBest heard: left lateral position, expiration',
'Pathological S3 → rapid ventricular filling in dilated LV (LVF, DCM, MR, AR)\nVentricular gallop rhythm: S1-S2-S3 like "Kentucky"\nRV S3 in RHF heard at lower sternal border, increases on inspiration'),
row('Fourth Heart Sound (S4)',
'Normally absent (may be normal in athletes)',
'S4: low-pitched, presystolic sound just before S1\nBest heard: apex with bell, left lateral position',
'S4 → stiff, non-compliant ventricle (atrial kick pushes blood into stiff LV)\nCauses: hypertensive heart disease, hypertrophic cardiomyopathy, HOCM, AS, acute MI\nAtrial gallop: S4-S1-S2 like "Tennessee"\nAbsent in AF (no atrial contraction)'),
row('Opening Snap (OS)',
'Absent',
'High-pitched click in early diastole, best heard at lower left sternal border',
'Mitral Stenosis (pliable non-calcified leaflets snap open)\nShorter A2-OS interval → more severe MS (higher LA pressure)\nA2-OS interval 0.04-0.12 sec'),
section_row('AUSCULTATION - MURMURS'),
row('Mid-Diastolic Murmur (MDM)',
'Absent',
'Low-pitched, rough, rumbling at apex\nBell of stethoscope, left lateral, expiration\nWith presystolic accentuation (in sinus rhythm)',
'Mitral Stenosis: turbulent flow across narrowed MV in diastole\nTricuspid stenosis: MDM at lower sternal border, increases with inspiration (Carvallo\'s sign)\nPresystolic accentuation disappears in AF (no atrial contraction)'),
row('Pan-Systolic Murmur (PSM)',
'Absent',
'High-pitched, blowing, throughout systole\nCovers S1 and S2',
'Mitral Regurgitation: MR PSM at apex, conducted to axilla and back\nTricuspid Regurgitation: TR PSM at lower sternal border, increases with inspiration\nVSD: PSM at lower left sternal border (Maladie de Roger if small)'),
row('Ejection Systolic Murmur (ESM)',
'Absent (innocent murmurs may be grade 1-2, no radiation)',
'High-pitched, crescendo-decrescendo, diamond-shaped\nStarts after S1, ends before S2\nPeak in midsystole',
'Aortic Stenosis: harsh ESM at aortic area, conducted to carotids\nPulmonary Stenosis: ESM at pulmonary area, conducted to back\nHOCM: ESM at lower left sternal border, increases on Valsalva / standing\nInnocent murmur: grade ≤2, no thrill, no radiation, varies with posture'),
row('Early Diastolic Murmur (EDM)',
'Absent',
'High-pitched, blowing, soft, decrescendo\nDiaphragm, patient sitting, leaning forward, breath held in expiration',
'Aortic Regurgitation: EDM at left sternal border (2nd-4th ICS)\nMost audible at Erb\'s point (3rd ICS left sternal border)\nPulmonary Regurgitation: EDM at pulmonary area (PHT → Graham-Steell murmur)'),
row('Continuous Murmur',
'Absent',
'High-pitched, machinery-type, peaks around S2\nCrescendo in systole, decrescendo in diastole\nHeard at left infraclavicular area (Gibson\'s area), conducted to back',
'Patent Ductus Arteriosus (PDA): left-to-right shunt through ductus arteriosus\nPDA closes normally at 24-48 hrs of life\nOther: coronary AV fistula, pulmonary AV fistula, cervical venous hum (obliterated by gentle jugular pressure)'),
section_row('OTHER SYSTEM EXAMINATION (CVS)'),
row('Lungs (in CVS exam)',
'Clear lung fields\nNo crackles at bases',
'Bilateral basal fine crackles: pulmonary oedema (LVF)\nRight-sided pleural effusion: chronic CCF\nReduced breath sounds: effusion',
'Bilateral basal crackles that do not clear with coughing → pulmonary oedema\nPost-tussive crackles clear → infection'),
row('Abdomen (in CVS exam)',
'Soft, non-tender\nLiver not palpable',
'Hepatomegaly: pulsatile (TR), tender (congestive hepatopathy)\nHepato-jugular reflux positive\nAscites: shifting dullness, fluid thrill\nSplenomegaly: infective endocarditis (IE)',
'Pulsatile hepatomegaly → tricuspid regurgitation (expansile pulsation)\nCongestive hepatomegaly → RHF (tender, smooth, firm)\nSplenomegaly in IE: Osler nodes + Roth spots + Janeway lesions = embolic phenomena'),
row('Peripheral Signs of AR',
'Absent',
'Corrigan\'s pulse (bounding carotid)\nDeMusset\'s sign (head nodding with each beat)\nQuincke\'s pulsation (nail bed capillary pulsation)\nDuroziez\'s sign (femoral bruit with light compression)\nHill\'s sign (popliteal BP > brachial by >20 mmHg)\nTraube\'s sign (pistol-shot sounds over femoral artery)',
'All peripheral signs of AR reflect wide pulse pressure and hyperdynamic circulation\nMost specific: Hill\'s sign and Duroziez\'s\nCorrigan\'s = classic visible neck pulsation of AR'),
]
# ══════════════════════════════════════════════════════════════════════════════
# TABLE STYLE APPLICATION
# ══════════════════════════════════════════════════════════════════════════════
def apply_table_style(table_obj, data_rows):
"""Apply cell-level background colours to Normal / Abnormal / Significance columns."""
style_cmds = []
for i, r in enumerate(data_rows, start=1):
# Check if section row (merged)
if r[1] == '' and r[2] == '' and r[3] == '':
continue
style_cmds += [
('BACKGROUND', (1, i), (1, i), C_NORMAL),
('BACKGROUND', (2, i), (2, i), C_ABNORMAL),
('BACKGROUND', (3, i), (3, i), C_SIG),
]
table_obj.setStyle(TableStyle(style_cmds))
return table_obj
# ══════════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ══════════════════════════════════════════════════════════════════════════════
def build_checklist_pdf(out_path):
ss = make_styles()
doc = SimpleDocTemplate(
out_path,
pagesize=A4,
leftMargin=20*mm, rightMargin=20*mm,
topMargin=18*mm, bottomMargin=18*mm,
)
story = []
# ── COVER ──────────────────────────────────────────────────────────────────
story.append(Spacer(1, 25*mm))
story.append(Paragraph('EXAMINATION CHECKLIST', ss['CoverTitle']))
story.append(Paragraph('Normal vs Abnormal Findings + Clinical Significance', ss['CoverSub']))
story.append(Spacer(1, 5*mm))
story.append(HRFlowable(width="60%", thickness=2, color=C_NAVY))
story.append(Spacer(1, 5*mm))
story.append(Paragraph('Medicine Posting · 2nd Year MBBS', ss['CoverSub']))
story.append(Spacer(1, 4*mm))
story.append(Paragraph(
'Systems covered: Gastrointestinal · Central Nervous System · Respiratory · Cardiovascular',
ss['CoverTiny']))
story.append(Spacer(1, 10*mm))
# Colour legend
legend_data = [
[Paragraph('<b>Column Colour Guide</b>', ss['Body8']), '', '', ''],
[Paragraph('FINDING / TEST', ss['ColHead']),
Paragraph('NORMAL', ss['ColHead']),
Paragraph('ABNORMAL', ss['ColHead']),
Paragraph('CLINICAL SIGNIFICANCE', ss['ColHead'])],
[Paragraph('What you are examining', ss['Body8']),
Paragraph('Expected finding in a healthy person', ss['CellNorm']),
Paragraph('Deviation from normal and clinical variants', ss['CellAbn']),
Paragraph('What the abnormality means + what it suggests', ss['CellSig'])],
]
lt = Table(legend_data, colWidths=CW)
lt.setStyle(TableStyle([
('SPAN', (0,0), (3,0)),
('BACKGROUND', (0,0), (3,0), colors.HexColor("#eceff1")),
('BACKGROUND', (0,1), (3,1), C_NAVY),
('BACKGROUND', (1,2), (1,2), C_NORMAL),
('BACKGROUND', (2,2), (2,2), C_ABNORMAL),
('BACKGROUND', (3,2), (3,2), C_SIG),
('GRID', (0,0), (-1,-1), 0.5, colors.HexColor("#bdbdbd")),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story.append(lt)
story.append(Spacer(1, 8*mm))
# Index
index = [
('1. GIT System', 'General Examination · Signs of Liver Failure · Abdominal Inspection · Palpation · Percussion · Auscultation'),
('2. CNS System', 'Higher Mental Functions · Cranial Nerves (I-XII) · Motor System · Sensory System · Cerebellar Signs · Meningeal Signs'),
('3. Respiratory', 'General & Vital Signs · Upper RT · Chest Inspection/Palpation/Percussion/Auscultation'),
('4. CVS System', 'General Exam · Vital Signs (Pulse, BP, JVP) · Precordial · Heart Sounds · Murmurs · Peripheral Signs'),
]
for sys_n, items in index:
story.append(Paragraph(
f'<b><font color="#1a3a5c">{sys_n}</font></b> — <font color="#455a64">{items}</font>',
ss['Body8']))
story.append(Spacer(1, 2*mm))
story.append(PageBreak())
# ── GIT ────────────────────────────────────────────────────────────────────
story.append(sys_header_block(
'1. GASTROINTESTINAL SYSTEM',
'General · Liver Failure Signs · Abdomen Inspection/Palpation/Percussion/Auscultation',
SYS_GIT))
story.append(Spacer(1, 4*mm))
git_rows = git_general_data()
t = make_table(git_rows)
apply_table_style(t, git_rows)
story.append(t)
story.append(PageBreak())
# ── CNS ────────────────────────────────────────────────────────────────────
story.append(sys_header_block(
'2. CENTRAL NERVOUS SYSTEM',
'Higher Mental Functions · Cranial Nerves · Motor · Sensory · Cerebellar · Meningeal',
SYS_CNS))
story.append(Spacer(1, 4*mm))
cns_rows = cns_general_data()
t = make_table(cns_rows)
apply_table_style(t, cns_rows)
story.append(t)
story.append(PageBreak())
# ── RESPIRATORY ────────────────────────────────────────────────────────────
story.append(sys_header_block(
'3. RESPIRATORY SYSTEM',
'General · URT · Chest Inspection / Palpation / Percussion / Auscultation',
SYS_RS))
story.append(Spacer(1, 4*mm))
rs_rows = rs_general_data()
t = make_table(rs_rows)
apply_table_style(t, rs_rows)
story.append(t)
story.append(PageBreak())
# ── CVS ────────────────────────────────────────────────────────────────────
story.append(sys_header_block(
'4. CARDIOVASCULAR SYSTEM',
'General · Pulse · BP · JVP · Precordium · Heart Sounds & Murmurs · Peripheral Signs',
SYS_CVS))
story.append(Spacer(1, 4*mm))
cvs_rows = cvs_general_data()
t = make_table(cvs_rows)
apply_table_style(t, cvs_rows)
story.append(t)
story.append(PageBreak())
# ── QUICK SUMMARY TABLE ────────────────────────────────────────────────────
story.append(sys_header_block(
'QUICK SUMMARY: KEY EXAMINATION MNEMONICS & RULES',
'Must-know principles for clinical examination',
C_NAVY))
story.append(Spacer(1, 5*mm))
summary_data = [
[Paragraph('<b>System</b>', ss['ColHead']),
Paragraph('<b>Key Rule / Mnemonic</b>', ss['ColHead']),
Paragraph('<b>What it means</b>', ss['ColHead'])],
[p('GIT - Liver', 'CellBold'), p('Cannot get above it + moves with respiration + dull to percussion = liver', 'CellNorm'), p('Distinguishes liver from other RUQ masses', 'CellSig')],
[p('GIT - Spleen', 'CellBold'), p('Notched edge + moves to RIF + cannot insunate fingers between it and ribs', 'CellNorm'), p('Distinguishes spleen from kidney (kidney: ballotable, colonic band, can insunate)', 'CellSig')],
[p('GIT - Ascites', 'CellBold'), p('Shifting dullness (>500 mL) + Fluid thrill (tense) + Puddle sign (120 mL)', 'CellNorm'), p('Order of sensitivity: puddle > shifting dullness > fluid thrill', 'CellSig')],
[p('GIT - Liver Failure', 'CellBold'), p('CLAD + Flap: Clubbing, Leukonychia, Asterixis, Dupuytren + Flapping tremor', 'CellNorm'), p('All due to chronic liver disease / portal hypertension', 'CellSig')],
[p('CNS - UMN vs LMN', 'CellBold'), p('UMN: ↑ tone, ↑ reflexes, Babinski +, no wasting\nLMN: ↓ tone, ↓ reflexes, Babinski -, wasting + fasciculations', 'CellNorm'), p('UMN = above anterior horn cell\nLMN = at or below AHC', 'CellSig')],
[p('CNS - Cerebellar DANISH', 'CellBold'), p('D: Dysdiadochokinesia A: Ataxia N: Nystagmus I: Intention tremor S: Scanning speech H: Hypotonia', 'CellNorm'), p('All ipsilateral to cerebellar lesion', 'CellSig')],
[p('CNS - Facial Palsy', 'CellBold'), p('Forehead spared → UMN (stroke)\nForehead involved → LMN (Bell\'s palsy)', 'CellNorm'), p('Frontalis has bilateral cortical representation - UMN lesion cannot abolish it alone', 'CellSig')],
[p('RS - Side of Pathology', 'CellBold'), p('Side with REDUCED expansion + REDUCED TVF = pathological side\nTrachea deviates TOWARDS collapse/fibrosis\nTrachea deviates AWAY FROM effusion/pneumothorax', 'CellNorm'), p('Tracheal position + chest movement together identify the lesion side', 'CellSig')],
[p('RS - Consolidation Triad', 'CellBold'), p('Dull percussion + Bronchial breathing + Increased VR/TVF', 'CellNorm'), p('All 3 needed for consolidation diagnosis clinically', 'CellSig')],
[p('RS - Effusion Triad', 'CellBold'), p('Stony dull percussion + Absent/reduced BS + Reduced VR + Aegophony at upper border', 'CellNorm'), p('Stony dull (differentiated from simple dull) + aegophony = pleural effusion', 'CellSig')],
[p('CVS - Mitral Stenosis', 'CellBold'), p('Tapping apex + MDM at apex (bell, left lateral, expiration) + Loud S1 + Opening snap + Parasternal heave (PHT)', 'CellNorm'), p('A2-OS interval inversely proportional to severity of MS', 'CellSig')],
[p('CVS - Aortic Regurgitation', 'CellBold'), p('EDM at Erb\'s point (3rd ICS LSB) + Water-hammer pulse + Wide pulse pressure + Hill\'s sign + Corrigan\'s / DeMusset\'s', 'CellNorm'), p('AR = volume overload → hyperdynamic apex + thrusting', 'CellSig')],
[p('CVS - Aortic Stenosis', 'CellBold'), p('Heaving apex + ESM conducted to carotids + Slow-rising pulse + Narrow pulse pressure\nTriad: angina + syncope + dyspnoea', 'CellNorm'), p('AS = pressure overload → heaving apex beat (sustained, forceful)', 'CellSig')],
[p('CVS - Heart Sounds', 'CellBold'), p('S1: MV + TV closure (start systole)\nS2: AV + PV closure (start diastole)\nS3: rapid filling (pathological in adults >40 → LVF)\nS4: stiff ventricle (HTN, HCM, AS)', 'CellNorm'), p('"Lubb-Dubb" = S1-S2\nS3 gallop = "Kentucky"\nS4 gallop = "Tennessee"', 'CellSig')],
[p('CVS - Murmur Positions', 'CellBold'), p('MR/MS: Left lateral, expiration\nAR: Sitting forward, expiration, at Erb\'s\nPS/AS: Sitting, held inspiration\nTR/TS: Supine, inspiration (↑ on inspiration = right-sided)', 'CellNorm'), p('Right-sided murmurs increase with inspiration (Carvallo\'s sign)\nLeft-sided murmurs increase with expiration', 'CellSig')],
]
summary_cw = [TW*0.18, TW*0.46, TW*0.36]
st = Table(summary_data, colWidths=summary_cw, repeatRows=1)
st.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), C_NAVY),
('TEXTCOLOR', (0,0), (-1,0), C_WHITE),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('FONTSIZE', (0,0), (-1,-1), 7.5),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('GRID', (0,0), (-1,-1), 0.35, colors.HexColor("#bdbdbd")),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 4),
('RIGHTPADDING', (0,0), (-1,-1), 4),
('ROWBACKGROUNDS',(0,1), (-1,-1), [colors.white, colors.HexColor("#f5f5f5")]),
('BACKGROUND', (1,1), (1,-1), colors.HexColor("#e8f5e9")),
('BACKGROUND', (2,1), (2,-1), colors.HexColor("#fff8e1")),
]))
story.append(st)
doc.build(story)
print(f"Checklist PDF written to: {out_path}")
if __name__ == '__main__':
build_checklist_pdf('/tmp/workspace/ddx-flowcharts/Examination_Checklist_Normal_vs_Abnormal.pdf')
cd /tmp/workspace/ddx-flowcharts && python3 generate_checklist.py 2>&1
python3 -c " from pypdf import PdfReader r = PdfReader('/tmp/workspace/ddx-flowcharts/Examination_Checklist_Normal_vs_Abnormal.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): t = p.extract_text() first = t.strip()[:70] if t else '(no text)' print(f' Page {i+1}: {first}') " ls -lh /tmp/workspace/ddx-flowcharts/Examination_Checklist_Normal_vs_Abnormal.pdf
Examination Checklist Normal vs Abnormal
PDF Document · PDF
| Page(s) | Content |
|---|---|
| 1 | Cover + Colour guide legend + Contents index |
| 2-3 | GIT System - General exam, Liver failure signs (spider naevi, asterixis, leukonychia etc), Abdominal inspection, Palpation (liver, spleen, kidney, Murphy's, McBurney's), Percussion, Auscultation |
| 4-6 | CNS System - Higher mental functions, All 12 cranial nerves, Motor system (tone, power MRC scale, reflexes, Babinski), Sensory system, Cerebellar signs, Meningeal signs (Kernig's, Brudzinski's) |
| 7-8 | Respiratory - Vital signs, Cyanosis/Clubbing, URT (trachea, flaring), Chest inspection, TVF, Percussion notes, Breath sounds, Added sounds (crackles, wheeze, pleural rub), Vocal resonance |
| 9-11 | CVS - General exam (Marfan's, xanthelasma), Pulse (rate/rhythm/volume/character), BP, JVP waveforms, Precordium (apex beat, thrills, heave), All heart sounds + murmurs (S1-S4, MDM, PSM, ESM, EDM, continuous), Peripheral signs of AR |
| 12 | Quick Summary - Key mnemonics, UMN vs LMN rules, DANISH, murmur positioning rules, consolidation vs effusion triad |
Can you tell me the significance of doing all these examination and what to rule out . I need for every examination . You can take long time
cd /tmp/workspace/attachments/d674b850-1b3c-4cd5-a4fb-32ebcf3bfe0d/ && python3 -c " import pdfplumber with pdfplumber.open('PDF (1).pdf') as pdf: for i, page in enumerate(pdf.pages): text = page.extract_text() print(f'=== PAGE {i+1} ===') print(text) print() "
Continue