As a physician, I want to learn about properly studying a CBC report. Teach me about it.

Reading File
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Finding Sources
Reading File
Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Reading File
Loading Image
Reading File
Loading Image
I now have all the material needed for a comprehensive, authoritative response. Let me compile the full teaching guide.

How to Study a CBC Report: A Physician's Guide

A complete blood count (CBC) is one of the most frequently ordered tests in clinical medicine, yet it rewards careful, systematic reading. The following is a structured approach covering every component, the math behind the indices, what each abnormality means, and the clinical pitfalls to avoid.

1. Overview of CBC Components

The automated cell counter reports both directly measured and calculated parameters. At minimum, a modern CBC includes:
ParameterUnitNormal Range (Adults)
WBC count×10³/µL4.0–11.0
RBC count×10⁶/µL4.0–5.4 (F), 4.5–6.0 (M)
Hemoglobin (Hgb)g/dL12–16 (F), 13.5–17.5 (M)
Hematocrit (Hct)%36–48 (F), 40–52 (M)
MCVfL81–99
MCHpg30–34
MCHCg/dL30–36
RDW-CV%12–15
Platelet count×10³/µL150–400
Reticulocyte %%0.5–1.5
Absolute reticulocyte/µL20,000–100,000
(Goldman-Cecil Medicine, Table 144-2)

2. The Red Cell Line: Hemoglobin, Hematocrit, RBC Count

Hemoglobin is the concentration of Hgb in whole blood (g/dL). Hematocrit is the percentage of blood volume occupied by red cells. These two track together closely; a rough rule is Hct ≈ Hgb × 3.
Anemia is defined as: Hgb < 12 g/dL or Hct < 36% in non-pregnant women; Hgb < 13 g/dL or Hct < 39% in men. (Washington Manual of Medical Therapeutics)

Key pitfall: Volume status distorts Hgb/Hct

Hgb and Hct reflect concentration, not total red cell mass. They are unreliable in acute volume shifts:
  • Hypervolemia (IV fluids, heart failure, pregnancy, renal failure) falsely lowers all three cell lines simultaneously
  • Hypovolemia falsely elevates them
  • When you see all three lines (WBC, RBC, platelets) shift congruently up or down, think hemoconcentration or hemodilution before disease. (Frameworks for Internal Medicine)

3. The Red Cell Indices: MCV, MCH, MCHC, RDW

These are the analytical core of the CBC for evaluating anemia.

MCV (Mean Corpuscular Volume)

Calculated as: MCV (fL) = [Hct (%) × 10] ÷ RBC count (×10⁶/µL)
MCV rangeCategory
< 80 fLMicrocytic
80–100 fLNormocytic
> 100 fLMacrocytic
The peripheral smear is more sensitive than MCV alone for detecting early changes in RBC size. Normal RBCs are roughly the same size as a small lymphocyte nucleus (~8 µm). (Frameworks for Internal Medicine)

MCH (Mean Corpuscular Hemoglobin)

Calculated as: MCH (pg) = Hgb ÷ RBC count
Reflects the mass of hemoglobin per cell. Low MCH is called hypochromia and is consistent with impaired hemoglobin synthesis (e.g., iron deficiency, thalassemia). MCH generally tracks with MCV, and according to the Textbook of Family Medicine it adds little independent diagnostic value once MCV is known.

MCHC (Mean Corpuscular Hemoglobin Concentration)

Calculated as: MCHC (g/dL) = (Hgb ÷ Hct) × 100
The concentration of Hgb within the RBC. A low MCHC confirms hypochromic cells. An elevated MCHC is strongly suggestive of spherocytosis or a hemoglobinopathy (cells are overpacked with Hgb). MCHC rarely rises above its upper limit in other conditions, so elevation is a specific finding. (Washington Manual; Goldman-Cecil)

RDW (Red Cell Distribution Width)

Reflects variability in RBC size (anisocytosis). Calculated as the coefficient of variation (RDW-CV, normal 12-15%) or standard deviation of MCV (RDW-SD, normal 37-47 fL).
Elevated RDW = mixed population of cell sizes. Clinically important because:
  • Iron deficiency anemia: early on, small deficient cells mix with still-normal cells → high RDW, low MCV
  • Thalassemia trait: all cells are uniformly small → normal RDW, low MCV (useful distinguishing point)
  • Folate/B12 deficiency: large cells + reticulocytes → high RDW, high MCV
  • Post-transfusion: two cell populations → high RDW
