Hwy send me all the important thing in reasoning manner for rs cases... as a flowcharts as all in one
ls /home/daytona/workspace/ 2>/dev/null && echo "---" && which python3 && python…
python3 -c "import PIL; print('PIL OK')" 2>/dev/null && python3 -c "from PIL imp…
mkdir -p /home/daytona/workspace/rs-flowchart && ls /home/daytona/workspace/rs-f…
~/rs-flowchart/gen_flowchart.py
from PIL import Image, ImageDraw, ImageFont
import textwrap, os
# ── Canvas ──────────────────────────────────────────────────────────────────
W, H = 2400, 5600
bg = (10, 12, 30) # deep navy
canvas = Image.new("RGB", (W, H), bg)
draw = ImageDraw.Draw(canvas)
# ── Fonts ────────────────────────────────────────────────────────────────────
def font(size, bold=False):
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf" if bold else
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
]
for c in candidates:
if os.path.exists(c):
return ImageFont.truetype(c, size)
return ImageFont.load_default()
F_TITLE = font(62, True)
F_TOPIC = font(46, True)
F_SECTION = font(34, True)
F_BODY = font(27)
F_LABEL = font(25)
F_SMALL = font(22)
# ── Palette ──────────────────────────────────────────────────────────────────
COL = {
"pleural": {"hdr": (0, 168, 232), "box": (0, 60, 90), "arrow": (0, 168, 232)},
"bronch": {"hdr": (255, 140, 0), "box": (80, 40, 0), "arrow": (255, 165, 0)},
"fibrosis": {"hdr": (120, 200, 80), "box": (30, 60, 10), "arrow": (120, 200, 80)},
"pneumo": {"hdr": (220, 80, 160), "box": (70, 15, 50), "arrow": (220, 80, 160)},
}
WHITE = (240, 245, 255)
YELLOW = (255, 220, 60)
LGREY = (170, 180, 200)
DIMGREY = (80, 90, 110)
# ── Helpers ───────────────────────────────────────────────────────────────────
def draw_rounded_rect(draw, xy, fill, radius=18, outline=None, outline_w=2):
x1,y1,x2,y2 = xy
draw.rounded_rectangle([x1,y1,x2,y2], radius=radius, fill=fill,
outline=outline, width=outline_w)
def draw_diamond(draw, cx, cy, w, h, fill, outline, outline_w=2):
pts = [(cx, cy-h//2),(cx+w//2, cy),(cx, cy+h//2),(cx-w//2, cy)]
draw.polygon(pts, fill=fill, outline=outline)
def draw_arrow(draw, x1,y1,x2,y2, color, width=3):
draw.line([(x1,y1),(x2,y2)], fill=color, width=width)
# arrowhead (pointing down)
ah = 14
aw = 8
draw.polygon([(x2,y2),(x2-aw,y2-ah),(x2+aw,y2-ah)], fill=color)
def wrapped_text(draw, text, x, y, w, fnt, color, align="left", line_spacing=6):
max_chars = max(int(w / (fnt.size * 0.6)), 10)
lines = []
for raw in text.split("\n"):
if raw.strip() == "":
lines.append("")
else:
lines.extend(textwrap.wrap(raw, width=max_chars) or [""])
lh = fnt.size + line_spacing
for i, line in enumerate(lines):
if align == "center":
bbox = draw.textbbox((0,0), line, font=fnt)
lw = bbox[2] - bbox[0]
draw.text((x + (w - lw)//2, y + i*lh), line, font=fnt, fill=color)
else:
draw.text((x, y + i*lh), line, font=fnt, fill=color)
return y + len(lines) * lh
def section_box(draw, title, lines, x, y, box_w, pal, icon=""):
# Header bar
draw_rounded_rect(draw, [x, y, x+box_w, y+46], fill=pal["hdr"], radius=12)
draw.text((x+14, y+6), f"{icon} {title}" if icon else title, font=F_SECTION, fill=(10,12,30))
y += 54
# Body
body_h = len(lines)*32 + 16
draw_rounded_rect(draw, [x, y, x+box_w, y+body_h], fill=pal["box"], radius=10,
outline=pal["hdr"], outline_w=2)
ty = y + 10
for line in lines:
bullet = " • " if not line.startswith(" ") else ""
draw.text((x+12, ty), bullet + line, font=F_BODY, fill=WHITE)
ty += 32
return y + body_h + 8
def flow_diamond(draw, text, cx, cy, fill, outline):
draw_diamond(draw, cx, cy, 320, 80, fill, outline, 2)
bbox = draw.textbbox((0,0), text, font=F_LABEL)
tw = bbox[2]-bbox[0]
draw.text((cx - tw//2, cy - F_LABEL.size//2), text, font=F_LABEL, fill=WHITE)
return cy
# ═══════════════════════════════════════════════════════════════════════════════
# MASTER TITLE
# ═══════════════════════════════════════════════════════════════════════════════
y = 24
draw_rounded_rect(draw, [30, y, W-30, y+90], fill=(25, 30, 70), radius=20,
outline=(80,120,200), outline_w=3)
draw.text((W//2, y+18), "RESPIRATORY SYSTEM — Clinical Reasoning Flowcharts",
font=F_TITLE, fill=YELLOW, anchor="mt")
draw.text((W//2, y+72), "Pleural Effusion | Bronchiectasis | Pulmonary Fibrosis | Pneumonia",
font=F_LABEL, fill=LGREY, anchor="mt")
y += 110
# ── divider
draw.line([(30, y), (W-30, y)], fill=DIMGREY, width=2)
y += 14
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 1 — PLEURAL EFFUSION
# ═══════════════════════════════════════════════════════════════════════════════
PAL = COL["pleural"]
x0, col_w = 40, W-80
# Topic header
draw_rounded_rect(draw, [x0, y, x0+col_w, y+58], fill=PAL["hdr"], radius=14)
draw.text((x0+20, y+8), "① PLEURAL EFFUSION", font=F_TOPIC, fill=(10,12,30))
y += 68
# ---- Row 1: Definition + Epidemiology
cx = x0
col2 = (col_w - 20) // 2
y = section_box(draw, "DEFINITION", [
"Accumulation of excess fluid in the pleural space",
">1 million cases/year in the US",
"Most common causes (US): CHF, malignancy,",
" bacterial pneumonia, pulmonary embolism",
], cx, y, col2, PAL)
cx2 = x0 + col2 + 20
y_save = y - section_box(draw, "DEFINITION", [], cx2, y, col2, PAL) + y # align
section_box(draw, "CLASSIFICATION", [
"TRANSUDATES — systemic causes (↓oncotic / ↑hydrostatic P)",
" CHF, cirrhosis, nephrotic syndrome, hypoalbuminaemia",
"EXUDATES — pleural/lung pathology",
" Infections, malignancy, PE, RA, SLE, TB, drugs",
], cx2, y_save - 8, col2, PAL)
y = max(y, y_save) + 8
# ---- Light's Criteria diamond
y += 10
draw.text((W//2, y), "▼ Apply LIGHT'S CRITERIA to pleural fluid", font=F_SECTION, fill=YELLOW, anchor="mt")
y += 38
# criteria box
draw_rounded_rect(draw, [x0+80, y, x0+col_w-80, y+160], fill=(0,40,70), radius=14,
outline=PAL["hdr"], outline_w=3)
draw.text((W//2, y+10), "LIGHT'S CRITERIA — ANY ONE = Exudate:", font=F_SECTION, fill=YELLOW, anchor="mt")
criteria = [
"1. Pleural fluid protein / serum protein > 0.5",
"2. Pleural fluid LDH / serum LDH > 0.6",
"3. Pleural fluid LDH > 2/3 upper limit of normal serum LDH",
]
for i, c in enumerate(criteria):
draw.text((x0+110, y+56+i*32), c, font=F_BODY, fill=WHITE)
y += 170
# Arrow + two outcomes
CX = W//2
draw_arrow(draw, CX, y, CX-400, y+60, PAL["arrow"], 3)
draw_arrow(draw, CX, y, CX+400, y+60, PAL["arrow"], 3)
draw.text((CX-550, y+20), "TRANSUDATE", font=F_SECTION, fill=(100,200,255))
draw.text((CX+260, y+20), "EXUDATE", font=F_SECTION, fill=(255,120,80))
y += 70
# Transudate box
draw_rounded_rect(draw, [x0, y, x0+col2-10, y+100], fill=(0,40,70), radius=10,
outline=(100,200,255), outline_w=2)
draw.text((x0+10, y+10), "Treat underlying cause:", font=F_SECTION, fill=(100,200,255))
for i, t in enumerate(["Diuretics for CHF", "Treat cirrhosis / hypoalbuminaemia", "Drain if symptomatic"]):
draw.text((x0+20, y+46+i*18), f"• {t}", font=F_SMALL, fill=WHITE)
# Exudate box
draw_rounded_rect(draw, [x0+col2+10, y, x0+col_w, y+100], fill=(70,20,10), radius=10,
outline=(255,120,80), outline_w=2)
draw.text((x0+col2+20, y+10), "Further workup:", font=F_SECTION, fill=(255,140,80))
for i, t in enumerate(["Culture, cytology, ADA (TB)", "CT chest / bronchoscopy", "Drain + treat (empyema → antibiotics + tube)"]):
draw.text((x0+col2+30, y+46+i*18), f"• {t}", font=F_SMALL, fill=WHITE)
y += 110
# Investigations row
y = section_box(draw, "INVESTIGATIONS", [
"Imaging: CXR (blunting costophrenic angle ≥200 mL), US (5 mL detectable), CT",
"Thoracocentesis: protein, LDH, glucose, pH, cell count, culture, cytology, ADA",
"Empyema markers: pH <7.2, glucose <60, LDH >1000 → needs drainage",
"Malignant: cytology +ve, pleural biopsy; TB: ADA >40, lymphocytic",
], x0, y, col_w, PAL)
y = section_box(draw, "MANAGEMENT SUMMARY", [
"Transudates → treat cause | Small exudates → observe + treat",
"Parapneumonic → antibiotics ± drainage if complicated",
"Empyema → chest tube + antibiotics (VATS if loculated)",
"Malignant → pleurodesis (talc), indwelling catheter",
"TB → anti-TB therapy (HRZE × 2 months, then HR × 4 months)",
], x0, y, col_w, PAL)
y += 10
draw.line([(30, y), (W-30, y)], fill=DIMGREY, width=2)
y += 16
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 2 — BRONCHIECTASIS
# ═══════════════════════════════════════════════════════════════════════════════
PAL = COL["bronch"]
draw_rounded_rect(draw, [x0, y, x0+col_w, y+58], fill=PAL["hdr"], radius=14)
draw.text((x0+20, y+8), "② BRONCHIECTASIS", font=F_TOPIC, fill=(10,12,30))
y += 68
y = section_box(draw, "DEFINITION & PATHOGENESIS — Vicious Cycle Hypothesis", [
"Permanent, abnormal dilation of bronchi (≥1.5× diameter of adjacent pulmonary artery)",
"Infection → inflammation → airway wall damage → impaired clearance → more infection",
"Key mediators: neutrophil elastase, reactive oxygen species, inflammatory cytokines",
"Organisms: Pseudomonas aeruginosa (most virulent), H. influenzae, S. aureus, NTM",
], x0, y, col_w, PAL)
# Two-column: Causes + Features
cx = x0
y_a = section_box(draw, "CAUSES (Mnemonic: ABCDEF)", [
"A — Allergic bronchopulmonary aspergillosis (ABPA)",
"B — Bronchial obstruction (foreign body, tumour)",
"C — Cystic fibrosis / Ciliary dyskinesia (PCD)",
"D — Deficiency (immunoglobulin, α1-antitrypsin)",
"E — Erosion by TB / NTM / post-pertussis",
"F — Fibrosis (post-infective), GORD/aspiration",
"Idiopathic: 25–50% of cases",
], cx, y, col2, PAL)
y_b = section_box(draw, "CLINICAL FEATURES", [
"Chronic productive cough (large volumes, 3-layered sputum)",
"Recurrent chest infections / haemoptysis",
"Dyspnoea, wheeze, pleuritic chest pain",
"Signs: coarse crackles, wheeze, finger clubbing",
"Worsening with Pseudomonas colonisation",
], cx2, y, col2, PAL)
y = max(y_a, y_b)
y = section_box(draw, "INVESTIGATIONS", [
"Gold standard: HRCT chest — airway dilation, tram-track sign, signet-ring sign",
"Sputum culture (especially for Pseudomonas, NTM, Aspergillus)",
"PFTs: obstructive pattern (FEV1/FVC ↓); may have mixed pattern",
"Screen for cause: serum Ig, CFTR mutation, ciliary biopsy, ABPA (IgE, Aspergillus precipitins)",
"Bronchoscopy if focal bronchiectasis (exclude obstruction)",
], x0, y, col_w, PAL)
# Exacerbation decision diamond
y += 6
draw.text((W//2, y), "▼ Is there an ACUTE EXACERBATION?", font=F_SECTION, fill=YELLOW, anchor="mt")
y += 38
draw_arrow(draw, CX, y, CX-350, y+55, PAL["arrow"], 3)
draw_arrow(draw, CX, y, CX+350, y+55, PAL["arrow"], 3)
draw.text((CX-600, y+18), "YES — Acute management", font=F_SECTION, fill=(255,180,0))
draw.text((CX+220, y+18), "NO — Stable management", font=F_SECTION, fill=(180,220,100))
y += 65
draw_rounded_rect(draw, [x0, y, x0+col2-10, y+120], fill=PAL["box"], radius=10,
outline=PAL["hdr"], outline_w=2)
draw.text((x0+10, y+8), "Acute:", font=F_SECTION, fill=PAL["hdr"])
for i, t in enumerate(["Oral/IV antibiotics (guided by culture)",
"Pseudomonas: ciprofloxacin PO or ceftazidime IV",
"Airway clearance: physio, hypertonic saline",
"Bronchodilators if wheeze"]):
draw.text((x0+18, y+44+i*18), f"• {t}", font=F_SMALL, fill=WHITE)
draw_rounded_rect(draw, [x0+col2+10, y, x0+col_w, y+120], fill=PAL["box"], radius=10,
outline=PAL["hdr"], outline_w=2)
draw.text((x0+col2+20, y+8), "Stable / Long-term:", font=F_SECTION, fill=PAL["hdr"])
for i, t in enumerate(["Airway clearance techniques (daily physiotherapy)",
"Macrolide prophylaxis (azithromycin 3×/week)",
"Inhaled antibiotics (tobramycin, colistin) if Pseudomonas",
"Vaccinations: influenza, pneumococcal; treat cause"]):
draw.text((x0+col2+30, y+44+i*18), f"• {t}", font=F_SMALL, fill=WHITE)
y += 130
y += 10
draw.line([(30, y), (W-30, y)], fill=DIMGREY, width=2)
y += 16
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 3 — PULMONARY FIBROSIS (IPF)
# ═══════════════════════════════════════════════════════════════════════════════
PAL = COL["fibrosis"]
draw_rounded_rect(draw, [x0, y, x0+col_w, y+58], fill=PAL["hdr"], radius=14)
draw.text((x0+20, y+8), "③ PULMONARY FIBROSIS (IPF)", font=F_TOPIC, fill=(10,12,30))
y += 68
y = section_box(draw, "DEFINITION & EPIDEMIOLOGY", [
"IPF = chronic, progressive fibrosing interstitial pneumonia, unknown cause (UIP pattern)",
"50–60% of all idiopathic interstitial pneumonias",
"Mean age of onset: 62 years; more common in men; associated with cigarette smoking",
"Median survival: 3–5 years from diagnosis; progressive with acute exacerbations",
], x0, y, col_w, PAL)
# Two-column
y_a = section_box(draw, "RISK FACTORS", [
"Cigarette smoking (OR ~5.4)",
"Diabetes (OR ~4.1)",
"Familial (MUC5B gene, telomere genes)",
"Occupational dust (metal, wood, stone)",
"Micro-aspiration / GORD",
], cx, y, col2, PAL)
y_b = section_box(draw, "CLINICAL FEATURES", [
"Gradual onset exertional dyspnoea",
"Chronic non-productive cough",
"Bilateral fine end-inspiratory crackles (Velcro)",
"Clubbing (25–50%)",
"No fever/wheeze (unless AE-IPF)",
], cx2, y, col2, PAL)
y = max(y_a, y_b)
y = section_box(draw, "INVESTIGATIONS — HRCT IS KEY", [
"CXR: bibasilar reticular opacities, low lung volumes",
"HRCT (gold standard): UIP pattern — subpleural honeycombing + traction bronchiectasis",
" Lower lobe predominant; absence of GGO, micronodules, pleural effusion",
"PFTs: RESTRICTIVE (↓FVC, ↓TLC, ↓DLCO); FEV1/FVC normal or raised",
"BAL: excess neutrophils ± eosinophils (non-specific)",
"Surgical lung biopsy: UIP histology if HRCT non-diagnostic",
], x0, y, col_w, PAL)
# IPF management flow
y += 6
draw.text((W//2, y), "▼ MANAGEMENT DECISION", font=F_SECTION, fill=YELLOW, anchor="mt")
y += 36
draw_rounded_rect(draw, [x0, y, x0+col_w, y+170], fill=PAL["box"], radius=12,
outline=PAL["hdr"], outline_w=2)
headers = ["Antifibrotic Drugs", "Supportive Care", "Acute Exacerbation (AE-IPF)", "End-Stage"]
contents = [
["Nintedanib 150 mg BD", "Pirfenidone 2403 mg/day", "Slow FVC decline by ~50%", "Do NOT use steroids+azathioprine+NAC (harm)"],
["O2 therapy for hypoxaemia", "Pulmonary rehab", "Treat GORD (PPIs)", "Vaccinations"],
["High-dose IV corticosteroids", "Rule out infection", "Mechanical ventilation rarely beneficial", "Consider palliative care"],
["Lung transplant (single/bilateral)", "Palliative/hospice care", "DNR discussion", "Opiates for dyspnoea"],
]
col4 = (col_w - 30) // 4
for j, (hdr, items) in enumerate(zip(headers, contents)):
bx = x0 + j*(col4+10)
draw.text((bx+10, y+8), hdr, font=F_SMALL, fill=PAL["hdr"])
for i, item in enumerate(items):
draw.text((bx+6, y+34+i*30), f"• {item}", font=F_SMALL, fill=WHITE)
y += 180
y += 10
draw.line([(30, y), (W-30, y)], fill=DIMGREY, width=2)
y += 16
# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 4 — PNEUMONIA
# ═══════════════════════════════════════════════════════════════════════════════
PAL = COL["pneumo"]
draw_rounded_rect(draw, [x0, y, x0+col_w, y+58], fill=PAL["hdr"], radius=14)
draw.text((x0+20, y+8), "④ PNEUMONIA", font=F_TOPIC, fill=(10,12,30))
y += 68
y = section_box(draw, "CLASSIFICATION", [
"CAP — Community-Acquired Pneumonia (onset outside hospital or <48h of admission)",
"HAP — Hospital-Acquired Pneumonia (≥48h after admission, not incubating on admission)",
"VAP — Ventilator-Associated Pneumonia (≥48–72h after ET intubation)",
"HCAP — Healthcare-Associated (nursing home, haemodialysis, recent hospitalisation)",
"Aspiration pneumonia — gastric contents/oropharyngeal flora",
], x0, y, col_w, PAL)
# Organisms
y_a = section_box(draw, "KEY ORGANISMS — CAP", [
"Typical: S. pneumoniae (most common), H. influenzae, K. pneumoniae",
"Atypical: Mycoplasma, Legionella, Chlamydophila, viruses",
"Immunocompromised: PCP (Pneumocystis), fungi, CMV",
], cx, y, col2, PAL)
y_b = section_box(draw, "KEY ORGANISMS — HAP/VAP", [
"MRSA, Pseudomonas aeruginosa, Acinetobacter",
"Klebsiella pneumoniae (lobar consolidation)",
"Enterobacterales, polymicrobial",
], cx2, y, col2, PAL)
y = max(y_a, y_b)
# CURB-65
y += 8
draw.text((W//2, y), "▼ SEVERITY ASSESSMENT — CURB-65", font=F_SECTION, fill=YELLOW, anchor="mt")
y += 38
draw_rounded_rect(draw, [x0+60, y, x0+col_w-60, y+160], fill=(60,10,40), radius=14,
outline=PAL["hdr"], outline_w=3)
draw.text((W//2, y+10), "CURB-65 Score (1 point each):", font=F_SECTION, fill=YELLOW, anchor="mt")
curb = [
("C", "Confusion (new onset)"),
("U", "Urea > 7 mmol/L"),
("R", "Respiratory rate ≥ 30/min"),
("B", "Blood pressure — systolic <90 or diastolic ≤60"),
("65","Age ≥ 65 years"),
]
for i, (letter, desc) in enumerate(curb):
draw.text((x0+90, y+50+i*20), letter, font=F_SECTION, fill=PAL["hdr"])
draw.text((x0+130, y+50+i*20), f"— {desc}", font=F_BODY, fill=WHITE)
scores = [
("Score 0–1", "Low risk → Outpatient oral antibiotics (amoxicillin 500mg TDS × 5d)", (80,200,80)),
("Score 2", "Moderate → Hospital admission, oral/IV antibiotics", (255,200,0)),
("Score 3–5", "Severe → ICU consideration, IV antibiotics, supportive care", (255,80,80)),
]
for i, (sc, rec, col) in enumerate(scores):
bx = x0 + 60 + i*(( col_w-120)//3)
draw_rounded_rect(draw, [bx, y+155, bx+(col_w-120)//3-8, y+155+50], fill=(30,0,20), radius=8,
outline=col, outline_w=2)
draw.text((bx+6, y+157), sc, font=F_SMALL, fill=col)
wrapped_text(draw, rec, bx+6, y+172, (col_w-120)//3-12, F_SMALL, WHITE)
y += 215
y = section_box(draw, "INVESTIGATIONS", [
"Bloods: FBC (↑WCC), CRP, U&E (urea for CURB), LFTs, blood cultures (2 sets before Abx)",
"Sputum MC&S (before antibiotics if possible); urinary antigen (Strep, Legionella)",
"CXR: lobar/segmental consolidation (typical); diffuse patchy (atypical/viral)",
"ABG if SpO2 <94% or severe; HIV test if PCP suspected",
"Procalcitonin — helps guide antibiotic duration",
], x0, y, col_w, PAL)
y = section_box(draw, "ANTIBIOTIC TREATMENT — CAP", [
"Mild CAP (outpatient): Amoxicillin 500mg TDS × 5 days OR Doxycycline 200mg day 1 → 100mg OD",
"Moderate CAP (inpatient): Amoxicillin IV + Clarithromycin IV (dual for atypicals)",
"Severe CAP / ICU: Co-amoxiclav IV + Clarithromycin IV OR Piperacillin-tazobactam + azithromycin",
"Penicillin allergy: Doxycycline (mild); Levofloxacin/Moxifloxacin (moderate-severe)",
"Aspiration: Add metronidazole for anaerobic cover",
"Duration: minimum 5 days once afebrile; 7–10d if severe or bacteraemic",
], x0, y, col_w, PAL)
y = section_box(draw, "COMPLICATIONS & RESOLUTION", [
"Parapneumonic effusion → if complicated (pH<7.2) → drain",
"Empyema → chest tube + antibiotics; lung abscess → prolonged antibiotics ± drainage",
"Sepsis / septic shock → fluids, vasopressors, early Abx within 1h",
"ARDS → lung protective ventilation (low tidal volume 6 mL/kg IBW)",
"CXR resolution: 2–4 weeks (young); 6–12 weeks (elderly / severe) — repeat at 6 weeks",
], x0, y, col_w, PAL)
y += 10
draw.line([(30, y), (W-30, y)], fill=DIMGREY, width=2)
y += 16
# ═══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ═══════════════════════════════════════════════════════════════════════════════
draw_rounded_rect(draw, [30, y, W-30, y+60], fill=(20,25,55), radius=14,
outline=(80,100,180), outline_w=2)
draw.text((W//2, y+10),
"Sources: Harrison's IM 22E (2025) | Goldman-Cecil Medicine | Murray & Nadel's Respiratory | Washington Manual",
font=F_SMALL, fill=LGREY, anchor="mt")
draw.text((W//2, y+36), "For educational use — MBBS/MD clinical reasoning aid",
font=F_SMALL, fill=DIMGREY, anchor="mt")
y += 70
# ── Crop to actual content ────────────────────────────────────────────────────
canvas = canvas.crop((0, 0, W, min(y + 20, H)))
canvas.save("/home/daytona/workspace/rs-flowchart/RS_Flowchart.png", dpi=(150,150))
print(f"Saved! Size: {canvas.size}, y_end={y}")
cd /home/daytona/workspace/rs-flowchart && python3 gen_flowchart.py 2>&1
ls -lh /home/daytona/workspace/rs-flowchart/RS_Flowchart.png
RS Flowchart
Image · PNG
Lets go to pnemonia alone right now ... i want a perfect flowcgart for case sheet on oneumia what to ask and y to ask like that as a flowchart
~/rs-flowchart/pneumonia_casesheet.py
from PIL import Image, ImageDraw, ImageFont
import os, textwrap
# ── Canvas ─────────────────────────────────────────────────────────────────
W = 2600
H = 7800
BG = (8, 10, 28)
canvas = Image.new("RGB", (W, H), BG)
draw = ImageDraw.Draw(canvas)
# ── Fonts ──────────────────────────────────────────────────────────────────
def fnt(size, bold=False):
paths = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf" if bold else
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
]
for p in paths:
if os.path.exists(p):
return ImageFont.truetype(p, size)
return ImageFont.load_default()
F_MEGA = fnt(72, True)
F_BIG = fnt(52, True)
F_HEAD = fnt(40, True)
F_SUB = fnt(33, True)
F_BODY = fnt(27)
F_SMALL = fnt(23)
F_TINY = fnt(20)
# ── Palette ────────────────────────────────────────────────────────────────
# Each step has a colour
C = {
"step": (255, 200, 0), # yellow - step labels
"why": (100, 210, 255), # cyan - "why ask" text
"box_hdr": (30, 35, 80), # dark box header
"arrow": (200, 200, 200),
"green": (80, 220, 120),
"orange": (255, 160, 60),
"red": (255, 80, 80),
"white": (240, 245, 255),
"dimgrey": (90, 100, 130),
"lgrey": (160, 175, 200),
}
STEPS = [
(255, 200, 0), # 1 CHIEF COMPLAINT
(255, 120, 50), # 2 HISTORY OF PRESENTING ILLNESS
(180, 120, 255), # 3 PAST HISTORY
(80, 200, 255), # 4 PERSONAL / SOCIAL HISTORY
(80, 230, 120), # 5 SYSTEMIC REVIEW
(255, 100, 150), # 6 GENERAL EXAM
(255, 210, 80), # 7 RESPIRATORY EXAM
(140, 220, 255), # 8 INVESTIGATIONS
(255, 140, 80), # 9 SEVERITY SCORE
(140, 255, 160), # 10 DIAGNOSIS
(255, 100, 120), # 11 MANAGEMENT
(200, 180, 255), # 12 COMPLICATIONS / FOLLOW UP
]
# ── Helpers ────────────────────────────────────────────────────────────────
def rr(xy, fill, r=16, outline=None, ow=2):
draw.rounded_rectangle(xy, radius=r, fill=fill, outline=outline, width=ow)
def text_c(text, cx, y, fnt, color):
bb = draw.textbbox((0,0), text, font=fnt)
w = bb[2]-bb[0]
draw.text((cx - w//2, y), text, font=fnt, fill=color)
def wrapped(text, x, y, max_w, fnt, color, lsp=6, indent=0):
cw = max(int(max_w / (fnt.size * 0.58)), 12)
lines = []
for raw in text.split("\n"):
if raw == "":
lines.append("")
else:
lines += textwrap.wrap(raw, width=cw) or [""]
lh = fnt.size + lsp
for i, ln in enumerate(lines):
draw.text((x + indent, y + i*lh), ln, font=fnt, fill=color)
return y + len(lines)*lh + 4
def arrow_down(x, y1, y2, color=(180,180,200), w=4):
draw.line([(x, y1),(x, y2)], fill=color, width=w)
draw.polygon([(x,y2),(x-10,y2-18),(x+10,y2-18)], fill=color)
def section_divider(y):
draw.line([(40, y), (W-40, y)], fill=(40,45,90), width=2)
return y + 8
# ── BIG step block ─────────────────────────────────────────────────────────
def step_block(step_num, step_title, step_color, content_rows, y,
ask_col=(240,245,255), why_col=(100,210,255)):
"""
content_rows: list of (question_text, why_text)
"""
PAD = 40
BW = W - 2*PAD
x = PAD
# Step number badge
badge_w = 130
rr([x, y, x+badge_w, y+50], fill=step_color, r=10)
text_c(f"STEP {step_num}", x + badge_w//2, y+8, F_SUB, (10,12,30))
# Step title
draw.text((x + badge_w + 18, y+6), step_title, font=F_HEAD, fill=step_color)
y += 60
# Column headers
col_q = int(BW * 0.44)
col_w_w = BW - col_q - 12
hdr_h = 38
rr([x, y, x+col_q, y+hdr_h], fill=(30,35,75), r=8)
rr([x+col_q+12, y, x+BW, y+hdr_h], fill=(10,30,50), r=8)
draw.text((x+10, y+6), "WHAT TO ASK / EXAMINE / ORDER", font=F_SMALL, fill=(220,220,80))
draw.text((x+col_q+22, y+6), "WHY — Clinical Reasoning", font=F_SMALL, fill=(100,210,255))
y += hdr_h + 4
# Rows
for i, (q, w) in enumerate(content_rows):
row_bg = (18, 20, 50) if i%2==0 else (22, 25, 58)
# Estimate row height
cw_q = max(int(col_q / (F_BODY.size * 0.6)), 12)
cw_w = max(int(col_w_w / (F_BODY.size * 0.6)), 12)
lines_q = []
for r2 in q.split("\n"):
lines_q += textwrap.wrap(r2, width=cw_q) or [""]
lines_w = []
for r2 in w.split("\n"):
lines_w += textwrap.wrap(r2, width=cw_w) or [""]
row_h = max(len(lines_q), len(lines_w)) * (F_BODY.size + 5) + 14
rr([x, y, x+BW, y+row_h], fill=row_bg, r=6)
# bullet circle
draw.ellipse([x+6, y+row_h//2-6, x+18, y+row_h//2+6], fill=step_color)
# Q text
ty = y+8
for ln in lines_q:
draw.text((x+26, ty), ln, font=F_BODY, fill=ask_col)
ty += F_BODY.size + 5
# W text
ty = y+8
for ln in lines_w:
draw.text((x+col_q+22, ty), ln, font=F_BODY, fill=why_col)
ty += F_BODY.size + 5
y += row_h + 3
y += 12
return y
# ════════════════════════════════════════════════════════════════════════════
# MASTER TITLE
# ════════════════════════════════════════════════════════════════════════════
y = 20
rr([20, y, W-20, y+100], fill=(18,22,60), r=20, outline=(80,120,240), ow=3)
text_c("PNEUMONIA — Complete Clinical Case Sheet Flowchart", W//2, y+10, F_BIG, (255,220,60))
text_c("What to Ask | What to Examine | Why it Matters | Investigations | Management",
W//2, y+68, F_SMALL, (140,160,220))
y += 115
# Column legend
rr([40, y, 600, y+34], fill=(30,35,75), r=8)
rr([620, y, W-40, y+34], fill=(10,30,50), r=8)
draw.text((52, y+6), "WHAT TO ASK / EXAMINE / ORDER", font=F_SMALL, fill=(220,220,80))
draw.text((632, y+6), "WHY — Clinical Reasoning", font=F_SMALL, fill=(100,210,255))
y += 44
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 1 — CHIEF COMPLAINT
# ════════════════════════════════════════════════════════════════════════════
y = step_block(1, "CHIEF COMPLAINT", STEPS[0], [
("Patient's main presenting symptom in their own words",
"Sets the clinical frame — respiratory vs systemic vs cardiac"),
("Duration of the complaint",
"Acute (<1 wk) = likely infection; Chronic = consider TB, malignancy"),
("Onset — sudden vs gradual",
"Sudden = lobar pneumonia (S. pneumoniae); gradual = atypical (Mycoplasma)"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 2 — HISTORY OF PRESENTING ILLNESS
# ════════════════════════════════════════════════════════════════════════════
y = step_block(2, "HISTORY OF PRESENTING ILLNESS (HOPI)", STEPS[1], [
("Cough — present or absent?",
"Most sensitive symptom; absent cough does NOT exclude pneumonia"),
("Cough character — dry vs productive",
"Productive = bacterial (typical); Dry = atypical/viral/Mycoplasma/Legionella"),
("Sputum — colour, amount, blood",
"Rusty = pneumococcal; Green/yellow = bacterial; Frank haemoptysis → TB, abscess, cancer"),
("Fever — onset, pattern, chills/rigors",
"High fever + single rigor = S. pneumoniae; Relative bradycardia with fever = Legionella/Typhoid"),
("Dyspnoea — at rest or exertional, severity",
"RR ≥30 is part of CURB-65 (severe marker); hypoxia drives confusion and cyanosis"),
("Pleuritic chest pain — sharp, worsens with breathing",
"Indicates pleural involvement = consolidation reaching pleura (typical bacterial)"),
("Haemoptysis",
"Blood-streaked = bronchitis/pneumonia; Massive = TB, lung abscess, cancer — must exclude"),
("Associated symptoms: sweats, weight loss",
"Night sweats + weight loss + haemoptysis → TB until proven otherwise"),
("Myalgia, headache, sore throat preceding illness",
"Prodromal viral illness → secondary bacterial pneumonia (influenza → S. aureus/S. pneumoniae)"),
("Any vomiting, aspiration, altered consciousness",
"Aspiration risk → anaerobic organisms, Klebsiella; aspiration pneumonia = lower lobe"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 3 — PAST MEDICAL HISTORY
# ════════════════════════════════════════════════════════════════════════════
y = step_block(3, "PAST MEDICAL HISTORY (PMH)", STEPS[2], [
("Previous pneumonia / chest infections",
"Recurrent = consider bronchiectasis, immunodeficiency, structural lung disease"),
("COPD / Asthma / Bronchiectasis",
"Pre-existing lung disease worsens severity; CURB-65 may underestimate severity"),
("Diabetes mellitus",
"Immunosuppression → risk of Klebsiella, S. aureus, fungi; poor glucose control → worse outcome"),
("HIV / Immunosuppression",
"PCP (P. jirovecii) if CD4 <200; CMV, fungi (Aspergillus, Cryptococcus) if severely immunocompromised"),
("Cardiac disease (CHF, valvular)",
"Heart failure can mimic pneumonia; CHF + pneumonia → worse prognosis; right-sided endocarditis → septic emboli"),
("Malignancy / chemotherapy",
"Chemotherapy → neutropenia → atypical organisms; malignancy can cause post-obstructive pneumonia"),
("Renal failure / dialysis",
"Immunosuppressed + HCAP risk organisms (MRSA, Pseudomonas); may affect drug dosing"),
("Recent surgery / hospitalisation (last 90 days)",
"If within 90 days → HAP/HCAP organisms (MRSA, resistant GNBs, Pseudomonas)"),
("Swallowing problems / neurological disease",
"Stroke, Parkinson's, dementia → aspiration risk → lower lobe patchy pneumonia, anaerobes"),
("Previous TB / contact with TB",
"Reactivation TB in immunosuppressed; atypical cavitating picture"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 4 — PERSONAL / SOCIAL / TRAVEL HISTORY
# ════════════════════════════════════════════════════════════════════════════
y = step_block(4, "PERSONAL / SOCIAL / TRAVEL / DRUG HISTORY", STEPS[3], [
("Smoking history — pack years",
"Damages mucociliary clearance; increases risk of S. pneumoniae, H. influenzae, S. aureus"),
("Alcohol use",
"Aspiration risk when intoxicated; impairs immunity → S. pneumoniae, Klebsiella, anaerobes"),
("IV drug use",
"Right-sided endocarditis → septic pulmonary emboli mimicking pneumonia (MRSA)"),
("Occupation — healthcare worker, farmer, abattoir",
"Healthcare → MRSA, MDR organisms; farmers → Q fever (Coxiella), psittacosis (birds)"),
("Travel history — where? when?",
"Southeast Asia/Middle East → MERS-CoV; US southwest → Coccidioidomycosis; Ohio valley → Histoplasmosis; Sub-Saharan Africa → TB"),
("Animal / bird exposure",
"Birds → Chlamydophila psittaci (psittacosis); Bats/caves → Histoplasma; Sheep/cattle → Q fever"),
("Hostel / prison / close-contact living",
"TB risk; Legionella if hotel/spa/AC exposure"),
("Current medications — steroids, immunosuppressants, ACE inhibitors",
"ACE inhibitor → cough (mimics symptom); steroids/immunosuppressants → opportunistic infections"),
("Vaccination status — influenza, pneumococcal",
"Vaccinated → less likely typical pneumococcal; unvaccinated elderly → high risk"),
("HIV risk factors — sexual history, transfusions",
"Determines need for PCP prophylaxis and HIV testing"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 5 — SYSTEMIC REVIEW (targeted)
# ════════════════════════════════════════════════════════════════════════════
y = step_block(5, "SYSTEMIC REVIEW (Targeted)", STEPS[4], [
("Confusion / altered mental status",
"C in CURB-65 — confusion is a SEVERE sign; also consider meningitis"),
("Abdominal pain, diarrhoea",
"Lower lobe pneumonia can refer pain to abdomen; Legionella causes GI symptoms"),
("Rash, joint pain",
"Erythema multiforme → Mycoplasma; Erythema nodosum → TB/fungi; SLE → lupus pneumonitis"),
("Ear pain, sore throat",
"Preceding URTI → viral then secondary bacterial pneumonia"),
("Urinary symptoms",
"Elderly often present with incontinence/UTI-like symptoms as atypical pneumonia presentation"),
("Leg swelling / calf pain",
"DVT → pulmonary embolism mimics or complicates pneumonia"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 6 — GENERAL EXAMINATION
# ════════════════════════════════════════════════════════════════════════════
y = step_block(6, "GENERAL PHYSICAL EXAMINATION", STEPS[5], [
("Temperature — fever or hypothermia",
"Fever = active infection; Hypothermia in elderly/immunocompromised = sepsis (WORSE sign)"),
("Pulse rate — tachycardia?",
"HR >100 = systemic response to infection; relative bradycardia + fever → Legionella/typhoid"),
("Blood pressure — hypotension?",
"Systolic <90 or diastolic ≤60 = B in CURB-65 = SEVERE; suggests sepsis → need vasopressors"),
("Respiratory rate — count for full minute",
"RR ≥30 = R in CURB-65 = SEVERE; most sensitive early sign of respiratory compromise"),
("SpO2 on room air",
"SpO2 <92% → hospitalization; <88% → likely need supplemental O2; <90% → may need ICU"),
("Consciousness — GCS or AVPU",
"New confusion = C in CURB-65; low GCS → risk of aspiration, ICU needed"),
("Cyanosis — peripheral vs central",
"Central cyanosis = hypoxaemia; indicates O2 Sat usually <85%"),
("Clubbing of fingers",
"Not seen in acute pneumonia; if present → chronic lung disease (bronchiectasis, ILD, lung cancer)"),
("Lymphadenopathy",
"Hilar adenopathy → TB, sarcoidosis, lymphoma masquerading as pneumonia"),
("Jaundice",
"Hepatitis with viral pneumonia (influenza); leptospirosis if exposure; haemolysis (Mycoplasma)"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 7 — RESPIRATORY SYSTEM EXAMINATION
# ════════════════════════════════════════════════════════════════════════════
y = step_block(7, "RESPIRATORY SYSTEM EXAMINATION", STEPS[6], [
("INSPECTION — chest shape, movement, accessory muscles",
"Asymmetric chest movement → ipsilateral consolidation/effusion/collapse; accessory muscle use → severe dyspnoea"),
("INSPECTION — trachea position",
"Trachea central in pneumonia; deviated AWAY from lesion = effusion/tension pneumothorax; TOWARD = collapse"),
("PALPATION — tactile vocal fremitus (TVF)",
"INCREASED TVF over consolidation (solid transmits better); DECREASED over effusion or collapse"),
("PALPATION — expansion",
"REDUCED on affected side in consolidation, effusion, or collapse"),
("PERCUSSION — note over affected area",
"DULL = consolidation or pleural effusion; STONY DULL = effusion (different from dull)"),
("AUSCULTATION — breath sounds",
"BRONCHIAL breathing over consolidation (high-pitched, loud, gap between I and E); Absent = effusion"),
("AUSCULTATION — crackles",
"Fine late-inspiratory crackles over consolidation; do not clear with coughing (vs bronchitis)"),
("AUSCULTATION — vocal resonance (VR)",
"INCREASED VR / whispering pectoriloquy = consolidation; DECREASED = effusion"),
("AUSCULTATION — aegophony (E to A change)",
"Say 'E' → heard as 'A' over consolidation — highly specific for lobar pneumonia"),
("PLEURAL RUB",
"Creaking sound (like walking on fresh snow) = inflamed pleura = pleuritis; pleural effusion silences it"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 8 — INVESTIGATIONS
# ════════════════════════════════════════════════════════════════════════════
y = step_block(8, "INVESTIGATIONS — ORDER & REASON", STEPS[7], [
("CXR PA (Chest X-Ray) — MANDATORY",
"Diagnosis requires NEW opacity on imaging; lobar = S. pneumoniae; patchy bilateral = viral/atypical; cavitation = S. aureus/TB; RLL = aspiration"),
("FBC (Full Blood Count)",
"WBC >15k = bacterial; Neutrophilia = bacterial; Lymphocytosis = viral; Thrombocytopenia = sepsis (severe)"),
("CRP (C-reactive protein)",
"Elevated in infection; used for monitoring response and antibiotic stewardship; normal CRP makes pneumonia less likely"),
("Procalcitonin (PCT)",
"More specific for bacterial infection than CRP; serial PCT guides stopping antibiotics; NOT for initial Dx alone"),
("Urea and Electrolytes (U&E)",
"Urea >7 mmol/L = U in CURB-65; hyponatraemia → Legionella; renal function for drug dosing"),
("Liver function tests (LFTs)",
"Legionella causes raised ALT; severe sepsis → hepatic dysfunction; baseline for antibiotics"),
("Blood cultures × 2 (before antibiotics)",
"Bacteraemia in 10-30% of CAP; positive = escalate Abx; take BEFORE first antibiotic dose"),
("Sputum MC&S (Microscopy, Culture, Sensitivity)",
"Identify organism; guide de-escalation; most useful in moderate-severe CAP; ensure taken BEFORE Abx"),
("Urinary Legionella antigen",
"Detects L. pneumophila serogroup 1; quick, non-invasive; positive → add macrolide/fluoroquinolone"),
("Urinary pneumococcal antigen",
"Positive confirms S. pneumoniae even after antibiotics started; not affected by prior Abx treatment"),
("ABG (Arterial Blood Gas) if SpO2 <94%",
"PaO2, PaCO2, pH; PaO2 <60 = type 1 RF; rising PaCO2 = type 2 RF = ventilatory failure → ICU"),
("HIV test (if risk factors)",
"PCP presents as bilateral diffuse pneumonia in HIV (CD4 <200); requires co-trimoxazole NOT standard Abx"),
("CT Chest if diagnosis uncertain / poor response",
"Higher sensitivity than CXR; detects cavitation, effusion, adenopathy; excludes PE, malignancy"),
("Nasopharyngeal swab PCR (influenza/COVID/RSV)",
"Viral pneumonia → antivirals (oseltamivir for flu); no/de-escalate antibiotics; cohort isolation"),
("Thoracocentesis if pleural effusion present",
"pH <7.2 or glucose <60 or LDH >1000 = COMPLICATED → needs chest drain; send for culture/cytology/Light's"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 9 — SEVERITY SCORING
# ════════════════════════════════════════════════════════════════════════════
y_start = y
# CURB-65 box
BW = W - 80
rr([40, y, 40+BW, y+58], fill=STEPS[8], r=14)
text_c("STEP 9 — SEVERITY SCORING: CURB-65", W//2, y+10, F_HEAD, (10,12,30))
y += 66
# Table header
col5 = (BW - 10) // 5
headers5 = ["Variable", "C Confusion", "U Urea >7", "R RR ≥30", "B BP sys<90", "65 Age ≥65"]
# 6 cols but 5 variables + label col
col6 = (BW - 10) // 6
h_labels = ["Variable", "C — Confusion\n(new onset)", "U — Urea\n>7 mmol/L",
"R — RR\n≥30/min", "B — BP sys<90\nor dia≤60", "65 — Age\n≥65 years"]
sub_labels = ["Why it matters", "= C in CURB-65;\nICU risk, aspiration",
"Severity marker,\nrenal impairment",
"Respiratory\nfailure marker",
"Sepsis /\ncirculatory shock",
"Reduced reserve,\nhigh mortality"]
for j, (hl, sl) in enumerate(zip(h_labels, sub_labels)):
bx = 40 + j*col6
bg_c = (30,35,80) if j==0 else (18,20,55)
rr([bx, y, bx+col6-4, y+100], fill=bg_c, r=8, outline=STEPS[8], ow=1)
draw.text((bx+6, y+6), hl, font=F_SMALL, fill=STEPS[8])
draw.text((bx+6, y+54), sl, font=F_TINY, fill=(160,185,220))
y += 108
# Score guide
score_rows = [
("0", "Low risk (30-day mortality ~1.5%)", "Outpatient, oral amoxicillin 500mg TDS × 5d", C["green"]),
("1–2", "Moderate risk", "Admit if any concern; oral/IV dual therapy", C["orange"]),
("3–5", "Severe (mortality ≥22%)", "ICU consideration; IV piperacillin-tazobactam + azithromycin; vasopressors/MV if needed", C["red"]),
]
for sc, risk, rec, col in score_rows:
rr([40, y, 40+BW, y+52], fill=(15,18,45), r=8, outline=col, ow=2)
draw.text((50, y+8), f"Score {sc}", font=F_SUB, fill=col)
draw.text((300, y+10), risk, font=F_BODY, fill=(200,210,230))
draw.text((900, y+10), f"→ {rec}", font=F_BODY, fill=C["white"])
y += 56
# ICU criteria
y += 8
rr([40, y, 40+BW, y+50], fill=(50,10,10), r=10, outline=C["red"], ow=2)
draw.text((52, y+8), "DIRECT ICU CRITERIA (regardless of CURB-65):", font=F_SUB, fill=C["red"])
draw.text((740, y+8), "Septic shock (vasopressors needed) OR Acute respiratory failure (intubation/MV needed)",
font=F_BODY, fill=C["white"])
y += 58
y = section_divider(y + 4)
# ════════════════════════════════════════════════════════════════════════════
# STEP 10 — DIFFERENTIALS / CONFIRM DIAGNOSIS
# ════════════════════════════════════════════════════════════════════════════
y = step_block(10, "DIFFERENTIAL DIAGNOSIS & CONFIRM", STEPS[9], [
("Pulmonary oedema / CHF",
"Bilateral basal crackles + raised JVP + orthopnoea; CXR: cardiomegaly + bat-wing shadowing; BNP elevated"),
("Pulmonary embolism (PE)",
"Pleuritic chest pain + dyspnoea + normal CXR + hypoxia; Wells score + D-dimer + CTPA"),
("Lung cancer with post-obstructive pneumonia",
"Recurrent pneumonia in same lobe; smoker; CXR mass; needs bronchoscopy"),
("Tuberculosis",
"Upper lobe cavitation; night sweats, weight loss, chronic cough; AFB smear + culture; contact history"),
("Lung abscess",
"Fever, putrid sputum, cavitation with fluid level on CXR; anaerobes/S. aureus; prolonged Abx"),
("Pneumocystis pneumonia (PCP)",
"HIV + bilateral diffuse GGO; no bacteria on culture; responds to co-trimoxazole NOT standard Abx"),
("COVID-19 / Viral pneumonia",
"Bilateral patchy GGO on CT; PCR positive; antivirals if appropriate; no routine Abx"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 11 — MANAGEMENT
# ════════════════════════════════════════════════════════════════════════════
y = step_block(11, "MANAGEMENT — ANTIBIOTICS + SUPPORTIVE", STEPS[10], [
("Administer O2 to keep SpO2 ≥94% (or 88–92% in COPD)",
"Prevents hypoxaemic organ damage; titrate to avoid hypercapnia in COPD patients"),
("IV access + IV fluids if hypotensive / dehydrated",
"Sepsis resuscitation: 30 mL/kg crystalloid bolus; monitor lactate; target MAP ≥65 mmHg"),
("MILD CAP (outpatient, CURB-65 = 0-1):\nAmoxicillin 500mg TDS × 5 days",
"S. pneumoniae is most common; amoxicillin has excellent coverage; use doxycycline if penicillin allergy"),
("MODERATE CAP (ward, CURB-65 = 2):\nAmoxicillin IV + Clarithromycin IV",
"Dual covers typical (amoxicillin) AND atypical (Legionella, Mycoplasma) organisms"),
("SEVERE CAP (ICU, CURB-65 ≥3):\nPiperacillin-tazobactam IV + Azithromycin IV\nOR Co-amoxiclav + Clarithromycin",
"Broader spectrum for resistant organisms; macrolide covers Legionella, Mycoplasma; consider MRSA cover (vancomycin) if risk factors"),
("ASPIRATION pneumonia:\nAdd Metronidazole 400mg TDS",
"Anaerobic cover for oral flora (Bacteroides, Fusobacterium); lower lobe involved typically"),
("HAP / VAP:\nPiperacillin-tazobactam ± Vancomycin/Linezolid (for MRSA)",
"MRSA risk in HAP; Pseudomonas cover essential; use local antibiogram to guide choice"),
("PCP (HIV):\nCo-trimoxazole (Trimethoprim-sulfamethoxazole) high dose",
"First-line for PCP; add prednisolone if PaO2 <70 mmHg; standard Abx will FAIL PCP"),
("Antibiotic DURATION:\n5 days if afebrile; 7–10d if severe / bacteraemic",
"PCT-guided stopping reduces duration; ensure clinical stability first: afebrile, HR/RR normal, tolerating oral"),
("Analgesia for pleuritic pain",
"Paracetamol/NSAIDs; adequate pain relief improves breathing effort and sputum clearance"),
("VTE prophylaxis (LMWH + TED stockings)",
"Hospitalised patients at VTE risk; pneumonia itself increases clotting tendency"),
("Physiotherapy / positioning",
"Upright posture improves VQ matching; physio helps sputum clearance"),
], y)
y = section_divider(y)
# ════════════════════════════════════════════════════════════════════════════
# STEP 12 — COMPLICATIONS + FOLLOW UP
# ════════════════════════════════════════════════════════════════════════════
y = step_block(12, "COMPLICATIONS & FOLLOW UP", STEPS[11], [
("Parapneumonic effusion / Empyema",
"If effusion develops → thoracocentesis; pH <7.2 / glucose <60 / turbid = complicated → chest drain + prolonged Abx"),
("Lung abscess",
"Cavity with fluid level; prolonged IV/oral Abx (6–8 weeks); percutaneous drainage if not responding"),
("Respiratory failure (type 1 / type 2)",
"Type 1 (↓PaO2, normal CO2) → high-flow O2; Type 2 (↓PaO2, ↑CO2) → NIV/BiPAP; intubation if failing"),
("ARDS (Acute Respiratory Distress Syndrome)",
"Bilateral infiltrates + PaO2/FiO2 <300 + no heart failure; lung protective ventilation 6 mL/kg; prone positioning"),
("Sepsis / Septic Shock",
"Sepsis-3 criteria: suspected infection + SOFA ≥2; septic shock: vasopressors to maintain MAP ≥65; early Abx within 1h"),
("Metastatic infection (meningitis, endocarditis)",
"Bacteraemia → seeding; persistent fever + new focal signs → investigate; LP if meningism"),
("Hepatitis / Multi-organ dysfunction",
"Legionella, severe sepsis → LFT derangement; monitor renal, liver, coag in severe disease"),
("FOLLOW UP: Repeat CXR at 6 weeks",
"CXR should clear by 6 wks (young) or 12 wks (elderly); failure to clear → exclude malignancy, TB"),
("FOLLOW UP: Offer pneumococcal + influenza vaccine",
"Reduces future episodes; give before discharge or at follow-up; part of prevention strategy"),
], y)
y = section_divider(y)
# ── Footer ─────────────────────────────────────────────────────────────────
y += 6
rr([20, y, W-20, y+65], fill=(16,20,50), r=14, outline=(60,80,180), ow=2)
text_c("Sources: Harrison's Principles IM 22E (2025) | Goldman-Cecil Medicine | Murray & Nadel's Respiratory Medicine",
W//2, y+10, F_SMALL, C["lgrey"])
text_c("For clinical education — MBBS/MD Pneumonia Case Sheet | CURB-65 based on British Thoracic Society guidelines",
W//2, y+38, F_TINY, C["dimgrey"])
y += 75
# ── Crop & Save ──────────────────────────────────────────────────────────
final = canvas.crop((0, 0, W, min(y, H)))
out_path = "/home/daytona/workspace/rs-flowchart/Pneumonia_CaseSheet_Flowchart.png"
final.save(out_path, dpi=(150, 150))
print(f"Saved: {out_path}")
print(f"Canvas size: {final.size}")
cd /home/daytona/workspace/rs-flowchart && python3 pneumonia_casesheet.py 2>&1
ls -lh /home/daytona/workspace/rs-flowchart/Pneumonia_CaseSheet_Flowchart.png
Pneumonia CaseSheet Flowchart
Image · PNG
| Step | Section | What it covers |
|---|---|---|
| 1 | Chief Complaint | Onset, duration, sudden vs gradual - frames the whole case |
| 2 | HOPI | Cough type, sputum colour, fever pattern, haemoptysis, pleuritic pain, myalgia - each with its clinical reason |
| 3 | Past Medical History | COPD, DM, HIV, CHF, malignancy, prior TB, recent hospitalisation |
| 4 | Personal/Social/Travel | Smoking, alcohol, IVDU, occupation, travel (specific organisms per region), animal exposure, drugs |
| 5 | Systemic Review | Confusion (CURB), abdo pain (Legionella), rash (Mycoplasma), DVT |
| 6 | General Exam | Temp, pulse, BP, RR, SpO2, GCS, cyanosis, clubbing, lymph nodes |
| 7 | Respiratory Exam | Full IPTPA — inspection, palpation (TVF, expansion), percussion, auscultation (bronchial breathing, crackles, aegophony, pleural rub) |
| 8 | Investigations | 15 tests — CXR, FBC, CRP, PCT, blood cultures, sputum, urinary Legionella/pneumococcal Ag, ABG, HIV, CTPA — each with exact reason |
| 9 | CURB-65 Scoring | Full table + score = 0/1-2/3-5 actions + direct ICU criteria |
| 10 | Differentials | CHF, PE, TB, lung abscess, PCP, lung cancer, COVID |
| 11 | Management | Mild/Moderate/Severe CAP, aspiration, HAP, PCP - doses + durations + reasoning |
| 12 | Complications & Follow-up | Empyema, ARDS, septic shock, lung abscess + repeat CXR at 6 weeks rule |