~/anemia-pdf/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import PageBreak
OUTPUT = "/home/daytona/workspace/anemia-pdf/Anemia_Laboratory_Investigations.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=1.8*cm,
leftMargin=1.8*cm,
topMargin=2*cm,
bottomMargin=2*cm,
title="Laboratory Investigations for Anemia",
author="Orris Medical Reference"
)
styles = getSampleStyleSheet()
# Custom styles
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Title'],
fontSize=20,
textColor=colors.HexColor('#1a3a5c'),
spaceAfter=6,
fontName='Helvetica-Bold',
alignment=TA_CENTER
)
subtitle_style = ParagraphStyle(
'Subtitle',
parent=styles['Normal'],
fontSize=10,
textColor=colors.HexColor('#555555'),
spaceAfter=14,
alignment=TA_CENTER,
fontName='Helvetica-Oblique'
)
h1_style = ParagraphStyle(
'H1',
parent=styles['Heading1'],
fontSize=13,
textColor=colors.white,
backColor=colors.HexColor('#1a3a5c'),
spaceBefore=14,
spaceAfter=6,
fontName='Helvetica-Bold',
leftIndent=-10,
rightIndent=-10,
borderPad=5,
leading=18
)
h2_style = ParagraphStyle(
'H2',
parent=styles['Heading2'],
fontSize=11,
textColor=colors.HexColor('#1a3a5c'),
spaceBefore=10,
spaceAfter=4,
fontName='Helvetica-Bold',
borderPad=2
)
body_style = ParagraphStyle(
'Body',
parent=styles['Normal'],
fontSize=9,
leading=13,
spaceAfter=5,
fontName='Helvetica',
textColor=colors.HexColor('#222222'),
alignment=TA_JUSTIFY
)
note_style = ParagraphStyle(
'Note',
parent=styles['Normal'],
fontSize=8.5,
leading=12,
spaceAfter=6,
fontName='Helvetica-Oblique',
textColor=colors.HexColor('#444444'),
leftIndent=10,
backColor=colors.HexColor('#f0f4f8'),
borderPad=5,
borderColor=colors.HexColor('#1a3a5c'),
borderWidth=0,
borderRadius=3
)
source_style = ParagraphStyle(
'Source',
parent=styles['Normal'],
fontSize=7.5,
leading=11,
textColor=colors.HexColor('#888888'),
fontName='Helvetica-Oblique',
spaceAfter=3
)
# Table styles
def make_header_table_style(header_color=colors.HexColor('#1a3a5c'), alt_color=colors.HexColor('#eaf0f8')):
return TableStyle([
('BACKGROUND', (0, 0), (-1, 0), header_color),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 8.5),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 8),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, alt_color]),
('GRID', (0, 0), (-1, -1), 0.4, colors.HexColor('#c0cfe0')),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('TOPPADDING', (0, 0), (-1, -1), 4),
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('WORDWRAP', (0, 0), (-1, -1), True),
])
def section_header(text):
return Paragraph(f" {text}", h1_style)
def sub_header(text):
return Paragraph(text, h2_style)
def body(text):
return Paragraph(text, body_style)
def note(text):
return Paragraph(f"<b>Note:</b> {text}", note_style)
def spacer(h=0.3):
return Spacer(1, h*cm)
def hr():
return HRFlowable(width="100%", thickness=0.5, color=colors.HexColor('#c0cfe0'), spaceAfter=4, spaceBefore=4)
# ──────────────────────────────────────────────────────────
# BUILD CONTENT
# ──────────────────────────────────────────────────────────
story = []
# Title Block
story.append(spacer(0.5))
story.append(Paragraph("Laboratory Investigations for Anemia", title_style))
story.append(Paragraph("A Comprehensive Reference Guide", subtitle_style))
story.append(HRFlowable(width="100%", thickness=2, color=colors.HexColor('#1a3a5c'), spaceAfter=8))
story.append(Paragraph(
"Sources: Henry's Clinical Diagnosis and Management by Laboratory Methods | "
"Robbins & Kumar Basic Pathology | Tietz Textbook of Laboratory Medicine",
source_style
))
story.append(spacer(0.3))
# ── OVERVIEW ──
story.append(section_header("Overview: Approach to Lab Investigation"))
story.append(spacer(0.2))
story.append(body(
"The investigation of anemia follows a stepwise approach guided by careful history and physical "
"examination. Anemias of <b>acute onset</b> with rapidly progressive course are likely hemolytic "
"in origin, while <b>slowly progressive</b> anemias suggest bone marrow production defects. "
"The key question is: does the hemoglobin, hematocrit, or RBC count lie below the reference "
"interval for the patient's age, sex, and altitude?"
))
story.append(spacer(0.2))
# ── STEP 1: CBC ──
story.append(section_header("STEP 1: Complete Blood Count (CBC)"))
story.append(spacer(0.2))
story.append(body("First-line investigation for every suspected anemia (includes WBC, RBC, Hb, Hct, MCV, MCH, MCHC, RDW, platelets)."))
story.append(spacer(0.15))
cbc_data = [
["Parameter", "Normal Range (Men)", "Normal Range (Women)", "Significance"],
["Hemoglobin (Hb)", "13.2–16.6 g/dL", "11.6–15.0 g/dL", "Primary measure of anemia severity"],
["Hematocrit (Hct)", "41–50%", "36–44%", "Packed cell volume"],
["RBC Count", "4.5–5.9 × 10⁶/µL", "4.1–5.3 × 10⁶/µL", "Elevated in thalassemia trait"],
["MCV", "78–98 fL", "78–98 fL", "Morphologic classification of anemia"],
["MCH", "26–34 pg", "26–34 pg", "Average Hb mass per red cell"],
["MCHC", "31–36 g/dL", "31–36 g/dL", "Average Hb concentration per cell"],
["RDW", "CV of RBC volume", "CV of RBC volume", "↑ = Anisocytosis (mixed cell populations)"],
["Platelets", "150,000–400,000/µL", "150,000–400,000/µL", "Pancytopenia → marrow failure"],
["WBC + Differential", "4,500–11,000/µL", "4,500–11,000/µL", "Pancytopenia → aplastic anemia"],
]
cbc_table = Table(cbc_data, colWidths=[3.5*cm, 3.2*cm, 3.2*cm, 6.2*cm])
cbc_table.setStyle(make_header_table_style())
story.append(cbc_table)
story.append(spacer(0.15))
story.append(note(
"MCV and MCHC are mean values — they will NOT detect dimorphic populations that "
"balance each other (e.g., combined iron + B12 deficiency). In early anemia, MCV "
"may change before Hb and Hct."
))
story.append(spacer(0.2))
# ── STEP 2: PBS ──
story.append(section_header("STEP 2: Peripheral Blood Film (PBS)"))
story.append(spacer(0.2))
story.append(body(
"One of the most informative and essential investigations. Always examine a Wright's-stained film "
"alongside CBC. Findings on PBS suggest specific mechanisms of anemia."
))
story.append(spacer(0.15))
pbs_data = [
["Finding on PBS", "Interpretation"],
["Microcytes (small, pale cells)", "Iron deficiency, thalassemia, anemia of chronic disease"],
["Macrocytes (large oval cells)", "B12/folate deficiency (megaloblastic), liver disease, hypothyroidism"],
["Hypochromia (increased central pallor)", "Iron deficiency, thalassemia, sideroblastic anemia"],
["Polychromatic macrocytes (shift cells)", "Increased erythropoiesis — hemorrhage or hemolysis"],
["Sickle cells", "Sickle cell disease (HbSS)"],
["Target cells", "Hemoglobinopathies (HbC, HbD, HbE), thalassemia, liver disease, post-splenectomy"],
["Spherocytes", "Hereditary spherocytosis, autoimmune hemolytic anemia (AIHA)"],
["Schistocytes (fragmented RBCs)", "Microangiopathic hemolytic anemia (TTP, HUS, DIC)"],
["Elliptocytes", "Hereditary elliptocytosis"],
["Poikilocytes (abnormally shaped cells)", "General hemolysis marker"],
["Hypersegmented neutrophils (≥5 lobes)", "Megaloblastic anemia (B12/folate deficiency)"],
["Normoblasts (nucleated RBCs)", "Severe hemolysis, marrow infiltration"],
["Basophilic stippling (fine)", "Increased RBC turnover (hemorrhage/hemolysis, precipitation of RNA)"],
["Basophilic stippling (coarse)", "Megaloblastic anemia, thalassemia, refractory anemias, lead poisoning"],
["Heinz bodies", "G6PD deficiency, unstable Hb, drug-induced oxidative hemolysis"],
["Rouleaux formation", "Multiple myeloma, hyperglobulinemia"],
["Low mean cell diameter (6.0–6.5 µm)", "Hereditary spherocytosis (normal = 7.0–7.4 µm)"],
]
pbs_table = Table(pbs_data, colWidths=[7.5*cm, 8.7*cm])
pbs_table.setStyle(make_header_table_style())
story.append(pbs_table)
story.append(spacer(0.15))
story.append(note(
"Hypochromia/microcytosis WITH significant coarse basophilic stippling argues against IDA and "
"is more suggestive of thalassemia or lead poisoning."
))
story.append(spacer(0.2))
# ── STEP 3: RETICULOCYTE COUNT ──
story.append(section_header("STEP 3: Reticulocyte Count"))
story.append(spacer(0.2))
story.append(body(
"Critical for distinguishing <b>production failure</b> from <b>hemolysis/blood loss</b>. "
"The Reticulocyte Production Index (RPI) corrects for the degree of anemia and reticulocyte maturation time."
))
story.append(spacer(0.15))
retic_data = [
["Reticulocyte Count / RPI", "Interpretation"],
["Elevated (> 2–3%)", "Adequate marrow response → Blood loss or hemolysis"],
["Low/Normal despite anemia", "Inadequate marrow response → Production defect (IDA, megaloblastic, aplastic, CKD)"],
["RPI > 2", "Hemolytic anemia / recent blood loss (hyperproliferative)"],
["RPI < 2", "Hypoproliferative / ineffective erythropoiesis"],
]
retic_table = Table(retic_data, colWidths=[7*cm, 9.2*cm])
retic_table.setStyle(make_header_table_style())
story.append(retic_table)
story.append(spacer(0.3))
# ── STEP 4: IRON STUDIES ──
story.append(section_header("STEP 4: Iron Studies"))
story.append(spacer(0.2))
story.append(body(
"Essential for microcytic hypochromic anemia. Differentiates iron deficiency anemia (IDA), "
"beta-thalassemia trait, anemia of chronic disease (ACD), and sideroblastic anemia."
))
story.append(spacer(0.15))
story.append(note(
"In both IDA and ACD, serum iron is LOW. Key differentiator: In IDA, TIBC is ELEVATED "
"and marrow iron is DEPLETED; in ACD, TIBC is NORMAL or DECREASED and marrow iron is NORMAL or ELEVATED."
))
story.append(spacer(0.15))
iron_data = [
["Test", "Iron Deficiency", "β-Thalassemia Trait", "ACD", "Sideroblastic Anemia"],
["Serum Iron", "↓", "N (or ↑)", "↓", "↑"],
["Serum TIBC", "↑", "N", "N or ↓", "↓"],
["% Transferrin Saturation", "↓", "N", "↓", "↑"],
["Serum Ferritin", "↓", "N or ↑", "N or ↑", "↑"],
["% Sideroblasts (marrow)", "↓", "N", "↓", "↑ (ring sideroblasts)"],
["Iron Stores (marrow)", "↓ (depleted)", "N or ↑", "N or ↑", "↑"],
["ZPP (Zinc protoporphyrin)", "↑", "N", "↑", "↑ (or ↓)"],
["HbA₂", "N or ↓", "↑ (> 3.5%)", "N", "N"],
["HbF", "N", "N or ↑", "N", "N"],
["MCV", "↓", "↓ (often < 70 fL)", "N or ↓", "N or ↑ (dimorphic)"],
]
iron_table = Table(iron_data, colWidths=[4.2*cm, 2.8*cm, 3.2*cm, 2.5*cm, 3.5*cm])
iron_table.setStyle(make_header_table_style())
story.append(iron_table)
story.append(spacer(0.2))
# ── STEP 5: HEMOLYSIS INVESTIGATIONS ──
story.append(section_header("STEP 5: Hemolysis Investigations"))
story.append(spacer(0.2))
story.append(body(
"For normocytic anemia with elevated reticulocytes. Differentiate extravascular "
"(spleen-mediated) from intravascular hemolysis."
))
story.append(spacer(0.15))
hemolysis_data = [
["Test", "Finding in Hemolysis", "Notes"],
["Serum LDH", "↑", "Released from lysed RBCs; marker of cell destruction"],
["Serum unconjugated bilirubin", "↑", "From Hb degradation in macrophages (extravascular)"],
["Serum haptoglobin", "↓ or absent", "Binds free Hb; depleted in intravascular hemolysis"],
["Plasma free hemoglobin", "↑", "Intravascular hemolysis marker"],
["Urine hemoglobin/hemosiderinuria", "Positive", "Chronic intravascular hemolysis (e.g., PNH)"],
["Urine urobilinogen", "↑", "Extravascular hemolysis"],
["Direct Coombs Test (DAT)", "Positive", "Autoimmune hemolytic anemia — IgG ± complement on RBCs"],
["Indirect Coombs Test (IAT)", "Positive", "Antibody in serum against RBC antigens"],
["Peripheral smear", "Spherocytes, schistocytes, sickle cells", "Morphologic clue to hemolytic mechanism"],
]
hem_table = Table(hemolysis_data, colWidths=[4.5*cm, 3.5*cm, 8.2*cm])
hem_table.setStyle(make_header_table_style())
story.append(hem_table)
story.append(spacer(0.15))
# extravascular vs intravascular
ev_iv_data = [
["Extravascular Hemolysis", "Intravascular Hemolysis"],
["Hyperbilirubinemia + jaundice", "Hemoglobinuria (red/brown urine)"],
["Splenomegaly", "Hemoglobinemia (pink plasma)"],
["↑ Urobilinogen in urine/stool", "Hemosiderinuria"],
["Positive DAT (if immune)", "Very low / absent haptoglobin"],
["Spherocytes on PBS", "Schistocytes on PBS (if microangiopathic)"],
]
ev_iv_table = Table(ev_iv_data, colWidths=[8.1*cm, 8.1*cm])
ev_iv_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2e6da4')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 8.5),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('GRID', (0, 0), (-1, -1), 0.4, colors.HexColor('#c0cfe0')),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#eaf0f8')]),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('TOPPADDING', (0, 0), (-1, -1), 4),
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(ev_iv_table)
story.append(spacer(0.3))
story.append(PageBreak())
# ── STEP 6: SPECIFIC INVESTIGATIONS ──
story.append(section_header("STEP 6: Specific Investigations by Anemia Type"))
story.append(spacer(0.2))
# ── 6A: Megaloblastic ──
story.append(sub_header("A. Megaloblastic Anemia (Macrocytic, MCV > 98 fL)"))
story.append(spacer(0.1))
story.append(body(
"Characterized by macrocytes, hypersegmented neutrophils, and pancytopenia in severe cases. "
"Megaloblastic changes in bone marrow confirm the diagnosis."
))
story.append(spacer(0.1))
mega_data = [
["Test", "Finding / Interpretation"],
["Serum Vitamin B12", "↓ (< 200 pg/mL in deficiency); < 100 pg/mL = severe deficiency"],
["Serum folate", "↓ in folate deficiency"],
["RBC folate", "↓ (more reliable than serum folate; reflects tissue stores)"],
["Serum methylmalonic acid (MMA)", "↑ — specific for B12 deficiency"],
["Serum homocysteine", "↑ in both B12 and folate deficiency"],
["Bone marrow aspiration", "Megaloblastic changes: giant erythroid precursors, giant metamyelocytes, hypersegmented neutrophils"],
["Anti-intrinsic factor antibodies", "Positive in pernicious anemia (highly specific)"],
["Anti-parietal cell antibodies", "Positive in pernicious anemia (sensitive but less specific)"],
["Schilling test", "Differentiates pernicious anemia from dietary B12 deficiency"],
["LDH", "Markedly elevated due to intramedullary hemolysis (ineffective erythropoiesis)"],
]
mega_table = Table(mega_data, colWidths=[5.5*cm, 10.7*cm])
mega_table.setStyle(make_header_table_style())
story.append(mega_table)
story.append(spacer(0.25))
# ── 6B: Hereditary Spherocytosis ──
story.append(sub_header("B. Hereditary Spherocytosis (HS)"))
story.append(spacer(0.1))
hs_data = [
["Test", "Finding"],
["Osmotic fragility test", "Increased fragility — before and after 24-hr incubation at 37°C"],
["Flow cytometry (EMA binding)", "Reduced EMA binding — alternative/preferred to osmotic fragility"],
["MCHC", "Often elevated (> 36 g/dL)"],
["Mean cell diameter (MCD)", "Low: 6.0–6.5 µm (normal = 7.0–7.4 µm)"],
["PBS", "Spherocytes with small/no central pallor; more intensely staining"],
["Family studies", "Positive family history (autosomal dominant in most cases)"],
]
hs_table = Table(hs_data, colWidths=[5.5*cm, 10.7*cm])
hs_table.setStyle(make_header_table_style())
story.append(hs_table)
story.append(spacer(0.25))
# ── 6C: G6PD Deficiency ──
story.append(sub_header("C. G6PD Deficiency"))
story.append(spacer(0.1))
g6pd_data = [
["Test", "Finding"],
["G6PD enzyme screening test", "Reduced G6PD enzyme activity"],
["Heinz body preparation", "Heinz bodies present during hemolytic crisis"],
["PBS during crisis", "\"Bite cells\" (degmacytes), blister cells"],
["Urine (during crisis)", "Hemoglobinuria (dark urine)"],
["Quantitative G6PD assay", "Confirms diagnosis; note: may be normal immediately post-crisis (younger cells have normal activity)"],
]
g6pd_table = Table(g6pd_data, colWidths=[5.5*cm, 10.7*cm])
g6pd_table.setStyle(make_header_table_style())
story.append(g6pd_table)
story.append(spacer(0.25))
# ── 6D: Hemoglobinopathies ──
story.append(sub_header("D. Hemoglobinopathies (Sickle Cell Disease / Thalassemia)"))
story.append(spacer(0.1))
hemo_data = [
["Test", "Finding / Significance"],
["Hemoglobin electrophoresis", "HbS in SCD; HbA₂ ↑ in β-thalassemia; HbF ↑ in various conditions; HbC, HbD, HbE identification"],
["HPLC (High-Performance Liquid Chromatography)", "Gold standard for Hb variant identification and quantification"],
["Sickle solubility test", "Positive (turbid) in presence of HbS"],
["HbA₂ level", "> 3.5% diagnostic of β-thalassemia trait"],
["HbF level", "↑ in β-thalassemia major/intermedia, HPFH, some SCD"],
["HbH inclusions", "HbH disease (3-gene α-thalassemia deletion)"],
["Brilliant cresyl blue stain", "Reveals HbH inclusions as golf-ball appearance"],
["RBC count", "Elevated (5–7 × 10⁶/µL) in β-thalassemia trait despite low MCV"],
["DNA analysis / molecular testing", "Definitive diagnosis of thalassemia gene deletions/mutations"],
]
hemo_table = Table(hemo_data, colWidths=[5.5*cm, 10.7*cm])
hemo_table.setStyle(make_header_table_style())
story.append(hemo_table)
story.append(spacer(0.25))
# ── 6E: AIHA ──
story.append(sub_header("E. Autoimmune Hemolytic Anemia (AIHA)"))
story.append(spacer(0.1))
aiha_data = [
["Test", "Finding"],
["Direct Antiglobulin Test (DAT/Coombs)", "Positive — IgG ± complement on RBCs"],
["IgG subclass testing", "Warm AIHA (IgG) vs. Cold agglutinin disease (IgM + complement)"],
["Cold agglutinin titer", "Elevated in cold agglutinin disease (> 1:64 significant)"],
["Donath-Landsteiner antibody", "Positive in paroxysmal cold hemoglobinuria (PCH) — IgG, biphasic"],
["Serum protein electrophoresis", "Evaluates immunoglobulin abnormalities, associated myeloma"],
["Drug history review", "Rule out drug-induced AIHA (methyldopa, penicillin, cephalosporins)"],
]
aiha_table = Table(aiha_data, colWidths=[5.5*cm, 10.7*cm])
aiha_table.setStyle(make_header_table_style())
story.append(aiha_table)
story.append(spacer(0.25))
# ── 6F: Aplastic Anemia ──
story.append(sub_header("F. Aplastic Anemia / Bone Marrow Failure"))
story.append(spacer(0.1))
aplastic_data = [
["Test", "Finding"],
["CBC", "Pancytopenia: ↓ Hb + ↓ WBC + ↓ platelets"],
["Reticulocyte count", "Very low / absent (< 1%)"],
["Bone marrow biopsy", "Hypocellular marrow with fatty replacement (< 25% cellularity in severe AA)"],
["Flow cytometry (PNH testing)", "Absent CD55/CD59 on RBCs and WBCs in paroxysmal nocturnal hemoglobinuria"],
["Chromosomal studies (cytogenetics)", "Abnormalities suggest MDS rather than pure aplastic anemia"],
]
aplastic_table = Table(aplastic_data, colWidths=[5.5*cm, 10.7*cm])
aplastic_table.setStyle(make_header_table_style())
story.append(aplastic_table)
story.append(spacer(0.25))
# ── 6G: Anemia of CKD ──
story.append(sub_header("G. Anemia of Chronic Kidney Disease (CKD)"))
story.append(spacer(0.1))
ckd_data = [
["Test", "Finding"],
["Serum creatinine / eGFR", "Elevated creatinine; eGFR typically < 30 mL/min/1.73m² for significant anemia"],
["Serum EPO (erythropoietin)", "Low or inappropriately normal for degree of anemia"],
["Reticulocyte count", "Low (inadequate marrow stimulation)"],
["Iron studies", "May coexist with functional iron deficiency (low transferrin saturation)"],
["PBS", "Normocytic normochromic; may show burr cells (echinocytes) in uremia"],
]
ckd_table = Table(ckd_data, colWidths=[5.5*cm, 10.7*cm])
ckd_table.setStyle(make_header_table_style())
story.append(ckd_table)
story.append(spacer(0.3))
story.append(PageBreak())
# ── STEP 7: BONE MARROW EXAMINATION ──
story.append(section_header("STEP 7: Bone Marrow Examination"))
story.append(spacer(0.2))
story.append(body(
"Indicated when anemia occurs alongside thrombocytopenia and/or granulocytopenia (suggesting "
"marrow aplasia or infiltration), for suspected myelodysplastic syndrome, unexplained "
"normocytic anemia with low reticulocytes, or to confirm megaloblastosis."
))
story.append(spacer(0.15))
bm_data = [
["Bone Marrow Finding", "Diagnosis"],
["Megaloblastic changes (giant erythroid precursors, hypersegmented granulocytes)", "B12 / folate deficiency (megaloblastic anemia)"],
["Hypercellular with erythroid hyperplasia", "Hemolytic anemia; compensated blood loss"],
["Hypocellular marrow with fatty replacement", "Aplastic anemia"],
["Ring sideroblasts (iron-laden mitochondria around nucleus)", "Sideroblastic anemia (primary or secondary)"],
["Infiltration by tumor cells or fibrous tissue", "Myelophthisic anemia (leukaemia, lymphoma, myelofibrosis, metastatic cancer)"],
["Absent stainable iron (Perl's Prussian blue)", "Iron deficiency anemia"],
["Increased iron stores with ringed sideroblasts", "Sideroblastic anemia, ACD"],
["Hyperplastic with dysplastic features (dyserythropoiesis)", "Myelodysplastic syndrome (MDS)"],
]
bm_table = Table(bm_data, colWidths=[8*cm, 8.2*cm])
bm_table.setStyle(make_header_table_style())
story.append(bm_table)
story.append(spacer(0.3))
# ── SUMMARY TABLE ──
story.append(section_header("Summary: Morphologic Classification with Investigations"))
story.append(spacer(0.2))
summary_data = [
["Morphology", "MCV", "Common Causes", "Key Investigations"],
["Microcytic\nHypochromic", "< 78 fL", "IDA, β-thalassemia, ACD, sideroblastic anemia", "Iron studies, Hb electrophoresis/HPLC, bone marrow"],
["Normocytic\nNormochromic", "78–98 fL", "Hemolysis, acute blood loss, aplastic anemia, CKD, ACD", "Reticulocyte count, LDH, Coombs test, serum EPO, bone marrow"],
["Macrocytic\nNormochromic", "> 98 fL", "B12/folate deficiency, liver disease, hypothyroidism, alcohol, MDS", "Serum B12, folate, RBC folate, MMA, homocysteine, bone marrow"],
["Dimorphic\n(two populations)", "N or variable", "Sideroblastic anemia, combined IDA + B12/folate deficiency, post-transfusion", "Iron studies, B12/folate, bone marrow (ring sideroblasts)"],
]
summary_table = Table(summary_data, colWidths=[3.2*cm, 1.8*cm, 5.5*cm, 5.7*cm])
summary_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1a3a5c')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 8.5),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 8),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#eaf0f8')]),
('GRID', (0, 0), (-1, -1), 0.4, colors.HexColor('#c0cfe0')),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('FONTNAME', (0, 1), (0, -1), 'Helvetica-Bold'),
('TEXTCOLOR', (0, 1), (0, -1), colors.HexColor('#1a3a5c')),
]))
story.append(summary_table)
story.append(spacer(0.4))
# Footer note
story.append(hr())
story.append(Paragraph(
"<b>References:</b> Henry's Clinical Diagnosis and Management by Laboratory Methods (pp. 715–716) | "
"Robbins & Kumar Basic Pathology (pp. 383–384) | Tietz Textbook of Laboratory Medicine, 7th Edition",
source_style
))
story.append(Paragraph(
"Generated by Orris Medical Reference | July 2026 | For educational purposes only.",
source_style
))
# ── BUILD PDF ──
doc.build(story)
print(f"PDF successfully created: {OUTPUT}")