(Henry's Clinical Diagnosis; Washington Manual)

4. Anemia Classification by MCV

Using MCV as the first branch point is the most practical framework:
Anemia classified as microcytic, normocytic, or macrocytic with sub-diagnoses
Frameworks for Internal Medicine, Figure 25-4

Microcytic (MCV < 80 fL)

Classic causes - the mnemonic TAILS: Thalassemia, Anemia of chronic disease (some cases), Iron deficiency, Lead poisoning, Sideroblastic anemia.
  • Iron deficiency is the most common cause of anemia worldwide. Clues: microcytosis + hypochromia + elevated RDW + low ferritin (< 15 ng/mL confirms iron deficiency in any patient)
  • Thalassemia trait: microcytosis disproportionate to the degree of anemia, normal RDW, target cells (codocytes) on smear, normal or elevated RBC count

Normocytic (MCV 80–100 fL)

Large differential: acute blood loss, anemia of chronic disease/inflammation, renal failure (decreased EPO), mixed deficiencies (IDA + B12 simultaneously cancels out MCV), hypothyroidism, bone marrow suppression, hemolysis.
The reticulocyte count is your key discriminator here (see Section 5).

Macrocytic (MCV > 100 fL)

  • Megaloblastic: B12 or folate deficiency - look for hypersegmented neutrophils (>5 lobes in a neutrophil = pathognomonic) on peripheral smear
  • Non-megaloblastic: alcohol, liver disease, hypothyroidism, medications (hydroxyurea, methotrexate, azathioprine), myelodysplastic syndrome
  • Reticulocytosis itself raises MCV (reticulocytes are larger than mature RBCs)
(Goldman-Cecil Medicine; Frameworks for Internal Medicine)

5. Reticulocyte Count and Reticulocyte Production Index (RPI)

The reticulocyte count is the bone marrow's "report card." Normal RBC lifespan is ~120 days; reticulocytes circulate for about 1 day, so the normal reticulocyte count is 0.4–2.9%.
In anemia, the marrow should be working overtime. A reticulocyte count of 1% in the setting of anemia is inappropriately low - it means the marrow is not responding.

Reticulocyte Production Index (RPI)

Because a low Hct "concentrates" reticulocytes falsely, correct for dilution:
Corrected reticulocyte count = Retic% × (Patient Hct ÷ 45)
Then correct for early release of reticulocytes (shift cells) in severe anemia: RPI = Corrected retic count ÷ Maturation factor
HctMaturation factor
45%1.0
35%1.5
25%2.0
15%2.5
  • RPI ≥ 2–3: hyperproliferative - marrow is responding appropriately → think hemolysis or blood loss
  • RPI < 2: hypoproliferative - marrow is not responding → iron deficiency, B12/folate, aplasia, marrow infiltration, anemia of chronic disease, renal failure
(Washington Manual; Goldman-Cecil, Table 144-3)

6. The White Blood Cell (WBC) Differential

Total WBC count > 10,000/mm³ = leukocytosis. The critical first step is identifying which cell type is elevated, because each has a distinct differential diagnosis.
Cell TypeNormal %Absolute Normal
Neutrophils (segs + bands)50–70%1800–7800/mm³
Lymphocytes20–40%1000–4800/mm³
Monocytes2–8%200–900/mm³
Eosinophils1–4%100–400/mm³
Basophils0–1%0–100/mm³

Common causes by cell type (Textbook of Family Medicine, Table 14-10):

Neutrophilia: infections, leukemia, rheumatic/autoimmune disorders, neoplasms, trauma, endocrine/metabolic disorders, drugs (corticosteroids, G-CSF)
Neutropenia (ANC < 1500/mm³):
  • ANC = Total WBC × (% segs + % bands) ÷ 100
  • ANC < 500/mm³ = severe neutropenia with high infection risk
  • Causes: drug reactions, viral infections, hematopoietic disease, hypersplenism, autoimmune disease
Lymphocytosis: viral infections (EBV, CMV, hepatitis), pertussis, CLL, ALL
Lymphopenia (< 1500/mm³ in adults): immunosuppressants, corticosteroids, viral infections including HIV, genetic immunodeficiencies
Eosinophilia: parasitic infections, allergic conditions, Churg-Strauss (EGPA), malignancy, adrenal insufficiency, drug reactions
Monocytosis: chronic infections (TB, subacute bacterial endocarditis), autoimmune disease, malignancy, CMML
Basophilia: rare; seen in CML and other myeloproliferative disorders (important diagnostic clue)
Left shift: presence of band forms and/or earlier precursors (metamyelocytes, myelocytes) in the peripheral blood indicates marrow stress - seen in severe infection, leukemia, or leukemoid reaction. Bands + toxic granulation + Döhle bodies = severe sepsis until proven otherwise.

7. Platelet Count

RangeTermClinical implication
> 400,000/mm³ThrombocytosisReactive (infection, inflammation, iron deficiency, post-splenectomy) vs. clonal (essential thrombocythemia)
150,000–400,000Normal--
50,000–150,000Mild-moderate thrombocytopeniaMonitor; bleeding risk low
< 50,000Significant thrombocytopeniaIncreased surgical bleeding risk
< 20,000Severe thrombocytopeniaSpontaneous bleeding risk
First step in evaluating thrombocytopenia: examine the peripheral smear for platelet clumping (pseudo-thrombocytopenia caused by EDTA-induced aggregation). If clumping is present, repeat with sodium citrate tube. (Textbook of Family Medicine)
Additional important causes:
  • ITP, TTP/HUS (with microangiopathic hemolytic anemia), DIC
  • Drug-induced (heparin → HIT; quinine, sulfa drugs)
  • Hypersplenism
  • Bone marrow failure/infiltration
  • Viral (HIV, EBV)
(Goldman-Cecil Medicine; Rosen's Emergency Medicine)

8. A Systematic Reading Sequence

When you pick up a CBC, work through it in this order:
  1. Scan for flags - does the analyzer flag any abnormalities? Request peripheral smear review if so.
  2. WBC - Is there leukocytosis or leukopenia? What does the differential show?
  3. Hgb/Hct - Is the patient anemic or polycythemic?
  4. MCV - Categorize: micro/normo/macrocytic?
  5. RDW - Uniform or mixed cell population?
  6. MCH/MCHC - Confirms hypo/hyperchromia?
  7. Reticulocyte count + RPI - Hyper- vs. hypoproliferative?
  8. Platelets - Thrombocytopenia or thrombocytosis?
  9. Compare with prior values - Trends matter more than single values; always compare to the patient's own baseline.
  10. Correlate with clinical context - IV fluids given recently? Pregnant? Splenomegaly? Chronic disease?

9. Common Pitfalls

  • Acute hemorrhage: Hgb/Hct normal immediately after acute bleed; it takes 4–6 hours of hemodilution for the CBC to reflect true loss
  • High altitude/smoking: physiologically elevated baseline Hgb; do not diagnose polycythemia without comparing to age/altitude-adjusted norms
  • Mixed deficiencies: Iron deficiency + B12 deficiency can produce a normal MCV with markedly elevated RDW
  • Pseudo-neutrophilia: exercise, stress, and epinephrine cause demargination of neutrophils → transient WBC rise with no shift, resolves within hours
  • EDTA artifacts: platelet clumping (pseudo-thrombocytopenia), RBC agglutination causing spuriously high MCV
  • Reticulocyte pitfall: a 2% reticulocyte count sounds normal, but in a patient with Hct of 20%, the RPI is < 1 - still a hypoproliferative pattern

10. Peripheral Smear: The CBC's Best Partner

No CBC interpretation is complete without knowing when to call for a peripheral smear. Indications include:
  • Unexplained cytopenia in any cell line
  • Suspected hemolysis
  • Leukocytosis with concern for leukemia
  • Thrombocytopenia (rule out pseudo, TTP)
  • Unexplained elevated RDW
  • Any blast cells flagged by the analyzer
The smear can reveal findings no CBC number captures: schistocytes (TTP/DIC), target cells (thalassemia, liver disease), hypersegmented neutrophils (megaloblastic), teardrop cells (myelofibrosis), spherocytes (hereditary spherocytosis, autoimmune hemolysis), and more.

Sources:
  • Goldman-Cecil Medicine, 26E (Table 144-2, Table 144-3)
  • Washington Manual of Medical Therapeutics (Chapter 21)
  • Frameworks for Internal Medicine (Chapter 25)
  • Textbook of Family Medicine, 9E (Chapter 14)
  • Henry's Clinical Diagnosis and Management by Laboratory Methods

Create a quick reference PDF with normal ranges and calculations I can print for my office

Reading File
Running Command

ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"

Running Command

mkdir -p /home/daytona/workspace/cbc-reference && python3 -c "import reportlab; print('reportlab ok')"

Writing File

~/cbc-reference/build_cbc_pdf.py

from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer,
    HRFlowable, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import PageBreak

OUTPUT = "/home/daytona/workspace/cbc-reference/CBC_Quick_Reference.pdf"

# ---------- Colour palette ----------
DARK_BLUE  = colors.HexColor("#1B3A6B")
MED_BLUE   = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
ACCENT     = colors.HexColor("#C0392B")   # red for critical values
YELLOW_BG  = colors.HexColor("#FFF9E6")
GREY_ROW   = colors.HexColor("#F2F5F8")
WHITE      = colors.white

# ---------- Document ----------
doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=letter,
    leftMargin=0.55*inch,
    rightMargin=0.55*inch,
    topMargin=0.55*inch,
    bottomMargin=0.55*inch,
)

styles = getSampleStyleSheet()

# Custom styles
title_style = ParagraphStyle(
    "DocTitle", parent=styles["Normal"],
    fontSize=18, fontName="Helvetica-Bold",
    textColor=WHITE, alignment=TA_CENTER,
    spaceAfter=2,
)
subtitle_style = ParagraphStyle(
    "DocSubtitle", parent=styles["Normal"],
    fontSize=9, fontName="Helvetica",
    textColor=colors.HexColor("#AACCE8"), alignment=TA_CENTER,
)
section_style = ParagraphStyle(
    "Section", parent=styles["Normal"],
    fontSize=11, fontName="Helvetica-Bold",
    textColor=WHITE, alignment=TA_LEFT,
    leftIndent=4, spaceAfter=0, spaceBefore=0,
)
body = ParagraphStyle(
    "Body", parent=styles["Normal"],
    fontSize=8, fontName="Helvetica",
    textColor=colors.HexColor("#1A1A2E"),
    leading=11,
)
small_bold = ParagraphStyle(
    "SmallBold", parent=styles["Normal"],
    fontSize=7.5, fontName="Helvetica-Bold",
    textColor=DARK_BLUE,
)
formula_style = ParagraphStyle(
    "Formula", parent=styles["Normal"],
    fontSize=8.5, fontName="Helvetica-Oblique",
    textColor=colors.HexColor("#154360"),
    leftIndent=8, leading=13,
)
note_style = ParagraphStyle(
    "Note", parent=styles["Normal"],
    fontSize=7, fontName="Helvetica-Oblique",
    textColor=colors.HexColor("#555555"),
    leading=10,
)
footer_style = ParagraphStyle(
    "Footer", parent=styles["Normal"],
    fontSize=7, fontName="Helvetica",
    textColor=colors.HexColor("#888888"),
    alignment=TA_CENTER,
)

# ---- Helper to make a coloured section header row in a table ----
def sec_header(text, span=4):
    return ([Paragraph(text, section_style)] + [""] * (span - 1),)

def tbl_style_base(data, col_widths, header_rows=1):
    n = len(data)
    style = [
        # outer border
        ("BOX",        (0,0), (-1,-1), 0.8, DARK_BLUE),
        # inner grid
        ("INNERGRID",  (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
        # alternating rows (skip header rows)
        ("ROWBACKGROUNDS", (0, header_rows), (-1,-1), [WHITE, GREY_ROW]),
        # header rows background
        ("BACKGROUND", (0,0), (-1, header_rows-1), MED_BLUE),
        ("TEXTCOLOR",  (0,0), (-1, header_rows-1), WHITE),
        ("FONTNAME",   (0,0), (-1, header_rows-1), "Helvetica-Bold"),
        ("FONTSIZE",   (0,0), (-1, header_rows-1), 8),
        ("ALIGN",      (0,0), (-1,-1), "LEFT"),
        ("VALIGN",     (0,0), (-1,-1), "MIDDLE"),
        ("FONTNAME",   (0, header_rows), (-1,-1), "Helvetica"),
        ("FONTSIZE",   (0, header_rows), (-1,-1), 8),
        ("TOPPADDING", (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING", (0,0), (-1,-1), 5),
    ]
    return TableStyle(style)

# =====================================================================
# Build story
# =====================================================================
story = []

# ---- TITLE BANNER ----
banner_data = [[
    Paragraph("CBC Quick Reference Card", title_style),
]]
banner_sub = [[
    Paragraph("Normal Ranges · Calculations · Clinical Interpretation  |  Goldman-Cecil / Washington Manual / Frameworks for IM", subtitle_style),
]]
banner_tbl = Table([[Paragraph("CBC Quick Reference Card", title_style)]], colWidths=[7.4*inch])
banner_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 10),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
    ("BOX", (0,0), (-1,-1), 1, DARK_BLUE),
]))
sub_tbl = Table([[Paragraph("Normal Ranges · Calculations · Clinical Interpretation", subtitle_style)]], colWidths=[7.4*inch])
sub_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 0),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(banner_tbl)
story.append(sub_tbl)
story.append(Spacer(1, 6))

