Polyarticular pain and it’s causes
polyarticular arthritis differential diagnosis

| Condition | Hallmark Features |
|---|---|
| Rheumatoid Arthritis (RA) | Symmetric, small joint involvement (MCPs, PIPs, wrists, MTPs); anti-CCP antibodies; joint erosions on X-ray; female predominance 2-3:1 |
| Systemic Lupus Erythematosus (SLE) | Positive ANA and anti-dsDNA antibodies; multisystem involvement; arthritis often non-erosive |
| Polymyositis / Dermatomyositis | Proximal muscle weakness; elevated serum creatine kinase; skin rash in DM |
| Adult-Onset Still's Disease | Markedly elevated serum ferritin; quotidian fever; salmon-colored rash |
| Sarcoidosis | Granulomatous disease; primarily affects lungs; polyarticular arthritis as extrapulmonary manifestation |
| Serum Sickness | Drug/foreign protein exposure history (e.g., antithymocyte globulin); immune complex-mediated |
| Pathogen | Presentation |
|---|---|
| Gonococcal arthritis | Migratory polyarthralgias + skin lesions (pustular/vasculitic); sexually active young adults; most common septic arthritis in this age group |
| Lyme disease | Tick exposure; large joint involvement (especially knee); preceded by erythema migrans |
| Septic arthritis | A significant proportion of septic joint cases present with multijoint involvement; always include in differential |
| Infective endocarditis | Polyarthritis as immune-complex manifestation; heart murmur, fever, embolic phenomena |
| Rheumatic fever | Group A streptococcal pharyngitis; migratory inflammatory arthritis; most common in developing countries |
| Virus | Arthritis Pattern |
|---|---|
| Parvovirus B19 | "Slapped-cheek" rash in children; symmetric polyarticular arthritis in adults; self-limited |
| Hepatitis B | Symmetric, migratory; subsides with onset of jaundice |
| Hepatitis C | RA-like mimic, symmetric |
| HIV | Often monoarticular (feet/ankles); variable course |
| Rubella / rubella vaccine | 30% of females; self-limited within 2 weeks |
| Alphaviruses (Chikungunya, Ross River, Mayaro) | Mosquito-borne; can last 3-6 months |
| Flaviviruses (Dengue, Zika, West Nile) | Acute joint + bone pain; high fever; supportive treatment |

