Hyponatremia workup algorithm

Reading File
Finding Sources
Finding Sources
Searching the Web

hyponatremia workup diagnostic algorithm serum osmolality urine sodium 2024

Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Reading File
Finding Sources
Loading Image
Finding Sources
I now have comprehensive content from multiple authoritative sources plus the two diagnostic flowchart images. Let me compile the full, detailed answer.

Hyponatremia Workup Algorithm

Hyponatremia = serum [Na+] < 135 mEq/L. It is among the most common electrolyte disorders encountered in hospitalized patients. The approach below follows a logical stepwise algorithm grounded in physiology.

Initial Labs to Order (All Patients)

TestPurpose
Serum sodium, BMPConfirm hyponatremia, assess K+, glucose, BUN/Cr
Serum osmolalityFirst branch point - classify type
Urine osmolalityAssess ADH activity / renal concentrating ability
Urine sodium (spot)Assess renal sodium handling / volume status
TSHRule out hypothyroidism
Morning cortisolRule out adrenal insufficiency
Serum lipids, proteinIf pseudohyponatremia suspected

Step 1 - Measure Serum Osmolality (First Branch Point)

Normal serum osmolality = 275-295 mOsm/kg
Serum Osmolality
├── Normal (275-295) or HIGH (>295)  →  NOT true hypotonic hyponatremia
│   ├── Normal Posm:  Pseudohyponatremia
│   │     • Marked hyperlipidemia
│   │     • Marked hyperproteinemia (e.g., multiple myeloma)
│   └── High Posm:  Translocational hyponatremia
│         • Hyperglycemia (Na drops ~1.6 mEq/L per 100 mg/dL glucose rise)
│         • Mannitol, sorbitol, glycine (urologic irrigants)
│
└── LOW (<275)  →  TRUE hypotonic hyponatremia  →  PROCEED TO STEP 2
Note: Hyperglycemia correction formula: corrected Na = measured Na + 1.6 × [(glucose - 100) / 100]

Step 2 - Search for Specific Diagnostic Clues (Quick Screen)

Before volume assessment, scan for highly specific findings (Figure 24-1, Symptom to Diagnosis 4e):
Hyponatremia Step 1 - Search for specific clues
FindingDiagnosis to Consider
Thiazide useThiazide-induced hyponatremia
Urine Osm < 100 mOsm/kgWater intoxication: psychogenic polydipsia, beer potomania, ecstasy, exercise-associated hyponatremia (EAH)
Marked hyperglycemiaHyperglycemic-induced hyponatremia (translocational)
Markedly elevated creatinineKidney failure
Hyperkalemia + unexplained hypoglycemiaPrimary adrenal insufficiency
Normal/elevated serum osmolalityPseudohyponatremia
Recent marathon / endurance eventExercise-associated hyponatremia
Recent "party"/raveEcstasy (MDMA) use
If none of the above clues are present - proceed to Step 3.

Step 3 - Clinical Volume Status Assessment

Assess volume status by physical examination (JVP, skin turgor, mucous membranes, edema, orthostatic BP):
TRUE Hypotonic Hyponatremia (Posm <275)
│
├── HYPOVOLEMIC (low volume)
│   Signs: Dry mucous membranes, decreased skin turgor,
│          tachycardia, orthostasis, low JVP
│
├── EUVOLEMIC (normal volume)
│   Signs: No edema, no orthostasis
│
└── HYPERVOLEMIC (excess volume)
    Signs: Edema, ascites, elevated JVP

Step 4 - Urine Sodium and Urine Osmolality (Refine the Differential)

Hypovolemic Hyponatremia (Volume-depleted)

Urine NaEtiology
< 20-30 mEq/L (renal Na conserving)Extra-renal losses: vomiting, diarrhea, sweating, third-spacing, burns
> 20-30 mEq/L (renal Na wasting)Diuretics (thiazides > loop), salt-wasting nephropathy, cerebral salt wasting (CSW), primary adrenal insufficiency, mineralocorticoid deficiency
Key distinguisher: CSW vs SIADH - both have elevated urine Na and elevated urine Osm, but CSW is hypovolemic while SIADH is euvolemic.

Hypervolemic Hyponatremia (Edematous states)

Urine NaEtiology
< 20 mEq/L (low effective arterial blood volume)Heart failure, cirrhosis, nephrotic syndrome
> 20 mEq/LAdvanced renal failure (GFR < 5-10 mL/min)
Mechanism: In HF and cirrhosis, decreased effective circulating volume triggers ADH and promotes avid Na reabsorption - hence low UNa despite apparent volume overload.

Euvolemic Hyponatremia (Most common outpatient scenario)

This is the most complex category. Urine osmolality is the first branch:
Euvolemia
│
├── Urine Osm < 100 mOsm/kg  →  Maximally dilute urine, ADH suppressed
│     • Primary polydipsia (psychogenic)
│     • Beer potomania
│     • Ecstasy / MDMA
│     • Low-solute diet ("tea and toast")
│
└── Urine Osm > 100 mOsm/kg  →  Inappropriately concentrated, ADH active
      │
      ├── Check Urine Na
      │     • UNa < 20-30  →  Consider subtle hypovolemia
      │                        (re-classify as hypovolemic)
      │     • UNa > 30    →  Proceed to hormonal workup
      │
      └── Check TSH and Morning Cortisol  (Figure 24-4)
Euvolemic hyponatremia - Step 4 workup
FindingDiagnosis
TSH markedly elevatedSevere hypothyroidism
Morning cortisol < 16.3 mcg/dLEvaluate for adrenal insufficiency (requires cosyntropin stimulation test to confirm)
Other etiologies excluded + Uosm elevated + UNa elevatedSIADH (diagnosis of exclusion)

