What is the differential diagnosis for using the different parameters of CBC
| MCV Category | Cutoff | Differential Diagnosis |
|---|---|---|
| Microcytic (↓ MCV) | < 80 fL | Iron deficiency anemia, Thalassemia, Sideroblastic anemia, Lead poisoning, Anemia of chronic disease (also normocytic) |
| Normocytic (normal MCV) | 80–100 fL | Aplastic anemia, Myelofibrosis, Myelophthisic anemia; Hypoendocrine states (hypothyroid, hypoadrenal, hypopituitary), Uremia, Chronic inflammation, Liver disease, Acute blood loss, Hemolysis |
| Macrocytic (↑ MCV) | > 100 fL | Vitamin B12 deficiency, Folate deficiency, Liver disease, Hypothyroidism, Drugs (methotrexate, hydroxyurea), Myelodysplastic syndrome |
For microcytic anemia, serum ferritin < 30 ng/mL confirms iron deficiency. For normocytic anemia, exclude hemolysis (reticulocyte count, LDH, haptoglobin) and renal insufficiency. For macrocytic, check B12/folate levels.
| Cell Type | Condition | Causes |
|---|---|---|
| Neutrophilia | Most common | Bacterial infections, rheumatic/autoimmune disorders, neoplastic disorders, trauma, drugs (corticosteroids, G-CSF), endocrine/metabolic disorders, CML, leukemoid reaction |
| Eosinophilia | Parasitic infections, allergic diseases (asthma, atopic dermatitis), myeloproliferative/neoplastic diseases, cutaneous diseases, GI diseases (IBD), drug reactions | |
| Basophilia | Allergic reactions, CML, myeloid metaplasia, polycythemia vera, ionizing radiation, hypothyroidism, chronic hemolytic anemia, post-splenectomy | |
| Monocytosis | Infections (TB, subacute bacterial endocarditis), neoplastic disorders, GI disorders, sarcoidosis, drug reactions, recovery from marrow suppression | |
| Lymphocytosis | Viral infections (EBV, CMV, hepatitis), lymphocytic leukemia (CLL), pertussis, other infectious diseases, lymphoproliferative neoplasms |
| Cell Type | Condition | Causes |
|---|---|---|
| Neutropenia | Overwhelming bacterial infection, viral infections, drug reactions (chemotherapy, carbimazole, clozapine), ionizing radiation, hematopoietic diseases, hypersplenism, anaphylactic shock, autoimmune disease (SLE), aplastic anemia | |
| Eosinopenia | Acute physical stress, acute inflammatory states, Cushing's syndrome, corticosteroid use | |
| Basopenia | Sustained glucocorticoid treatment, acute infection/stress, hyperthyroidism | |
| Monocytopenia | Onset of steroid therapy, hairy cell leukemia | |
| Lymphopenia | Immunodeficiency disorders (HIV/AIDS), adrenocortical hormone excess, chemotherapy, irradiation, impaired intestinal lymphatic drainage, advanced lymphomas/carcinomas, anorexia nervosa |
| Mechanism | Causes |
|---|---|
| Decreased Production | Aplastic anemia, congenital disorders, chemotherapy/radiation, vitamin B12 or folate deficiency, drugs, SLE, marrow infiltration (myelophthisis), myelodysplastic syndrome |
| Increased Destruction (Immune) | Immune thrombocytopenia (ITP), drug-induced (heparin → HIT, quinine, sulfonamides), neonatal alloimmune thrombocytopenia, post-transfusion purpura, SLE, antiphospholipid syndrome |
| Increased Destruction (Non-immune) | TTP (thrombotic thrombocytopenic purpura), HUS (hemolytic uremic syndrome), DIC, prosthetic heart valves, preeclampsia/HELLP |
| Sequestration / Dilutional | Hypersplenism (cirrhosis, storage diseases), massive transfusion |
| Factitious | EDTA-dependent platelet aggregation (pseudothrombocytopenia), platelet satellitism — always confirm with peripheral smear |
| Type | Causes |
|---|---|
| Reactive (Secondary) | Iron deficiency, acute blood loss, inflammatory disorders (RA, IBD), malignancies, post-splenectomy, infections, trauma — counts rarely exceed 1,000,000/μL |
| Clonal (Primary/Myeloproliferative) | Essential thrombocythemia, polycythemia vera, CML, primary myelofibrosis |
| Reticulocyte Index | Interpretation | Examples |
|---|---|---|
| ↑ (> 2%) | Appropriate bone marrow response / hemolytic or blood-loss anemia | Hemolytic anemia, acute hemorrhage, post-treatment response |
| ↓ (< 2%) | Hypoproliferative / bone marrow failure | Iron deficiency, B12/folate deficiency, aplastic anemia, anemia of chronic disease, renal failure |
| CBC Parameter | High (↑) | Low (↓) |
|---|---|---|
| Hgb/Hct | Polycythemia vera, dehydration, COPD | Anemia (classify by MCV) |
| MCV | B12/folate deficiency, liver disease, hypothyroid | Iron deficiency, thalassemia, sideroblastic anemia |
| RDW | Mixed deficiencies, early iron/B12 deficiency | Typically normal; low RDW in thalassemia |
| WBC total | Infection, leukemia, steroids, stress | Chemotherapy, aplasia, viral illness, autoimmune |
| Neutrophils | Bacterial infection, steroids, CML | Viral illness, drugs, aplastic anemia, autoimmune |
| Lymphocytes | Viral infection, CLL, pertussis | HIV, steroids, chemo, lymphoma |
| Eosinophils | Parasites, allergy, malignancy | Cushing's, steroids, acute stress |
| Monocytes | TB, SBE, malignancy, sarcoidosis | Hairy cell leukemia, steroids |
| Basophils | CML, allergy, hypothyroid | Stress, hyperthyroidism, steroids |
| Platelets | Iron deficiency, inflammation, post-splenectomy, ET | ITP, TTP, HIT, DIC, aplasia, B12/folate deficiency |
Create a clinical flowchart for CBC interpretation
ls /home/daytona/workspace/ 2>/dev/null && echo "---" && python3 -c "import repo…
python3 -c "import matplotlib; print(matplotlib.__version__)" && python3 -c "imp…
mkdir -p /home/daytona/workspace/cbc-flowchart
~/cbc-flowchart/generate_flowchart.py
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
from matplotlib.backends.backend_pdf import PdfPages
import textwrap
# ─── COLOR PALETTE ──────────────────────────────────────────────
C = {
'start': '#1A3A5C', # dark navy
'decision': '#2E6DA4', # blue diamond
'rbc': '#C0392B', # red – RBC branch
'wbc': '#1F7A4D', # green – WBC branch
'plt': '#7D3C98', # purple – Platelet branch
'micro': '#E74C3C', # microcytic
'normo': '#E67E22', # normocytic
'macro': '#8E44AD', # macrocytic
'dx': '#F0F4F8', # light dx box fill
'dx_border': '#2C3E50',
'arrow': '#2C3E50',
'text_light':'#FFFFFF',
'text_dark': '#1A1A1A',
'header_bg': '#1A3A5C',
}
def draw_box(ax, x, y, w, h, text, fc, tc='white', fontsize=7.5, style='round,pad=0.1',
bold=False, wrap_width=22, lw=1.2, ec=None):
ec = ec or fc
box = FancyBboxPatch((x - w/2, y - h/2), w, h,
boxstyle=style, linewidth=lw,
edgecolor=ec, facecolor=fc, zorder=3)
ax.add_patch(box)
wrapped = '\n'.join(textwrap.wrap(text, wrap_width))
weight = 'bold' if bold else 'normal'
ax.text(x, y, wrapped, ha='center', va='center', fontsize=fontsize,
color=tc, fontweight=weight, zorder=4, multialignment='center')
def draw_diamond(ax, x, y, w, h, text, fc, tc='white', fontsize=7):
dx, dy = w/2, h/2
diamond = plt.Polygon([[x, y+dy],[x+dx, y],[x, y-dy],[x-dx, y]],
closed=True, facecolor=fc, edgecolor='white',
linewidth=1.2, zorder=3)
ax.add_patch(diamond)
wrapped = '\n'.join(textwrap.wrap(text, 14))
ax.text(x, y, wrapped, ha='center', va='center', fontsize=fontsize,
color=tc, fontweight='bold', zorder=4, multialignment='center')
def arrow(ax, x1, y1, x2, y2, label='', lc=C['arrow'], lw=1.5, fs=6.5):
ax.annotate('', xy=(x2, y2), xytext=(x1, y1),
arrowprops=dict(arrowstyle='->', color=lc, lw=lw), zorder=2)
if label:
mx, my = (x1+x2)/2, (y1+y2)/2
ax.text(mx+0.05, my, label, fontsize=fs, color=lc, va='center', fontweight='bold')
def bracket_list(ax, x, y, items, color, title='', fontsize=6.8, dot='•'):
"""Draw a bullet list at (x,y) going downward."""
if title:
ax.text(x, y, title, fontsize=7, color=color, fontweight='bold', va='top', zorder=4)
y -= 0.22
for item in items:
wrapped = textwrap.wrap(item, 26)
ax.text(x, y, f'{dot} {wrapped[0]}', fontsize=fontsize, color=C['text_dark'],
va='top', zorder=4)
y -= 0.19
for extra in wrapped[1:]:
ax.text(x+0.15, y, extra, fontsize=fontsize, color=C['text_dark'], va='top', zorder=4)
y -= 0.19
return y
# ══════════════════════════════════════════════════════════════════
# PAGE 1 — RBC / Anemia Flowchart
# ══════════════════════════════════════════════════════════════════
def page_rbc(pdf):
fig, ax = plt.subplots(figsize=(11, 17))
ax.set_xlim(0, 11)
ax.set_ylim(0, 17)
ax.axis('off')
fig.patch.set_facecolor('#F7F9FC')
# ── Header ──────────────────────────────────────────────────
header = FancyBboxPatch((0, 15.8), 11, 1.1, boxstyle='square',
facecolor=C['header_bg'], edgecolor='none')
ax.add_patch(header)
ax.text(5.5, 16.4, 'CBC INTERPRETATION — RBC / ANEMIA FLOWCHART',
ha='center', va='center', fontsize=13, color='white',
fontweight='bold')
ax.text(5.5, 16.05, 'Page 1 of 3 | Orris Clinical Reference',
ha='center', va='center', fontsize=8, color='#AED6F1')
# ── Start ────────────────────────────────────────────────────
draw_box(ax, 5.5, 15.2, 4.0, 0.55, 'CHECK HEMOGLOBIN / HEMATOCRIT',
C['start'], fontsize=9, bold=True, wrap_width=40)
arrow(ax, 5.5, 14.93, 5.5, 14.45)
# ── Decision: Anemia? ────────────────────────────────────────
draw_diamond(ax, 5.5, 14.1, 3.2, 0.7,
'Anemia?\n(Hgb <13 M / <12 F)', C['decision'])
ax.text(8.2, 14.1, 'No → Normal / Polycythemia',
fontsize=7, color=C['wbc'], va='center', style='italic')
arrow(ax, 5.5, 13.75, 5.5, 13.2)
ax.text(5.65, 13.47, 'Yes', fontsize=7, color=C['rbc'], fontweight='bold')
# ── Step: Reticulocyte ───────────────────────────────────────
draw_box(ax, 5.5, 12.95, 5.5, 0.5,
'Check Reticulocyte Count + MCV + Peripheral Blood Smear',
'#2C3E50', fontsize=8, bold=False, wrap_width=50)
arrow(ax, 5.5, 12.7, 5.5, 12.15)
# ── Decision: Reticulocyte ────────────────────────────────────
draw_diamond(ax, 5.5, 11.8, 3.2, 0.65,
'Reticulocyte\nIndex > 2%?', C['decision'])
# YES → right
arrow(ax, 7.1, 11.8, 8.8, 11.8, 'Yes')
draw_box(ax, 9.6, 11.8, 2.3, 0.55,
'HEMOLYTIC or\nBLOOD LOSS\nAnemia',
C['rbc'], fontsize=7.5, bold=True, wrap_width=18)
# Sub-list
items_hemolytic = ['Autoimmune hemolytic\nanemia (AIHA)',
'Hereditary spherocytosis',
'G6PD deficiency',
'Sickle cell disease',
'TTP / HUS',
'Acute blood loss',
'Malaria']
y0 = 11.42
for it in items_hemolytic:
ax.text(8.55, y0, f'• {it}', fontsize=5.8, color=C['text_dark'], va='top')
y0 -= 0.19
# NO → down
arrow(ax, 5.5, 11.47, 5.5, 10.9, 'No ↓')
# ── MCV Classification ───────────────────────────────────────
draw_box(ax, 5.5, 10.65, 4.5, 0.5, 'HYPOPROLIFERATIVE — Classify by MCV',
'#2C3E50', fontsize=8.5, bold=True, wrap_width=40)
arrow(ax, 5.5, 10.4, 5.5, 9.9)
draw_diamond(ax, 5.5, 9.55, 3.6, 0.7, 'MCV Value?', C['decision'])
# ── Three branches from MCV ─────────────────────────────────
# LEFT: Microcytic <80
arrow(ax, 3.7, 9.55, 1.8, 9.55)
ax.text(2.4, 9.65, '<80 fL', fontsize=7, color=C['micro'], fontweight='bold')
draw_box(ax, 1.2, 9.55, 2.0, 0.5, 'MICROCYTIC\nAnemia',
C['micro'], fontsize=8, bold=True, wrap_width=15)
arrow(ax, 1.2, 9.3, 1.2, 8.55)
micro_items = [
('Iron Deficiency Anemia\n(most common)', '#C0392B'),
('Thalassemia\n(α or β)', '#922B21'),
('Anemia of Chronic Disease\n(can be normocytic)', '#A93226'),
('Sideroblastic Anemia', '#7B241C'),
('Lead Poisoning', '#641E16'),
]
y0 = 8.45
for txt, col in micro_items:
wrapped = textwrap.wrap(txt, 20)
draw_box(ax, 1.2, y0, 2.0, 0.38, wrapped[0] + (' ...' if len(wrapped)>1 else ''),
col, tc='white', fontsize=6.5, bold=False, wrap_width=22)
arrow(ax, 1.2, y0 - 0.19, 1.2, y0 - 0.42)
y0 -= 0.6
# Center note for microcytic
ax.text(1.2, 5.3, 'Key next test:\nSerum Ferritin\n<30 ng/mL → IDA',
fontsize=6.5, ha='center', va='top', color='#7B241C',
bbox=dict(boxstyle='round,pad=0.3', fc='#FADBD8', ec='#C0392B', lw=1))
# CENTER: Normocytic 80-100
arrow(ax, 5.5, 9.2, 5.5, 8.55)
ax.text(5.65, 8.87, '80–100 fL', fontsize=7, color=C['normo'], fontweight='bold')
draw_box(ax, 5.5, 8.3, 2.0, 0.5, 'NORMOCYTIC\nAnemia',
C['normo'], fontsize=8, bold=True, wrap_width=15)
arrow(ax, 5.5, 8.05, 5.5, 7.5)
draw_box(ax, 5.5, 7.3, 2.8, 0.45,
'Check: Renal function, TSH, LFTs\nBone marrow, Peripheral smear',
'#D35400', tc='white', fontsize=6.5, wrap_width=32)
normo_items = [
('Aplastic Anemia', '#E67E22'),
('Anemia of CKD / Renal Failure', '#CA6F1E'),
('Anemia of Chronic Disease', '#B7770D'),
('Hypothyroidism', '#9A7D0A'),
('Myelofibrosis', '#7D6608'),
('Myelophthisic Anemia', '#6E6B0D'),
('Acute Blood Loss (early)', '#5B4A07'),
]
y0 = 6.85
for txt, col in normo_items:
draw_box(ax, 5.5, y0, 2.5, 0.35, txt, col, tc='white', fontsize=6.3, wrap_width=24)
if y0 > 4.5:
arrow(ax, 5.5, y0 - 0.18, 5.5, y0 - 0.38)
y0 -= 0.55
# RIGHT: Macrocytic >100
arrow(ax, 7.3, 9.55, 9.0, 9.55)
ax.text(7.9, 9.65, '>100 fL', fontsize=7, color=C['macro'], fontweight='bold')
draw_box(ax, 9.7, 9.55, 2.0, 0.5, 'MACROCYTIC\nAnemia',
C['macro'], fontsize=8, bold=True, wrap_width=15)
arrow(ax, 9.7, 9.3, 9.7, 8.55)
draw_box(ax, 9.7, 8.3, 2.2, 0.45,
'Check: B12, Folate,\nLFTs, TSH, Smear',
'#6C3483', tc='white', fontsize=6.5, wrap_width=22)
macro_items = [
('Vitamin B12 Deficiency', '#7D3C98'),
('Folate Deficiency', '#6C3483'),
('Liver Disease', '#5B2C6F'),
('Hypothyroidism', '#4A235A'),
('Myelodysplastic Syndrome', '#3B1A47'),
('Drugs (MTX, Hydroxyurea)', '#2C1236'),
]
y0 = 7.85
for txt, col in macro_items:
draw_box(ax, 9.7, y0, 2.2, 0.36, txt, col, tc='white', fontsize=6.3, wrap_width=22)
if y0 > 5.4:
arrow(ax, 9.7, y0 - 0.18, 9.7, y0 - 0.38)
y0 -= 0.55
# ── Legend ───────────────────────────────────────────────────
legend_items = [
(C['start'], 'Start/Process'),
(C['decision'], 'Decision'),
(C['rbc'], 'Hemolytic'),
(C['micro'], 'Microcytic'),
(C['normo'], 'Normocytic'),
(C['macro'], 'Macrocytic'),
]
lx, ly = 0.3, 2.0
ax.text(lx, ly + 0.25, 'LEGEND', fontsize=7, fontweight='bold', color=C['text_dark'])
for i, (col, label) in enumerate(legend_items):
rect = FancyBboxPatch((lx, ly - i*0.28 - 0.1), 0.22, 0.18,
boxstyle='round,pad=0.02', facecolor=col, edgecolor='none')
ax.add_patch(rect)
ax.text(lx + 0.30, ly - i*0.28 + 0.0, label, fontsize=6.5, color=C['text_dark'], va='center')
# Footer
ax.text(5.5, 0.18, 'Orris CBC Flowchart | Based on: Textbook of Family Medicine 9e, Rosen\'s Emergency Medicine 9e, Goldman-Cecil Medicine',
ha='center', fontsize=6, color='#7F8C8D', style='italic')
plt.tight_layout(pad=0)
pdf.savefig(fig, bbox_inches='tight', dpi=200)
plt.close(fig)
# ══════════════════════════════════════════════════════════════════
# PAGE 2 — WBC Flowchart
# ══════════════════════════════════════════════════════════════════
def page_wbc(pdf):
fig, ax = plt.subplots(figsize=(11, 17))
ax.set_xlim(0, 11)
ax.set_ylim(0, 17)
ax.axis('off')
fig.patch.set_facecolor('#F7F9FC')
# Header
header = FancyBboxPatch((0, 15.8), 11, 1.1, boxstyle='square',
facecolor='#1F4E2E', edgecolor='none')
ax.add_patch(header)
ax.text(5.5, 16.4, 'CBC INTERPRETATION — WHITE BLOOD CELL (WBC) FLOWCHART',
ha='center', va='center', fontsize=13, color='white', fontweight='bold')
ax.text(5.5, 16.05, 'Page 2 of 3 | Orris Clinical Reference',
ha='center', va='center', fontsize=8, color='#A9DFBF')
draw_box(ax, 5.5, 15.2, 4.5, 0.55, 'CHECK TOTAL WBC COUNT',
'#1F4E2E', fontsize=9, bold=True, wrap_width=40)
arrow(ax, 5.5, 14.93, 5.5, 14.3)
draw_diamond(ax, 5.5, 13.95, 4.2, 0.7,
'WBC Count (normal 4–11 × 10³/μL)?', '#2E6DA4', fontsize=7.5)
# ── HIGH branch ──────────────────────────────────────────────
arrow(ax, 7.6, 13.95, 9.5, 13.95)
ax.text(8.2, 14.1, '↑ HIGH', fontsize=8, color='#C0392B', fontweight='bold')
draw_box(ax, 10.2, 13.95, 1.3, 0.5, 'LEUKO-\nCYTOSIS',
'#C0392B', fontsize=8, bold=True)
arrow(ax, 10.2, 13.7, 10.2, 13.1)
draw_diamond(ax, 10.2, 12.75, 1.5, 0.6, 'Which\ncell?', '#922B21', fontsize=7)
high_cells = [
(10.2, 12.1, 'Neutrophilia', '#C0392B',
['Bacterial infection', 'CML', 'Steroids/G-CSF', 'Rheumatic disease',
'Trauma/MI', 'Pregnancy', 'Neoplasia']),
(10.2, 10.3, 'Eosinophilia', '#E67E22',
['Parasitic infection', 'Allergic disease', 'Asthma/Atopy',
'Myeloproliferative', 'Drug reaction', 'IBD']),
(10.2, 8.5, 'Basophilia', '#8E44AD',
['CML (hallmark)', 'Polycythemia vera', 'Allergic reactions',
'Hypothyroidism', 'Myeloid metaplasia']),
(10.2, 6.7, 'Monocytosis', '#1F7A4D',
['TB / SBE', 'Sarcoidosis', 'Malignancy', 'GI disorders',
'Marrow recovery', 'Drug reaction']),
(10.2, 4.9, 'Lymphocytosis', '#2E6DA4',
['EBV / CMV', 'Hepatitis', 'CLL', 'Pertussis',
'Other viral infections', 'Lymphoproliferative']),
]
for bx, by, label, col, items in high_cells:
arrow(ax, bx, by + 0.55, bx, by + 0.25)
draw_box(ax, bx, by + 0.0, 1.4, 0.4, label, col, fontsize=7, bold=True, wrap_width=16)
y0 = by - 0.22
for it in items:
ax.text(9.45, y0, f'• {it}', fontsize=5.8, color=C['text_dark'], va='top')
y0 -= 0.18
# ── LOW branch ───────────────────────────────────────────────
arrow(ax, 3.4, 13.95, 1.5, 13.95)
ax.text(2.0, 14.1, '↓ LOW', fontsize=8, color='#2980B9', fontweight='bold')
draw_box(ax, 0.8, 13.95, 1.3, 0.5, 'LEUKO-\nPENIA',
'#2980B9', fontsize=8, bold=True)
arrow(ax, 0.8, 13.7, 0.8, 13.1)
draw_diamond(ax, 0.8, 12.75, 1.5, 0.6, 'Which\ncell?', '#1A5276', fontsize=7)
low_cells = [
(0.8, 12.1, 'Neutropenia', '#2980B9',
['Viral infections', 'Drug reaction\n(chemo, clozapine)', 'Aplastic anemia',
'Autoimmune (SLE)', 'Overwhelming\nbacterial sepsis', 'Radiation']),
(0.8, 10.0, 'Lymphopenia', '#1F618D',
['HIV/AIDS', 'Corticosteroids', 'Chemotherapy', 'Radiation',
'Advanced lymphoma', 'Anorexia nervosa']),
(0.8, 8.1, 'Eosinopenia', '#154360',
['Acute physical stress', 'Cushing syndrome', 'Corticosteroids',
'Acute inflammation']),
(0.8, 6.5, 'Monocytopenia', '#0B2545',
['Hairy cell leukemia', 'Onset steroid therapy']),
(0.8, 5.3, 'Basopenia', '#17202A',
['Acute stress', 'Hyperthyroidism', 'Glucocorticoids']),
]
for bx, by, label, col, items in low_cells:
arrow(ax, bx, by + 0.55, bx, by + 0.25)
draw_box(ax, bx, by + 0.0, 1.4, 0.4, label, col, fontsize=7, bold=True, wrap_width=16)
y0 = by - 0.22
for it in items:
lines = textwrap.wrap(it, 18)
for ln in lines:
ax.text(0.1, y0, f'• {ln}', fontsize=5.8, color=C['text_dark'], va='top')
y0 -= 0.18
# ── Normal note ───────────────────────────────────────────────
arrow(ax, 5.5, 13.6, 5.5, 12.85)
ax.text(5.65, 13.22, 'Normal', fontsize=7, color='#1F7A4D', fontweight='bold')
draw_box(ax, 5.5, 12.6, 3.8, 0.45,
'Normal WBC — Check differential for\nrelative abnormalities; clinical correlation',
'#1F7A4D', fontsize=7, wrap_width=40)
# Key note box
note_box = FancyBboxPatch((3.5, 3.5), 4.5, 2.2, boxstyle='round,pad=0.15',
facecolor='#EBF5FB', edgecolor='#2E6DA4', lw=1.5)
ax.add_patch(note_box)
ax.text(5.75, 5.55, '⚠ KEY CLINICAL NOTES', fontsize=8, fontweight='bold',
color='#1A5276', ha='center', va='center')
notes = [
'• Toxic granulation + vacuolation of neutrophils → sepsis',
'• Blasts on smear → acute leukemia (urgent)',
'• Hyperleukocytosis (>100k) → leukostasis emergency',
'• "Left shift" (band neutrophils) → bacterial infection',
'• Pelger-Huët anomaly → MDS or drug effect',
'• Döhle bodies → infection, inflammation, MYH9 disorder',
]
y0 = 5.25
for n in notes:
ax.text(3.7, y0, n, fontsize=6.5, color='#1A3A5C', va='top')
y0 -= 0.28
ax.text(5.5, 0.18, 'Orris CBC Flowchart | Based on: Textbook of Family Medicine 9e, Goldman-Cecil Medicine, Rosen\'s Emergency Medicine 9e',
ha='center', fontsize=6, color='#7F8C8D', style='italic')
plt.tight_layout(pad=0)
pdf.savefig(fig, bbox_inches='tight', dpi=200)
plt.close(fig)
# ══════════════════════════════════════════════════════════════════
# PAGE 3 — Platelet Flowchart
# ══════════════════════════════════════════════════════════════════
def page_platelet(pdf):
fig, ax = plt.subplots(figsize=(11, 17))
ax.set_xlim(0, 11)
ax.set_ylim(0, 17)
ax.axis('off')
fig.patch.set_facecolor('#F7F9FC')
# Header
header = FancyBboxPatch((0, 15.8), 11, 1.1, boxstyle='square',
facecolor='#4A235A', edgecolor='none')
ax.add_patch(header)
ax.text(5.5, 16.4, 'CBC INTERPRETATION — PLATELET FLOWCHART',
ha='center', va='center', fontsize=13, color='white', fontweight='bold')
ax.text(5.5, 16.05, 'Page 3 of 3 | Orris Clinical Reference',
ha='center', va='center', fontsize=8, color='#D7BDE2')
draw_box(ax, 5.5, 15.2, 4.5, 0.55, 'CHECK PLATELET COUNT\n(Normal: 150–400 × 10³/μL)',
'#4A235A', fontsize=9, bold=True, wrap_width=40)
arrow(ax, 5.5, 14.93, 5.5, 14.3)
draw_diamond(ax, 5.5, 13.95, 4.0, 0.7, 'Platelet Count?', '#7D3C98')
# ── LOW (Thrombocytopenia) ────────────────────────────────────
arrow(ax, 3.5, 13.95, 1.4, 13.95)
ax.text(1.8, 14.12, '↓ <150k', fontsize=8, color='#C0392B', fontweight='bold')
draw_box(ax, 0.7, 13.95, 1.2, 0.55, 'THROMBO-\nCYTOPENIA', '#C0392B', fontsize=8, bold=True)
arrow(ax, 0.7, 13.68, 0.7, 12.95)
# FIRST: rule out pseudo
draw_box(ax, 0.7, 12.7, 2.6, 0.45,
'Step 1: Peripheral Smear — Rule out\nPseudothrombocytopenia (EDTA clumping)',
'#7F8C8D', tc='white', fontsize=6.8, wrap_width=30)
arrow(ax, 0.7, 12.47, 0.7, 11.85)
draw_diamond(ax, 0.7, 11.5, 2.6, 0.65, 'Mechanism?', '#922B21', fontsize=8)
mech_cols = [
(0.25, 10.6, 'Decreased\nProduction', '#C0392B',
['Aplastic anemia', 'Chemotherapy / radiation', 'Vit B12 / folate deficiency',
'Myelodysplastic syndrome', 'Marrow infiltration', 'Drugs (thiazides)', 'SLE']),
(3.3, 10.6, 'Increased\nDestruction', '#8E44AD',
['ITP (immune)', 'Heparin-induced (HIT)', 'Drug-induced (quinine,\nsulfonamides)',
'TTP / HUS', 'DIC', 'Neonatal alloimmune', 'Preeclampsia / HELLP']),
(-2.1, 10.6, 'Sequestration/\nDilutional', '#2980B9',
['Hypersplenism\n(cirrhosis, storage dis.)', 'Massive transfusion', 'Pregnancy (gestational)']),
]
# Draw 3 sub-branches
positions = [(0.7, 10.85), (3.3, 10.85)] # left & right of diamond
arrow(ax, 0.7, 11.18, 0.7, 10.95)
arrow(ax, 1.95, 11.5, 3.3, 11.5)
ax.text(2.4, 11.62, 'Immune/\nConsumption', fontsize=6, color='#8E44AD', ha='center')
arrow(ax, 3.3, 11.18, 3.3, 10.95)
arrow(ax, -0.55, 11.5, -1.4, 11.5)
ax.text(-1.1, 11.62, 'Sequestration', fontsize=5.8, color='#2980B9', ha='center')
# For sequestration: offset left of page — instead use bottom placement
# Reposition: place all 3 vertically
# 3 columns: x=1.2, 5.0, 9.0
col_xs = [1.1, 5.0, 9.0]
mech_data = [
('↓ Production', '#C0392B',
['Aplastic anemia', 'Chemotherapy / radiation', 'Vit B12 / folate deficiency',
'Myelodysplastic syndrome', 'Marrow infiltration (lymphoma, mets)',
'Drugs (thiazides, linezolid)', 'SLE']),
('Consumption /\nImmune Destruction', '#8E44AD',
['ITP', 'Heparin-induced (HIT)', 'Drug-induced\n(quinine, sulfonamides)',
'TTP / HUS (MAHA)', 'DIC', 'Neonatal alloimmune TP', 'Preeclampsia / HELLP']),
('Sequestration /\nDilutional', '#2980B9',
['Hypersplenism (cirrhosis)', 'Portal hypertension',
'Storage diseases\n(Gaucher, Niemann-Pick)',
'Massive transfusion', 'Gestational thrombocytopenia']),
]
# Redraw mechanism section cleanly
ax.cla()
ax.set_xlim(0, 11)
ax.set_ylim(0, 17)
ax.axis('off')
fig.patch.set_facecolor('#F7F9FC')
# Re-header
header = FancyBboxPatch((0, 15.8), 11, 1.1, boxstyle='square', facecolor='#4A235A', edgecolor='none')
ax.add_patch(header)
ax.text(5.5, 16.4, 'CBC INTERPRETATION — PLATELET FLOWCHART',
ha='center', va='center', fontsize=13, color='white', fontweight='bold')
ax.text(5.5, 16.05, 'Page 3 of 3 | Orris Clinical Reference',
ha='center', va='center', fontsize=8, color='#D7BDE2')
draw_box(ax, 5.5, 15.2, 4.5, 0.55, 'CHECK PLATELET COUNT\n(Normal: 150–400 × 10³/μL)',
'#4A235A', fontsize=9, bold=True, wrap_width=40)
arrow(ax, 5.5, 14.93, 5.5, 14.3)
draw_diamond(ax, 5.5, 13.95, 4.0, 0.7, 'Platelet Count?', '#7D3C98')
# ── HIGH (Thrombocytosis) ─────────────────────────────────────
arrow(ax, 7.5, 13.95, 9.5, 13.95)
ax.text(8.0, 14.12, '↑ >400k', fontsize=8, color='#1F7A4D', fontweight='bold')
draw_box(ax, 10.2, 13.95, 1.3, 0.55, 'THROMBO-\nCYTOSIS', '#1F7A4D', fontsize=8, bold=True)
arrow(ax, 10.2, 13.68, 10.2, 13.0)
draw_diamond(ax, 10.2, 12.65, 1.5, 0.6, 'Primary\nor\nReactive?', '#1F4E2E', fontsize=7)
arrow(ax, 10.2, 12.35, 10.2, 11.75)
draw_box(ax, 10.2, 11.5, 2.0, 0.45, 'REACTIVE\n(Secondary)', '#27AE60', fontsize=7.5, bold=True)
react_items = ['Iron deficiency', 'Acute blood loss', 'Post-splenectomy', 'Inflammatory disease (RA, IBD)',
'Malignancy', 'Infection/surgery', 'Count rarely >1,000k/μL']
y0 = 11.22
for it in react_items:
ax.text(9.2, y0, f'• {it}', fontsize=6, color=C['text_dark'], va='top')
y0 -= 0.2
ax.text(10.2, 9.75, 'PRIMARY\n(Myeloproliferative)', ha='center', fontsize=7.5,
fontweight='bold', color='#145A32', va='center',
bbox=dict(boxstyle='round,pad=0.3', fc='#A9DFBF', ec='#1F7A4D', lw=1.2))
primary_items = ['Essential thrombocythemia', 'Polycythemia vera', 'CML', 'Primary myelofibrosis']
y0 = 9.45
for it in primary_items:
ax.text(9.2, y0, f'• {it}', fontsize=6, color=C['text_dark'], va='top')
y0 -= 0.2
# ── LOW (Thrombocytopenia) ────────────────────────────────────
arrow(ax, 3.5, 13.95, 1.5, 13.95)
ax.text(1.85, 14.12, '↓ <150k', fontsize=8, color='#C0392B', fontweight='bold')
draw_box(ax, 0.75, 13.95, 1.3, 0.55, 'THROMBO-\nCYTOPENIA', '#C0392B', fontsize=8, bold=True)
arrow(ax, 0.75, 13.68, 0.75, 13.1)
draw_box(ax, 0.75, 12.85, 2.8, 0.45,
'Peripheral smear → exclude\nPseudothrombocytopenia (EDTA clumps)',
'#7F8C8D', tc='white', fontsize=6.8, wrap_width=32)
arrow(ax, 0.75, 12.62, 0.75, 12.1)
draw_diamond(ax, 0.75, 11.75, 1.35, 0.65, 'Bleeding\nSeverity?', '#922B21', fontsize=7)
ax.text(0.9, 11.38, '<10k: HIGH risk spontaneous bleed', fontsize=6.2, color='#C0392B')
ax.text(0.9, 11.18, '10–50k: Bleeding with trauma/surgery', fontsize=6.2, color='#E67E22')
ax.text(0.9, 10.98, '>50k: Usually asymptomatic', fontsize=6.2, color='#27AE60')
# 3 mechanism columns
col_xs_p = [1.4, 5.5, 9.7]
mech_data_p = [
('↓ Production', '#C0392B',
['Aplastic anemia', 'Chemotherapy/radiation', 'Vit B12/folate deficiency',
'Myelodysplastic syndrome', 'Marrow infiltration', 'Drugs (thiazides, linezolid)',
'SLE, alcohol']),
('Consumption /\nImmune Destruction', '#8E44AD',
['ITP (immune)', 'Heparin-induced\nthrombocytopenia (HIT)',
'Drug-induced (quinine, sulfa)', 'TTP / HUS (microangiopathic)',
'DIC (check fibrinogen/PT)', 'Neonatal alloimmune TP',
'Preeclampsia / HELLP']),
('Sequestration /\nDilutional', '#2980B9',
['Hypersplenism (cirrhosis)', 'Portal hypertension',
'Storage diseases (Gaucher)', 'Massive transfusion',
'Gestational thrombocytopenia']),
]
arrow(ax, 0.75, 10.82, 0.75, 10.35)
ax.text(0.85, 10.55, 'Classify:', fontsize=7, color='#922B21', fontweight='bold')
mech_y_top = 10.1
mech_h = 0.42
for mx, (title, col, items) in zip(col_xs_p, mech_data_p):
draw_box(ax, mx, mech_y_top, 2.4, mech_h, title, col, fontsize=7.5, bold=True, wrap_width=20)
y0 = mech_y_top - mech_h/2 - 0.22
for it in items:
lines = textwrap.wrap(it, 22)
for line in lines:
ax.text(mx - 1.1, y0, f'• {line}', fontsize=5.8, color=C['text_dark'], va='top')
y0 -= 0.185
# ── Normal note ───────────────────────────────────────────────
arrow(ax, 5.5, 13.6, 5.5, 13.05)
ax.text(5.65, 13.32, 'Normal\n150–400k', fontsize=6.5, color='#1F7A4D', fontweight='bold')
draw_box(ax, 5.5, 12.8, 3.5, 0.38,
'Normal platelets — correlate clinically, check smear morphology',
'#1F7A4D', fontsize=7, wrap_width=38)
# ── RDW / Peripheral Smear key note ──────────────────────────
note_box = FancyBboxPatch((2.5, 2.5), 6.0, 2.2, boxstyle='round,pad=0.15',
facecolor='#FEF9E7', edgecolor='#D4AC0D', lw=1.5)
ax.add_patch(note_box)
ax.text(5.5, 4.57, '⚠ THROMBOCYTOPENIA — KEY NEXT STEPS',
fontsize=8.5, fontweight='bold', color='#7D6608', ha='center')
next_steps = [
'1. Always confirm with peripheral smear before acting on low platelet count.',
'2. Check coagulation (PT/INR, aPTT, fibrinogen) if DIC suspected.',
'3. If HIT suspected: stop heparin immediately; use alternative anticoagulation.',
'4. Check LDH, haptoglobin, schistocytes on smear if TTP/HUS suspected.',
'5. Transfuse platelets: <10k (prophylactic), <50k (before procedure).',
]
y0 = 4.27
for s in next_steps:
lines = textwrap.wrap(s, 60)
for line in lines:
ax.text(2.7, y0, line, fontsize=6.5, color='#4D4D00', va='top')
y0 -= 0.27
ax.text(5.5, 0.18, 'Orris CBC Flowchart | Based on: Textbook of Family Medicine 9e, Goldman-Cecil Medicine, Rosen\'s Emergency Medicine 9e',
ha='center', fontsize=6, color='#7F8C8D', style='italic')
plt.tight_layout(pad=0)
pdf.savefig(fig, bbox_inches='tight', dpi=200)
plt.close(fig)
# ══════════════════════════════════════════════════════════════════
# PAGE 4 — Summary / Master Table
# ══════════════════════════════════════════════════════════════════
def page_summary(pdf):
fig, ax = plt.subplots(figsize=(11, 17))
ax.set_xlim(0, 11)
ax.set_ylim(0, 17)
ax.axis('off')
fig.patch.set_facecolor('#F7F9FC')
header = FancyBboxPatch((0, 15.8), 11, 1.1, boxstyle='square',
facecolor='#1A3A5C', edgecolor='none')
ax.add_patch(header)
ax.text(5.5, 16.4, 'CBC INTERPRETATION — MASTER REFERENCE SUMMARY',
ha='center', va='center', fontsize=13, color='white', fontweight='bold')
ax.text(5.5, 16.05, 'Page 4 of 4 | Orris Clinical Reference',
ha='center', va='center', fontsize=8, color='#AED6F1')
# Table headers
headers = ['CBC Parameter', 'HIGH (↑)', 'LOW (↓) / Absent', 'Key Next Test']
col_x = [0.1, 2.8, 6.2, 9.1]
col_w = [2.7, 3.3, 2.9, 2.0]
header_row = FancyBboxPatch((0.05, 14.8), 10.9, 0.5,
boxstyle='square', facecolor='#2E6DA4', edgecolor='none')
ax.add_patch(header_row)
for hx, ht, hw in zip(col_x, headers, col_w):
ax.text(hx + hw/2, 15.05, ht, fontsize=8, fontweight='bold',
color='white', ha='center', va='center')
# Table rows
rows = [
('Hemoglobin /\nHematocrit',
'Polycythemia vera\nDehydration\nCOPD / altitude\nSmoking',
'Anemia — classify by MCV\n(see pages 1–2)',
'MCV, Reticulocyte\ncount, Peripheral smear'),
('MCV',
'Macrocytic: B12/folate def.\nLiver disease, Hypothyroid\nMDS, Drugs (MTX)',
'Microcytic: Iron deficiency\nThalassemia, Sideroblastic\nanemia, ACD',
'Ferritin, B12/Folate,\nTSH, LFTs, Hgb electrophoresis'),
('RDW',
'Iron def (early), mixed\ndeficiency, B12/folate def.\n— early anemia',
'Normal-low: Thalassemia\n(key: low RDW + low MCV)',
'Ferritin, Hgb electrophoresis'),
('WBC (Total)',
'Leukocytosis: Infection,\nLeukemia, Steroids, Stress',
'Leukopenia: Viral illness,\nChemo, Aplastic anemia',
'5-part differential,\nPeripheral smear'),
('Neutrophils',
'Bacterial infection, CML,\nSteroids, G-CSF, Trauma',
'Viral illness, Drug reaction\nAplastic anemia, SLE, Sepsis',
'Blood culture, ANC, smear\nfor left shift / blasts'),
('Lymphocytes',
'Viral (EBV/CMV), CLL,\nPertussis, ALL',
'HIV/AIDS, Steroids,\nChemo, SLE, lymphoma',
'HIV, EBV/CMV serology,\nFlow cytometry'),
('Eosinophils',
'Parasites, Allergy/Asthma,\nDrug reaction, IBD,\nMyeloproliferative',
'Cushing syndrome,\nCorticosteroids,\nAcute stress',
'Stool O&P, IgE, skin prick'),
('Monocytes',
'TB, SBE, Sarcoidosis,\nMalignancy, GI disease',
'Hairy cell leukemia,\nSteroid onset',
'BMAT for hairy cell,\nChest X-ray for TB'),
('Basophils',
'CML (hallmark), Allergy,\nPolycythemia vera,\nHypothyroidism',
'Hyperthyroidism,\nSteroids, Acute stress',
'BCR-ABL for CML,\nTSH'),
('Platelets',
'Thrombocytosis: Reactive\n(IDA, inflammation) vs.\nPrimary (ET, PV, CML)',
'Thrombocytopenia: ITP,\nHIT, TTP/HUS, DIC,\nAplastic anemia, B12 def.',
'Peripheral smear, Coags\n(PT/aPTT/fibrinogen),\nAnti-PF4 for HIT'),
]
alt_colors = ['#FFFFFF', '#EBF5FB']
y_start = 14.65
row_h = 1.32
for i, (param, high, low, nxt) in enumerate(rows):
fc = alt_colors[i % 2]
row_bg = FancyBboxPatch((0.05, y_start - row_h), 10.9, row_h - 0.04,
boxstyle='square', facecolor=fc, edgecolor='#D5D8DC', lw=0.5)
ax.add_patch(row_bg)
ax.text(col_x[0] + col_w[0]/2, y_start - row_h/2, param,
fontsize=7, fontweight='bold', ha='center', va='center',
color='#1A3A5C', multialignment='center')
ax.text(col_x[1] + 0.1, y_start - 0.18, high,
fontsize=6.5, va='top', color='#C0392B', multialignment='left')
ax.text(col_x[2] + 0.1, y_start - 0.18, low,
fontsize=6.5, va='top', color='#2471A3', multialignment='left')
ax.text(col_x[3] + 0.1, y_start - 0.18, nxt,
fontsize=6.3, va='top', color='#1F7A4D', multialignment='left')
y_start -= row_h
ax.text(5.5, 0.4, 'Normal values (adult): Hgb >13 g/dL (M), >12 g/dL (F) • WBC 4–11 × 10³/μL • Platelets 150–400 × 10³/μL • MCV 80–100 fL',
ha='center', fontsize=7, color='#2C3E50',
bbox=dict(boxstyle='round,pad=0.3', fc='#EBF5FB', ec='#2E6DA4', lw=1))
ax.text(5.5, 0.12, 'Orris CBC Flowchart | Sources: Textbook of Family Medicine 9e • Goldman-Cecil Medicine • Rosen\'s Emergency Medicine 9e • Tietz Laboratory Medicine 7e',
ha='center', fontsize=6, color='#7F8C8D', style='italic')
plt.tight_layout(pad=0)
pdf.savefig(fig, bbox_inches='tight', dpi=200)
plt.close(fig)
# ── GENERATE PDF ─────────────────────────────────────────────────
output_path = '/home/daytona/workspace/cbc-flowchart/CBC_Clinical_Flowchart.pdf'
with PdfPages(output_path) as pdf:
page_rbc(pdf)
page_wbc(pdf)
page_platelet(pdf)
page_summary(pdf)
d = pdf.infodict()
d['Title'] = 'CBC Clinical Interpretation Flowchart'
d['Author'] = 'Orris Clinical Reference'
d['Subject'] = 'Complete Blood Count Differential Diagnosis'
d['Keywords'] = 'CBC, anemia, leukocytosis, thrombocytopenia, hematology'
print(f"PDF generated: {output_path}")
cd /home/daytona/workspace/cbc-flowchart && python3 generate_flowchart.py 2>&1
Create a quick-reference pocket card (PDF) with CBC interpretation algorithms
Download the CBC flowchart PDF I just created