| Pattern | Top Diagnoses |
|---|---|
| Symmetric small joints, chronic | RA, SLE, viral (Hep B/C, Parvovirus) |
| Asymmetric, large joints, young male | Seronegative spondyloarthropathy, reactive arthritis |
| Migratory, fever, young sexually active | Gonococcal arthritis |
| Migratory, fever, recent strep pharyngitis | Acute rheumatic fever |
| Quotidian fever, high ferritin, rash | Adult-onset Still's disease |
| Post-viral, self-limited | Parvovirus B19, Rubella, Chikungunya |
| Elderly, large + small joints, no anti-CCP | CPDD ("pseudorheumatoid") |
| Multisystem, ANA+ | SLE, sarcoidosis, dermatomyositis |
It’s diagnostic approach
| Feature | Significance |
|---|---|
| Acute (<6 weeks) | Infection (gonococcal, viral, septic), crystal arthropathy (gout, pseudogout), acute rheumatic fever, reactive arthritis |
| Chronic (>6 weeks) | RA, SLE, seronegative spondyloarthropathies, polyarticular OA |
| Migratory arthritis | Gonococcal arthritis, acute rheumatic fever, viral arthritides (Hep B) |
| Additive/cumulative | RA, psoriatic arthritis, reactive arthritis |
| Pattern | Likely Diagnosis |
|---|---|
| Symmetric, small joints (MCPs, PIPs, wrists, MTPs) | RA |
| Asymmetric, large joints | Seronegative spondyloarthropathy, reactive arthritis |
| DIP joints prominent | Psoriatic arthritis, OA (Heberden's nodes) |
| Axial + peripheral | Ankylosing spondylitis, IBD-related arthropathy |
| Finding | Implication |
|---|---|
| Soft boggy swelling | Synovial thickening = inflammatory |
| Hard bony enlargement | OA (Heberden's/Bouchard's nodes) |
| Effusion (ballottement/bulge sign) | Inflammatory - warrants arthrocentesis |
| Ulnar deviation, swan-neck, Boutonnière deformities | Chronic RA |
| Dactylitis ("sausage digit") | Psoriatic arthritis, reactive arthritis |
| Sacroiliac joint tenderness | Spondyloarthropathy |
| Test | Purpose |
|---|---|
| ESR, CRP | Confirm inflammation; elevated in inflammatory arthritis, infection |
| CBC with differential | Leukocytosis (infection, Still's); anemia of chronic disease (RA, SLE); thrombocytopenia (SLE) |
| Rheumatoid factor (RF) | Positive in RA (~70-80%); also in SLE, hepatitis C, CPDD (~10%) |
| Anti-CCP (anti-citrullinated peptide) | Highly specific for RA; predicts erosive disease |
| ANA | Screen for SLE and other CTDs; high sensitivity, low specificity |
| Anti-dsDNA, Anti-Sm | Specific for SLE if ANA positive |
| Uric acid | Elevated in gout (note: may be normal during acute attack) |
| Serum ferritin | Markedly elevated (often >10,000 ng/mL) in Still's disease |
| ASO titer | Elevated in acute rheumatic fever (recent streptococcal infection) |
| Blood cultures | If infective endocarditis or septic arthritis suspected |
| LFTs, Hepatitis B/C serology | Viral arthritis workup |
| Parvovirus B19 IgM/IgG | Acute viral arthritis |
| Lyme serology (ELISA + Western blot) | Endemic area exposure / migratory large-joint arthritis |
| HLA-B27 | Seronegative spondyloarthropathies |
| Complement (C3, C4) | Low in active SLE (consumption) |
| TSH | Hypothyroidism can mimic arthropathy |
| Serum creatine kinase | Elevated in polymyositis/dermatomyositis |
| Parameter | Normal | Noninflammatory (OA) | Inflammatory (RA, crystal) | Septic |
|---|---|---|---|---|
| Appearance | Clear, straw | Clear/yellow | Turbid, yellow | Purulent/cloudy |
| Viscosity | High | High | Low | Low |
| WBC (cells/µL) | <200 | <2,000 | 2,000-50,000 | >50,000 (often >100,000) |
| % PMNs | <25% | <25% | >50% | >75% |
| Glucose | = serum | = serum | Low | Very low |
| Culture | Negative | Negative | Negative | Positive (in septic) |
| Crystals | None | None | Urate (gout) or CPP (pseudogout) | None |
| Modality | Use |
|---|---|
| Plain X-rays | First-line: erosions (RA), joint space narrowing (OA), chondrocalcinosis (CPDD), periosteal reaction |
| Ultrasound | Detect early synovitis, guide arthrocentesis, assess tendons/entheses |
| MRI | Best for early erosions (RA), sacroiliac joints (AS), soft tissue involvement |
| CT | Bony detail; axial spondyloarthropathy, sacroiliitis |
| Bone scan | Diffuse inflammatory activity; polyarticular involvement |

| Clue | Diagnosis to Consider |
|---|---|
| Anti-CCP positive + erosions | RA |
| ANA + anti-dsDNA | SLE |
| Ferritin >10,000 + quotidian fever + rash | Adult-onset Still's disease |
| Urate crystals, negatively birefringent | Gout |
| CPP crystals, positively birefringent | Pseudogout / CPDD |
| HLA-B27 + sacroiliitis | Ankylosing spondylitis |
| Post-GI/GU infection + urethritis + conjunctivitis | Reactive arthritis |
| Migratory arthritis + skin pustules, sexually active | Gonococcal arthritis |
| Post-pharyngitis + carditis + migratory arthritis | Acute rheumatic fever |
| Child with "slapped cheek" contact | Parvovirus B19 |
| Mosquito exposure, tropical travel | Chikungunya, Dengue, Zika |
Create a diagnostic reference chart summarizing this approach in PDF format
mkdir -p /tmp/workspace/polyarticular-chart && ls /tmp/workspace/
~/polyarticular-chart/generate_chart.py
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import HRFlowable
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Arrow
from reportlab.graphics import renderPDF
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1A3A5C")
TEAL = colors.HexColor("#1A7A8A")
TEAL_LIGHT= colors.HexColor("#D4EFF3")
BLUE_PALE = colors.HexColor("#E8F4FD")
ORANGE = colors.HexColor("#E07B2A")
ORANGE_LT = colors.HexColor("#FEF0E2")
RED_LT = colors.HexColor("#FDECEA")
RED = colors.HexColor("#C0392B")
GREEN = colors.HexColor("#1E7E4B")
GREEN_LT = colors.HexColor("#E6F5EC")
PURPLE = colors.HexColor("#5B3FA6")
PURPLE_LT = colors.HexColor("#EDE8F9")
YELLOW_LT = colors.HexColor("#FFFBEA")
YELLOW = colors.HexColor("#D4AC0D")
GREY_LT = colors.HexColor("#F4F6F8")
GREY_MID = colors.HexColor("#CED4DA")
GREY_DARK = colors.HexColor("#495057")
WHITE = colors.white
BLACK = colors.black
OUTPUT = "/tmp/workspace/polyarticular-chart/Polyarticular_Pain_Diagnostic_Reference.pdf"
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.4*cm, rightMargin=1.4*cm,
topMargin=1.4*cm, bottomMargin=1.4*cm,
title="Polyarticular Pain – Diagnostic Reference Chart",
author="Orris Medical Reference",
)
styles = getSampleStyleSheet()
def S(name, **kw):
return ParagraphStyle(name, **kw)
# ── Shared styles ────────────────────────────────────────────────────────────
TITLE_STYLE = S("Title2", fontName="Helvetica-Bold", fontSize=18,
textColor=WHITE, alignment=TA_CENTER, leading=22)
SUB_TITLE = S("SubTitle2", fontName="Helvetica", fontSize=9,
textColor=WHITE, alignment=TA_CENTER, leading=12)
SEC_TITLE = S("SecTitle", fontName="Helvetica-Bold", fontSize=10,
textColor=WHITE, alignment=TA_CENTER, leading=13, spaceAfter=0)
COL_HEADER = S("ColHdr", fontName="Helvetica-Bold", fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)
BODY = S("Body2", fontName="Helvetica", fontSize=7.5,
textColor=GREY_DARK, alignment=TA_LEFT, leading=10)
BODY_BOLD = S("BodyBold", fontName="Helvetica-Bold", fontSize=7.5,
textColor=NAVY, alignment=TA_LEFT, leading=10)
BODY_C = S("BodyC", fontName="Helvetica", fontSize=7.5,
textColor=GREY_DARK, alignment=TA_CENTER, leading=10)
BODY_BOLD_C = S("BodyBoldC", fontName="Helvetica-Bold", fontSize=7.5,
textColor=NAVY, alignment=TA_CENTER, leading=10)
SMALL = S("Small", fontName="Helvetica", fontSize=6.8,
textColor=GREY_DARK, alignment=TA_LEFT, leading=9)
SMALL_C = S("SmallC", fontName="Helvetica", fontSize=6.8,
textColor=GREY_DARK, alignment=TA_CENTER, leading=9)
SMALL_BOLD = S("SmallBold", fontName="Helvetica-Bold", fontSize=7,
textColor=NAVY, alignment=TA_LEFT, leading=9)
SMALL_BOLD_C= S("SmallBoldC", fontName="Helvetica-Bold", fontSize=7,
textColor=NAVY, alignment=TA_CENTER, leading=9)
FOOT = S("Foot", fontName="Helvetica-Oblique", fontSize=6.5,
textColor=GREY_DARK, alignment=TA_CENTER, leading=9)
# ── Helper: section header bar ───────────────────────────────────────────────
def section_header(text, bg=NAVY, fg=WHITE, fs=10):
p = Paragraph(text, ParagraphStyle("sh", fontName="Helvetica-Bold",
fontSize=fs, textColor=fg, alignment=TA_CENTER, leading=fs+3))
t = Table([[p]], colWidths=[doc.width])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING",(0,0), (-1,-1), 6),
("ROUNDEDCORNERS", [4]),
]))
return t
# ── Helper: two-tone sub-header ───────────────────────────────────────────────
def sub_header(text, bg=TEAL):
p = Paragraph(text, ParagraphStyle("sbh", fontName="Helvetica-Bold",
fontSize=8, textColor=WHITE, alignment=TA_LEFT, leading=10))
t = Table([[p]], colWidths=[doc.width])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING",(0,0),(-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING",(0,0), (-1,-1), 6),
]))
return t
# ── Helper: standard table style ─────────────────────────────────────────────
def base_style(header_bg=NAVY, alt_bg=GREY_LT, grid=GREY_MID):
return TableStyle([
("BACKGROUND", (0,0), (-1,0), header_bg),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 7.5),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, alt_bg]),
("GRID", (0,0), (-1,-1), 0.4, grid),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 7),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
])
def P(txt, style=BODY):
return Paragraph(txt, style)
W = doc.width # full usable width
story = []
# ══════════════════════════════════════════════════════════════════════════════
# TITLE BANNER
# ══════════════════════════════════════════════════════════════════════════════
title_data = [[
Paragraph("POLYARTICULAR PAIN", TITLE_STYLE),
Paragraph("Diagnostic Reference Chart", SUB_TITLE),
]]
# stack vertically
banner_inner = Table([
[Paragraph("POLYARTICULAR PAIN", TITLE_STYLE)],
[Paragraph("Diagnostic Reference Chart • >4 joints involved", SUB_TITLE)],
], colWidths=[W])
banner_inner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0),(-1,-1), 8),
]))
story.append(banner_inner)
story.append(Spacer(1, 5))
# ══════════════════════════════════════════════════════════════════════════════
# ROW 1 — STEP 1: HISTORY (full width)
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STEP 1 — HISTORY", bg=NAVY))
story.append(Spacer(1, 3))
# --- Onset + Morning stiffness side by side
onset_data = [
[P("Onset", COL_HEADER), P("Consider", COL_HEADER)],
[P("Acute (<6 wks)", SMALL_BOLD), P("Infection (GC, viral, septic), crystal arthropathy, rheumatic fever, reactive arthritis", SMALL)],
[P("Chronic (>6 wks)", SMALL_BOLD), P("RA, SLE, seronegative spondyloarthropathy, polyarticular OA", SMALL)],
[P("Migratory", SMALL_BOLD), P("Gonococcal arthritis, acute rheumatic fever, Hepatitis B", SMALL)],
[P("Additive", SMALL_BOLD), P("RA, psoriatic arthritis, reactive arthritis", SMALL)],
]
onset_t = Table(onset_data, colWidths=[3.2*cm, 8.2*cm])
onset_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, TEAL_LIGHT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
stiff_data = [
[P("Morning Stiffness", COL_HEADER)],
[P("⬛ >1 hour → Inflammatory (RA, SLE, SpA)", SMALL_BOLD)],
[P("⬜ <30 min → Non-inflammatory / OA", SMALL)],
]
stiff_t = Table(stiff_data, colWidths=[5.6*cm])
stiff_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, TEAL_LIGHT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
row1 = Table([[onset_t, stiff_t]], colWidths=[11.7*cm, 5.8*cm],
hAlign="LEFT")
row1.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),
("LINEBETWEEN", (0,0), (0,-1), 0, WHITE),
]))
story.append(row1)
story.append(Spacer(1, 4))
# --- Joint pattern + Associated symptoms side by side
jp_data = [
[P("Joint Pattern", COL_HEADER), P("Likely Diagnosis", COL_HEADER)],
[P("Symmetric small joints\n(MCPs, PIPs, wrists, MTPs)", SMALL_BOLD), P("Rheumatoid Arthritis", SMALL)],
[P("Asymmetric large joints", SMALL_BOLD), P("Seronegative SpA, reactive arthritis", SMALL)],
[P("DIP joints prominent", SMALL_BOLD), P("Psoriatic arthritis, OA (Heberden's nodes)", SMALL)],
[P("Axial + peripheral", SMALL_BOLD), P("Ankylosing spondylitis, IBD-related arthropathy", SMALL)],
[P("Migratory large joints", SMALL_BOLD), P("Acute rheumatic fever, gonococcal arthritis", SMALL)],
]
jp_t = Table(jp_data, colWidths=[5.0*cm, 6.5*cm])
jp_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, ORANGE_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
sx_data = [
[P("Associated Symptom / Exposure", COL_HEADER), P("Consider", COL_HEADER)],
[P("Fever + quotidian + rash", SMALL_BOLD), P("Still's disease", SMALL)],
[P("Malar rash, serositis", SMALL_BOLD), P("SLE", SMALL)],
[P("Psoriatic plaques/nail pitting", SMALL_BOLD), P("Psoriatic arthritis", SMALL)],
[P("Urethritis + conjunctivitis", SMALL_BOLD), P("Reactive / GC arthritis", SMALL)],
[P("Post-pharyngitis + carditis", SMALL_BOLD), P("Acute rheumatic fever", SMALL)],
[P("Recent viral / child 'slapped cheek'", SMALL_BOLD), P("Parvovirus B19", SMALL)],
[P("Tick exposure / endemic area", SMALL_BOLD), P("Lyme disease", SMALL)],
[P("Mosquito bite / tropical travel", SMALL_BOLD), P("Chikungunya, Dengue, Zika", SMALL)],
[P("Proximal muscle weakness", SMALL_BOLD), P("Polymyositis / dermatomyositis", SMALL)],
]
sx_t = Table(sx_data, colWidths=[5.0*cm, 3.8*cm])
sx_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), ORANGE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, ORANGE_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
row2 = Table([[jp_t, sx_t]], colWidths=[11.7*cm, 5.8*cm], hAlign="LEFT")
row2.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(row2)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# STEP 2 — PHYSICAL EXAMINATION
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STEP 2 — PHYSICAL EXAMINATION", bg=PURPLE))
story.append(Spacer(1, 3))
exam_data = [
[P("Finding", COL_HEADER), P("Interpretation", COL_HEADER),
P("Finding", COL_HEADER), P("Interpretation", COL_HEADER)],
[P("Soft boggy swelling", SMALL_BOLD), P("Synovial thickening = inflammatory", SMALL),
P("Ulnar deviation / swan-neck /\nBoutonnière deformity", SMALL_BOLD), P("Chronic RA", SMALL)],
[P("Hard bony enlargement", SMALL_BOLD), P("OA — Heberden's / Bouchard's nodes", SMALL),
P("Dactylitis ('sausage digit')", SMALL_BOLD), P("Psoriatic or reactive arthritis", SMALL)],
[P("Joint effusion", SMALL_BOLD), P("Inflammatory → do arthrocentesis", SMALL),
P("Sacroiliac tenderness", SMALL_BOLD), P("Spondyloarthropathy", SMALL)],
[P("Pain active only\n(not passive ROM)", SMALL_BOLD), P("Periarticular (bursitis/tendinitis)", SMALL),
P("Tophi (ears, tendons)", SMALL_BOLD), P("Chronic tophaceous gout", SMALL)],
[P("Morning stiffness >1 hr", SMALL_BOLD), P("Inflammatory arthritis", SMALL),
P("Subcutaneous nodules", SMALL_BOLD), P("Rheumatoid arthritis", SMALL)],
]
cw_exam = [3.8*cm, 5.0*cm, 3.8*cm, 5.0*cm]
exam_t = Table(exam_data, colWidths=cw_exam)
exam_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), PURPLE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, PURPLE_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
("LINEAFTER", (1,0), (1,-1), 1.2, PURPLE),
]))
story.append(exam_t)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# STEP 3 — INVESTIGATIONS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STEP 3 — INVESTIGATIONS", bg=colors.HexColor("#1E7E4B")))
story.append(Spacer(1, 3))
# --- Blood tests (two columns)
blood_data = [
[P("Test", COL_HEADER), P("Significance", COL_HEADER),
P("Test", COL_HEADER), P("Significance", COL_HEADER)],
[P("ESR / CRP", SMALL_BOLD), P("Elevated → confirms inflammation; infection", SMALL),
P("ANA", SMALL_BOLD), P("Screen for SLE & CTDs (sensitive, not specific)", SMALL)],
[P("CBC + differential", SMALL_BOLD), P("Leukocytosis (infection/Still's); anaemia (RA/SLE)", SMALL),
P("Anti-dsDNA / Anti-Sm", SMALL_BOLD), P("Specific for SLE if ANA +ve", SMALL)],
[P("RF (rheumatoid factor)", SMALL_BOLD), P("+ve in RA (70-80%); also SLE, Hep C, CPDD", SMALL),
P("Serum uric acid", SMALL_BOLD), P("Elevated in gout (may be normal in acute attack)", SMALL)],
[P("Anti-CCP", SMALL_BOLD), P("Highly specific for RA; predicts erosive disease", SMALL),
P("Serum ferritin", SMALL_BOLD), P(">10,000 ng/mL — strongly suggests Still's disease", SMALL)],
[P("Blood cultures", SMALL_BOLD), P("If endocarditis or septic arthritis suspected", SMALL),
P("ASO titer", SMALL_BOLD), P("Elevated after streptococcal infection (rheumatic fever)", SMALL)],
[P("Hepatitis B/C serology", SMALL_BOLD), P("Viral arthritis workup", SMALL),
P("Parvovirus B19 IgM/IgG", SMALL_BOLD), P("Acute viral polyarthritis", SMALL)],
[P("Lyme serology\n(ELISA + Western blot)", SMALL_BOLD), P("Endemic area / tick exposure / migratory large-joint", SMALL),
P("HLA-B27", SMALL_BOLD), P("Seronegative spondyloarthropathies (AS, PsA, ReA)", SMALL)],
[P("Complement C3/C4", SMALL_BOLD), P("Low in active SLE (consumption)", SMALL),
P("CK (creatine kinase)", SMALL_BOLD), P("Elevated in polymyositis / dermatomyositis", SMALL)],
[P("TSH", SMALL_BOLD), P("Hypothyroidism can mimic arthropathy", SMALL),
P("Urine dipstick / ANCA", SMALL_BOLD), P("Renal involvement (SLE, vasculitis)", SMALL)],
]
cw_blood = [3.0*cm, 5.5*cm, 3.0*cm, 6.0*cm]
blood_t = Table(blood_data, colWidths=cw_blood)
blood_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), GREEN),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, GREEN_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
("LINEAFTER", (1,0), (1,-1), 1.2, GREEN),
]))
story.append(blood_t)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# STEP 4 — SYNOVIAL FLUID + IMAGING (side by side)
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STEP 4 — SYNOVIAL FLUID ANALYSIS & IMAGING", bg=RED))
story.append(Spacer(1, 3))
sf_data = [
[P("Parameter", COL_HEADER), P("Normal", COL_HEADER),
P("Non-inflam.", COL_HEADER), P("Inflammatory", COL_HEADER), P("Septic", COL_HEADER)],
[P("Appearance", SMALL_BOLD), P("Clear", SMALL_C), P("Clear/yellow", SMALL_C),
P("Turbid yellow", SMALL_C), P("Purulent", SMALL_C)],
[P("Viscosity", SMALL_BOLD), P("High", SMALL_C), P("High", SMALL_C),
P("Low", SMALL_C), P("Low", SMALL_C)],
[P("WBC (/µL)", SMALL_BOLD), P("<200", SMALL_C), P("<2,000", SMALL_C),
P("2,000–50,000", SMALL_C), P(">50,000", SMALL_C)],
[P("% PMNs", SMALL_BOLD), P("<25%", SMALL_C), P("<25%", SMALL_C),
P(">50%", SMALL_C), P(">75%", SMALL_C)],
[P("Glucose", SMALL_BOLD), P("= serum", SMALL_C), P("= serum", SMALL_C),
P("Low", SMALL_C), P("Very low", SMALL_C)],
[P("Culture", SMALL_BOLD), P("Neg", SMALL_C), P("Neg", SMALL_C),
P("Neg", SMALL_C), P("Positive", SMALL_C)],
[P("Crystals", SMALL_BOLD), P("None", SMALL_C), P("None", SMALL_C),
P("Urate/CPP", SMALL_C), P("None", SMALL_C)],
]
cw_sf = [2.8*cm, 1.6*cm, 1.9*cm, 2.4*cm, 2.0*cm]
sf_t = Table(sf_data, colWidths=cw_sf)
sf_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, RED_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 3),
("RIGHTPADDING", (0,0), (-1,-1), 3),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
# highlight septic column
("BACKGROUND", (4,1), (4,-1), colors.HexColor("#FDECEA")),
("TEXTCOLOR", (4,1), (4,-1), RED),
("FONTNAME", (4,1), (4,-1), "Helvetica-Bold"),
]))
crystal_data = [
[P("Crystal Analysis", COL_HEADER)],
[P("Gout (MSU)", SMALL_BOLD)],
[P("Needle-shaped\nNegatively birefringent\n(yellow under parallel light)", SMALL)],
[P("Pseudogout (CPP)", SMALL_BOLD)],
[P("Rhomboid-shaped\nWeakly + birefringent\n(blue under parallel light)", SMALL)],
]
cry_t = Table(crystal_data, colWidths=[3.5*cm])
cry_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [RED_LT, WHITE, RED_LT, WHITE]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
img_data = [
[P("Imaging Modality", COL_HEADER), P("Best For", COL_HEADER)],
[P("Plain X-ray", SMALL_BOLD), P("Erosions (RA), joint space (OA), chondrocalcinosis (CPDD)", SMALL)],
[P("Ultrasound", SMALL_BOLD), P("Early synovitis, guide arthrocentesis, tendons/entheses", SMALL)],
[P("MRI", SMALL_BOLD), P("Early erosions (RA), sacroiliac joints (AS), soft tissue", SMALL)],
[P("CT", SMALL_BOLD), P("Bony detail; axial SpA, sacroiliitis", SMALL)],
[P("Bone scan", SMALL_BOLD), P("Diffuse inflammatory activity across multiple joints", SMALL)],
]
cw_img = [2.2*cm, 4.1*cm]
img_t = Table(img_data, colWidths=cw_img)
img_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, RED_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
]))
sf_row = Table([[sf_t, cry_t, img_t]], colWidths=[11.0*cm, 3.5*cm, 6.5*cm+0.5*cm], hAlign="LEFT")
sf_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(sf_row)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# STEP 5 — DIAGNOSTIC ALGORITHM (text-based flowchart)
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("STEP 5 — DIAGNOSTIC ALGORITHM", bg=colors.HexColor("#2C3E50")))
story.append(Spacer(1, 3))
BOX_STYLE = ParagraphStyle("box", fontName="Helvetica-Bold", fontSize=7.5,
textColor=NAVY, alignment=TA_CENTER, leading=10)
BOX_SMALL = ParagraphStyle("boxs", fontName="Helvetica", fontSize=6.8,
textColor=GREY_DARK, alignment=TA_LEFT, leading=9)
def flow_box(text, bg, tc=NAVY, bold=True):
fn = "Helvetica-Bold" if bold else "Helvetica"
p = Paragraph(text, ParagraphStyle("fb", fontName=fn, fontSize=7.5,
textColor=tc, alignment=TA_CENTER, leading=10))
t = Table([[p]])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 0.8, GREY_DARK),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
]))
return t
def arrow_cell(text="▼", col=GREY_DARK):
return Paragraph(f'<font color="#{col.hexval()[1:] if hasattr(col,"hexval") else "495057"}">{text}</font>',
ParagraphStyle("arr", fontName="Helvetica-Bold", fontSize=10,
alignment=TA_CENTER, leading=12))
# Build flowchart as a structured table
# Row labels: start, arrow, question1, branches, outcomes
ALGO = [
# row 0: START
[Paragraph("", BODY_C),
flow_box("Polyarticular Joint Pain (>4 joints)", TEAL_LIGHT),
Paragraph("", BODY_C)],
# row 1: arrow down
[Paragraph("", BODY_C), Paragraph("▼", ParagraphStyle("a", fontName="Helvetica-Bold", fontSize=12, alignment=TA_CENTER)), Paragraph("", BODY_C)],
# row 2: history + exam
[Paragraph("", BODY_C),
flow_box("Complete History & Physical Examination\nArticular vs. Periarticular?", colors.HexColor("#EAF6FB")),
Paragraph("", BODY_C)],
# row 3: split
[flow_box("Periarticular", ORANGE_LT, tc=ORANGE, bold=False),
Paragraph("", BODY_C),
flow_box("Articular", TEAL_LIGHT, tc=TEAL, bold=False)],
# row 4: periarticular outcome + spacer + articular question
[Paragraph("→ Bursitis / Tendinitis\nSymptomatic Rx\nConsider viral testing",
ParagraphStyle("peri", fontName="Helvetica", fontSize=7, textColor=ORANGE, leading=10)),
Paragraph("", BODY_C),
flow_box("Joint Effusion Present?", colors.HexColor("#EAF6FB"))],
# row 5: No effusion / Yes effusion
[Paragraph("", BODY_C),
flow_box("NO effusion", GREEN_LT, tc=GREEN, bold=False),
flow_box("YES → Arthrocentesis", RED_LT, tc=RED, bold=False)],
# row 6: outcomes
[Paragraph("", BODY_C),
Paragraph("• Viral arthritis\n• Osteoarthritis\n• Early rheumatologic illness\n→ Outpatient viral / rheum testing",
ParagraphStyle("ne", fontName="Helvetica", fontSize=6.8, textColor=GREEN, leading=9)),
flow_box("Suggestive of Septic Arthritis?", RED_LT, tc=RED)],
# row 7: septic branches
[Paragraph("", BODY_C),
Paragraph("", BODY_C),
flow_box("NO YES", RED_LT, tc=RED, bold=False)],
# row 8: septic outcomes
[Paragraph("", BODY_C),
Paragraph("NO:\n• Gout / Pseudogout\n• Crystal arthropathy\n• Rheumatologic illness\n• Seroneg. SpA, viral\n→ Close f/u + rheum referral",
ParagraphStyle("ns", fontName="Helvetica", fontSize=6.8, textColor=GREY_DARK, leading=9)),
Paragraph("YES:\n• GC swabs (genital/oral/skin)\n• Lyme serology (endemic areas)\n• Blood cultures\n→ IV antibiotics + ortho consult",
ParagraphStyle("ys", fontName="Helvetica", fontSize=6.8, textColor=RED, leading=9))],
]
algo_t = Table(ALGO, colWidths=[3.8*cm, 9.4*cm, 4.3*cm])
algo_t.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 2),
("BOTTOMPADDING", (0,0), (-1,-1), 2),
("LEFTPADDING", (0,0), (-1,-1), 2),
("RIGHTPADDING", (0,0), (-1,-1), 2),
("SPAN", (0,0), (2,0)), # start box spans all
("SPAN", (0,1), (2,1)), # arrow
("SPAN", (0,2), (2,2)), # h&p box
]))
story.append(algo_t)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# STEP 6 — QUICK DISCRIMINATOR CLUES
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header("QUICK DISCRIMINATOR CLUES", bg=colors.HexColor("#7D3C98")))
story.append(Spacer(1, 3))
clue_data = [
[P("Key Clue", COL_HEADER), P("Diagnosis", COL_HEADER),
P("Key Clue", COL_HEADER), P("Diagnosis", COL_HEADER)],
[P("Anti-CCP +ve + erosions on X-ray", SMALL_BOLD), P("Rheumatoid Arthritis", SMALL),
P("Migratory + skin pustules + sexually active", SMALL_BOLD), P("Gonococcal arthritis", SMALL)],
[P("ANA +ve + anti-dsDNA +ve", SMALL_BOLD), P("Systemic Lupus Erythematosus", SMALL),
P("Post-pharyngitis + carditis + migratory", SMALL_BOLD), P("Acute Rheumatic Fever", SMALL)],
[P("Ferritin >10,000 + quotidian fever + salmon rash", SMALL_BOLD), P("Adult-onset Still's", SMALL),
P("Child contact: 'slapped cheek' rash", SMALL_BOLD), P("Parvovirus B19", SMALL)],
[P("Needle crystals, negatively birefringent", SMALL_BOLD), P("Gout (MSU)", SMALL),
P("Mosquito exposure / tropical travel", SMALL_BOLD), P("Chikungunya / Dengue / Zika", SMALL)],
[P("Rhomboid crystals, weakly +ve birefringent", SMALL_BOLD), P("Pseudogout (CPDD)", SMALL),
P("Proximal muscle weakness + elevated CK", SMALL_BOLD), P("Polymyositis / Dermatomyositis", SMALL)],
[P("HLA-B27 + sacroiliitis + young male", SMALL_BOLD), P("Ankylosing Spondylitis", SMALL),
P("Lung infiltrates + bilateral ankle swelling", SMALL_BOLD), P("Sarcoidosis (Löfgren's)", SMALL)],
[P("Post-GI/GU infection + urethritis + conjunctivitis", SMALL_BOLD), P("Reactive Arthritis", SMALL),
P("Serum sickness: drug/foreign protein exposure", SMALL_BOLD), P("Serum Sickness", SMALL)],
]
cw_clue = [5.8*cm, 3.5*cm, 5.8*cm, 3.5*cm]
clue_t = Table(clue_data, colWidths=cw_clue)
clue_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#7D3C98")),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,-1), 7),
("GRID", (0,0), (-1,-1), 0.4, GREY_MID),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, PURPLE_LT]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("TEXTCOLOR", (0,1), (-1,-1), GREY_DARK),
("LINEAFTER", (1,0), (1,-1), 1.2, colors.HexColor("#7D3C98")),
]))
story.append(clue_t)
story.append(Spacer(1, 4))
# ══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ══════════════════════════════════════════════════════════════════════════════
story.append(HRFlowable(width=W, thickness=0.5, color=GREY_MID))
story.append(Spacer(1, 2))
story.append(Paragraph(
"Sources: Firestein & Kelley's Textbook of Rheumatology (2-Vol Set) • ROSEN's Emergency Medicine • "
"Miller's Review of Orthopaedics 9E • Frameworks for Internal Medicine • Harrison's Principles of Internal Medicine 22E | "
"Generated by Orris Medical Reference • July 2026",
FOOT
))
# ══════════════════════════════════════════════════════════════════════════════
# BUILD
# ══════════════════════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF saved: {OUTPUT}")
cd /tmp/workspace/polyarticular-chart && python generate_chart.py 2>&1
~/polyarticular-chart/generate_chart.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/polyarticular-chart/generate_chart.py'
cd /tmp/workspace/polyarticular-chart && python generate_chart.py 2>&1
ls -lh /tmp/workspace/polyarticular-chart/
Polyarticular Pain Diagnostic Reference
PDF Document · PDF
| Section | Content |
|---|---|
| Step 1 — History | Onset/duration patterns, morning stiffness rule, joint distribution patterns, associated symptoms and exposure clues |
| Step 2 — Physical Exam | Inflammatory vs. non-inflammatory signs, key findings and their interpretations (effusion, deformities, dactylitis, tophi, etc.) |
| Step 3 — Investigations | Full blood test panel with clinical significance (ESR, CRP, RF, anti-CCP, ANA, anti-dsDNA, ferritin, ASO, HLA-B27, cultures, and more) |
| Step 4 — Synovial Fluid + Imaging | Fluid analysis comparison table (normal vs. OA vs. inflammatory vs. septic), crystal analysis (gout vs. pseudogout birefringence), imaging modalities |
| Step 5 — Diagnostic Algorithm | Structured flowchart from presentation → H&P → effusion → arthrocentesis → septic vs. non-septic branches |
| Step 6 — Quick Discriminator Clues | 8 high-yield key clue → diagnosis pairings for rapid bedside reference |
1. Psoriasis 2. Scabies 3. Stevens–Johnson Syndrome (SJS) 4. Pemphigus vulgaris 5. Acne vulgaris 6. Leprosy (Hansen’s disease) 7. Syphilis 8. Lichen planus Make me detail pdf of this topics of derma that I can write in mbbs proff exam
SLE