SIADH Diagnostic Criteria (Tintinalli / classic criteria)

All of the following must be present:
  1. Hypotonic hyponatremia (Posm < 275 mOsm/kg)
  2. Urine osmolality inappropriately elevated (usually > 200, often > 100 mOsm/kg)
  3. Urine Na > 20-40 mEq/L (ongoing renal Na loss)
  4. Clinical euvolemia
  5. Normal adrenal, renal, cardiac, hepatic, and thyroid function
SIADH causes: CNS disease (stroke, trauma, infection, mass), pulmonary disease (pneumonia, malignancy, respiratory failure), drugs (SSRIs, carbamazepine, cyclophosphamide, vincristine, NSAIDs, opioids), ectopic ADH production (small cell lung cancer, head/neck tumors, pancreas, lymphoma), HIV, postoperative state.

Summary Table: Urine Na + Urine Osm by Category

Volume StatusUNaUrine OsmLikely Etiology
Hypovolemic< 20> 100GI/skin losses, third-spacing
Hypovolemic> 20> 100Diuretics, salt-wasting nephropathy, adrenal insufficiency
Hypervolemic< 20> 100HF, cirrhosis, nephrotic syndrome
Hypervolemic> 20VariableAdvanced renal failure
Euvolemic< 20< 100Primary polydipsia, beer potomania
Euvolemic> 40> 100SIADH, hypothyroidism, adrenal insufficiency
Rule of thumb (Tintinalli): UNa < 10 mEq/L is found only in edematous syndromes (HF, cirrhosis) and in vomiting/diarrhea.

Acute vs. Chronic Distinction (Critical for Treatment)

  • Acute: onset < 24-48 hours - higher risk of cerebral edema; more aggressive correction acceptable
  • Chronic: onset > 48 hours (or unknown) - assume chronic; risk of osmotic demyelination syndrome (ODS/CPM) with rapid correction
  • When duration is unknown, always assume chronic

Treatment Correction Rate Limits

Clinical ScenarioMax Correction Rate
Severe symptoms (seizures, obtundation, coma)Raise by 4-6 mEq/L acutely with 3% NaCl bolus (100 mL over ~15 min), then reassess
Chronic / asymptomatic≤ 6-8 mEq/L per 24 hours (high-risk patients)
Chronic / mild-moderate symptoms≤ 10-12 mEq/L per 24 hours (low-risk patients)
48-hour limit≤ 18 mEq/L total over 48 hours
High-risk for ODS: liver disease, alcoholism, malnutrition, severe hyponatremia (< 105), hypokalemia.

Sources: Symptom to Diagnosis, 4e (Figures 24-1, 24-4) | Tintinalli's Emergency Medicine (Table 17-6) | Harrison's Principles of Internal Medicine 22e | Brenner and Rector's The Kidney | ROSEN's Emergency Medicine

Create a quick reference PDF with the hyponatremia workup algorithm and correction limits

Reading File
Running Command

ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not foun…

Running Command

mkdir -p /home/daytona/workspace/hyponatremia-pdf && python3 -c "import reportla…

Writing File

~/hyponatremia-pdf/build_pdf.py

#!/usr/bin/env python3
"""Build a polished 2-page quick-reference PDF for hyponatremia workup."""

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus.flowables import Flowable
import reportlab.rl_config

OUTPUT = "/home/daytona/workspace/hyponatremia-pdf/Hyponatremia_Quick_Reference.pdf"

# ── Colour palette ────────────────────────────────────────────────────────────
C_NAVY   = colors.HexColor("#1a3a5c")   # header backgrounds
C_TEAL   = colors.HexColor("#1a7a7a")   # section banners
C_BLUE_L = colors.HexColor("#ddeeff")   # light blue fills
C_TEAL_L = colors.HexColor("#d4f0f0")   # light teal fills
C_AMBER  = colors.HexColor("#fff3cd")   # warning boxes
C_RED    = colors.HexColor("#c0392b")   # urgent text
C_GREEN  = colors.HexColor("#1d6a30")   # safe/normal
C_ORANGE = colors.HexColor("#d35400")
C_GREY   = colors.HexColor("#f5f6fa")
C_BORDER = colors.HexColor("#aabbcc")
C_WHITE  = colors.white

# ── Style helpers ─────────────────────────────────────────────────────────────
def S(name, **kw):
    base = {
        "fontName": "Helvetica",
        "fontSize": 9,
        "leading":  12,
        "textColor": colors.black,
        "spaceAfter": 0,
        "spaceBefore": 0,
    }
    base.update(kw)
    return ParagraphStyle(name, **base)

TITLE   = S("title",  fontName="Helvetica-Bold", fontSize=16, textColor=C_WHITE,
            alignment=TA_CENTER, leading=20)
SUBTITLE= S("sub",    fontName="Helvetica",      fontSize=9,  textColor=C_BLUE_L,
            alignment=TA_CENTER)
SEC_HDR = S("sechdr", fontName="Helvetica-Bold", fontSize=10, textColor=C_WHITE,
            alignment=TA_LEFT, leading=13)
BODY    = S("body",   fontSize=8.5, leading=12)
BODYB   = S("bodyb",  fontName="Helvetica-Bold", fontSize=8.5, leading=12)
SMALL   = S("small",  fontSize=7.5, leading=10, textColor=colors.HexColor("#444"))
NOTE    = S("note",   fontSize=7.5, leading=10, textColor=C_RED, fontName="Helvetica-Oblique")
TH      = S("th",     fontName="Helvetica-Bold", fontSize=8, textColor=C_WHITE,
            alignment=TA_CENTER, leading=11)