# =====================================================================
# SECTION 1: NORMAL REFERENCE RANGES
# =====================================================================
hdr = Table([[Paragraph("  1.  NORMAL REFERENCE RANGES", section_style)]], colWidths=[7.4*inch])
hdr.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
story.append(hdr)
story.append(Spacer(1, 3))

# --- RBC parameters table ---
rbc_data = [
    ["Parameter", "Female", "Male", "Unit"],
    ["Hemoglobin (Hgb)", "12.0–16.0", "13.5–17.5", "g/dL"],
    ["Hematocrit (Hct)", "36–48", "40–52", "%"],
    ["RBC Count", "4.0–5.4", "4.5–6.0", "×10⁶/µL"],
    ["MCV", "81–99", "81–99", "fL"],
    ["MCH", "30–34", "30–34", "pg"],
    ["MCHC", "30–36", "30–36", "g/dL"],
    ["RDW-CV", "12–15", "12–15", "%"],
    ["RDW-SD", "37–47", "37–47", "fL"],
    ["Reticulocyte %", "0.5–1.5", "0.5–1.5", "%"],
    ["Absolute Reticulocyte", "20,000–100,000", "20,000–100,000", "/µL"],
    ["Reticulocyte Prod. Index (RPI)", "0.5–2.5", "0.5–2.5", "—"],
]
rbc_cw = [2.3*inch, 1.5*inch, 1.5*inch, 1.4*inch]
rbc_tbl = Table(rbc_data, colWidths=rbc_cw, repeatRows=1)
rbc_tbl.setStyle(tbl_style_base(rbc_data, rbc_cw))
# Highlight critical row
story.append(Paragraph("Red Blood Cell Parameters", small_bold))
story.append(Spacer(1, 2))
story.append(rbc_tbl)
story.append(Spacer(1, 6))

