As a physician, I want to learn about properly studying a CBC report. Teach me about it.
| Parameter | Unit | Normal Range (Adults) |
|---|---|---|
| WBC count | ×10³/µL | 4.0–11.0 |
| RBC count | ×10⁶/µL | 4.0–5.4 (F), 4.5–6.0 (M) |
| Hemoglobin (Hgb) | g/dL | 12–16 (F), 13.5–17.5 (M) |
| Hematocrit (Hct) | % | 36–48 (F), 40–52 (M) |
| MCV | fL | 81–99 |
| MCH | pg | 30–34 |
| MCHC | g/dL | 30–36 |
| RDW-CV | % | 12–15 |
| Platelet count | ×10³/µL | 150–400 |
| Reticulocyte % | % | 0.5–1.5 |
| Absolute reticulocyte | /µL | 20,000–100,000 |
| MCV range | Category |
|---|---|
| < 80 fL | Microcytic |
| 80–100 fL | Normocytic |
| > 100 fL | Macrocytic |

| Hct | Maturation factor |
|---|---|
| 45% | 1.0 |
| 35% | 1.5 |
| 25% | 2.0 |
| 15% | 2.5 |
| Cell Type | Normal % | Absolute Normal |
|---|---|---|
| Neutrophils (segs + bands) | 50–70% | 1800–7800/mm³ |
| Lymphocytes | 20–40% | 1000–4800/mm³ |
| Monocytes | 2–8% | 200–900/mm³ |
| Eosinophils | 1–4% | 100–400/mm³ |
| Basophils | 0–1% | 0–100/mm³ |
| Range | Term | Clinical implication |
|---|---|---|
| > 400,000/mm³ | Thrombocytosis | Reactive (infection, inflammation, iron deficiency, post-splenectomy) vs. clonal (essential thrombocythemia) |
| 150,000–400,000 | Normal | -- |
| 50,000–150,000 | Mild-moderate thrombocytopenia | Monitor; bleeding risk low |
| < 50,000 | Significant thrombocytopenia | Increased surgical bleeding risk |
| < 20,000 | Severe thrombocytopenia | Spontaneous bleeding risk |
Create a quick reference PDF with normal ranges and calculations I can print for my office
ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/cbc-reference && python3 -c "import reportlab; print('reportlab ok')"
~/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}")
cd /home/daytona/workspace/cbc-reference && python3 build_cbc_pdf.py 2>&1
ls -lh /home/daytona/workspace/cbc-reference/CBC_Quick_Reference.pdf
CBC Quick Reference
PDF Document · PDF