TD      = S("td",     fontSize=8, leading=11, alignment=TA_LEFT)
TDC     = S("tdc",    fontSize=8, leading=11, alignment=TA_CENTER)
TDCB    = S("tdcb",   fontName="Helvetica-Bold", fontSize=8, leading=11,
            alignment=TA_CENTER, textColor=C_NAVY)
ARROW   = S("arrow",  fontName="Helvetica-Bold", fontSize=11, textColor=C_TEAL,
            alignment=TA_CENTER)

def sp(h=3):
    return Spacer(1, h*mm)

def hr(color=C_BORDER, thickness=0.5):
    return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=2, spaceBefore=2)

def section_banner(text, bg=C_NAVY):
    data = [[Paragraph(text, SEC_HDR)]]
    t = Table(data, colWidths=["100%"])
    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), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("ROUNDEDCORNERS", [3,3,3,3]),
    ]))
    return t

def make_table(headers, rows, col_widths, row_colors=None, header_bg=C_NAVY):
    header_row = [Paragraph(h, TH) for h in headers]
    body_rows  = []
    for r in rows:
        body_rows.append([Paragraph(str(c), TD) for c in r])
    data = [header_row] + body_rows
    t = Table(data, colWidths=col_widths, repeatRows=1)
    style = [
        ("BACKGROUND",    (0,0),  (-1,0),  header_bg),
        ("TOPPADDING",    (0,0),  (-1,-1), 4),
        ("BOTTOMPADDING", (0,0),  (-1,-1), 4),
        ("LEFTPADDING",   (0,0),  (-1,-1), 5),
        ("RIGHTPADDING",  (0,0),  (-1,-1), 5),
        ("GRID",          (0,0),  (-1,-1), 0.4, C_BORDER),
        ("ROWBACKGROUNDS",(0,1),  (-1,-1), [C_WHITE, C_GREY]),
        ("VALIGN",        (0,0),  (-1,-1), "MIDDLE"),
        ("FONTSIZE",      (0,1),  (-1,-1), 8),
    ]
    if row_colors:
        for (row_idx, col_start, col_end, bg) in row_colors:
            style.append(("BACKGROUND", (col_start, row_idx), (col_end, row_idx), bg))
    t.setStyle(TableStyle(style))
    return t

# ── Arrow / box flowchart helper ──────────────────────────────────────────────
def algo_box(label, sublabel="", bg=C_BLUE_L, border=C_NAVY, text_color=C_NAVY,
             bold=True):
    fn = "Helvetica-Bold" if bold else "Helvetica"
    sty = S("box", fontName=fn, fontSize=8.5, textColor=text_color,
            alignment=TA_CENTER, leading=12)
    sty2 = S("boxsub", fontName="Helvetica", fontSize=7.5,
              textColor=colors.HexColor("#555"), alignment=TA_CENTER, leading=10)
    content = [[Paragraph(label, sty)]]
    if sublabel:
        content.append([Paragraph(sublabel, sty2)])
    t = Table(content, colWidths=["100%"])
    t.setStyle(TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 1.2, border),
        ("TOPPADDING",    (0,0), (-1,-1), 5),
        ("BOTTOMPADDING", (0,0), (-1,-1), 5),
        ("LEFTPADDING",   (0,0), (-1,-1), 6),
        ("RIGHTPADDING",  (0,0), (-1,-1), 6),
    ]))
    return t

def down_arrow():
    return Paragraph("&#9660;", ARROW)

# ══════════════════════════════════════════════════════════════════════════════
# BUILD DOCUMENT
# ══════════════════════════════════════════════════════════════════════════════
doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=A4,
    leftMargin=15*mm, rightMargin=15*mm,
    topMargin=12*mm,  bottomMargin=12*mm,
    title="Hyponatremia Quick Reference",
    author="Orris Clinical Reference",
    subject="Hyponatremia Workup Algorithm & Correction Limits",
)

W = A4[0] - 30*mm   # usable width

story = []

# ── PAGE HEADER ───────────────────────────────────────────────────────────────
hdr_data = [
    [Paragraph("HYPONATREMIA", TITLE),
     Paragraph("Quick Reference  |  Workup Algorithm & Correction Limits", SUBTITLE)]
]
hdr_table = Table([[
    Paragraph("HYPONATREMIA", S("t2", fontName="Helvetica-Bold", fontSize=18,
              textColor=C_WHITE, alignment=TA_CENTER, leading=22)),
    Paragraph("Quick Reference\nWorkup Algorithm &amp; Correction Limits",
              S("sub2", fontSize=9, textColor=C_BLUE_L, alignment=TA_LEFT, leading=13))
]], colWidths=[60*mm, W-60*mm])
hdr_table.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), C_NAVY),
    ("TOPPADDING",    (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story.append(hdr_table)
story.append(sp(3))

# ── DEFINITION ROW ────────────────────────────────────────────────────────────
def_data = [
    [Paragraph("<b>Definition:</b> Serum [Na<sup>+</sup>] &lt; 135 mEq/L", BODY),
     Paragraph("<b>Prevalence:</b> Most common electrolyte disorder in hospitalized patients (~15-30%)", BODY),
     Paragraph("<b>Key risk:</b> Osmotic demyelination syndrome (ODS) with overly rapid correction", BODY)]
]
def_table = Table(def_data, colWidths=[W/3]*3)
def_table.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), C_BLUE_L),
    ("BOX",           (0,0), (-1,-1), 0.5, C_BORDER),
    ("INNERGRID",     (0,0), (-1,-1), 0.3, C_BORDER),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story.append(def_table)