# --- WBC table ---
wbc_data = [
    ["Cell Type", "% of Total WBC", "Absolute Count (/mm³)"],
    ["Total WBC", "—", "4,000–11,000"],
    ["Neutrophils (segs)", "50–70%", "1,800–7,800"],
    ["Band forms", "0–5%", "0–500"],
    ["Lymphocytes", "20–40%", "1,000–4,800"],
    ["Monocytes", "2–8%", "200–900"],
    ["Eosinophils", "1–4%", "100–400"],
    ["Basophils", "0–1%", "0–100"],
]
wbc_cw = [2.8*inch, 2.1*inch, 2.2*inch]
wbc_tbl = Table(wbc_data, colWidths=wbc_cw, repeatRows=1)
wbc_tbl.setStyle(tbl_style_base(wbc_data, wbc_cw))
story.append(Paragraph("WBC Differential", small_bold))
story.append(Spacer(1, 2))
story.append(wbc_tbl)
story.append(Spacer(1, 6))

# --- Platelets table ---
plt_data = [
    ["Parameter", "Normal Range", "Unit"],
    ["Platelet Count", "150,000–400,000", "/mm³"],
    ["MPV (Mean Platelet Volume)", "7.5–12.5", "fL"],
]
plt_cw = [2.8*inch, 2.3*inch, 2.0*inch]
plt_tbl = Table(plt_data, colWidths=plt_cw, repeatRows=1)
plt_tbl.setStyle(tbl_style_base(plt_data, plt_cw))
story.append(Paragraph("Platelets", small_bold))
story.append(Spacer(1, 2))
story.append(plt_tbl)
story.append(Spacer(1, 8))

