Hyponatremia workup algorithm

Reading File
Now let me search the library for hyponatremia content in parallel:
Finding Sources
Finding Sources
Searching the Web

hyponatremia workup diagnostic algorithm serum osmolality urine sodium 2024

Good results. Let me read the most comprehensive sources - Brenner's Kidney and Comprehensive Clinical Nephrology in parallel:
Reading File
Reading File
Finding Sources
I can see a flowchart in Brenner's was truncated and there's great content in Symptom to Diagnosis and Harrison's. Let me read those in parallel:
Reading File
Reading File
Reading File
Excellent - rich content. Let me also fetch the diagnostic flowchart image and read the Symptom to Diagnosis algorithm continuation:
Loading Image
Reading File
Finding Sources
Let me fetch the second flowchart and read the Tintinalli table on UNa/Uosm values:
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 found"

Running Command

mkdir -p /home/daytona/workspace/hyponatremia-pdf && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "from reportlab.lib.pagesizes import A4; print('A4 ok')"

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.