story.append(sp(4))

# ── INITIAL LABS ─────────────────────────────────────────────────────────────
story.append(section_banner("STEP 0  |  INITIAL LABS TO ORDER"))
story.append(sp(2))
labs_rows = [
    ["Serum BMP (Na, K, Cl, HCO3, BUN, Cr, Glucose)", "Confirm hyponatremia; K elevation suggests adrenal insufficiency/renal failure; glucose for correction"],
    ["Serum Osmolality", "FIRST BRANCH POINT - determines type of hyponatremia (normal 275-295 mOsm/kg)"],
    ["Urine Osmolality", "Assess ADH activity and renal diluting ability (<100 = ADH suppressed; >100 = ADH active)"],
    ["Spot Urine Sodium (UNa)", "Assess renal Na handling (<20 = Na conserving; >20-30 = Na wasting)"],
    ["TSH", "Rule out hypothyroidism (euvolemic hyponatremia)"],
    ["Morning Cortisol (8 AM)", "Rule out adrenal insufficiency (cortisol <16.3 mcg/dL warrants cosyntropin stim test)"],
    ["Serum Lipids / Total Protein", "If normal/elevated Posm - rule out pseudohyponatremia"],
]
labs_t = make_table(
    ["Test", "Rationale"],
    labs_rows,
    [70*mm, W-70*mm],
    header_bg=C_TEAL
)
story.append(labs_t)
story.append(sp(4))

# ── STEP 1: SERUM OSMOLALITY ──────────────────────────────────────────────────
story.append(section_banner("STEP 1  |  SERUM OSMOLALITY - FIRST BRANCH POINT"))
story.append(sp(2))

osm_rows = [
    ["NORMAL / HIGH\n(≥275 mOsm/kg)",
     "Normal Posm\n(275-295)",
     "Pseudohyponatremia\n• Marked hyperlipidemia\n• Marked hyperproteinemia\n(e.g., multiple myeloma)"],
    ["", "High Posm\n(>295)",
     "Translocational Hyponatremia\n• Hyperglycemia: Na drops ~1.6 mEq/L per 100 mg/dL rise in glucose\n• Mannitol, sorbitol, glycine (urologic irrigants)"],
    ["LOW\n(<275 mOsm/kg)\n= TRUE hypotonic\nhyponatremia",
     "Proceed to\nStep 2 & 3", "Assess for specific clues (Step 2) then volume status (Step 3)"],
]
sty_osm = [
    ("BACKGROUND",    (0,0),  (-1,0),  C_NAVY),
    ("FONTNAME",      (0,0),  (-1,0),  "Helvetica-Bold"),
    ("TEXTCOLOR",     (0,0),  (-1,0),  C_WHITE),
    ("FONTSIZE",      (0,0),  (-1,-1), 8),
    ("BACKGROUND",    (0,1),  (0,1),   colors.HexColor("#ffe0b2")),
    ("BACKGROUND",    (0,2),  (0,2),   colors.HexColor("#ffe0b2")),
    ("BACKGROUND",    (0,3),  (0,3),   colors.HexColor("#c8e6c9")),
    ("BACKGROUND",    (1,3),  (-1,3),  colors.HexColor("#c8e6c9")),
    ("BACKGROUND",    (1,1),  (-1,1),  C_GREY),
    ("BACKGROUND",    (1,2),  (-1,2),  C_WHITE),
    ("GRID",          (0,0),  (-1,-1), 0.4, C_BORDER),
    ("TOPPADDING",    (0,0),  (-1,-1), 5),
    ("BOTTOMPADDING", (0,0),  (-1,-1), 5),
    ("LEFTPADDING",   (0,0),  (-1,-1), 5),
    ("RIGHTPADDING",  (0,0),  (-1,-1), 5),
    ("VALIGN",        (0,0),  (-1,-1), "TOP"),
    ("SPAN",          (0,1),  (0,2)),
]
osm_headers = [["Serum Osm Result", "Sub-category", "Diagnosis / Action"]]
osm_data_full = osm_headers + osm_rows
# Use Paragraph cells for proper wrapping
def p(txt, bold=False, color=colors.black, center=False):
    fn = "Helvetica-Bold" if bold else "Helvetica"
    aln = TA_CENTER if center else TA_LEFT
    return Paragraph(txt.replace("\n","<br/>"),
                     S("_", fontName=fn, fontSize=8, textColor=color,
                       alignment=aln, leading=11))

osm_table_data = [
    [p("Serum Osm Result",True,C_WHITE,True), p("Sub-category",True,C_WHITE,True), p("Diagnosis / Action",True,C_WHITE,True)],
    [p("NORMAL or HIGH\n(≥275 mOsm/kg)",True,C_ORANGE), p("Normal (275-295)"), p("Pseudohyponatremia\n• Marked hyperlipidemia\n• Marked hyperproteinemia (e.g., myeloma)")],
    ["",                                                   p("High (>295)"),      p("Translocational Hyponatremia\n• Hyperglycemia: Na drops ~1.6 mEq/L per 100 mg/dL glucose rise\n• Mannitol, sorbitol, glycine")],
    [p("LOW (<275 mOsm/kg)\nTRUE Hypotonic\nHyponatremia",True,C_GREEN), p("→ Proceed to Steps 2 & 3",True,C_NAVY,True), p("Assess specific clues then volume status")],
]
osm_t = Table(osm_table_data, colWidths=[38*mm, 38*mm, W-76*mm])
osm_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0),  C_NAVY),
    ("BACKGROUND",    (0,1), (-1,2),  colors.HexColor("#fff3e0")),
    ("BACKGROUND",    (0,3), (-1,3),  colors.HexColor("#e8f5e9")),
    ("GRID",          (0,0), (-1,-1), 0.4, C_BORDER),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("SPAN",          (0,1), (0,2)),
]))
story.append(osm_t)
story.append(sp(4))