# =====================================================================
# SECTION 2: KEY CALCULATIONS
# =====================================================================
hdr2 = Table([[Paragraph("  2.  KEY CALCULATIONS", section_style)]], colWidths=[7.4*inch])
hdr2.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
story.append(hdr2)
story.append(Spacer(1, 4))

calc_data = [
    ["Formula", "Expression", "Notes"],
    ["MCV", "Hct (%) × 10 ÷ RBC count (×10⁶/µL)", "Result in fL"],
    ["MCH", "Hgb (g/dL) ÷ RBC count (×10⁶/µL)", "Result in pg"],
    ["MCHC", "(Hgb ÷ Hct) × 100", "Result in g/dL"],
    ["ANC (Absolute Neutrophil Count)", "Total WBC × (% segs + % bands) ÷ 100", "Result in cells/mm³"],
    ["Corrected Retic Count", "Retic% × (Patient Hct ÷ 45)", "Corrects for dilution"],
    ["RPI (Reticulocyte Production Index)", "Corrected Retic Count ÷ Maturation Factor", "See maturation table below"],
    ["Corrected WBC (nucleated RBCs)", "Measured WBC × 100 ÷ (100 + nRBC/100 WBC)", "Use when nRBCs present"],
]
calc_cw = [2.4*inch, 2.8*inch, 1.95*inch]
calc_tbl = Table(calc_data, colWidths=calc_cw, repeatRows=1)
s = tbl_style_base(calc_data, calc_cw)
calc_tbl.setStyle(s)
story.append(calc_tbl)
story.append(Spacer(1, 6))

# Maturation factor mini-table
mat_data = [
    ["Hematocrit (%)", "45", "35", "25", "15"],
    ["Maturation Factor", "1.0", "1.5", "2.0", "2.5"],
]
mat_cw = [1.8*inch, 1.2*inch, 1.2*inch, 1.2*inch, 1.2*inch]
mat_tbl = Table(mat_data, colWidths=mat_cw)
mat_tbl.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,-1), 8),
    ("BACKGROUND", (0,1), (-1,-1), LIGHT_BLUE),
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, MED_BLUE),
    ("ALIGN", (0,0), (-1,-1), "CENTER"),
    ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING", (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
]))
story.append(Paragraph("Maturation Correction Table (for RPI)", small_bold))
story.append(Spacer(1, 2))
story.append(mat_tbl)
story.append(Spacer(1, 4))
story.append(Paragraph(
    "RPI interpretation: ≥2–3 = hyperproliferative (hemolysis/blood loss) | <2 = hypoproliferative (iron def., B12/folate, aplasia, CKD)",
    note_style
))
story.append(Spacer(1, 8))

# =====================================================================
# SECTION 3: CLINICAL INTERPRETATION AT A GLANCE
# =====================================================================
hdr3 = Table([[Paragraph("  3.  CLINICAL INTERPRETATION AT A GLANCE", section_style)]], colWidths=[7.4*inch])
hdr3.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
story.append(hdr3)
story.append(Spacer(1, 4))

# ---- Anemia by MCV ----
story.append(Paragraph("Anemia Classification by MCV", small_bold))
story.append(Spacer(1, 2))

