What is multiple myeloma
"multiple myeloma"[MeSH Terms] AND treatment
| M Protein Type | Frequency |
|---|---|
| IgG | 52-60% |
| IgA | 20-25% |
| Light chain only | 16% |
| IgD | 2% |
| Biclonal | 2% |
| Nonsecretory | 1-3% |
| Letter | Manifestation | Mechanism |
|---|---|---|
| C | Hypercalcemia | Osteoclast activation causing bone resorption |
| R | Renal insufficiency | Bence Jones protein casts, light chain deposition, hypercalcemia |
| A | Anemia | Bone marrow infiltration; present in ~75% at diagnosis |
| B | Bone lesions/pain | Lytic lesions, pathologic fractures |
| Regimen | Abbreviation |
|---|---|
| Bortezomib + cyclophosphamide + dexamethasone | VCd |
| Bortezomib + lenalidomide + dexamethasone | VRd |
| Carfilzomib + lenalidomide + dexamethasone | KRd |
| Daratumumab + bortezomib + lenalidomide + dexamethasone | DVRd |
| Daratumumab + lenalidomide + dexamethasone | DRd |
| Drug Class | Examples | Mechanism |
|---|---|---|
| Proteasome inhibitors | Bortezomib, carfilzomib, ixazomib | Block protein degradation - myeloma cells accumulate misfolded immunoglobulins and undergo apoptosis |
| Immunomodulatory drugs (IMiDs) | Thalidomide, lenalidomide, pomalidomide | Anti-angiogenic, immune stimulation, direct anti-tumor effects |
| Anti-CD38 monoclonal antibodies | Daratumumab, isatuximab | Target CD38 expressed on plasma cells |
| Anti-SLAMF7 | Elotuzumab | Targets SLAMF7 on myeloma cells |
| Corticosteroids | Dexamethasone | Anti-inflammatory, direct cytotoxicity to plasma cells |
MGUS → Smoldering Myeloma → Symptomatic Multiple Myeloma
(1%/year risk) (CRAB criteria)
↓
Bone marrow: ≥10% clonal plasma cells
M protein: IgG (52%), IgA (21%), light chain (16%)
Lytic bone lesions → fractures, hypercalcemia
Bence Jones proteins → renal failure
Immune suppression → bacterial infections
↓
VRd / DVRd induction → ASCT
Proteasome inhibitors + IMiDs + anti-CD38
Create a summary document on myeloma diagnosis and staging I can share with colleagues
mkdir -p /home/daytona/workspace/myeloma-summary && ls /home/daytona/workspace/myeloma-summary/
~/myeloma-summary/build_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import PageBreak
from reportlab.lib.colors import HexColor
OUTPUT = "/home/daytona/workspace/myeloma-summary/Multiple_Myeloma_Diagnosis_Staging.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
NAVY = HexColor("#1a2e4a")
TEAL = HexColor("#1a7a8a")
LTBLUE = HexColor("#e8f4f8")
LTGREY = HexColor("#f5f5f5")
MIDGREY = HexColor("#cccccc")
WHITE = colors.white
BLACK = colors.black
RED = HexColor("#c0392b")
AMBER = HexColor("#e67e22")
GREEN = HexColor("#27ae60")
def make_doc():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm,
title="Multiple Myeloma – Diagnosis & Staging",
author="Orris Medical Summary",
subject="Multiple Myeloma"
)
styles = getSampleStyleSheet()
# Custom paragraph styles
def S(name, parent='Normal', **kw):
return ParagraphStyle(name, parent=styles[parent], **kw)
title_style = S('DocTitle', fontSize=22, textColor=WHITE, alignment=TA_CENTER,
fontName='Helvetica-Bold', spaceAfter=4, leading=28)
subtitle_style = S('DocSub', fontSize=11, textColor=LTBLUE, alignment=TA_CENTER,
fontName='Helvetica', spaceAfter=2)
tagline_style = S('TagLine', fontSize=9, textColor=LTBLUE, alignment=TA_CENTER,
fontName='Helvetica-Oblique', spaceAfter=0)
h1 = S('H1', fontSize=13, textColor=WHITE, fontName='Helvetica-Bold',
spaceAfter=0, spaceBefore=0, leading=18)
h2 = S('H2', fontSize=11, textColor=NAVY, fontName='Helvetica-Bold',
spaceAfter=3, spaceBefore=8, leading=15)
body = S('Body', fontSize=9.5, textColor=BLACK, fontName='Helvetica',
spaceAfter=5, leading=14, alignment=TA_JUSTIFY)
bullet = S('Bullet', fontSize=9.5, textColor=BLACK, fontName='Helvetica',
spaceAfter=3, leading=13, leftIndent=14, firstLineIndent=-10)
small = S('Small', fontSize=8, textColor=HexColor("#555555"), fontName='Helvetica-Oblique',
spaceAfter=3, leading=11)
table_hdr = S('TblHdr', fontSize=9, textColor=WHITE, fontName='Helvetica-Bold',
alignment=TA_CENTER, leading=12)
table_cell = S('TblCell', fontSize=8.5, textColor=BLACK, fontName='Helvetica',
alignment=TA_LEFT, leading=12)
table_cell_c = S('TblCellC', fontSize=8.5, textColor=BLACK, fontName='Helvetica',
alignment=TA_CENTER, leading=12)
emph = S('Emph', fontSize=9.5, textColor=NAVY, fontName='Helvetica-Bold',
spaceAfter=5, leading=14)
note = S('Note', fontSize=8.5, textColor=HexColor("#444444"), fontName='Helvetica-Oblique',
spaceAfter=4, leading=12, leftIndent=8)
story = []
# ── COVER BANNER ─────────────────────────────────────────────────────────
banner_data = [[Paragraph("MULTIPLE MYELOMA", title_style)],
[Paragraph("Diagnosis & Staging – Clinical Reference Summary", subtitle_style)],
[Paragraph("For Medical Students & Trainees | Based on Goldman-Cecil Medicine & Robbins Pathology", tagline_style)]]
banner = Table(banner_data, colWidths=[17*cm])
banner.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), NAVY),
('TOPPADDING', (0,0), (-1,0), 16),
('BOTTOMPADDING', (0,2), (-1,-1), 14),
('LEFTPADDING', (0,0), (-1,-1), 12),
('RIGHTPADDING', (0,0), (-1,-1), 12),
]))
story.append(banner)
story.append(Spacer(1, 0.5*cm))
# ── Helper: section header ────────────────────────────────────────────────
def section_header(text):
tbl = Table([[Paragraph(text, h1)]], colWidths=[17*cm])
tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), TEAL),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
]))
return tbl
def bullet_item(text):
return Paragraph(f"• {text}", bullet)
def sub_bullet(text):
return Paragraph(f" – {text}", S('SubBullet', parent='Normal', fontSize=9,
fontName='Helvetica', spaceAfter=2, leading=12,
leftIndent=24, firstLineIndent=-10, textColor=BLACK))
# ═══════════════════════════════════════════════════════════════════════════
# 1. OVERVIEW
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("1. OVERVIEW"))
story.append(Spacer(1, 0.3*cm))
overview_data = [
[Paragraph("Definition", emph),
Paragraph("Malignant neoplasm of clonal plasma cells infiltrating the bone marrow, secreting a monoclonal immunoglobulin (M protein), and causing skeletal destruction.", body)],
[Paragraph("Incidence", emph),
Paragraph("~30,000 new cases/year in the US. Accounts for ~1% of all malignancies and >10% of haematologic malignancies.", body)],
[Paragraph("Demographics", emph),
Paragraph("Median age at diagnosis 65–70 years; only 2% <40 years. More common in males. Incidence in Black Americans is ~2× that of White Americans.", body)],
[Paragraph("Precursor", emph),
Paragraph("Nearly all cases evolve from MGUS (monoclonal gammopathy of undetermined significance), which progresses at ~1%/year.", body)],
]
overview_tbl = Table(overview_data, colWidths=[3.5*cm, 13.5*cm])
overview_tbl.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('ROWBACKGROUNDS',(0,0),(-1,-1), [LTGREY, WHITE]),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING',(0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('GRID', (0,0), (-1,-1), 0.25, MIDGREY),
]))
story.append(overview_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 2. M PROTEIN TYPES
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("2. M PROTEIN DISTRIBUTION"))
story.append(Spacer(1, 0.3*cm))
mp_data = [
[Paragraph("M Protein Type", table_hdr), Paragraph("Frequency", table_hdr),
Paragraph("Light Chain", table_hdr), Paragraph("Frequency", table_hdr)],
["IgG", "52%", "Kappa (κ)", "65%"],
["IgA", "21%", "Lambda (λ)", "35%"],
["Light chain only", "16%", "", ""],
["IgD", "2%", "", ""],
["Biclonal", "2%", "", ""],
["Nonsecretory", "1–3%", "", ""],
]
for row in mp_data[1:]:
for i in range(len(row)):
if isinstance(row[i], str):
row[i] = Paragraph(row[i], table_cell_c)
mp_tbl = Table(mp_data, colWidths=[4.5*cm, 3.5*cm, 5*cm, 3.5*cm])
mp_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LTGREY, WHITE]),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0), (-1,-1), 4),
('SPAN', (2,3), (3,5)),
]))
story.append(mp_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 3. CLINICAL FEATURES – CRAB
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header('3. CLINICAL FEATURES – "CRAB" CRITERIA'))
story.append(Spacer(1, 0.3*cm))
crab_data = [
[Paragraph("Letter", table_hdr), Paragraph("Feature", table_hdr),
Paragraph("Mechanism", table_hdr), Paragraph("Key Points", table_hdr)],
[Paragraph("C", ParagraphStyle('CRed', parent=styles['Normal'], fontSize=14,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER)),
Paragraph("Hyper-\ncalcaemia", table_cell_c),
Paragraph("Osteoclast activation → bone resorption", table_cell),
Paragraph("Confusion, weakness, lethargy, polyuria, constipation", table_cell)],
[Paragraph("R", ParagraphStyle('CAmb', parent=styles['Normal'], fontSize=14,
fontName='Helvetica-Bold', textColor=AMBER, alignment=TA_CENTER)),
Paragraph("Renal\ninsufficiency", table_cell_c),
Paragraph("Bence Jones casts, light chain deposition, hypercalcaemia", table_cell),
Paragraph("Renal failure in up to 50% of patients; leading cause of death", table_cell)],
[Paragraph("A", ParagraphStyle('CGrn', parent=styles['Normal'], fontSize=14,
fontName='Helvetica-Bold', textColor=TEAL, alignment=TA_CENTER)),
Paragraph("Anaemia", table_cell_c),
Paragraph("Bone marrow infiltration by plasma cells", table_cell),
Paragraph("Normocytic normochromic; present in ~75% at diagnosis", table_cell)],
[Paragraph("B", ParagraphStyle('CNav', parent=styles['Normal'], fontSize=14,
fontName='Helvetica-Bold', textColor=NAVY, alignment=TA_CENTER)),
Paragraph("Bone\nlesions", table_cell_c),
Paragraph("RANKL/OPG imbalance → osteoclast activation + osteoblast suppression", table_cell),
Paragraph("Punched-out lytic lesions; pathologic fractures; bone pain", table_cell)],
]
crab_tbl = Table(crab_data, colWidths=[1.5*cm, 3*cm, 6.5*cm, 6*cm])
crab_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS',(0,1),(-1,-1), [LTGREY, WHITE, LTGREY, WHITE]),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('ALIGN', (0,0), (1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING',(0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
story.append(crab_tbl)
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Additional features: recurrent bacterial infections (depressed normal Ig production), "
"radiculopathy/spinal cord compression (nerve compression by vertebral lesions), "
"peripheral neuropathy (usually from AL amyloidosis).",
note))
story.append(Spacer(1, 0.3*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 4. DIAGNOSTIC CRITERIA
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("4. DIAGNOSTIC CRITERIA (IMWG 2014)"))
story.append(Spacer(1, 0.3*cm))
story.append(Paragraph(
"Multiple myeloma is diagnosed when ALL THREE of the following are met:", body))
criteria = [
("1", "≥10% clonal plasma cells on bone marrow biopsy", TEAL),
("2", "Serum and/or urine M protein (except nonsecretory myeloma)", TEAL),
("3", "One or more myeloma-defining events (MDE):", TEAL),
]
mde_items = [
"Hypercalcaemia: serum Ca >0.25 mmol/L above upper limit of normal, or >2.75 mmol/L",
"Renal insufficiency: creatinine clearance <40 mL/min or serum creatinine >177 μmol/L",
"Anaemia: Hb >20 g/L below lower limit of normal, or Hb <100 g/L",
"Bone lesions: one or more lytic lesions on skeletal survey, CT, or PET-CT",
"Clonal BMPC ≥60% (biomarker MDE)",
"Serum involved/uninvolved free light chain (FLC) ratio ≥100 (biomarker MDE)",
"MRI: >1 focal lesion ≥5 mm (biomarker MDE)",
]
criteria_data = []
for num, text, col in criteria:
criteria_data.append([
Paragraph(num, ParagraphStyle('Num', parent=styles['Normal'],
fontSize=12, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER)),
Paragraph(text, body)
])
criteria_tbl = Table(criteria_data, colWidths=[1*cm, 16*cm])
criteria_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), TEAL),
('BACKGROUND', (1,0), (1,-1), LTBLUE),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING',(0,0), (-1,-1), 6),
('LEFTPADDING', (1,0), (1,-1), 10),
]))
story.append(criteria_tbl)
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph("<b>Myeloma-Defining Events (MDE) include:</b>", emph))
for item in mde_items:
story.append(bullet_item(item))
story.append(Spacer(1, 0.2*cm))
# Differentiation box
diff_data = [[
Paragraph("<b>Differentiating from Precursor States</b>",
ParagraphStyle('DiffHdr', parent=styles['Normal'], fontSize=10,
fontName='Helvetica-Bold', textColor=WHITE)),
],[
Paragraph(
"<b>MGUS:</b> M protein <30 g/L, clonal BMPC <10%, <i>no</i> MDE or amyloidosis<br/>"
"<b>Smoldering MM:</b> M protein ≥30 g/L or urine M ≥500 mg/24h or BMPC 10–60%, <i>no</i> MDE<br/>"
"<b>Active MM:</b> BMPC ≥10% (or biopsy-proven plasmacytoma) <i>plus</i> ≥1 MDE",
ParagraphStyle('DiffBody', parent=styles['Normal'], fontSize=9,
fontName='Helvetica', leading=14, textColor=BLACK)
)
]]
diff_tbl = Table(diff_data, colWidths=[17*cm])
diff_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('BACKGROUND', (0,1), (-1,1), LTBLUE),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING',(0,0), (-1,-1), 6),
('LEFTPADDING', (0,0), (-1,-1), 10),
('RIGHTPADDING', (0,0), (-1,-1), 10),
('BOX', (0,0), (-1,-1), 0.5, TEAL),
]))
story.append(diff_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 5. INVESTIGATIONS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("5. INVESTIGATIONS"))
story.append(Spacer(1, 0.3*cm))
inv_data = [
[Paragraph("Test", table_hdr), Paragraph("Finding / Purpose", table_hdr),
Paragraph("Sensitivity", table_hdr)],
[Paragraph("Serum protein electrophoresis (SPEP)", table_cell),
Paragraph("M protein spike (paraprotein band)", table_cell),
Paragraph("80%", table_cell_c)],
[Paragraph("Serum immunofixation", table_cell),
Paragraph("Confirms M protein type (more sensitive)", table_cell),
Paragraph("93%", table_cell_c)],
[Paragraph("SPEP + urine electrophoresis + immunofixation", table_cell),
Paragraph("Combined approach for M protein detection", table_cell),
Paragraph("97%", table_cell_c)],
[Paragraph("Serum free light chain (FLC) assay", table_cell),
Paragraph("Convenient alternative to urine studies; κ/λ ratio", table_cell),
Paragraph("High", table_cell_c)],
[Paragraph("24-hour urine protein electrophoresis", table_cell),
Paragraph("Quantifies Bence Jones protein (free light chains)", table_cell),
Paragraph("—", table_cell_c)],
[Paragraph("Bone marrow biopsy", table_cell),
Paragraph(">10% plasma cells; flow cytometry (CD38+, CD138+, CD45−, CD56+); κ/λ clonality", table_cell),
Paragraph("Definitive", table_cell_c)],
[Paragraph("Full blood count", table_cell),
Paragraph("Normocytic normochromic anaemia; leukopenia; thrombocytopenia", table_cell),
Paragraph("—", table_cell_c)],
[Paragraph("Biochemistry panel", table_cell),
Paragraph("Calcium, creatinine, uric acid, LDH, β2-microglobulin, albumin", table_cell),
Paragraph("—", table_cell_c)],
[Paragraph("Imaging: skeletal survey (X-ray)", table_cell),
Paragraph("Punched-out lytic lesions (vertebrae, ribs, skull, pelvis, femur)", table_cell),
Paragraph("First-line", table_cell_c)],
[Paragraph("Imaging: whole-body low-dose CT", table_cell),
Paragraph("More sensitive than X-ray; preferred for bone assessment", table_cell),
Paragraph("Preferred", table_cell_c)],
[Paragraph("PET-CT / MRI", table_cell),
Paragraph("Extramedullary disease, focal bone marrow lesions, treatment response", table_cell),
Paragraph("Highest", table_cell_c)],
]
inv_tbl = Table(inv_data, colWidths=[5*cm, 9*cm, 3*cm])
inv_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS',(0,1),(-1,-1), [LTGREY, WHITE]),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
story.append(inv_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# PAGE BREAK before staging
# ═══════════════════════════════════════════════════════════════════════════
story.append(PageBreak())
# ═══════════════════════════════════════════════════════════════════════════
# 6. STAGING – R-ISS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("6. STAGING – REVISED INTERNATIONAL STAGING SYSTEM (R-ISS)"))
story.append(Spacer(1, 0.3*cm))
riss_data = [
[Paragraph("Stage", table_hdr), Paragraph("Criteria", table_hdr),
Paragraph("Median OS", table_hdr)],
[Paragraph("I", ParagraphStyle('S1', parent=styles['Normal'], fontSize=13,
fontName='Helvetica-Bold', textColor=GREEN, alignment=TA_CENTER)),
Paragraph(
"• Serum β2-microglobulin <3.5 mg/L<br/>"
"• Serum albumin ≥35 g/L<br/>"
"• Standard-risk cytogenetics<br/>"
"• Normal LDH",
table_cell),
Paragraph("~62 months", table_cell_c)],
[Paragraph("II", ParagraphStyle('S2', parent=styles['Normal'], fontSize=13,
fontName='Helvetica-Bold', textColor=AMBER, alignment=TA_CENTER)),
Paragraph(
"• Not Stage I or III<br/>"
"(intermediate — any combination not meeting Stage I or III)",
table_cell),
Paragraph("~44 months", table_cell_c)],
[Paragraph("III", ParagraphStyle('S3', parent=styles['Normal'], fontSize=13,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER)),
Paragraph(
"• Serum β2-microglobulin ≥5.5 mg/L<br/>"
"• PLUS high-risk cytogenetics: del(17p), t(4;14), or t(14;16)<br/>"
"• OR elevated LDH",
table_cell),
Paragraph("~29 months", table_cell_c)],
]
riss_tbl = Table(riss_data, colWidths=[2*cm, 11.5*cm, 3.5*cm])
riss_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('BACKGROUND', (0,1), (-1,1), HexColor("#e8f8ee")),
('BACKGROUND', (0,2), (-1,2), HexColor("#fff8e8")),
('BACKGROUND', (0,3), (-1,3), HexColor("#fdecea")),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('ALIGN', (2,0), (2,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING', (0,0), (-1,-1), 6),
('LEFTPADDING', (1,0), (1,-1), 10),
]))
story.append(riss_tbl)
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"OS = overall survival with modern treatment. Cytogenetics assessed by FISH.",
note))
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 7. CYTOGENETICS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("7. CYTOGENETIC ABNORMALITIES"))
story.append(Spacer(1, 0.3*cm))
cyto_data = [
[Paragraph("Abnormality", table_hdr), Paragraph("Type", table_hdr),
Paragraph("Frequency", table_hdr), Paragraph("Prognosis", table_hdr)],
[Paragraph("Hyperdiploidy (trisomies)", table_cell), Paragraph("Primary", table_cell_c),
Paragraph("~40%", table_cell_c), Paragraph("Standard risk", table_cell_c)],
[Paragraph("IgH translocations (any)", table_cell), Paragraph("Primary", table_cell_c),
Paragraph("~40%", table_cell_c), Paragraph("Varies by partner", table_cell_c)],
[Paragraph("t(11;14) – CCND1", table_cell), Paragraph("Primary", table_cell_c),
Paragraph("~15%", table_cell_c), Paragraph("Standard risk", table_cell_c)],
[Paragraph("t(4;14) – FGFR3/MMSET", table_cell), Paragraph("Primary", table_cell_c),
Paragraph("~15%", table_cell_c),
Paragraph("HIGH RISK", ParagraphStyle('HR', parent=styles['Normal'], fontSize=8.5,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER))],
[Paragraph("t(14;16) – MAF", table_cell), Paragraph("Primary", table_cell_c),
Paragraph("~5%", table_cell_c),
Paragraph("HIGH RISK", ParagraphStyle('HR2', parent=styles['Normal'], fontSize=8.5,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER))],
[Paragraph("del(17p) – TP53", table_cell), Paragraph("Secondary", table_cell_c),
Paragraph("~10%", table_cell_c),
Paragraph("HIGH RISK", ParagraphStyle('HR3', parent=styles['Normal'], fontSize=8.5,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER))],
[Paragraph("1q gain/amplification", table_cell), Paragraph("Secondary", table_cell_c),
Paragraph("~40%", table_cell_c),
Paragraph("HIGH RISK", ParagraphStyle('HR4', parent=styles['Normal'], fontSize=8.5,
fontName='Helvetica-Bold', textColor=RED, alignment=TA_CENTER))],
[Paragraph("MYC translocation", table_cell), Paragraph("Late/Secondary", table_cell_c),
Paragraph("Variable", table_cell_c), Paragraph("Aggressive disease", table_cell_c)],
]
cyto_tbl = Table(cyto_data, colWidths=[5.5*cm, 3*cm, 3*cm, 5.5*cm])
cyto_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS',(0,1),(-1,-1), [LTGREY, WHITE]),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('ALIGN', (1,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
story.append(cyto_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 8. BONE MARROW FINDINGS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("8. BONE MARROW & HISTOLOGY FINDINGS"))
story.append(Spacer(1, 0.3*cm))
bm_blocks = [
("Plasma cell percentage",
">10% of nucleated cells in 96% of patients. May be focal — repeat biopsy may be needed."),
("Morphology",
"Plasma cells typically eccentric nucleus with 'clock-face' chromatin and basophilic cytoplasm. "
"Myeloma cells often show prominent nucleoli or cytoplasmic inclusions (Russell bodies — "
"eosinophilic immunoglobulin aggregates)."),
("Immunophenotype",
"Cytoplasmic Ig+, CD38+, CD138+, CD45−, CD56+. Only 20% express CD20. "
"Clonality confirmed by abnormal κ/λ ratio (>4:1 kappa or <1:2 lambda)."),
("Skeletal lesions",
"Punched-out lytic defects 1–4 cm. Most commonly: vertebral column, ribs, skull, pelvis, femur, "
"clavicle, scapula. Arise in medullary cavity and erode cortex."),
("Myeloma kidney",
"Bence Jones protein casts in distal tubules and collecting ducts, surrounded by multinucleate "
"giant cells. Adjacent epithelial cells may be necrotic. AL amyloidosis may also affect glomeruli."),
]
for label, text in bm_blocks:
row_data = [[Paragraph(f"<b>{label}</b>",
ParagraphStyle('BML', parent=styles['Normal'], fontSize=9.5,
fontName='Helvetica-Bold', textColor=TEAL, leading=13)),
Paragraph(text, body)]]
rt = Table(row_data, colWidths=[4*cm, 13*cm])
rt.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING',(0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 6),
('LINEBELOW', (0,0), (-1,-1), 0.25, MIDGREY),
]))
story.append(rt)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 9. PROGNOSIS
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("9. PROGNOSIS"))
story.append(Spacer(1, 0.3*cm))
prog_data = [
[Paragraph("Scenario", table_hdr), Paragraph("Median Survival", table_hdr)],
[Paragraph("Active multiple myeloma (modern treatment)", table_cell),
Paragraph("~5 years (significantly better in younger, standard-risk patients)", table_cell)],
[Paragraph("Multiple bony lesions — untreated", table_cell),
Paragraph("6–12 months", table_cell)],
[Paragraph("Smoldering myeloma", table_cell),
Paragraph("May be asymptomatic for many years; ~1%/year progression risk", table_cell)],
[Paragraph("High-risk cytogenetics (del17p, t(4;14), t(14;16))", table_cell),
Paragraph("Significantly shorter — R-ISS III median ~29 months", table_cell)],
]
prog_tbl = Table(prog_data, colWidths=[9*cm, 8*cm])
prog_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('ROWBACKGROUNDS',(0,1),(-1,-1), [LTGREY, WHITE]),
('GRID', (0,0), (-1,-1), 0.5, MIDGREY),
('VALIGN', (0,0), (-1,-1), 'TOP'),
('TOPPADDING', (0,0), (-1,-1), 5),
('BOTTOMPADDING', (0,0), (-1,-1), 5),
('LEFTPADDING', (0,0), (-1,-1), 6),
]))
story.append(prog_tbl)
story.append(Spacer(1, 0.2*cm))
cause_data = [[
Paragraph("Leading causes of death: (1) Bacterial infections — due to suppressed normal antibody production | (2) Renal failure — Bence Jones proteinuria and cast nephropathy",
ParagraphStyle('CauseBox', parent=styles['Normal'], fontSize=9,
fontName='Helvetica-Bold', textColor=NAVY, leading=14))
]]
cause_tbl = Table(cause_data, colWidths=[17*cm])
cause_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), HexColor("#fdecea")),
('BOX', (0,0), (-1,-1), 1, RED),
('TOPPADDING', (0,0), (-1,-1), 8),
('BOTTOMPADDING',(0,0), (-1,-1), 8),
('LEFTPADDING', (0,0), (-1,-1), 10),
]))
story.append(cause_tbl)
story.append(Spacer(1, 0.4*cm))
# ═══════════════════════════════════════════════════════════════════════════
# 10. QUICK REFERENCE
# ═══════════════════════════════════════════════════════════════════════════
story.append(section_header("10. QUICK DIAGNOSTIC APPROACH"))
story.append(Spacer(1, 0.3*cm))
steps = [
("Step 1", "Suspect myeloma",
"Unexplained anaemia, bone pain/lytic lesions, hypercalcaemia, renal impairment, "
"recurrent infections, or elevated total protein in patient >50 years"),
("Step 2", "Initial investigations",
"FBC, U&E, calcium, LDH, albumin; SPEP + serum immunofixation; serum FLC ratio; "
"urine electrophoresis + immunofixation; skeletal survey / whole-body CT"),
("Step 3", "Bone marrow examination",
"Trephine biopsy + aspirate; flow cytometry for immunophenotype; FISH for cytogenetics"),
("Step 4", "Apply diagnostic criteria",
"BMPC ≥10% + M protein + ≥1 MDE = active MM. If criteria not fully met, "
"consider smoldering MM or MGUS"),
("Step 5", "Stage with R-ISS",
"β2-microglobulin, albumin, LDH, FISH cytogenetics → Stage I / II / III"),
("Step 6", "Assess transplant eligibility",
"Age, performance status, comorbidities → determines induction regimen and ASCT suitability"),
]
for step, title, detail in steps:
sd = [[
Paragraph(step, ParagraphStyle('StepNum', parent=styles['Normal'], fontSize=9,
fontName='Helvetica-Bold', textColor=WHITE, alignment=TA_CENTER)),
Paragraph(f"<b>{title}</b><br/>{detail}",
ParagraphStyle('StepBody', parent=styles['Normal'], fontSize=9,
fontName='Helvetica', leading=13, textColor=BLACK))
]]
st = Table(sd, colWidths=[2*cm, 15*cm])
st.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,-1), TEAL),
('BACKGROUND', (1,0), (1,-1), LTBLUE),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
('TOPPADDING', (0,0), (-1,-1), 6),
('BOTTOMPADDING',(0,0), (-1,-1), 6),
('LEFTPADDING', (1,0), (1,-1), 10),
('LINEBELOW', (0,0), (-1,-1), 0.5, WHITE),
]))
story.append(st)
story.append(Spacer(1, 0.5*cm))
# ── Footer note ───────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=0.5, color=MIDGREY))
story.append(Spacer(1, 0.15*cm))
story.append(Paragraph(
"Sources: Goldman-Cecil Medicine International Edition (2022), pp. 1977–1982 | "
"Robbins & Kumar Basic Pathology 10e, pp. 420–423 | "
"IMWG Diagnostic Criteria 2014 (Rajkumar et al., Lancet Oncology) | "
"Revised ISS (Palumbo et al., JCO 2015) | "
"Prepared using Orris Medical Knowledge System – June 2026",
ParagraphStyle('Footer', parent=styles['Normal'], fontSize=7.5,
fontName='Helvetica-Oblique', textColor=HexColor("#777777"),
alignment=TA_CENTER, leading=11)
))
doc.build(story)
print(f"PDF written to {OUTPUT}")
make_doc()
cd /home/daytona/workspace/myeloma-summary && python build_pdf.py 2>&1
ls -lh /home/daytona/workspace/myeloma-summary/
Multiple Myeloma Diagnosis Staging
PDF Document · PDF
| Section | Contents |
|---|---|
| 1. Overview | Definition, incidence, demographics, MGUS precursor |
| 2. M Protein Distribution | IgG/IgA/light chain breakdown with frequencies |
| 3. CRAB Criteria | Colour-coded table with mechanism and key points per feature |
| 4. Diagnostic Criteria | IMWG 2014 criteria with full MDE list; differentiation from MGUS/smoldering MM |
| 5. Investigations | All key tests with sensitivity — SPEP, immunofixation, FLC, bone marrow biopsy, imaging |
| 6. R-ISS Staging | Stages I/II/III with full criteria and median OS figures |
| 7. Cytogenetics | Primary vs secondary abnormalities with risk classification |
| 8. Bone Marrow Findings | Plasma cell morphology, immunophenotype, myeloma kidney |
| 9. Prognosis | Survival by scenario; leading causes of death highlighted in a red alert box |
| 10. Quick Diagnostic Approach | Step-by-step 6-step pathway from suspicion to staging |