# ── STEP 2: SPECIFIC CLUES ────────────────────────────────────────────────────
story.append(section_banner("STEP 2  |  SEARCH FOR SPECIFIC DIAGNOSTIC CLUES (Quick Screen)"))
story.append(sp(2))
clue_rows = [
    ["Thiazide use",                          "Thiazide-induced hyponatremia (re-evaluate if Na fails to improve after stopping)"],
    ["Urine Osm < 100 mOsm/kg",              "Water intoxication: psychogenic polydipsia, beer potomania, Ecstasy/MDMA, low-solute diet"],
    ["Urine Osm < 100 + marathon/exercise",  "Exercise-associated hyponatremia (EAH) - over-hydration with hypotonic fluids"],
    ["Marked hyperglycemia",                  "Hyperglycemic-induced (translocational) - already addressed in Step 1"],
    ["Markedly elevated creatinine",          "Renal failure-associated hyponatremia"],
    ["Hyperkalemia",                          "Renal failure or primary adrenal insufficiency (Addison's)"],
    ["Unexplained hypoglycemia + hyperK",    "Primary adrenal insufficiency (Addison's disease)"],
    ["Normal or elevated serum osmolality",  "Pseudohyponatremia (hyperlipidemia, hyperproteinemia)"],
    ["Recent 'party' / rave attendance",     "Consider MDMA/Ecstasy use - causes SIADH + excessive free water intake"],
]
clue_t = make_table(
    ["Clinical / Lab Finding", "Diagnosis to Consider"],
    clue_rows,
    [72*mm, W-72*mm],
    header_bg=C_TEAL
)
story.append(clue_t)
story.append(sp(2))
story.append(Paragraph(
    "<i><b>If none of the above clues present</b> - proceed to Step 3 (volume status assessment).</i>",
    S("n2", fontSize=8, textColor=C_NAVY, fontName="Helvetica-Oblique")
))
story.append(sp(4))

# ── STEP 3+4: VOLUME STATUS + UNa/UOsm ───────────────────────────────────────
story.append(section_banner("STEPS 3 & 4  |  VOLUME STATUS + URINE SODIUM & OSMOLALITY"))
story.append(sp(2))

# Three-column layout: HYPOVOLEMIC | EUVOLEMIC | HYPERVOLEMIC
def vol_box(title, signs, ddx_items, bg, border_c, title_bg):
    rows = [[Paragraph(title, S("vt", fontName="Helvetica-Bold", fontSize=9,
                                textColor=C_WHITE, alignment=TA_CENTER, leading=12))]]
    rows.append([Paragraph("<i>Signs:</i> " + signs,
                           S("vs", fontSize=7.5, leading=10, textColor=colors.HexColor("#333")))])
    for item in ddx_items:
        rows.append([Paragraph("• " + item,
                               S("vi", fontSize=7.5, leading=10))])
    t = Table(rows, colWidths=["100%"])
    ts = [
        ("BACKGROUND",    (0,0), (-1,0),  title_bg),
        ("BACKGROUND",    (0,1), (-1,-1), bg),
        ("BOX",           (0,0), (-1,-1), 1.2, border_c),
        ("TOPPADDING",    (0,0), (-1,-1), 4),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("LEFTPADDING",   (0,0), (-1,-1), 5),
        ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ]
    t.setStyle(TableStyle(ts))
    return t

hypo_box = vol_box(
    "HYPOVOLEMIC",
    "Dry mucous membranes, decreased turgor, tachycardia, orthostasis, low JVP",
    [
        "UNa < 20: Extra-renal losses",
        "  - Vomiting / diarrhea",
        "  - Skin losses (burns, sweating)",
        "  - Third-spacing",
        "UNa > 20: Renal Na wasting",
        "  - Thiazide/loop diuretics",
        "  - Salt-wasting nephropathy",
        "  - Primary adrenal insufficiency",
        "  - Cerebral salt wasting (CSW)",
        "  - Mineralocorticoid deficiency",
    ],
    colors.HexColor("#fff8e1"), C_ORANGE, C_ORANGE
)

eu_box = vol_box(
    "EUVOLEMIC",
    "No edema, no orthostasis, no signs of volume depletion or overload",
    [
        "UOsm < 100: ADH suppressed",
        "  - Psychogenic polydipsia",
        "  - Beer potomania",
        "  - Ecstasy / MDMA",
        "  - Low-solute diet",
        "UOsm > 100: ADH active",
        "  UNa < 20: Subtle hypovolemia",
        "  UNa > 30: Check TSH & cortisol",
        "    TSH elevated → Hypothyroidism",
        "    Cortisol <16.3 → Adrenal insuff.",
        "    All excluded → SIADH",
    ],
    colors.HexColor("#e8f5e9"), C_GREEN, C_GREEN
)