anemia_data = [
    ["MCV Category", "Range", "Key Causes", "Supporting Indices"],
    ["Microcytic", "< 80 fL",
     "Iron deficiency (most common)\nThalassemia\nSideroblastic anemia\nLead poisoning\nAnemia of chronic disease (some)",
     "IDA: ↑RDW, ↓ferritin, ↓MCHC\nThal: normal RDW, ↑RBC count\nSideroblastic: ring sideroblasts on smear"],
    ["Normocytic", "80–100 fL",
     "Acute blood loss\nAnemia of inflammation/chronic disease\nRenal failure (↓EPO)\nHypothyroidism\nMixed deficiency (IDA + B12)\nHemolysis (compensated)",
     "Use RPI to distinguish:\nRPI ≥2: hemolysis/blood loss\nRPI <2: hypoproliferative"],
    ["Macrocytic", "> 100 fL",
     "B12 deficiency\nFolate deficiency\nAlcohol / liver disease\nHypothyroidism\nMedications (MTX, hydroxyurea)\nMDS",
     "Megaloblastic: hypersegmented PMNs\n(>5 lobes = pathognomonic)\nNon-megaloblastic: check TSH, LFTs, meds"],
]
anemia_cw = [1.1*inch, 0.75*inch, 2.6*inch, 2.7*inch]
anemia_tbl = Table(anemia_data, colWidths=anemia_cw, repeatRows=1)
anemia_tbl.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,0), 8),
    ("BACKGROUND", (1,1), (1,1), colors.HexColor("#E8F5E9")),
    ("BACKGROUND", (1,2), (1,2), colors.HexColor("#FFF9C4")),
    ("BACKGROUND", (1,3), (1,3), colors.HexColor("#FCE4EC")),
    ("ALIGN", (0,0), (-1,-1), "LEFT"),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
    ("FONTNAME", (0,1), (-1,-1), "Helvetica"),
    ("FONTSIZE", (0,1), (-1,-1), 7.5),
    ("TOPPADDING", (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(anemia_tbl)
story.append(Spacer(1, 8))

# ---- WBC interpretation ----
story.append(Paragraph("WBC Differential Interpretation", small_bold))
story.append(Spacer(1, 2))

wbc_interp = [
    ["Finding", "Threshold", "Common Causes"],
    ["Leukocytosis", "> 11,000/mm³", "Infection (bacterial), stress, corticosteroids, leukemia, inflammation, tissue necrosis"],
    ["Neutrophilia", "> 7,800/mm³", "Bacterial infection, MI, burns, surgery, CML, G-CSF, corticosteroids"],
    ["Left shift + toxic granules", "Bands > 10% + granules", "Severe bacterial sepsis, leukemoid reaction"],
    ["Neutropenia", "< 1,500/mm³", "Drugs, viral infection (incl. HIV), aplastic anemia, B12/folate, hypersplenism"],
    ["Severe neutropenia (ANC)", "ANC < 500/mm³", "Febrile neutropenia risk — urgent evaluation"],
    ["Lymphocytosis", "> 4,800/mm³", "Viral (EBV, CMV, hepatitis), pertussis, CLL, ALL"],
    ["Lymphopenia", "< 1,500/mm³", "Corticosteroids, HIV, immunosuppressants, SLE, malnutrition"],
    ["Eosinophilia", "> 500/mm³", "Parasites, allergies, drug reactions, EGPA (Churg-Strauss), adrenal insufficiency"],
    ["Monocytosis", "> 900/mm³", "TB, SBE, autoimmune disease, CMML, recovery from neutropenia"],
    ["Basophilia", "> 100/mm³", "CML (classic), other MPDs — always investigate"],
]
wbc_interp_cw = [1.8*inch, 1.4*inch, 4.0*inch]
wbc_interp_tbl = Table(wbc_interp, colWidths=wbc_interp_cw, repeatRows=1)
wbc_interp_tbl.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,0), 8),
    ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_ROW]),
    # Highlight critical row (ANC < 500)
    ("BACKGROUND", (0,5), (-1,5), colors.HexColor("#FFEBEE")),
    ("TEXTCOLOR", (0,5), (0,5), ACCENT),
    ("FONTNAME", (0,5), (0,5), "Helvetica-Bold"),
    ("ALIGN", (0,0), (-1,-1), "LEFT"),
    ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ("FONTNAME", (0,1), (-1,-1), "Helvetica"),
    ("FONTSIZE", (0,1), (-1,-1), 7.5),
    ("TOPPADDING", (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(wbc_interp_tbl)
story.append(Spacer(1, 8))

# ---- Platelet interpretation ----
story.append(Paragraph("Platelet Count Interpretation", small_bold))
story.append(Spacer(1, 2))

plt_interp = [
    ["Count (/mm³)", "Term", "Significance & Causes"],
    ["> 1,000,000", "Extreme thrombocytosis", "Essential thrombocythemia, reactive (iron def., infection, post-splenectomy)"],
    ["400,000–1,000,000", "Thrombocytosis", "Reactive (most common) vs. clonal (ET, PV, CML)"],
    ["150,000–400,000", "Normal", "—"],
    ["50,000–150,000", "Mild-mod thrombocytopenia", "Increased surgical bleeding risk; monitor closely"],
    ["20,000–50,000", "Moderate", "Spontaneous bleeding possible; platelet transfusion may be needed"],
    ["< 20,000", "Severe thrombocytopenia", "High spontaneous bleeding risk; urgent evaluation"],
]
plt_interp_cw = [1.4*inch, 1.6*inch, 4.2*inch]
plt_interp_tbl = Table(plt_interp, colWidths=plt_interp_cw, repeatRows=1)
plt_interp_tbl.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,0), 8),
    ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_ROW]),
    ("BACKGROUND", (0,6), (-1,6), colors.HexColor("#FFEBEE")),
    ("TEXTCOLOR", (0,6), (0,6), ACCENT),
    ("FONTNAME", (0,6), (0,6), "Helvetica-Bold"),
    ("ALIGN", (0,0), (-1,-1), "LEFT"),
    ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ("FONTNAME", (0,1), (-1,-1), "Helvetica"),
    ("FONTSIZE", (0,1), (-1,-1), 7.5),
    ("TOPPADDING", (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(plt_interp_tbl)
story.append(Spacer(1, 8))

# =====================================================================
# SECTION 4: PITFALLS & CLINICAL PEARLS
# =====================================================================
hdr4 = Table([[Paragraph("  4.  PITFALLS & CLINICAL PEARLS", section_style)]], colWidths=[7.4*inch])
hdr4.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
story.append(hdr4)
story.append(Spacer(1, 4))

pearls = [
    ("Acute hemorrhage", "Hgb/Hct normal immediately after acute bleed. Takes 4–6 h of hemodilution to drop. Do NOT rule out hemorrhage based on an early CBC."),
    ("Hemoconcentration / Hemodilution", "Congruent shifts in all 3 cell lines (WBC + RBC + Plt) suggest fluid shifts (IV fluids, heart failure, pregnancy, dehydration) rather than hematologic disease."),
    ("Mixed deficiency masking MCV", "Simultaneous IDA + B12/folate deficiency can produce a normal MCV with markedly elevated RDW — never rely on MCV alone."),
    ("Pseudo-thrombocytopenia", "EDTA causes platelet clumping in ~1% of patients → spuriously low platelet count. Check peripheral smear; repeat with sodium citrate tube."),
    ("Pseudo-neutrophilia", "Exercise, stress, and epinephrine cause demargination → transient leukocytosis with no left shift, resolves in hours."),
    ("Reticulocyte trap", "A 2% reticulocyte count seems 'normal' but in a patient with Hct 20%, the corrected count and RPI are still hypoproliferative. Always calculate RPI in anemia."),
    ("Thal vs. IDA microcytosis", "Thalassemia trait: MCV low but RBC count normal/high, RDW often normal. IDA: RBC count low, RDW elevated, ferritin < 15 ng/mL confirms."),
    ("Elevated MCHC", "MCHC rarely exceeds 36 g/dL in normal conditions. High MCHC strongly suggests spherocytes or a hemoglobinopathy — always review smear."),
    ("Left shift + toxic granulation", "Bands > 10% + toxic granules (± Döhle bodies) = severe sepsis until proven otherwise. Urgently correlate with clinical status."),
    ("Nucleated RBCs on differential", "Automated counters count nRBCs as WBCs → falsely elevated WBC count. Apply correction: WBC_corrected = WBC_measured × 100 ÷ (100 + nRBC per 100 WBC)."),
]

pearl_rows = [["Pitfall / Pearl", "Key Point"]]
for title, detail in pearls:
    pearl_rows.append([Paragraph(f"<b>{title}</b>", ParagraphStyle("pb", fontSize=7.5, fontName="Helvetica-Bold", textColor=DARK_BLUE, leading=11)),
                       Paragraph(detail, ParagraphStyle("pd", fontSize=7.5, fontName="Helvetica", textColor=colors.HexColor("#1A1A2E"), leading=11))])

pearl_cw = [1.9*inch, 5.2*inch]
pearl_tbl = Table(pearl_rows, colWidths=pearl_cw, repeatRows=1)
pearl_tbl.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,0), 8),
    ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, YELLOW_BG]),
    ("ALIGN", (0,0), (-1,-1), "LEFT"),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
    ("TOPPADDING", (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(pearl_tbl)
story.append(Spacer(1, 8))

# =====================================================================
# SECTION 5: WHEN TO ORDER A PERIPHERAL SMEAR
# =====================================================================
hdr5 = Table([[Paragraph("  5.  WHEN TO ORDER A PERIPHERAL BLOOD SMEAR", section_style)]], colWidths=[7.4*inch])
hdr5.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
]))
story.append(hdr5)
story.append(Spacer(1, 4))

