How to evaluate anemia case according to harrison
"66-17" in /textbooks/9781265977061
| Mechanism | Time to Activation | Effect |
|---|---|---|
| Increased cardiac output | Minutes | Maintains O2 delivery |
| Increased 2,3-DPG | Hours to days | Shifts O2-Hgb dissociation curve right; more O2 unloading to tissues |
| Increased plasma volume | Weeks | Preserves cardiac output; can cause heart failure if excessive |
| Index | Formula | Normal | Interpretation |
|---|---|---|---|
| MCV | Hct/RBC × 10 | 85-95 fL | Red cell size |
| MCH | Hgb/RBC count | 28-33 pg | Hgb per cell |
| MCHC | Hgb/Hct × 100 | 33.8-34.2 g/dL | Limited value; changes little |
| RDW | SD of RBC size/MCV × 100 | 11-14% | Anisocytosis; high in iron deficiency |
CBC + Reticulocyte Count
|
├── HIGH reticulocyte count
│ |
│ ├── Rule out BLEEDING first
│ └── If bleeding excluded → HEMOLYSIS workup:
│ - LDH (elevated)
│ - Haptoglobin (low)
│ - Direct Coombs (DAT) test
│ - Peripheral smear (schistocytes, spherocytes, sickle cells)
│
└── LOW/Normal reticulocyte count → CHECK MCV
|
├── LOW MCV (Microcytic)
│ |
│ ├── Ferritin LOW → Iron deficiency
│ ├── Ferritin NORMAL → Thalassemia workup
│ │ - RDW: HIGH = iron deficiency, LOW = thalassemia
│ └── Anemia of inflammation (diagnosis of exclusion;
│ requires active inflammatory process)
|
├── HIGH MCV (Macrocytic)
│ |
│ ├── Peripheral smear:
│ │ - Hypersegmented neutrophils + macro-ovalocytes
│ │ → B12 and folate levels
│ │ - Macro-ovalocytes + normal B12/folate
│ │ → Bone marrow (myelodysplasia)
│ └── Round macrocytes → History (alcohol, liver disease)
│
└── NORMAL MCV (Normocytic)
|
├── Renal function (BUN/Cr) → Renal anemia
├── EPO levels
├── Other systemic disease evaluation
└── Bone marrow exam if no cause found
Create a quick reference PDF summarizing the anemia workup algorithm
mkdir -p /home/daytona/workspace/anemia-pdf && ls /home/daytona/workspace/
~/anemia-pdf/generate_anemia_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.pdfgen import canvas
import os
OUTPUT = "/home/daytona/workspace/anemia-pdf/Anemia_Workup_Quick_Reference.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
C_DARK_BLUE = colors.HexColor("#1B3A6B")
C_MID_BLUE = colors.HexColor("#2563A8")
C_LIGHT_BLUE = colors.HexColor("#DBEAFE")
C_ACCENT_RED = colors.HexColor("#B91C1C")
C_ACCENT_TEAL = colors.HexColor("#0F766E")
C_ACCENT_AMB = colors.HexColor("#92400E")
C_GREEN = colors.HexColor("#166534")
C_GREEN_LIGHT = colors.HexColor("#DCFCE7")
C_ORANGE_LIGHT= colors.HexColor("#FFF7ED")
C_ORANGE = colors.HexColor("#C2410C")
C_GRAY_LIGHT = colors.HexColor("#F1F5F9")
C_GRAY_MED = colors.HexColor("#CBD5E1")
C_WHITE = colors.white
C_BLACK = colors.HexColor("#1E293B")
# ── Page setup ────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
rightMargin=15*mm, leftMargin=15*mm,
topMargin=18*mm, bottomMargin=18*mm,
title="Anemia Workup Quick Reference",
author="Harrison's 22E – Orris Medical Reference"
)
W, H = A4
content_w = W - 30*mm
styles = getSampleStyleSheet()
# ── Custom paragraph styles ───────────────────────────────────────────────────
def S(name, **kw):
base = kw.pop("parent", "Normal")
s = ParagraphStyle(name, parent=styles[base], **kw)
return s
sTitle = S("sTitle", fontSize=20, textColor=C_WHITE, leading=26,
alignment=TA_CENTER, fontName="Helvetica-Bold")
sSubtitle = S("sSubtitle", fontSize=9, textColor=C_LIGHT_BLUE, leading=13,
alignment=TA_CENTER, fontName="Helvetica")
sSecHdr = S("sSecHdr", fontSize=11, textColor=C_WHITE, leading=15,
fontName="Helvetica-Bold", leftIndent=4)
sBody = S("sBody", fontSize=8.5,textColor=C_BLACK, leading=12,
fontName="Helvetica")
sBold = S("sBold", fontSize=8.5,textColor=C_BLACK, leading=12,
fontName="Helvetica-Bold")
sBullet = S("sBullet", fontSize=8, textColor=C_BLACK, leading=11,
fontName="Helvetica", leftIndent=8, bulletIndent=0)
sLabel = S("sLabel", fontSize=8, textColor=C_WHITE, leading=11,
fontName="Helvetica-Bold", alignment=TA_CENTER)
sFooter = S("sFooter", fontSize=7, textColor=colors.HexColor("#64748B"),
leading=9, alignment=TA_CENTER, fontName="Helvetica-Oblique")
sCellHdr = S("sCellHdr", fontSize=8, textColor=C_WHITE, leading=11,
fontName="Helvetica-Bold", alignment=TA_CENTER)
sCellBody = S("sCellBody", fontSize=7.5,textColor=C_BLACK, leading=10,
fontName="Helvetica")
sCellBold = S("sCellBold", fontSize=7.5,textColor=C_BLACK, leading=10,
fontName="Helvetica-Bold")
# ── Helper: coloured section header ──────────────────────────────────────────
def sec_header(text, bg=C_DARK_BLUE):
tbl = Table([[Paragraph(text, sSecHdr)]], colWidths=[content_w])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("ROWPADDING", (0,0), (-1,-1), 5),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("ROUNDEDCORNERS", [4]),
]))
return tbl
# ── Helper: two-column layout ─────────────────────────────────────────────────
def two_col(left_content, right_content, ratio=(1,1)):
total = sum(ratio)
cw = [content_w * r / total for r in ratio]
tbl = Table([[left_content, right_content]], colWidths=cw)
tbl.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
return tbl
# ── Helper: simple info card ──────────────────────────────────────────────────
def card(rows, col_widths, hdr_bg=C_MID_BLUE, alt_bg=C_GRAY_LIGHT):
tbl_data = []
for i, row in enumerate(rows):
tbl_data.append([Paragraph(str(c), sCellHdr if i == 0 else sCellBody) for c in row])
tbl = Table(tbl_data, colWidths=col_widths)
ts = [
("BACKGROUND", (0,0), (-1,0), hdr_bg),
("ROWPADDING", (0,0), (-1,-1), 4),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
for i in range(1, len(rows)):
if i % 2 == 0:
ts.append(("BACKGROUND", (0,i), (-1,i), alt_bg))
tbl.setStyle(TableStyle(ts))
return tbl
# ── Arrow flowable (for algorithm) ───────────────────────────────────────────
class ArrowDown(Flowable):
def __init__(self, w=20, h=12):
Flowable.__init__(self)
self.width = w
self.height = h
def draw(self):
c = self.canv
c.setFillColor(C_MID_BLUE)
c.setStrokeColor(C_MID_BLUE)
mid = self.width / 2
c.beginPath()
c.moveTo(mid-5, self.height)
c.lineTo(mid+5, self.height)
c.lineTo(mid+5, 4)
c.lineTo(mid+10, 4)
c.lineTo(mid, 0)
c.lineTo(mid-10, 4)
c.lineTo(mid-5, 4)
c.lineTo(mid-5, self.height)
c.closePath()
c.drawPath(fill=1, stroke=0)
# ── Algorithm box helper ──────────────────────────────────────────────────────
def algo_box(text, bg, fg=C_WHITE, w=None, font="Helvetica-Bold", fsize=8.5):
w = w or content_w
sty = ParagraphStyle("ab", fontSize=fsize, textColor=fg, leading=12,
fontName=font, alignment=TA_CENTER)
tbl = Table([[Paragraph(text, sty)]], colWidths=[w])
tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("ROWPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("BOX", (0,0), (-1,-1), 0.8, bg),
("ROUNDEDCORNERS",[4]),
]))
return tbl
# ─────────────────────────────────────────────────────────────────────────────
# PAGE HEADER CANVAS
# ─────────────────────────────────────────────────────────────────────────────
def header_footer(canv, doc):
canv.saveState()
# top banner
canv.setFillColor(C_DARK_BLUE)
canv.rect(0, H - 28*mm, W, 28*mm, fill=1, stroke=0)
# title
canv.setFillColor(C_WHITE)
canv.setFont("Helvetica-Bold", 16)
canv.drawCentredString(W/2, H - 15*mm, "ANEMIA WORKUP – QUICK REFERENCE")
canv.setFont("Helvetica", 8)
canv.setFillColor(colors.HexColor("#93C5FD"))
canv.drawCentredString(W/2, H - 22*mm,
"Based on Harrison's Principles of Internal Medicine, 22nd Edition (2025)")
# footer
canv.setFillColor(colors.HexColor("#64748B"))
canv.setFont("Helvetica-Oblique", 6.5)
canv.drawCentredString(W/2, 10*mm,
"For educational use only | Always apply clinical judgment | Orris Medical Reference")
canv.drawRightString(W - 15*mm, 10*mm, f"Page {doc.page}")
canv.restoreState()
# ─────────────────────────────────────────────────────────────────────────────
# BUILD STORY
# ─────────────────────────────────────────────────────────────────────────────
story = []
SP = lambda n=4: Spacer(1, n*mm)
story.append(SP(2)) # space below banner
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1 – DEFINITION & INITIAL LABS
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("1. DEFINITION & INITIAL LABORATORY TESTS"))
story.append(SP(2))
def_data = [
[Paragraph("<b>Definition</b>", sCellHdr),
Paragraph("Hemoglobin/hematocrit below normal for age/sex. Most often detected on routine CBC.", sCellBody)],
[Paragraph("<b>First-line tests</b>", sCellHdr),
Paragraph("CBC with red cell indices (MCV, MCH, MCHC, RDW) + Reticulocyte count + Peripheral blood smear", sCellBody)],
]
def_tbl = Table(def_data, colWidths=[32*mm, content_w - 32*mm])
def_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), C_MID_BLUE),
("BACKGROUND", (1,0), (1,-1), C_GRAY_LIGHT),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(def_tbl)
story.append(SP(3))
# Red Cell Indices table
indices_data = [
["Index", "Formula", "Normal", "Key Use"],
["MCV", "Hct ÷ RBC × 10", "85–95 fL", "Size classification (micro/normo/macro)"],
["MCH", "Hgb ÷ RBC", "28–33 pg", "Hgb per cell"],
["MCHC", "Hgb ÷ Hct × 100","33.8–34.2 g/dL","Low only in severe iron def. / HS"],
["RDW", "SD of MCV/MCV × 100","11–14%","High = anisocytosis (iron def.);\nLow = thalassemia (uniform cells)"],
]
cw_idx = [22*mm, 42*mm, 38*mm, content_w - 102*mm]
story.append(card(indices_data, cw_idx))
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2 – CLASSIFICATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("2. CLASSIFICATION (Two Parallel Frameworks)", bg=C_ACCENT_TEAL))
story.append(SP(2))
# --- By MCV ---
mcv_title_tbl = Table(
[[Paragraph("BY RED CELL SIZE (MCV)", sLabel)]],
colWidths=[(content_w/2) - 3*mm]
)
mcv_title_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
mcv_data = [
[Paragraph("<b>Microcytic</b>\n<font color='#64748B'>(MCV <85 fL)</font>", sCellBold),
Paragraph("Iron deficiency\nThalassemia (alpha/beta)\nAnemia of inflammation (often normocytic)\nSideroblastic anemia", sCellBody)],
[Paragraph("<b>Normocytic</b>\n<font color='#64748B'>(MCV 85–95 fL)</font>", sCellBold),
Paragraph("Aplastic anemia | Renal disease\nEndocrinopathy | Myeloma\nPure red cell aplasia | Early iron/B12 def.", sCellBody)],
[Paragraph("<b>Macrocytic</b>\n<font color='#64748B'>(MCV >95 fL)</font>", sCellBold),
Paragraph("<b>Oval macrocytes:</b> B12/folate def., drugs, MDS\n<b>Round macrocytes:</b> Alcohol, liver disease,\nhypothyroidism, reticulocytosis", sCellBody)],
]
half_w = (content_w / 2) - 3*mm
mcv_tbl = Table(mcv_data, colWidths=[28*mm, half_w - 28*mm])
mcv_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), colors.HexColor("#EFF6FF")),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
mcv_block = Table(
[[mcv_title_tbl], [mcv_tbl]],
colWidths=[half_w]
)
mcv_block.setStyle(TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
# --- By Mechanism ---
mech_title_tbl = Table(
[[Paragraph("BY MECHANISM (Reticulocyte Count)", sLabel)]],
colWidths=[(content_w/2) - 3*mm]
)
mech_title_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_ACCENT_TEAL),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
mech_data = [
[Paragraph("<b>HIGH Retic.</b>\n<font color='#64748B'>(Hyperproduction)</font>", sCellBold),
Paragraph("Acute bleeding (rule out first!)\n<b>Hemolysis:</b>\n Acquired: Autoimmune, microangiopathic\n Congenital: Hgbopathies, G6PD, HS", sCellBody)],
[Paragraph("<b>LOW Retic.</b>\n<font color='#64748B'>(Underproduction\n~75% of all anemia)</font>", sCellBold),
Paragraph("Nutritional: Iron, B12, folate, copper\nNo precursors: Aplastic anemia, PRCA\nStem cell defect: MDS, leukemia\nLow EPO: CKD, inflammation, aging\nMarrow replacement: Cancer, granuloma", sCellBody)],
]
mech_tbl = Table(mech_data, colWidths=[30*mm, half_w - 30*mm])
mech_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,-1), colors.HexColor("#F0FDF4")),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]))
mech_block = Table(
[[mech_title_tbl], [mech_tbl]],
colWidths=[half_w]
)
mech_block.setStyle(TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
class_row = Table(
[[mcv_block, Spacer(6*mm, 1), mech_block]],
colWidths=[half_w, 6*mm, half_w]
)
class_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(class_row)
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3 – DIAGNOSTIC ALGORITHM
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("3. STEPWISE DIAGNOSTIC ALGORITHM (Harrison's Fig. 66-17)", bg=C_ACCENT_RED))
story.append(SP(3))
# Step 1
story.append(algo_box("STEP 1: Obtain CBC + Reticulocyte Count + Peripheral Smear",
bg=C_DARK_BLUE, w=content_w))
story.append(SP(2))
story.append(ArrowDown(w=content_w))
story.append(SP(1))
# Branch header
branch_hdr = Table(
[[Paragraph("RETICULOCYTE COUNT HIGH?", sLabel),
Paragraph("RETICULOCYTE COUNT LOW / NORMAL?", sLabel)]],
colWidths=[content_w/2 - 3*mm, content_w/2 - 3*mm],
spaceBefore=0
)
branch_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), C_ACCENT_RED),
("BACKGROUND", (1,0), (1,0), C_MID_BLUE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("COLPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
story.append(branch_hdr)
# Branch content
left_branch_data = [
[Paragraph("<b>1. Rule out acute bleeding first</b>", sCellBold)],
[Paragraph("If bleeding excluded → Hemolysis workup:", sCellBody)],
[Paragraph("• LDH <font color='#B91C1C'>(elevated)</font>", sCellBody)],
[Paragraph("• Haptoglobin <font color='#B91C1C'>(low)</font>", sCellBody)],
[Paragraph("• Direct Antiglobulin Test (DAT/Coombs)", sCellBody)],
[Paragraph("• Peripheral smear for schistocytes, spherocytes, sickle cells", sCellBody)],
[Paragraph("• Urine for free Hgb/hemosiderinuria", sCellBody)],
]
left_branch = Table(left_branch_data, colWidths=[content_w/2 - 3*mm])
left_branch.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FEF2F2")),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 6),
("GRID", (0,0), (-1,-1), 0.3, colors.HexColor("#FECACA")),
]))
right_branch_data = [
[Paragraph("<b>Proceed to MCV-guided workup:</b>", sCellBold)],
[Paragraph("<font color='#1B3A6B'><b>MCV LOW</b></font> → Check Ferritin:", sCellBody)],
[Paragraph(" • Ferritin low → Iron deficiency anemia", sCellBody)],
[Paragraph(" • Ferritin normal → Thalassemia screen (Hgb electrophoresis, RDW)", sCellBody)],
[Paragraph(" • Exclusion diagnosis → Anemia of inflammation", sCellBody)],
[Paragraph("<font color='#0F766E'><b>MCV HIGH</b></font> → Peripheral smear:", sCellBody)],
[Paragraph(" • Hyperseg. PMNs + macro-ovalocytes → B12 & folate levels", sCellBody)],
[Paragraph(" • Oval macrocytes + normal B12/folate → Bone marrow (MDS)", sCellBody)],
[Paragraph(" • Round macrocytes → History: alcohol, liver, hypothyroid", sCellBody)],
[Paragraph("<font color='#92400E'><b>MCV NORMAL</b></font> → Assess:", sCellBody)],
[Paragraph(" • Renal function + EPO level", sCellBody)],
[Paragraph(" • Systemic disease screen", sCellBody)],
[Paragraph(" • Bone marrow if no cause found", sCellBody)],
]
right_branch = Table(right_branch_data, colWidths=[content_w/2 - 3*mm])
right_branch.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LIGHT_BLUE),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 6),
("GRID", (0,0), (-1,-1), 0.3, C_GRAY_MED),
]))
branch_content = Table(
[[left_branch, Spacer(6*mm, 1), right_branch]],
colWidths=[content_w/2 - 3*mm, 6*mm, content_w/2 - 3*mm]
)
branch_content.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
]))
story.append(branch_content)
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4 – KEY DIFFERENTIALS
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("4. KEY DIFFERENTIALS & CONFIRMATORY TESTS", bg=C_ACCENT_AMB))
story.append(SP(2))
diff_data = [
["Diagnosis", "Key Lab Findings", "Confirmatory Test"],
["Iron Deficiency",
"Low ferritin, low Fe, high TIBC, high RDW, microcytic hypochromic",
"Ferritin <30 ng/mL (or <100 if inflammation);\nresponse to iron trial"],
["Thalassemia Trait",
"Low MCV, LOW RDW (uniform cells), normal/high RBC count,\nMentzer index <13",
"Hgb electrophoresis (↑HbA2 in beta-thal);\nDNA testing"],
["Anemia of Inflammation",
"Low-normal MCV, low serum iron, low TIBC,\nnormal/high ferritin",
"Diagnosis of exclusion; underlying inflammatory disease"],
["B12 / Folate Deficiency",
"High MCV, macro-ovalocytes, hypersegmented PMNs,\nhigh MMA (B12 def.)",
"Serum B12 <200 pg/mL; RBC folate;\nserum MMA & homocysteine"],
["Autoimmune Hemolysis",
"High retic., spherocytes, high LDH, low haptoglobin,\n+ DAT (Coombs)",
"Direct/indirect Coombs; flow cytometry"],
["Aplastic Anemia",
"Pancytopenia, low retic., normal/macrocytic MCV,\nhypocellular smear",
"Bone marrow biopsy (hypocellular)"],
["CKD Anemia",
"Normocytic, low retic., inappropriately low EPO",
"eGFR + EPO level"],
["MDS",
"Macro-ovalocytes, dysplastic cells, cytopenias,\nnormal B12/folate",
"Bone marrow biopsy + cytogenetics + molecular panel"],
]
diff_cw = [35*mm, 68*mm, content_w - 103*mm]
diff_tbl = Table(
[[Paragraph(c if i == 0 else str(c), sCellHdr if i == 0 else (sCellBold if j==0 else sCellBody))
for j, c in enumerate(row)]
for i, row in enumerate(diff_data)],
colWidths=diff_cw
)
ts_diff = [
("BACKGROUND", (0,0), (-1,0), C_ACCENT_AMB),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
for i in range(2, len(diff_data), 2):
ts_diff.append(("BACKGROUND", (0,i), (-1,i), C_GRAY_LIGHT))
diff_tbl.setStyle(TableStyle(ts_diff))
story.append(diff_tbl)
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5 – COMPENSATORY MECHANISMS & SMEAR INDICATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("5. COMPENSATION & PERIPHERAL SMEAR INDICATIONS", bg=C_GREEN))
story.append(SP(2))
comp_data = [
["Mechanism", "Onset", "Effect"],
["Increased cardiac output", "Minutes", "Maintains O₂ delivery; limited by cardiac reserve"],
["Increased 2,3-DPG", "Hours–days", "Shifts O₂-Hgb curve right → more O₂ released to tissues"],
["Expanded plasma volume", "Weeks", "Preserves preload; risk of heart failure if excessive"],
]
comp_cw = [52*mm, 28*mm, content_w - 80*mm]
comp_tbl = card(comp_data, comp_cw, hdr_bg=C_GREEN, alt_bg=C_GREEN_LIGHT)
smear_items = [
"Hypochromic microcytic anemia unresponsive to iron",
"Hypersegmented neutrophils on differential",
"Abnormal RBC morphology flagged by counter",
"Circulating nucleated RBCs",
"Teardrop cells / leukoerythroblastic picture (marrow infiltration)",
"Staging of lymphoma / haematological malignancy",
"Unexplained severe or refractory anemia",
]
smear_data = [[Paragraph("PERIPHERAL SMEAR – When to Order", sLabel)]]
smear_tbl_hdr = Table(smear_data, colWidths=[(content_w/2) - 3*mm])
smear_tbl_hdr.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_GREEN),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
]))
smear_rows = [[Paragraph(f"{'✓'} {item}", sCellBody)] for item in smear_items]
smear_body = Table(smear_rows, colWidths=[(content_w/2) - 3*mm])
smear_body.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_GREEN_LIGHT),
("GRID", (0,0), (-1,-1), 0.3, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING",(0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 6),
]))
smear_block = Table([[smear_tbl_hdr], [smear_body]], colWidths=[(content_w/2) - 3*mm])
smear_block.setStyle(TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
comp_half = (content_w/2) - 3*mm
comp_block = Table([[comp_tbl]], colWidths=[comp_half])
comp_block.setStyle(TableStyle([
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
sec5_row = Table(
[[comp_block, Spacer(6*mm, 1), smear_block]],
colWidths=[comp_half, 6*mm, comp_half]
)
sec5_row.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING",(0,0), (-1,-1), 0),
]))
story.append(sec5_row)
story.append(SP(4))
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6 – TREATMENT PRINCIPLES
# ══════════════════════════════════════════════════════════════════════════════
story.append(sec_header("6. TREATMENT PRINCIPLES", bg=C_MID_BLUE))
story.append(SP(2))
tx_data = [
["Principle", "Detail"],
["Treat the cause",
"Definitive treatment always requires identifying and correcting the underlying aetiology."],
["Transfusion – clear indications",
"Haemodynamic instability (hypotension) or evidence of cardiac compromise (angina, ACS). NOT based on Hgb number alone."],
["Iron stores first",
"Evaluate iron status BEFORE and DURING treatment of any anemia – even non-iron deficiency cases."],
["Multifactorial causes",
"Anemia is often mixed (e.g., RA on NSAIDs may have ACI + GI-blood-loss IDA simultaneously)."],
["Targeted therapies",
"• Recombinant EPO: CKD on dialysis, chemotherapy-related anemia\n"
"• TGF-β inhibitors (luspatercept): MDS-related anemia\n"
"• Complement inhibitors (eculizumab): PNH\n"
"• Gene therapy: emerging for haemoglobinopathies"],
]
tx_cw = [40*mm, content_w - 40*mm]
tx_tbl = Table(
[[Paragraph(row[0], sCellHdr if i==0 else sCellBold),
Paragraph(row[1], sCellHdr if i==0 else sCellBody)] for i, row in enumerate(tx_data)],
colWidths=tx_cw
)
ts_tx = [
("BACKGROUND", (0,0), (-1,0), C_MID_BLUE),
("GRID", (0,0), (-1,-1), 0.4, C_GRAY_MED),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("VALIGN", (0,0), (-1,-1), "TOP"),
]
for i in range(2, len(tx_data), 2):
ts_tx.append(("BACKGROUND", (0,i), (-1,i), C_GRAY_LIGHT))
tx_tbl.setStyle(TableStyle(ts_tx))
story.append(tx_tbl)
story.append(SP(3))
# ── Final note ─────────────────────────────────────────────────────────────
story.append(HRFlowable(width=content_w, thickness=0.5, color=C_GRAY_MED))
story.append(SP(2))
story.append(Paragraph(
"<b>Reference:</b> Harrison's Principles of Internal Medicine, 22nd Edition (McGraw-Hill, 2025), "
"Chapter 66 – <i>Anemia and Polycythemia</i>. "
"See Chaps. 102–107 for disease-specific management.",
sFooter
))
# ── BUILD PDF ─────────────────────────────────────────────────────────────────
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
print(f"PDF created: {OUTPUT}")
cd /home/daytona/workspace/anemia-pdf && python generate_anemia_pdf.py 2>&1
~/anemia-pdf/generate_anemia_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/anemia-pdf/generate_anemia_pdf.py'
cd /home/daytona/workspace/anemia-pdf && python generate_anemia_pdf.py 2>&1
ls -lh /home/daytona/workspace/anemia-pdf/Anemia_Workup_Quick_Reference.pdf
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/…
Anemia Workup Quick Reference
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Definition & Initial Labs | CBC indices table (MCV, MCH, MCHC, RDW) with formulas and normal ranges |
| 2. Classification | Side-by-side: MCV-based (micro/normo/macro) vs. Mechanism-based (high/low reticulocyte) |
| 3. Stepwise Algorithm | Visual flow - CBC → reticulocyte split → MCV-guided branches (iron def., thalassemia, B12/folate, MDS, CKD, etc.) |
| 4. Key Differentials | 8-row table: each diagnosis with key lab findings + confirmatory test |
| 5. Compensation & Smear | 3 compensatory mechanisms with timing; peripheral smear indications checklist |
| 6. Treatment Principles | Transfusion triggers, iron status rule, targeted biologics (EPO, luspatercept, eculizumab) |