hyper_box = vol_box(
    "HYPERVOLEMIC",
    "Edema, ascites, elevated JVP, S3 gallop, pulmonary crackles",
    [
        "UNa < 20: Low effective volume",
        "  - Heart failure",
        "  - Cirrhosis",
        "  - Nephrotic syndrome",
        "UNa > 20: Renal Na loss",
        "  - Advanced renal failure",
        "    (GFR < 5-10 mL/min)",
        "",
        "Note: HF/cirrhosis - low effective",
        "  arterial volume despite edema",
        "  triggers both ADH + Na retention",
    ],
    colors.HexColor("#fce4ec"), C_NAVY, C_NAVY
)

col_w = (W - 4*mm) / 3
vol_table = Table([[hypo_box, eu_box, hyper_box]], colWidths=[col_w]*3,
                  rowHeights=None)
vol_table.setStyle(TableStyle([
    ("TOPPADDING",    (0,0), (-1,-1), 0),
    ("BOTTOMPADDING", (0,0), (-1,-1), 0),
    ("LEFTPADDING",   (0,0), (-1,-1), 2),
    ("RIGHTPADDING",  (0,0), (-1,-1), 2),
    ("VALIGN",        (0,0), (-1,-1), "TOP"),
]))
story.append(vol_table)
story.append(sp(4))

# ── SIADH DIAGNOSTIC CRITERIA ─────────────────────────────────────────────────
story.append(section_banner("SIADH DIAGNOSTIC CRITERIA  (Diagnosis of Exclusion)"))
story.append(sp(2))
siadh_cols = W / 2 - 2*mm
siadh_crit = [
    "Hypotonic hyponatremia (Posm < 275 mOsm/kg)",
    "Urine osmolality inappropriately elevated (>100 mOsm/kg, usually >200)",
    "Urine sodium > 20-40 mEq/L (ongoing renal Na excretion)",
    "Clinical euvolemia (no edema, no volume depletion)",
    "Normal adrenal, renal, cardiac, hepatic, and thyroid function",
]
siadh_causes_left = [
    "<b>CNS:</b> Stroke, trauma, infection, hemorrhage, mass",
    "<b>Pulmonary:</b> Pneumonia, TB, respiratory failure, abscess",
    "<b>Malignancy:</b> Small cell lung (10-15%), head/neck, lymphoma",
    "<b>Drugs:</b> SSRIs, carbamazepine, cyclophosphamide, vincristine, NSAIDs, opioids",
]
siadh_causes_right = [
    "<b>Postoperative:</b> Pain/nausea trigger non-osmotic ADH",
    "<b>HIV/AIDS</b>",
    "<b>Exercise</b> with excessive hypotonic fluid intake",
    "<b>Idiopathic</b> (diagnosis of exclusion after all causes excluded)",
]

crit_data = [[Paragraph("• " + c, S("sc", fontSize=8, leading=11))] for c in siadh_crit]
crit_t = Table(crit_data, colWidths=["100%"])
crit_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), C_TEAL_L),
    ("BOX",           (0,0), (-1,-1), 0.8, C_TEAL),
    ("INNERGRID",     (0,0), (-1,-1), 0.2, C_BORDER),
    ("TOPPADDING",    (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
]))

left_causes  = [[Paragraph("• " + c, S("cc", fontSize=7.5, leading=10))] for c in siadh_causes_left]
right_causes = [[Paragraph("• " + c, S("cc2", fontSize=7.5, leading=10))] for c in siadh_causes_right]

lc_t = Table(left_causes, colWidths=["100%"])
lc_t.setStyle(TableStyle([
    ("TOPPADDING",    (0,0), (-1,-1), 2),
    ("BOTTOMPADDING", (0,0), (-1,-1), 2),
    ("LEFTPADDING",   (0,0), (-1,-1), 3),
]))
rc_t = Table(right_causes, colWidths=["100%"])
rc_t.setStyle(TableStyle([
    ("TOPPADDING",    (0,0), (-1,-1), 2),
    ("BOTTOMPADDING", (0,0), (-1,-1), 2),
    ("LEFTPADDING",   (0,0), (-1,-1), 3),
]))

siadh_hdr_l = Paragraph("<b>All 5 criteria required:</b>",
                          S("sh", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_TEAL))
siadh_hdr_c = Paragraph("<b>Common Causes (Left):</b>",
                          S("sh2", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_NAVY))
siadh_hdr_r = Paragraph("<b>Common Causes (Right):</b>",
                          S("sh3", fontName="Helvetica-Bold", fontSize=8.5, textColor=C_NAVY))

siadh_layout = Table(
    [[siadh_hdr_l, siadh_hdr_c, siadh_hdr_r],
     [crit_t,      lc_t,         rc_t]],
    colWidths=[W*0.38, W*0.31, W*0.31]
)
siadh_layout.setStyle(TableStyle([
    ("TOPPADDING",    (0,0), (-1,-1), 2),
    ("BOTTOMPADDING", (0,0), (-1,-1), 2),
    ("LEFTPADDING",   (0,0), (-1,-1), 3),
    ("RIGHTPADDING",  (0,0), (-1,-1), 3),
    ("VALIGN",        (0,0), (-1,-1), "TOP"),
]))
story.append(siadh_layout)
story.append(sp(4))

# ── SUMMARY TABLE ─────────────────────────────────────────────────────────────
story.append(section_banner("DIAGNOSTIC SUMMARY TABLE  |  UNa + Urine Osm by Category"))
story.append(sp(2))