smear_indications = [
    "Unexplained cytopenia in any cell line",
    "Suspected hemolysis (elevated LDH, low haptoglobin, ↑bilirubin)",
    "Leukocytosis with concern for leukemia or leukemoid reaction",
    "Thrombocytopenia — rule out pseudo-thrombocytopenia, TTP/HUS (schistocytes)",
    "Unexplained elevated RDW",
    "Any blast cells or immature forms flagged by the analyzer",
    "Suspected malaria or other parasitic infections",
    "Abnormal morphology flag from automated counter",
]
smear_data = [[Paragraph(f"• {ind}", ParagraphStyle("si", fontSize=8, fontName="Helvetica", textColor=DARK_BLUE, leading=12))] for ind in smear_indications]
smear_tbl = Table(smear_data, colWidths=[7.2*inch])
smear_tbl.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.5, DARK_BLUE),
    ("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
    ("TOPPADDING", (0,0), (-1,-1), 2),
    ("BOTTOMPADDING", (0,0), (-1,-1), 2),
    ("LEFTPADDING", (0,0), (-1,-1), 8),
]))
story.append(smear_tbl)
story.append(Spacer(1, 6))

# Key smear findings
story.append(Paragraph("Key Peripheral Smear Findings", small_bold))
story.append(Spacer(1, 2))
smear_findings = [
    ["Finding", "Morphology", "Associated Condition(s)"],
    ["Schistocytes", "Fragmented RBCs", "TTP, HUS, DIC, mechanical valve hemolysis"],
    ["Spherocytes", "Small, round, no central pallor", "Hereditary spherocytosis, autoimmune hemolytic anemia"],
    ["Target cells (codocytes)", "Bull's-eye appearance", "Thalassemia, liver disease, HbC, splenectomy"],
    ["Teardrop cells (dacrocytes)", "Teardrop-shaped RBCs", "Myelofibrosis, thalassemia, bone marrow infiltration"],
    ["Hypersegmented neutrophils", "> 5 nuclear lobes", "Megaloblastic anemia (B12/folate deficiency) — PATHOGNOMONIC"],
    ["Toxic granules + Döhle bodies", "Dark granules in PMNs", "Severe bacterial infection, sepsis"],
    ["Rouleaux", "RBCs stacked like coins", "Multiple myeloma, elevated fibrinogen/paraproteins"],
    ["Blast cells", "Large immature cells", "Acute leukemia — URGENT hematology referral"],
]
smear_cw = [1.7*inch, 1.9*inch, 3.6*inch]
smear_tbl2 = Table(smear_findings, colWidths=smear_cw, repeatRows=1)
smear_tbl2.setStyle(TableStyle([
    ("BOX", (0,0), (-1,-1), 0.8, DARK_BLUE),
    ("INNERGRID", (0,0), (-1,-1), 0.3, colors.HexColor("#CCDDEE")),
    ("BACKGROUND", (0,0), (-1,0), MED_BLUE),
    ("TEXTCOLOR", (0,0), (-1,0), WHITE),
    ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
    ("FONTSIZE", (0,0), (-1,0), 8),
    ("ROWBACKGROUNDS", (0,1), (-1,-1), [WHITE, GREY_ROW]),
    # Blast cells row - urgent
    ("BACKGROUND", (0,8), (-1,8), colors.HexColor("#FFEBEE")),
    ("TEXTCOLOR", (0,8), (0,8), ACCENT),
    ("FONTNAME", (0,8), (0,8), "Helvetica-Bold"),
    ("ALIGN", (0,0), (-1,-1), "LEFT"),
    ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ("FONTNAME", (0,1), (-1,-1), "Helvetica"),
    ("FONTSIZE", (0,1), (-1,-1), 7.5),
    ("TOPPADDING", (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING", (0,0), (-1,-1), 5),
]))
story.append(smear_tbl2)
story.append(Spacer(1, 8))

# ---- Footer / Sources ----
story.append(HRFlowable(width="100%", thickness=0.5, color=DARK_BLUE))
story.append(Spacer(1, 3))
story.append(Paragraph(
    "Sources: Goldman-Cecil Medicine 26E (Table 144-2/3) · Washington Manual of Medical Therapeutics · Frameworks for Internal Medicine · Textbook of Family Medicine 9E · Henry's Clinical Diagnosis and Management by Laboratory Methods  |  For clinical use by licensed physicians only. Not a substitute for clinical judgment.",
    footer_style
))

# ---- BUILD ----
doc.build(story)
print(f"PDF created: {OUTPUT}")
Running Command

cd /home/daytona/workspace/cbc-reference && python3 build_cbc_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/cbc-reference/CBC_Quick_Reference.pdf

Your CBC quick reference PDF is ready for printing:

CBC Quick Reference

PDF Document · PDF


Here's what's included across the 5 sections on a single printable letter-sized page:
1. Normal Reference Ranges - Full tables for RBC parameters (Hgb, Hct, RBC count, MCV, MCH, MCHC, RDW, reticulocyte count) with sex-specific values, WBC differential with both % and absolute counts, and platelet parameters.
2. Key Calculations - Every formula you need at a glance: MCV, MCH, MCHC, ANC, corrected reticulocyte count, RPI, and the nucleated RBC correction. Includes the maturation factor table for RPI calculation.
3. Clinical Interpretation at a Glance - Anemia by MCV (micro/normo/macro with key causes and supporting indices), WBC differential interpretation table, and platelet count stratification with clinical thresholds.
4. Pitfalls & Clinical Pearls - 10 highlighted traps including the acute hemorrhage timing issue, hemo-dilution/concentration shifts, mixed deficiency masking MCV, pseudo-thrombocytopenia, and the reticulocyte calculation trap.
5. Peripheral Blood Smear - When to order it, plus a morphology quick reference (schistocytes, spherocytes, target cells, hypersegmented PMNs, blasts, etc.) with associated conditions. Critical findings like blast cells are highlighted in red.
This is a shared conversation. Sign in to Orris to start your own chat.