sum_data = [
    [p("Volume Status",True,C_WHITE,True), p("UNa (mEq/L)",True,C_WHITE,True),
     p("Urine Osm",True,C_WHITE,True), p("Likely Etiology",True,C_WHITE,True)],
    [p("Hypovolemic"), p("< 20",True,C_GREEN), p("> 100"), p("GI/skin losses, third-spacing, burns")],
    [p("Hypovolemic"), p("> 20",True,C_RED),   p("> 100"), p("Diuretics, CSW, salt-wasting, adrenal insufficiency")],
    [p("Hypervolemic"), p("< 20",True,C_GREEN), p("> 100"), p("Heart failure, cirrhosis, nephrotic syndrome")],
    [p("Hypervolemic"), p("> 20",True,C_RED),   p("Variable"),p("Advanced renal failure (GFR < 5-10 mL/min)")],
    [p("Euvolemic"),   p("< 20",True,C_GREEN), p("< 100",True,C_ORANGE), p("Primary polydipsia, beer potomania, Ecstasy, low-solute diet")],
    [p("Euvolemic"),   p("> 40",True,C_RED),   p("> 100",True,C_RED),    p("SIADH, hypothyroidism, adrenal insufficiency")],
]
sum_t = Table(sum_data, colWidths=[32*mm, 22*mm, 22*mm, W-76*mm])
sum_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,0),  C_NAVY),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_GREY]),
    ("GRID",          (0,0), (-1,-1), 0.4, C_BORDER),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 5),
    ("RIGHTPADDING",  (0,0), (-1,-1), 5),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
]))
story.append(sum_t)
story.append(sp(2))
story.append(Paragraph(
    "<i>Rule of thumb: UNa &lt; 10 mEq/L found almost exclusively in edematous syndromes (HF, cirrhosis) and GI losses with vomiting/diarrhea.</i>",
    S("note2", fontSize=7.5, textColor=colors.HexColor("#555"), fontName="Helvetica-Oblique")
))
story.append(sp(4))

# ── CORRECTION LIMITS ─────────────────────────────────────────────────────────
story.append(section_banner("CORRECTION LIMITS & TREATMENT  (CRITICAL - Risk of ODS)", bg=colors.HexColor("#8b1a1a")))
story.append(sp(2))

corr_rows = [
    ["Severe symptoms\n(seizures, obtundation,\ncoma, respiratory arrest)",
     "3% NaCl 100 mL IV bolus over ~10 min\nRepeat x2 PRN (max 3 boluses)\nTarget: raise Na by 4-6 mEq/L to break symptoms",
     "No absolute max in acute phase;\nstop once symptoms resolve;\nthen resume standard limits"],
    ["Acute hyponatremia\n(onset < 24-48 h)",
     "Correct underlying cause\n3% NaCl if symptomatic",
     "Up to 1-2 mEq/L/hr initially;\nno strict 24h cap in truly acute settings"],
    ["Chronic / unknown\nduration - LOW risk\n(no liver disease,\nno alcoholism,\nno malnutrition)",
     "Fluid restriction (SIADH)\nAddress underlying cause\nLoop diuretics + saline (hypovolemic)\nTolvaptan (SIADH, selected cases)",
     "≤ 10-12 mEq/L per 24 hours\n≤ 18 mEq/L per 48 hours"],
    ["Chronic / unknown\nduration - HIGH risk\n(cirrhosis, alcoholism,\nmalnutrition, K < 3,\nNa < 105 mEq/L)",
     "Same as above but more cautious;\nconsider prophylactic desmopressin\nto prevent overcorrection",
     "≤ 6-8 mEq/L per 24 hours\n≤ 14-18 mEq/L per 48 hours"],
]

def corr_p(txt, bold=False, color=colors.black):
    fn = "Helvetica-Bold" if bold else "Helvetica"
    return Paragraph(txt.replace("\n","<br/>"), S("cp", fontName=fn, fontSize=8,
                                                   textColor=color, leading=11))

corr_data = [
    [corr_p("Clinical Scenario", True, C_WHITE),
     corr_p("Treatment Approach", True, C_WHITE),
     corr_p("Correction Rate Limit", True, C_WHITE)],
    [corr_p("SEVERE SYMPTOMS\n(seizures, obtundation,\ncoma, cardiorespiratory\narrest)", True, C_RED),
     corr_p("3% NaCl 100 mL IV bolus over ~10-15 min\nMay repeat x2 (max 3 boluses, 300 mL)\nTarget: raise Na by 4-6 mEq/L to abort symptoms"),
     corr_p("No hard cap during acute rescue;\nstop once symptoms resolve;\nthen resume standard rate limits", True, C_RED)],
    [corr_p("ACUTE\n(onset < 24-48 h,\nclearly documented)"),
     corr_p("Treat underlying cause\n3% NaCl if symptomatic"),
     corr_p("Up to 1-2 mEq/L/hr initially;\ncorrect to ~125 mEq/L then slow")],
    [corr_p("CHRONIC or UNKNOWN\nLOW risk patient"),
     corr_p("Fluid restriction (SIADH)\nCausal treatment\nLoop diuretics + NS (hypovolemic)\nTolvaptan (selected SIADH cases)"),
     corr_p("≤ 10-12 mEq/L / 24 h\n≤ 18 mEq/L / 48 h", True, C_NAVY)],
    [corr_p("CHRONIC or UNKNOWN\nHIGH risk patient\n(cirrhosis, alcoholism,\nmalnutrition, K+ < 3,\nNa+ < 105 mEq/L)", True, C_ORANGE),
     corr_p("Same as above\nConsider prophylactic desmopressin\n(1-2 mcg IV/SC q6-8h) to prevent\naccidental overcorrection"),
     corr_p("≤ 6-8 mEq/L / 24 h\n≤ 14 mEq/L / 48 h", True, C_RED)],
]

corr_t = Table(corr_data, colWidths=[42*mm, W*0.42, W*0.32])
corr_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0),  (-1,0),  colors.HexColor("#8b1a1a")),
    ("BACKGROUND",    (0,1),  (-1,1),  colors.HexColor("#fdecea")),
    ("BACKGROUND",    (0,2),  (-1,2),  colors.HexColor("#fff9f0")),
    ("BACKGROUND",    (0,3),  (-1,3),  C_GREY),
    ("BACKGROUND",    (0,4),  (-1,4),  colors.HexColor("#fff3cd")),
    ("GRID",          (0,0),  (-1,-1), 0.5, C_BORDER),
    ("TOPPADDING",    (0,0),  (-1,-1), 5),
    ("BOTTOMPADDING", (0,0),  (-1,-1), 5),
    ("LEFTPADDING",   (0,0),  (-1,-1), 5),
    ("RIGHTPADDING",  (0,0),  (-1,-1), 5),
    ("VALIGN",        (0,0),  (-1,-1), "MIDDLE"),
]))
story.append(corr_t)
story.append(sp(3))

# ODS warning box
ods_data = [[
    Paragraph(
        "<b>&#9888; OSMOTIC DEMYELINATION SYNDROME (ODS) WARNING</b><br/>"
        "ODS (formerly central pontine myelinolysis) is caused by overly RAPID correction of chronic hyponatremia. "
        "Risk is highest with Na &lt;105, liver disease, alcoholism, and malnutrition. "
        "Symptoms (dysarthria, dysphagia, quadriplegia, locked-in syndrome) appear 2-6 days after correction. "
        "If overcorrection occurs: stop hypertonic saline, give desmopressin (DDAVP) 1-2 mcg IV + free water to re-lower Na.",
        S("ods", fontName="Helvetica", fontSize=8, textColor=colors.HexColor("#5d0000"),
          leading=12)
    )
]]
ods_t = Table(ods_data, colWidths=[W])
ods_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), colors.HexColor("#fdecea")),
    ("BOX",           (0,0), (-1,-1), 1.5, C_RED),
    ("TOPPADDING",    (0,0), (-1,-1), 6),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
]))
story.append(ods_t)
story.append(sp(3))

# ── OVERCORRECTION MANAGEMENT ─────────────────────────────────────────────────
story.append(section_banner("IF OVERCORRECTION OCCURS  |  Rescue Protocol", bg=C_TEAL))
story.append(sp(2))
over_rows = [
    ["Stop all hypertonic saline and any high-Na infusions immediately"],
    ["Give Desmopressin (DDAVP) 1-2 mcg IV or SC every 6-8 hours"],
    ["Give free water: D5W 3 mL/kg IV or oral water to re-lower serum Na"],
    ["Target: bring Na back down by ~2 mEq/L to within correction limits"],
    ["Monitor serum Na every 2-4 hours until stable"],
    ["Consult nephrology for ongoing management"],
]
over_data = [[Paragraph("• " + r[0], S("or", fontSize=8.5, leading=12))] for r in over_rows]
over_t = Table(over_data, colWidths=[W])
over_t.setStyle(TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), C_TEAL_L),
    ("INNERGRID",     (0,0), (-1,-1), 0.2, C_BORDER),
    ("BOX",           (0,0), (-1,-1), 0.8, C_TEAL),
    ("TOPPADDING",    (0,0), (-1,-1), 3),
    ("BOTTOMPADDING", (0,0), (-1,-1), 3),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
]))
story.append(over_t)
story.append(sp(3))

# ── FOOTER ─────────────────────────────────────────────────────────────────────
story.append(hr(C_NAVY, 1))
story.append(Paragraph(
    "Sources: Harrison's Principles of Internal Medicine 22e &nbsp;|&nbsp; "
    "Tintinalli's Emergency Medicine &nbsp;|&nbsp; "
    "Symptom to Diagnosis 4e &nbsp;|&nbsp; "
    "Brenner &amp; Rector's The Kidney &nbsp;|&nbsp; "
    "ROSEN's Emergency Medicine &nbsp;|&nbsp; "
    "Generated by Orris Clinical Reference",
    S("foot", fontSize=7, textColor=colors.HexColor("#888"), alignment=TA_CENTER)
))

# ── BUILD ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved: {OUTPUT}")
Running Command

cd /home/daytona/workspace/hyponatremia-pdf && python3 build_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/hyponatremia-pdf/Hyponatremia_Quick_Reference.pdf

The PDF built successfully. Here it is:

Hyponatremia Quick Reference

PDF Document · PDF

The quick reference PDF covers:
  • Step 0 - Initial labs checklist (serum Osm, urine Osm, urine Na, TSH, cortisol, etc.)
  • Step 1 - Serum osmolality branch point (pseudohyponatremia vs. translocational vs. true hypotonic)
  • Step 2 - Specific diagnostic clue screen (thiazides, Uosm < 100, hyperglycemia, hyperkalemia, party/Ecstasy, marathon, etc.)
  • Steps 3 & 4 - Three-column volume status layout (hypovolemic / euvolemic / hypervolemic) with UNa and Uosm cutoffs for each
  • SIADH criteria - All 5 diagnostic criteria + common causes organized in columns
  • Summary table - UNa + Uosm by volume category mapped to etiology
  • Correction limits table - Severe symptoms / acute / chronic low-risk / chronic high-risk scenarios with exact mEq/L/24h caps
  • ODS warning box - Red-bordered alert with rescue desmopressin + D5W protocol for overcorrection
This is a shared conversation. Sign in to Orris to start your own chat.