Ne0hritic vs nephritic syndrome
| Feature | Nephrotic | Nephritic |
|---|---|---|
| Onset | Insidious | Abrupt |
| Mechanism | Increased GBM permeability | Glomerular inflammation |
| Proteinuria | Massive (>3.5 g/day) | Mild-moderate (<3.5 g/day) |
| Hematuria | Absent or microscopic | Prominent (gross or microscopic) |
| RBC casts | Absent | Present (pathognomonic) |
| Serum albumin | Low (<3 g/dL) | Normal or slightly reduced |
| Edema | Severe, soft, pitting, periorbital | Mild-moderate |
| Blood pressure | Normal | Raised (hypertension) |
| JVP | Normal/low | Raised |
| GFR/azotemia | Usually preserved initially | Reduced, oliguria, azotemia |
| Urine color | Frothy (protein) | Smoky/brown (blood) |
| Lipids | Hyperlipidemia + lipiduria | Normal |
| Complement | Usually normal (except MPGN) | Often low (C3 low in PSAGN) |
| Finding | Nephrotic | Nephritic |
|---|---|---|
| Protein | +++++ | ++ |
| RBCs | Rare | +++ |
| RBC casts | No | Yes |
| WBCs | No | Possible |
| Oval fat bodies | Yes (lipiduria) | No |
| Fatty casts | Yes | No |
| Disease | Children | Adults |
|---|---|---|
| Minimal change disease (MCD) | 75% | 8% |
| Focal segmental glomerulosclerosis (FSGS) | 10% | 35% |
| Membranous nephropathy | 3% | 30% |
| MPGN | 10% | 10% |
| Disease | Key Feature |
|---|---|
| Post-streptococcal GN (PSAGN) | Latency 1-4 wks; subepithelial "humps" on EM; ↓C3 |
| IgA nephropathy (Berger's) | Episodic hematuria with URI; mesangial IgA deposits |
| Lupus nephritis (class III/IV) | Anti-dsDNA, ↓C3/C4 |
| ANCA-associated vasculitis (GPA, MPA) | pANCA/cANCA positive; pauci-immune |
| Anti-GBM disease (Goodpasture's) | Linear IgG on IF; pulmonary hemorrhage |
| MPGN | Can present as either nephrotic or nephritic; ↓C3 |
| Type | Mechanism | IF Pattern | Serology |
|---|---|---|---|
| Type I | Anti-GBM antibodies | Linear IgG | Anti-GBM Ab |
| Type II | Immune complex | Granular ("lumpy-bumpy") | Varies (ANA, ASOT, ↓C3) |
| Type III | Pauci-immune (ANCA) | Negative / trace | ANCA positive |

| Clue | Think |
|---|---|
| Frothy urine, periorbital puffiness, no hematuria | Nephrotic |
| Cola/tea-colored urine, hypertension, oliguria | Nephritic |
| Child with MCD | Nephrotic, steroid-responsive |
| Diabetic for 10+ years with proteinuria | Secondary nephrotic |
| Post-sore throat hematuria in child, ↓C3 | Post-streptococcal (nephritic) |
| Recurrent hematuria with upper respiratory infections | IgA nephropathy (nephritic) |
| Hemoptysis + hematuria, anti-GBM+ | Goodpasture's (nephritic/RPGN) |
| Rapidly deteriorating kidney function with crescents | RPGN |
| Can present as EITHER | MPGN |
Note: Some diseases - especially MPGN and lupus nephritis - blur the boundary and can present with mixed features of both syndromes simultaneously.
Create a clinical decision tree to differentiate these syndromes
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/nephro-decision-tree && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "from reportlab.lib import colors; print('colors ok')"
~/nephro-decision-tree/build_tree.py
#!/usr/bin/env python3
"""
Clinical Decision Tree: Nephrotic vs Nephritic Syndrome
Renders a full A3-landscape PDF using ReportLab
"""
from reportlab.lib.pagesizes import A3, landscape
from reportlab.lib import colors
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.fonts import addMapping
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import textwrap
OUTPUT = "/home/daytona/workspace/nephro-decision-tree/Nephro_Decision_Tree.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
C_BG = colors.HexColor("#F7F9FC")
C_START = colors.HexColor("#2C3E50") # dark slate – root node
C_QUESTION = colors.HexColor("#2980B9") # blue – decision diamonds
C_NEPHROTIC = colors.HexColor("#1A7A4A") # green – nephrotic branch
C_NEPHRITIC = colors.HexColor("#C0392B") # red – nephritic branch
C_MIXED = colors.HexColor("#7D3C98") # purple – mixed/MPGN
C_RPGN = colors.HexColor("#D35400") # orange – RPGN
C_DIAGNOSIS = colors.HexColor("#17202A") # near-black – final diagnosis boxes
C_ARROW = colors.HexColor("#555555")
C_YES = colors.HexColor("#27AE60")
C_NO = colors.HexColor("#E74C3C")
C_WHITE = colors.white
C_LIGHT_GRAY = colors.HexColor("#ECF0F1")
PW, PH = landscape(A3) # 420 × 297 mm → pt: ~1191 × 842
c = canvas.Canvas(OUTPUT, pagesize=landscape(A3))
c.setTitle("Clinical Decision Tree: Nephrotic vs Nephritic Syndrome")
# ── Helper functions ──────────────────────────────────────────────────────────
def rounded_rect(cv, x, y, w, h, r=8, fill_color=C_WHITE, stroke_color=C_QUESTION, lw=1.5):
cv.setFillColor(fill_color)
cv.setStrokeColor(stroke_color)
cv.setLineWidth(lw)
cv.roundRect(x, y, w, h, r, stroke=1, fill=1)
def diamond(cv, cx, cy, w, h, fill_color=C_QUESTION, stroke_color=None):
if stroke_color is None:
stroke_color = fill_color.clone() if hasattr(fill_color, 'clone') else fill_color
cv.setFillColor(fill_color)
cv.setStrokeColor(colors.HexColor("#1A252F"))
cv.setLineWidth(1.5)
p = cv.beginPath()
p.moveTo(cx, cy + h/2) # top
p.lineTo(cx + w/2, cy) # right
p.lineTo(cx, cy - h/2) # bottom
p.lineTo(cx - w/2, cy) # left
p.close()
cv.drawPath(p, stroke=1, fill=1)
def wrap_text_in_box(cv, text, x, y, w, h, font="Helvetica-Bold", size=8,
text_color=C_WHITE, align="center", padding=4):
cv.setFont(font, size)
cv.setFillColor(text_color)
lines = []
# simple word-wrap
words = text.split()
line = ""
for word in words:
test = (line + " " + word).strip()
if cv.stringWidth(test, font, size) <= w - padding*2:
line = test
else:
if line:
lines.append(line)
line = word
if line:
lines.append(line)
line_h = size * 1.3
total_h = len(lines) * line_h
start_y = y + h/2 + total_h/2 - line_h * 0.85
for l in lines:
if align == "center":
cv.drawCentredString(x + w/2, start_y, l)
else:
cv.drawString(x + padding, start_y, l)
start_y -= line_h
def wrap_text_diamond(cv, text, cx, cy, w, h, font="Helvetica-Bold", size=7.5,
text_color=C_WHITE):
cv.setFont(font, size)
cv.setFillColor(text_color)
words = text.split()
lines = []
line = ""
max_w = w * 0.65
for word in words:
test = (line + " " + word).strip()
if cv.stringWidth(test, font, size) <= max_w:
line = test
else:
if line:
lines.append(line)
line = word
if line:
lines.append(line)
line_h = size * 1.3
total_h = len(lines) * line_h
start_y = cy + total_h/2 - line_h * 0.7
for l in lines:
cv.drawCentredString(cx, start_y, l)
start_y -= line_h
def arrow(cv, x1, y1, x2, y2, label="", label_color=C_ARROW, lw=1.5, head=7):
cv.setStrokeColor(C_ARROW)
cv.setFillColor(C_ARROW)
cv.setLineWidth(lw)
cv.line(x1, y1, x2, y2)
# arrowhead
import math
angle = math.atan2(y2 - y1, x2 - x1)
for da in [2.5, -2.5]:
ax = x2 - head * math.cos(angle + da)
ay = y2 - head * math.sin(angle + da)
cv.line(x2, y2, ax, ay)
if label:
mx = (x1 + x2) / 2
my = (y1 + y2) / 2
cv.setFont("Helvetica-Bold", 7.5)
cv.setFillColor(label_color)
cv.drawCentredString(mx, my + 4, label)
def elbow_arrow(cv, x1, y1, x2, y2, bend_x=None, bend_y=None, label="",
label_side="left", lw=1.5, head=7, label_color=C_ARROW):
import math
cv.setStrokeColor(C_ARROW)
cv.setFillColor(C_ARROW)
cv.setLineWidth(lw)
if bend_x is not None and bend_y is None:
# L-shaped: go right/left then up/down
cv.line(x1, y1, bend_x, y1)
cv.line(bend_x, y1, bend_x, y2)
cv.line(bend_x, y2, x2, y2)
angle = math.atan2(y2 - y2, x2 - bend_x) # horizontal segment end
for da in [2.5, -2.5]:
ax = x2 - head * math.cos(angle + da)
ay = y2 - head * math.sin(angle + da)
cv.line(x2, y2, ax, ay)
if label:
cv.setFont("Helvetica-Bold", 7.5)
cv.setFillColor(label_color)
if label_side == "left":
cv.drawString(x1 + 3, y1 + 4, label)
else:
cv.drawCentredString(bend_x, (y1 + y2)/2, label)
elif bend_y is not None and bend_x is None:
# vertical then horizontal
cv.line(x1, y1, x1, bend_y)
cv.line(x1, bend_y, x2, bend_y)
cv.line(x2, bend_y, x2, y2)
angle = math.atan2(y2 - bend_y, 0)
for da in [2.5, -2.5]:
ax = x2 - head * math.cos(angle + da)
ay = y2 - head * math.sin(angle + da)
cv.line(x2, y2, ax, ay)
if label:
cv.setFont("Helvetica-Bold", 7.5)
cv.setFillColor(label_color)
cv.drawCentredString((x1+x2)/2, bend_y + 4, label)
# ══════════════════════════════════════════════════════════════════════════════
# PAGE BACKGROUND
# ══════════════════════════════════════════════════════════════════════════════
c.setFillColor(C_BG)
c.rect(0, 0, PW, PH, fill=1, stroke=0)
# ── Title bar ─────────────────────────────────────────────────────────────────
c.setFillColor(C_START)
c.rect(0, PH - 42, PW, 42, fill=1, stroke=0)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(PW/2, PH - 28, "Clinical Decision Tree: Differentiating Nephrotic vs. Nephritic Syndrome")
c.setFont("Helvetica", 9)
c.drawCentredString(PW/2, PH - 38, "Based on clinical presentation, urinalysis, and biochemistry | Robbins Pathology · Comprehensive Clinical Nephrology")
# ── Legend ─────────────────────────────────────────────────────────────────────
leg_x = PW - 185
leg_y = PH - 105
c.setFillColor(C_LIGHT_GRAY)
c.setStrokeColor(colors.HexColor("#BDC3C7"))
c.setLineWidth(0.8)
c.roundRect(leg_x - 8, leg_y - 8, 183, 65, 6, stroke=1, fill=1)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(C_START)
c.drawString(leg_x, leg_y + 48, "LEGEND")
items = [
(C_START, "Entry / Start"),
(C_QUESTION, "Decision point (diamond)"),
(C_NEPHROTIC, "Nephrotic pathway"),
(C_NEPHRITIC, "Nephritic pathway"),
(C_MIXED, "Mixed / MPGN"),
(C_RPGN, "RPGN / Crescentic GN"),
]
for i, (col, lbl) in enumerate(items):
row = i % 3
col_offset = 0 if i < 3 else 92
ly = leg_y + 36 - row * 14
c.setFillColor(col)
c.rect(leg_x + col_offset, ly, 10, 8, fill=1, stroke=0)
c.setFillColor(C_START)
c.setFont("Helvetica", 7.5)
c.drawString(leg_x + col_offset + 13, ly + 1, lbl)
# ══════════════════════════════════════════════════════════════════════════════
# TREE LAYOUT (coordinates in points; origin = bottom-left)
# We work top-down. PH ≈ 842 pt. Top of content ≈ PH-55 = 787
# Columns: LEFT=nephrotic CENTRE=shared entry RIGHT=nephritic
# ══════════════════════════════════════════════════════════════════════════════
# Row Y positions (top of each node row, rough)
Y = {
"start": 762,
"q1": 700, # Proteinuria ≥3.5 g/day?
"q2l": 630, # Hematuria / RBC casts? (left branch = no)
"q2r": 630, # same row – right branch = yes
"q3l": 555, # Serum albumin <3 g/dL?
"q3r": 555, # GFR reduced / oliguria?
"q4l": 480, # Complement low?
"q4r": 480, # Rapidly deteriorating Cr?
"diag_nt": 395, # nephrotic diagnoses
"diag_ni": 395, # nephritic diagnoses
"rpgn": 340,
}
CX = PW / 2 # centre x
# Node dimensions
NW, NH = 155, 38 # normal rect box
DW, DH = 148, 46 # diamond
BW, BH = 160, 50 # big diagnosis box
# ── ROOT NODE ─────────────────────────────────────────────────────────────────
rx = CX - NW/2
ry = Y["start"]
rounded_rect(c, rx, ry, NW, NH, r=10, fill_color=C_START, stroke_color=C_START, lw=2)
wrap_text_in_box(c, "PATIENT WITH SUSPECTED GLOMERULAR DISEASE", rx, ry, NW, NH, size=8)
# arrow down
arrow(c, CX, ry, CX, Y["q1"] + DH)
# ── Q1 : Proteinuria ≥3.5 g/day? ─────────────────────────────────────────────
q1cx, q1cy = CX, Y["q1"] + DH/2
diamond(c, q1cx, q1cy, DW, DH, fill_color=C_QUESTION)
wrap_text_diamond(c, "Proteinuria ≥ 3.5 g/day\n(nephrotic-range)?", q1cx, q1cy, DW, DH, size=8)
# YES → left (nephrotic)
LEFT_X = CX - 280
arrow(c, q1cx - DW/2, q1cy, LEFT_X + NW/2, q1cy, label="YES", label_color=C_YES)
# NO → right (nephritic)
RIGHT_X = CX + 280 - NW/2
arrow(c, q1cx + DW/2, q1cy, RIGHT_X + NW/2, q1cy, label="NO (sub-nephrotic)", label_color=C_NO)
# ── Q2-LEFT : Hematuria / RBC casts present? (nephrotic branch has low/none) ─
q2l_cx = LEFT_X + NW/2
q2l_cy = Y["q2l"] + DH/2
# first drop arrow on left
arrow(c, q2l_cx, q1cy, q2l_cx, Y["q2l"] + DH)
diamond(c, q2l_cx, q2l_cy, DW, DH, fill_color=C_NEPHROTIC)
wrap_text_diamond(c, "RBC casts or gross\nhematuria present?", q2l_cx, q2l_cy, DW, DH, size=8)
# ── Q2-RIGHT : Check hematuria in sub-nephrotic proteinuria ───────────────────
q2r_cx = RIGHT_X + NW/2
q2r_cy = Y["q2r"] + DH/2
arrow(c, q2r_cx, q1cy, q2r_cx, Y["q2r"] + DH)
diamond(c, q2r_cx, q2r_cy, DW, DH, fill_color=C_NEPHRITIC)
wrap_text_diamond(c, "Hematuria / RBC casts\n+ azotemia present?", q2r_cx, q2r_cy, DW, DH, size=8)
# ── Q3-LEFT (from Q2L NO): Serum albumin <3 g/dL? ────────────────────────────
q3l_cx = q2l_cx
q3l_cy = Y["q3l"] + DH/2
# NO arm goes down
arrow(c, q3l_cx, q2l_cy - DH/2, q3l_cx, Y["q3l"] + DH, label="NO", label_color=C_NO)
diamond(c, q3l_cx, q3l_cy, DW, DH, fill_color=C_NEPHROTIC)
wrap_text_diamond(c, "Serum albumin < 3 g/dL\n+ hyperlipidemia?", q3l_cx, q3l_cy, DW, DH, size=8)
# YES arm from Q2L → mixed / re-evaluate
mixed_cx = (q2l_cx + q2r_cx) / 2
mixed_cy = Y["q3l"] + DH/2
arrow(c, q2l_cx + DW/2, q2l_cy, mixed_cx - NW/2, mixed_cy, label="YES (mixed)", label_color=C_MIXED)
rounded_rect(c, mixed_cx - NW/2, Y["q3l"], NW, NH, r=8, fill_color=C_MIXED, stroke_color=C_MIXED)
wrap_text_in_box(c, "MIXED FEATURES\nConsider MPGN / Lupus nephritis", mixed_cx - NW/2, Y["q3l"], NW, NH, size=8)
# ── Q3-RIGHT (from Q2R YES): GFR reduced / oliguria? ─────────────────────────
q3r_cx = q2r_cx
q3r_cy = Y["q3r"] + DH/2
arrow(c, q3r_cx, q2r_cy - DH/2, q3r_cx, Y["q3r"] + DH, label="YES", label_color=C_YES)
diamond(c, q3r_cx, q3r_cy, DW, DH, fill_color=C_NEPHRITIC)
wrap_text_diamond(c, "Hypertension + ↓GFR\n/ oliguria present?", q3r_cx, q3r_cy, DW, DH, size=8)
# Q2R NO → recheck (could be mild glomerular disease, not classic)
recheck_x = q2r_cx + 200
recheck_y = Y["q2r"]
arrow(c, q2r_cx + DW/2, q2r_cy, recheck_x, q2r_cy, label="NO", label_color=C_NO)
rounded_rect(c, recheck_x, recheck_y, NW - 10, NH, r=8, fill_color=colors.HexColor("#7F8C8D"), stroke_color=colors.HexColor("#7F8C8D"))
wrap_text_in_box(c, "Non-glomerular cause\nor isolated microscopic\nhematuria – further W/U", recheck_x, recheck_y, NW - 10, NH, size=7)
# ── Q4-LEFT: Complement (C3) low? ─────────────────────────────────────────────
q4l_cx = q3l_cx
q4l_cy = Y["q4l"] + DH/2
arrow(c, q4l_cx, q3l_cy - DH/2, q4l_cx, Y["q4l"] + DH, label="YES", label_color=C_YES)
diamond(c, q4l_cx, q4l_cy, DW, DH, fill_color=C_NEPHROTIC)
wrap_text_diamond(c, "Serum C3 low\nor proteinuria > 3.5 g?", q4l_cx, q4l_cy, DW, DH, size=8)
# Q3L NO → reassess
q3l_no_x = q3l_cx - 215
q3l_no_y = Y["q3l"]
arrow(c, q3l_cx - DW/2, q3l_cy, q3l_no_x + NW/2, q3l_cy, label="NO", label_color=C_NO)
rounded_rect(c, q3l_no_x - NW/2 + 20, q3l_no_y, NW, NH, r=8, fill_color=colors.HexColor("#7F8C8D"), stroke_color=colors.HexColor("#7F8C8D"))
wrap_text_in_box(c, "Re-evaluate:\nOrthostatic / overflow\nproteinuria", q3l_no_x - NW/2 + 20, q3l_no_y, NW, NH, size=7.5)
# ── Q4-RIGHT: Rapidly progressive (Cr doubles < 3 months)? ───────────────────
q4r_cx = q3r_cx
q4r_cy = Y["q4r"] + DH/2
arrow(c, q4r_cx, q3r_cy - DH/2, q4r_cx, Y["q4r"] + DH, label="YES", label_color=C_YES)
diamond(c, q4r_cx, q4r_cy, DW, DH, fill_color=C_NEPHRITIC)
wrap_text_diamond(c, "Cr doubles in < 3 months\nor uremic emergency?", q4r_cx, q4r_cy, DW, DH, size=8)
# Q3R NO
q3r_no_x = q3r_cx + 200
q3r_no_y = Y["q3r"]
arrow(c, q3r_cx + DW/2, q3r_cy, q3r_no_x, q3r_cy, label="NO", label_color=C_NO)
rounded_rect(c, q3r_no_x, q3r_no_y, NW, NH, r=8, fill_color=colors.HexColor("#7F8C8D"), stroke_color=colors.HexColor("#7F8C8D"))
wrap_text_in_box(c, "Mild nephritic / isolated\nhematuria – consider IgA\nnephropathy, thin GBM dz", q3r_no_x, q3r_no_y, NW, NH, size=7.5)
# ══════════════════════════════════════════════════════════════════════════════
# DIAGNOSIS BOXES – Nephrotic
# ══════════════════════════════════════════════════════════════════════════════
# Q4L YES → MPGN
mpgn_dx_x = q4l_cx - 210
mpgn_dx_y = Y["diag_nt"]
arrow(c, q4l_cx - DW/2, q4l_cy, mpgn_dx_x + BW/2, q4l_cy, label="YES (↓C3)", label_color=C_MIXED)
# elbow down
arrow(c, mpgn_dx_x + BW/2, q4l_cy, mpgn_dx_x + BW/2, mpgn_dx_y + BH)
rounded_rect(c, mpgn_dx_x, mpgn_dx_y, BW, BH, r=8, fill_color=C_MIXED, stroke_color=C_MIXED)
wrap_text_in_box(c, "MPGN / Dense Deposit Disease\n↓C3, ↓C4 (MPGN I/III)\nIF: granular C3 ± IgG\nEM: subendothelial / intramembranous", mpgn_dx_x, mpgn_dx_y, BW, BH, size=7)
# Q4L NO → classic nephrotic (children vs. adults)
nt_dx_x = q4l_cx + 20
nt_dx_y = Y["diag_nt"]
arrow(c, q4l_cx, q4l_cy - DH/2, q4l_cx, nt_dx_y + BH + 10, label="NO", label_color=C_NO)
# age split
arrow(c, q4l_cx, nt_dx_y + BH + 10, nt_dx_x + BW/2, nt_dx_y + BH)
# Children box
ch_x = nt_dx_x
ch_y = nt_dx_y
rounded_rect(c, ch_x, ch_y, BW, BH, r=8, fill_color=C_NEPHROTIC, stroke_color=C_NEPHROTIC)
wrap_text_in_box(c, "CHILDREN (<16 yrs)\nMinimal Change Disease (75%)\nFSGS (10%)\nIF: negative EM: foot process fusion", ch_x, ch_y, BW, BH, size=7)
# Adults box
ad_x = nt_dx_x + BW + 12
ad_y = nt_dx_y
rounded_rect(c, ad_x, ad_y, BW, BH, r=8, fill_color=C_NEPHROTIC, stroke_color=C_NEPHROTIC)
wrap_text_in_box(c, "ADULTS\nFSGS (35%) Membranous (30%)\nDiabetic nephropathy (most common 2°)\nIF: IgG+C3 (subepithelial) in MN", ad_x, ad_y, BW, BH, size=7)
# age question label
arrow(c, q4l_cx, nt_dx_y + BH + 10, ad_x + BW/2, nt_dx_y + BH)
c.setFont("Helvetica-Bold", 7)
c.setFillColor(C_NEPHROTIC)
c.drawCentredString(ch_x + BW/2, ch_y + BH + 3, "Age < 16 yrs")
c.drawCentredString(ad_x + BW/2, ad_y + BH + 3, "Age ≥ 16 yrs")
# ══════════════════════════════════════════════════════════════════════════════
# DIAGNOSIS BOXES – Nephritic
# ══════════════════════════════════════════════════════════════════════════════
# Q4R YES → RPGN
rpgn_x = q4r_cx + 25
rpgn_y = Y["rpgn"]
arrow(c, q4r_cx, q4r_cy - DH/2, q4r_cx, rpgn_y + BH + 10, label="YES", label_color=C_YES)
# RPGN type split
c.setFillColor(C_RPGN)
c.setStrokeColor(C_RPGN)
c.setLineWidth(1.5)
c.roundRect(q4r_cx - BW/2, rpgn_y + 15, BW * 2.1, BH + 20, 8, fill=1, stroke=1)
c.setFillColor(C_WHITE)
c.setFont("Helvetica-Bold", 8.5)
c.drawCentredString(q4r_cx + BW/2*0.1 + 10, rpgn_y + BH + 22, "RPGN / CRESCENTIC GN (Cr doubles in weeks)")
c.setFont("Helvetica", 7.5)
lines = [
"Type I – Anti-GBM (Goodpasture): Linear IgG on IF | Anti-GBM Ab serology | ± pulmonary hemorrhage",
"Type II – Immune complex: Granular IF | ↓C3/C4 | Post-infectious, Lupus, IgA, MPGN",
"Type III – Pauci-immune (ANCA): Negative IF | cANCA (GPA) or pANCA (MPA) | Most common in adults",
]
for i, l in enumerate(lines):
c.drawCentredString(q4r_cx + BW/2*0.1 + 10, rpgn_y + 10 + (2-i)*14, l)
# Q4R NO → classic nephritic
ni_dx_x = q4r_cx - BW * 1.1
ni_dx_y = Y["diag_ni"]
arrow(c, q4r_cx - DW/2, q4r_cy, ni_dx_x + BW/2, q4r_cy, label="NO", label_color=C_NO)
arrow(c, ni_dx_x + BW/2, q4r_cy, ni_dx_x + BW/2, ni_dx_y + BH)
rounded_rect(c, ni_dx_x, ni_dx_y, BW * 1.15, BH + 20, r=8, fill_color=C_NEPHRITIC, stroke_color=C_NEPHRITIC)
wrap_text_in_box(c, "CLASSIC NEPHRITIC\nPost-streptococcal GN: ↓C3, antistreptolysin O+\nIgA nephropathy: IgA mesangial deposits\nLupus Class III/IV: ↓C3/C4, ANA+, anti-dsDNA+\nANCA vasculitis: pauci-immune", ni_dx_x, ni_dx_y, BW * 1.15, BH + 20, size=7.5)
# ══════════════════════════════════════════════════════════════════════════════
# COMPLICATIONS FOOTNOTE ROW
# ══════════════════════════════════════════════════════════════════════════════
footer_y = 22
c.setFillColor(C_LIGHT_GRAY)
c.rect(10, footer_y - 5, PW - 20, 22, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 7)
c.setFillColor(C_NEPHROTIC)
c.drawString(18, footer_y + 9, "NEPHROTIC complications:")
c.setFont("Helvetica", 7)
c.setFillColor(C_DIAGNOSIS)
c.drawString(130, footer_y + 9, "Thrombosis (↓AT-III, ↓Prot C/S) · Infection (↓IgG) · Hyperlipidemia · AKI (underfill)")
c.setFont("Helvetica-Bold", 7)
c.setFillColor(C_NEPHRITIC)
c.drawString(530, footer_y + 9, "NEPHRITIC complications:")
c.setFont("Helvetica", 7)
c.setFillColor(C_DIAGNOSIS)
c.drawString(640, footer_y + 9, "Hypertensive emergency · Pulmonary edema (fluid overload) · Rapid CKD progression")
c.setFont("Helvetica", 6.5)
c.setFillColor(colors.HexColor("#888888"))
c.drawCentredString(PW/2, footer_y - 2, "Robbins & Cotran Pathologic Basis of Disease 10e · Comprehensive Clinical Nephrology 7e · Brenner & Rector's The Kidney")
# ══════════════════════════════════════════════════════════════════════════════
c.save()
print(f"PDF saved to {OUTPUT}")
cd /home/daytona/workspace/nephro-decision-tree && python3 build_tree.py 2>&1
ls -lh /home/daytona/workspace/nephro-decision-tree/Nephro_Decision_Tree.pdf && python3 -c " from reportlab.lib.pagesizes import A3, landscape w, h = landscape(A3) print(f'Page size: {w:.0f} x {h:.0f} pt ({w/72*25.4:.0f} x {h/72*25.4:.0f} mm)') " && pdftoppm -r 150 /home/daytona/workspace/nephro-decision-tree/Nephro_Decision_Tree.pdf /home/daytona/workspace/nephro-decision-tree/preview && ls /home/daytona/workspace/nephro-decision-tree/
python3 -c " from PIL import Image img = Image.open('/home/daytona/workspace/nephro-decision-tree/preview-1.ppm') img.save('/home/daytona/workspace/nephro-decision-tree/preview.png') print(f'PNG size: {img.size}') "
~/nephro-decision-tree/build_tree.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/nephro-decision-tree/build_tree.py'
~/nephro-decision-tree/build_tree_v2.py
#!/usr/bin/env python3
"""
Clinical Decision Tree: Nephrotic vs Nephritic Syndrome (v2 – clean layout)
A3 landscape, carefully spaced so nothing clips.
"""
from reportlab.lib.pagesizes import A3, landscape
from reportlab.lib import colors
from reportlab.pdfgen import canvas
import math
OUT = "/home/daytona/workspace/nephro-decision-tree/Nephro_Decision_Tree.pdf"
PW, PH = landscape(A3) # ≈ 1191 × 842 pt
c = canvas.Canvas(OUT, pagesize=landscape(A3))
c.setTitle("Clinical Decision Tree: Nephrotic vs Nephritic Syndrome")
# ── Palette ──────────────────────────────────────────────────────────────────
BG = colors.HexColor("#F0F4F8")
HEADER = colors.HexColor("#1A2942")
QBLUE = colors.HexColor("#2471A3") # decision diamond
GREEN = colors.HexColor("#1E8449") # nephrotic
RED = colors.HexColor("#B03A2E") # nephritic
PURPLE = colors.HexColor("#6C3483") # mixed
ORANGE = colors.HexColor("#CA6F1E") # RPGN
GREY = colors.HexColor("#717D7E") # non-specific
WHITE = colors.white
DARK = colors.HexColor("#1C2833")
YARROW = colors.HexColor("#229954") # YES label
NRED = colors.HexColor("#C0392B") # NO label
ARROW_C = colors.HexColor("#4A4A4A")
# ── Background + header ───────────────────────────────────────────────────────
c.setFillColor(BG)
c.rect(0, 0, PW, PH, fill=1, stroke=0)
c.setFillColor(HEADER)
c.rect(0, PH-48, PW, 48, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 15)
c.drawCentredString(PW/2, PH-28, "Clinical Decision Tree: Differentiating Nephrotic vs. Nephritic Syndrome")
c.setFont("Helvetica", 8.5)
c.drawCentredString(PW/2, PH-40, "Based on clinical presentation · urinalysis · biochemistry | Robbins & Cotran Pathology · Comprehensive Clinical Nephrology 7e · Brenner & Rector's The Kidney")
# ── Helper draw fns ───────────────────────────────────────────────────────────
def rrect(x, y, w, h, fill=WHITE, stroke=QBLUE, lw=1.5, r=7):
c.setFillColor(fill)
c.setStrokeColor(stroke)
c.setLineWidth(lw)
c.roundRect(x, y, w, h, r, stroke=1, fill=1)
def text_in(x, y, w, h, lines, font="Helvetica-Bold", size=7.5, col=WHITE):
c.setFont(font, size)
c.setFillColor(col)
lh = size * 1.35
total = len(lines) * lh
sy = y + h/2 + total/2 - lh * 0.75
for ln in lines:
c.drawCentredString(x + w/2, sy, ln)
sy -= lh
def diamond(cx, cy, w, h, fill=QBLUE, stroke=DARK):
c.setFillColor(fill)
c.setStrokeColor(stroke)
c.setLineWidth(1.8)
p = c.beginPath()
p.moveTo(cx, cy+h/2)
p.lineTo(cx+w/2, cy)
p.lineTo(cx, cy-h/2)
p.lineTo(cx-w/2, cy)
p.close()
c.drawPath(p, stroke=1, fill=1)
def dtext(cx, cy, lines, font="Helvetica-Bold", size=7.5, col=WHITE, w_limit=90):
c.setFont(font, size)
c.setFillColor(col)
lh = size * 1.3
total = len(lines) * lh
sy = cy + total/2 - lh*0.65
for ln in lines:
c.drawCentredString(cx, sy, ln)
sy -= lh
def arr(x1, y1, x2, y2, lbl="", lbl_col=YARROW, lw=1.5, head=7):
c.setStrokeColor(ARROW_C)
c.setFillColor(ARROW_C)
c.setLineWidth(lw)
c.line(x1, y1, x2, y2)
ang = math.atan2(y2-y1, x2-x1)
for da in (0.45, -0.45):
ax = x2 - head*math.cos(ang+da)
ay = y2 - head*math.sin(ang+da)
c.line(x2, y2, ax, ay)
if lbl:
mx, my = (x1+x2)/2, (y1+y2)/2
c.setFont("Helvetica-Bold", 7.5)
c.setFillColor(lbl_col)
# offset perp to arrow direction
c.drawCentredString(mx + 6*math.sin(ang), my + 6*math.cos(ang) + 3, lbl)
def harrow(x1, y1, bend_x, x2, y2, lbl="", lbl_col=YARROW):
"""go horizontal to bend_x, then vertical to y2, then horizontal to x2"""
c.setStrokeColor(ARROW_C)
c.setFillColor(ARROW_C)
c.setLineWidth(1.5)
c.line(x1, y1, bend_x, y1)
c.line(bend_x, y1, bend_x, y2)
c.line(bend_x, y2, x2, y2)
# arrowhead
ang = math.atan2(0, x2-bend_x)
for da in (0.45, -0.45):
ax = x2 - 7*math.cos(ang+da)
ay = y2 - 7*math.sin(ang+da)
c.line(x2, y2, ax, ay)
if lbl:
c.setFont("Helvetica-Bold", 7.5)
c.setFillColor(lbl_col)
c.drawCentredString((x1+bend_x)/2, y1+5, lbl)
def varrow(x1, y1, bend_y, x2, y2, lbl="", lbl_col=YARROW):
"""go vertical to bend_y, then horizontal to x2, then vertical to y2"""
c.setStrokeColor(ARROW_C)
c.setFillColor(ARROW_C)
c.setLineWidth(1.5)
c.line(x1, y1, x1, bend_y)
c.line(x1, bend_y, x2, bend_y)
c.line(x2, bend_y, x2, y2)
ang = math.atan2(y2-bend_y, 0)
for da in (0.45, -0.45):
ax = x2 - 7*math.cos(ang+da)
ay = y2 - 7*math.sin(ang+da)
c.line(x2, y2, ax, ay)
if lbl:
c.setFont("Helvetica-Bold", 7.5)
c.setFillColor(lbl_col)
c.drawString(x1+4, (y1+bend_y)/2, lbl)
# ══════════════════════════════════════════════════════════════════════════════
# LAYOUT CONSTANTS
# X zones: FAR_LEFT LEFT CENTRE RIGHT FAR_RIGHT
# Y rows: R0(top) → R6(bottom)
# ══════════════════════════════════════════════════════════════════════════════
CX = PW / 2 # 595.5
# X centres of columns
X_FL = 110 # far-left (overflow/orthostatic)
X_L = 230 # left (nephrotic branch)
X_C = CX # centre (entry + q1)
X_R = 870 # right (nephritic branch)
X_FR = 1090 # far-right (non-glomerular)
# Y rows (centre of content)
R0 = 785 # root node
R1 = 720 # Q1
R2 = 648 # Q2 left & right
R3 = 575 # Q3 left & right
R4 = 500 # Q4 left & right
R5 = 415 # diagnosis boxes row 1
R6 = 330 # RPGN / extra row
# Standard box dims
NW, NH = 145, 36 # normal decision box
DW, DH = 155, 50 # diamond total width/height
BW, BH = 175, 62 # big diagnosis box
# ── ROOT ─────────────────────────────────────────────────────────────────────
rx, ry = X_C - NW/2, R0 - NH/2
rrect(rx, ry, NW, NH, fill=HEADER, stroke=HEADER, lw=2)
text_in(rx, ry, NW, NH, ["PATIENT WITH SUSPECTED", "GLOMERULAR DISEASE"], size=8.5)
arr(X_C, R0-NH/2, X_C, R1+DH/2) # down to Q1
# ── Q1 : Proteinuria ≥ 3.5 g/day? ───────────────────────────────────────────
diamond(X_C, R1, DW, DH, fill=QBLUE)
dtext(X_C, R1, ["Proteinuria", "≥ 3.5 g/day?"], size=8.5)
# Q1 → YES left
arr(X_C-DW/2, R1, X_L, R1, lbl="YES", lbl_col=YARROW)
# Q1 → NO right
arr(X_C+DW/2, R1, X_R, R1, lbl="NO (sub-nephrotic)", lbl_col=NRED)
# ── Q2-LEFT : RBC casts / gross hematuria? ───────────────────────────────────
arr(X_L, R1, X_L, R2+DH/2)
diamond(X_L, R2, DW, DH, fill=GREEN)
dtext(X_L, R2, ["RBC casts or", "gross hematuria?"], size=8.5)
# Q2L YES → mixed (centre, between branches)
X_MIX = CX
harrow(X_L+DW/2, R2, X_MIX-NW/2-10, X_MIX+NW/2, R3+NH/2, lbl="YES (mixed)", lbl_col=PURPLE)
# draw mixed box centred at X_MIX
rrect(X_MIX-NW/2, R3-NH/2, NW, NH+10, fill=PURPLE, stroke=PURPLE)
text_in(X_MIX-NW/2, R3-NH/2, NW, NH+10,
["MIXED FEATURES", "Consider MPGN", "or Lupus Nephritis"], size=8)
# Q2L NO → down to Q3L
arr(X_L, R2-DH/2, X_L, R3+DH/2, lbl="NO", lbl_col=NRED)
# ── Q3-LEFT : Albumin <3 g/dL + hyperlipidemia? ──────────────────────────────
diamond(X_L, R3, DW, DH, fill=GREEN)
dtext(X_L, R3, ["Albumin < 3 g/dL", "+ hyperlipidemia?"], size=8.5)
# Q3L NO → far-left re-evaluate
harrow(X_L-DW/2, R3, X_FL+NW/2+5, X_FL-NW/2+20, R3, lbl="NO", lbl_col=NRED)
rrect(X_FL-NW/2+20, R3-NH/2, NW, NH, fill=GREY, stroke=GREY)
text_in(X_FL-NW/2+20, R3-NH/2, NW, NH,
["Re-evaluate:", "Orthostatic / overflow", "proteinuria"], size=7.5)
# Q3L YES → down to Q4L
arr(X_L, R3-DH/2, X_L, R4+DH/2, lbl="YES", lbl_col=YARROW)
# ── Q4-LEFT : C3 low / MPGN pattern? ─────────────────────────────────────────
diamond(X_L, R4, DW, DH, fill=GREEN)
dtext(X_L, R4, ["Complement C3 low", "or MPGN pattern?"], size=8.5)
# Q4L YES → MPGN dx (left)
X_MPGN = X_L - 195
harrow(X_L-DW/2, R4, X_MPGN+BW/2, X_MPGN, R5+BH/2, lbl="YES (↓C3)", lbl_col=PURPLE)
rrect(X_MPGN, R5-BH/2, BW, BH, fill=PURPLE, stroke=PURPLE)
text_in(X_MPGN, R5-BH/2, BW, BH,
["MPGN / Dense Deposit Disease",
"↓C3 ± ↓C4",
"IF: granular C3 ± IgG",
"EM: subendothelial /",
"intramembranous deposits"], size=7.5)
# Q4L NO → age split → children / adults
arr(X_L, R4-DH/2, X_L, R5+BH/2, lbl="NO", lbl_col=NRED)
# children left, adults right
X_CH = X_L - 85
X_AD = X_L + 85
c.setStrokeColor(ARROW_C); c.setLineWidth(1.5)
c.line(X_L, R5+BH/2, X_CH+BW/4, R5+BH/2)
c.line(X_L, R5+BH/2, X_AD+BW/4, R5+BH/2)
arr(X_CH+BW/4, R5+BH/2, X_CH+BW/4, R5+BH/2-5)
arr(X_AD+BW/4, R5+BH/2, X_AD+BW/4, R5+BH/2-5)
# Children dx
rrect(X_CH-BW/4, R5-BH/2, BW-5, BH, fill=GREEN, stroke=GREEN)
text_in(X_CH-BW/4, R5-BH/2, BW-5, BH,
["CHILDREN (<16 yrs)",
"Minimal Change Dis. (75%)",
"FSGS (10%)",
"IF: negative",
"EM: foot process fusion"], size=7.5)
c.setFont("Helvetica-Bold", 7); c.setFillColor(GREEN)
c.drawCentredString(X_CH+BW/4, R5+BH/2+4, "Age < 16")
# Adults dx
rrect(X_AD-BW/4+10, R5-BH/2, BW+5, BH, fill=GREEN, stroke=GREEN)
text_in(X_AD-BW/4+10, R5-BH/2, BW+5, BH,
["ADULTS",
"FSGS (35%) Membranous (30%)",
"Diabetic nephropathy (2°)",
"IF: IgG subepithelial (MN)",
"EM: spike and dome (MN)"], size=7.5)
c.setFont("Helvetica-Bold", 7); c.setFillColor(GREEN)
c.drawCentredString(X_AD+BW/4+5, R5+BH/2+4, "Age ≥ 16")
# ── Q2-RIGHT : Hematuria + azotemia? ─────────────────────────────────────────
arr(X_R, R1, X_R, R2+DH/2)
diamond(X_R, R2, DW, DH, fill=RED)
dtext(X_R, R2, ["Hematuria / RBC casts", "+ azotemia present?"], size=8.5)
# Q2R NO → far-right
harrow(X_R+DW/2, R2, X_FR-NW/2+15, X_FR+NW/2-20, R2, lbl="NO", lbl_col=NRED)
rrect(X_FR-NW/2+15, R2-NH/2, NW, NH+10, fill=GREY, stroke=GREY)
text_in(X_FR-NW/2+15, R2-NH/2, NW, NH+10,
["Non-glomerular cause or", "isolated microscopic", "hematuria – further W/U"], size=7.5)
# Q2R YES → Q3R
arr(X_R, R2-DH/2, X_R, R3+DH/2, lbl="YES", lbl_col=YARROW)
# ── Q3-RIGHT : Hypertension + ↓GFR / oliguria? ───────────────────────────────
diamond(X_R, R3, DW, DH, fill=RED)
dtext(X_R, R3, ["Hypertension + ↓GFR", "/ oliguria?"], size=8.5)
# Q3R NO → right mild
harrow(X_R+DW/2, R3, X_FR-NW/2+15, X_FR+NW/2-20, R3, lbl="NO", lbl_col=NRED)
rrect(X_FR-NW/2+15, R3-NH/2, NW, NH+10, fill=GREY, stroke=GREY)
text_in(X_FR-NW/2+15, R3-NH/2, NW, NH+10,
["Mild nephritic:", "Consider IgA nephropathy", "or thin GBM disease"], size=7.5)
# Q3R YES → Q4R
arr(X_R, R3-DH/2, X_R, R4+DH/2, lbl="YES", lbl_col=YARROW)
# ── Q4-RIGHT : Creatinine doubles < 3 months? ────────────────────────────────
diamond(X_R, R4, DW, DH, fill=RED)
dtext(X_R, R4, ["Cr doubles in <3 months", "/ uremic emergency?"], size=8.5)
# Q4R NO → Classic nephritic
X_NI = X_R - 210
harrow(X_R-DW/2, R4, X_NI+BW/2+5, X_NI, R5+BH/2, lbl="NO", lbl_col=NRED)
rrect(X_NI, R5-BH/2, BW+10, BH+10, fill=RED, stroke=RED)
text_in(X_NI, R5-BH/2, BW+10, BH+10,
["CLASSIC NEPHRITIC",
"Post-strep GN: ↓C3, ASOT+",
"IgA nephropathy: IgA mesangial",
"Lupus III/IV: ANA+, anti-dsDNA+",
"ANCA vasculitis: pANCA / cANCA"], size=7.5)
# Q4R YES → RPGN box (below)
arr(X_R, R4-DH/2, X_R, R6+BH/2+25, lbl="YES", lbl_col=YARROW)
# ── RPGN big box ───────────────────────────────────────────────────────────────
RW, RH = 390, 90
rrect(X_R - RW/2, R6 - RH/2, RW, RH, fill=ORANGE, stroke=ORANGE, lw=2, r=9)
# title
c.setFont("Helvetica-Bold", 9); c.setFillColor(WHITE)
c.drawCentredString(X_R, R6 + RH/2 - 14, "RPGN / CRESCENTIC GN (Cr doubles in weeks – uremic emergency)")
# type sub-boxes
sub_w = (RW - 20) / 3
sub_h = 50
sub_y = R6 - RH/2 + 8
labels = [
("TYPE I – Anti-GBM", ["(Goodpasture)", "Linear IgG on IF", "Anti-GBM Ab serology", "± Pulmonary hemorrhage"], colors.HexColor("#922B21")),
("TYPE II – Immune Complex", ["Granular IF", "↓C3/C4", "Post-infect · Lupus · IgA · MPGN", ""], colors.HexColor("#784212")),
("TYPE III – Pauci-immune", ["Negative IF", "cANCA (GPA) · pANCA (MPA)", "Most common in adults", ""], colors.HexColor("#1A5276")),
]
for i, (title, lns, col) in enumerate(labels):
sx = X_R - RW/2 + 8 + i*(sub_w + 4)
c.setFillColor(col); c.setStrokeColor(WHITE); c.setLineWidth(0.8)
c.roundRect(sx, sub_y, sub_w, sub_h, 4, stroke=1, fill=1)
c.setFont("Helvetica-Bold", 7); c.setFillColor(WHITE)
c.drawCentredString(sx + sub_w/2, sub_y + sub_h - 10, title)
c.setFont("Helvetica", 6.5)
for j, ln in enumerate(lns):
c.drawCentredString(sx + sub_w/2, sub_y + sub_h - 22 - j*10, ln)
# ══════════════════════════════════════════════════════════════════════════════
# COMPLICATIONS FOOTER
# ══════════════════════════════════════════════════════════════════════════════
fy = 14
c.setFillColor(colors.HexColor("#DCE0E5"))
c.rect(0, fy-4, PW, 24, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 7); c.setFillColor(GREEN)
c.drawString(12, fy+11, "NEPHROTIC complications:")
c.setFont("Helvetica", 7); c.setFillColor(DARK)
c.drawString(148, fy+11, "Thrombosis (↓AT-III, ↓Prot C/S) · Infection (↓IgG, ↓IgM) · Hyperlipidemia / lipiduria · AKI (underfill, diuretics) · Pleural effusion / ascites")
c.setFont("Helvetica-Bold", 7); c.setFillColor(RED)
c.drawString(650, fy+11, "NEPHRITIC complications:")
c.setFont("Helvetica", 7); c.setFillColor(DARK)
c.drawString(785, fy+11, "Hypertensive emergency · Pulmonary edema · Rapid CKD / ESRD · Uremic emergency")
c.setFont("Helvetica", 6); c.setFillColor(GREY)
c.drawCentredString(PW/2, fy, "Sources: Robbins & Cotran Pathologic Basis of Disease 10e · Comprehensive Clinical Nephrology 7e · Brenner & Rector's The Kidney")
# ── LEGEND ────────────────────────────────────────────────────────────────────
lg_x, lg_y = PW - 178, PH - 48 - 110
c.setFillColor(colors.HexColor("#E8EDF2")); c.setStrokeColor(colors.HexColor("#B0BEC5"))
c.setLineWidth(0.8)
c.roundRect(lg_x-6, lg_y-8, 176, 104, 6, stroke=1, fill=1)
c.setFont("Helvetica-Bold", 8); c.setFillColor(DARK)
c.drawString(lg_x, lg_y + 88, "LEGEND")
entries = [
(HEADER, "Entry node"),
(QBLUE, "Decision diamond"),
(GREEN, "Nephrotic pathway"),
(RED, "Nephritic pathway"),
(PURPLE, "Mixed / MPGN"),
(ORANGE, "RPGN / Crescentic GN"),
(GREY, "Non-specific / reassess"),
]
for i, (col, lbl) in enumerate(entries):
row = i % 4
col_off = 0 if i < 4 else 88
ly = lg_y + 76 - row * 18
c.setFillColor(col); c.rect(lg_x + col_off, ly, 11, 9, fill=1, stroke=0)
c.setFont("Helvetica", 7); c.setFillColor(DARK)
c.drawString(lg_x + col_off + 14, ly + 1, lbl)
# ══════════════════════════════════════════════════════════════════════════════
c.save()
print(f"Saved: {OUT}")
cd /home/daytona/workspace/nephro-decision-tree && python3 build_tree_v2.py 2>&1 && pdftoppm -r 150 Nephro_Decision_Tree.pdf preview2 && python3 -c "from PIL import Image; img=Image.open('preview2-1.ppm'); img.save('preview2.png'); print(img.size)"
cd /home/daytona/workspace/nephro-decision-tree && python3 -c " # Quick coordinate patch: push X_L further left, fix RPGN placement # Read the script, apply targeted fixes import re code = open('build_tree_v2.py').read() # Move left column further left code = code.replace('X_L = 230', 'X_L = 255') # Move right column slightly left so RPGN fits code = code.replace('X_R = 870', 'X_R = 820') # Widen far-right margin code = code.replace('X_FR = 1090', 'X_FR = 1060') # Fix RPGN box: centre it between X_R and right edge, not too far right # Current: X_R - RW/2 ... with RW=390 → 820-195=625 to 820+195=1015 → fits # RPGN Y: R6-RH/2 where R6=330, RH=90 → 285 to 375 # Fix RPGN bottom cut: RH needs to be taller code = code.replace('RW, RH = 390, 90', 'RW, RH = 400, 95') open('build_tree_v3.py','w').write(code) print('done') " python3 build_tree_v3.py 2>&1 pdftoppm -r 150 Nephro_Decision_Tree.pdf preview3 python3 -c \"from PIL import Image; Image.open('preview3-1.ppm').save('preview3.png'); print('ok')\"
cd /home/daytona/workspace/nephro-decision-tree && python3 build_tree_v3.py 2>&1
cd /home/daytona/workspace/nephro-decision-tree && pdftoppm -r 150 Nephro_Decision_Tree.pdf preview3 && python3 -c "from PIL import Image; Image.open('preview3-1.ppm').save('preview3.png'); print('saved')"
ls -lh /home/daytona/workspace/nephro-decision-tree/Nephro_Decision_Tree.pdf
Nephro Decision Tree
PDF Document · PDF
Gonadal hormones drugs pharmacology and medicine
Hypothalamus → GnRH (pulsatile)
↓
Anterior pituitary → FSH + LH
↓
Gonads → Estradiol / Progesterone / Testosterone
↓
Target tissues (ER / PR / AR)
↓ (negative feedback)
| Class | Examples |
|---|---|
| Steroidal (natural) | Estradiol (17β-estradiol), estrone, estriol |
| Steroidal (synthetic) | Ethinyl estradiol, mestranol, estradiol valerate/cypionate |
| Nonsteroidal synthetic | Diethylstilbestrol (DES) |
| Conjugated equine estrogens (CEE) | Premarin - mixture of sulfated estrogens |
| Class | Key Drugs | Notes |
|---|---|---|
| Pregnanes | Progesterone (natural), MPA (medroxyprogesterone acetate), dydrogesterone | Progesterone has rapid first-pass metabolism; micronized form available |
| Estranes (19-nortestosterone) | Norethindrone, norethynodrel | Some androgenic activity |
| Gonanes (19-nortestosterone) | Levonorgestrel, norgestrel, norgestimate, desogestrel | Progestational; components of contraceptives |
| Spirolactone-derived | Drospirenone | Antimineralocorticoid activity; used in OCs |
| Antiprogestins/modulators | Mifepristone, ulipristal | See below |
| Stage | Level | Significance |
|---|---|---|
| 1st trimester fetus | ~250 ng/dL | Male sexual differentiation (Wolffian duct development, external genitalia) |
| Birth | ~250 ng/dL | Brief postnatal surge |
| 2-3 months postnatal | ~250 ng/dL | "Minipuberty" |
| Childhood | <50 ng/dL | Minimal |
| Puberty (12-17 yrs) | Rising to adult levels | External genitalia growth, muscle, bone, voice, sexual hair, spermatogenesis |
| Adult male | 300-1000 ng/dL | Maintained by HPG axis |
| Postpuberty deficiency | Variable | Libido ↓ (weeks), Hgb ↓ (months), bone density ↓ (1 yr), muscle loss (years) |
| Preparation | Route | Details |
|---|---|---|
| Testosterone enanthate / cypionate | Deep IM injection q 1-2 weeks | Formulated in oil; wide serum fluctuations → mood/libido swings |
| Testosterone undecanoate | IM q 10-14 weeks or oral | Long-acting IM; oral form bypasses first-pass via lymphatics |
| Testosterone gel/solution (1-2%) | Transdermal daily | Stable levels; risk of transfer to others via skin contact |
| Testosterone patch | Transdermal daily | Skin irritation common |
| Buccal testosterone | Buccal mucosa twice daily | Avoids first-pass; gum irritation |
| Alkylated androgens (methyltestosterone, oxandrolone) | Oral | 17α-alkylated - resist first-pass but hepatotoxic (peliosis hepatis, cholestasis) |
| Nandrolone decanoate | IM | Anabolic with lower androgenic ratio |
Oral testosterone itself is ineffective due to rapid hepatic catabolism. Preparations are designed to bypass this.
| Generation | Progestins | Notes |
|---|---|---|
| 1st | Norethindrone, norethynodrel | Moderate androgenic activity |
| 2nd | Levonorgestrel, norgestrel | Standard; lower VTE risk vs. 3rd/4th gen |
| 3rd | Desogestrel, gestodene, norgestimate | Less androgenic; slightly higher VTE risk vs. 2nd gen |
| 4th | Drospirenone, dienogest | Antimineralocorticoid (drospirenone); anti-androgenic; used in PCOS, acne |
| Drug | Route |
|---|---|
| Leuprolide (leuprorelin) | SC/IM depot (monthly, 3-monthly, 6-monthly) |
| Goserelin | SC implant |
| Buserelin, nafarelin | Intranasal |
| Triptorelin | IM depot |
| Drug | Use |
|---|---|
| Cetrorelix, ganirelix | IVF - prevent premature LH surge |
| Degarelix | Prostate cancer (no flare; preferred if spinal metastases or urinary obstruction) |
| Elagolix | Endometriosis (oral); also used in uterine fibroids |
| Relugolix | Prostate cancer (oral, daily) |
| Drug | Breast | Bone | Uterus | Uses |
|---|---|---|---|---|
| Tamoxifen | Antagonist | Agonist | Agonist | Breast cancer (adjuvant + treatment); risk of endometrial cancer |
| Raloxifene | Antagonist | Agonist | Neutral/antagonist | Osteoporosis; breast cancer prevention; no endometrial risk |
| Toremifene | Antagonist | - | - | Breast cancer |
| Bazedoxifene | Antagonist | Agonist | Neutral | Osteoporosis prevention; paired with CEE ("TSEC" - tissue-selective estrogen complex) |
| Ospemifene | Neutral | - | Some agonism | Dyspareunia / HSDD (postmenopausal); oral |
| Clomiphene | Antagonist | - | - | Infertility (anovulation); blocks hypothalamic ER → removes negative feedback → ↑ FSH/LH |
| Drug | Type | Notes |
|---|---|---|
| Anastrozole | Non-steroidal (reversible) | Once daily oral; breast cancer adjuvant |
| Letrozole | Non-steroidal (reversible) | Also used for ovulation induction in PCOS |
| Exemestane | Steroidal (irreversible, "suicide inhibitor") | Aromatase inactivation; different resistance pattern |
| Drug | Mechanism | Use |
|---|---|---|
| Ketoconazole (high dose) | Blocks multiple steroidogenic enzymes (CYP17A1) | Rapid castration (adrenal crisis risk) |
| Abiraterone | Selective CYP17A1 inhibitor (17α-hydroxylase + 17,20-lyase) | Castration-resistant prostate cancer; given with prednisone (prevents mineralocorticoid excess) |
| Finasteride | 5α-reductase type II inhibitor → ↓ DHT | BPH, male-pattern baldness (Propecia) |
| Dutasteride | 5α-reductase type I + II inhibitor → ↓ DHT more completely | BPH |
| Drug | Generation | Notes |
|---|---|---|
| Flutamide | 1st gen | Hepatotoxic; rarely used now |
| Bicalutamide | 2nd gen | Better tolerated; prostate cancer |
| Enzalutamide | 3rd gen | No agonist activity; crosses BBB (seizure risk); castration-resistant prostate cancer |
| Apalutamide, darolutamide | 3rd gen | Metastatic/non-metastatic prostate cancer; darolutamide - lower CNS penetration |
| Spironolactone | Non-selective (also MR antagonist) | Hirsutism in women; acne; used off-label as anti-androgen in trans women |
| Cyproterone acetate | Progestogenic antiandrogen | Hirsutism; trans women; not available in USA |
| Drug | t½ | Onset | Duration | Notes |
|---|---|---|---|---|
| Sildenafil (Viagra) | 4 h | 30-60 min | 12 h | Also used for pulmonary arterial hypertension; affected by food (high-fat meal delays absorption) |
| Vardenafil | 4 h | 30-60 min | 10 h | Most potent PDE5 selectivity (Ki 0.1 nM) |
| Tadalafil (Cialis) | 17.5 h | 30-120 min | 36 h | "Weekend pill"; also used for BPH; not affected by food |
| Avanafil | 1.3-2 h | 15-30 min | 6 h | Fastest onset; least food effect |
| Drug | Composition | Use |
|---|---|---|
| FSH (follitropin α/β, urofollitropin) | Recombinant or urinary FSH | Ovulation induction (IVF); male infertility (spermatogenesis) |
| LH (lutropin α) | Recombinant LH | Combined with FSH in ovulation induction |
| hCG (choriogonadotropin) | LH-like activity | Trigger ovulation; male hypogonadism (stimulate Leydig cells); cryptorchidism |
| Menotropins (hMG) | FSH + LH from urinary source | Ovulation induction |
| Drug Class | Key Drugs | Main Use | Critical ADR/Interaction |
|---|---|---|---|
| Estrogens | Estradiol, ethinyl estradiol, CEE | MHT, contraception | VTE, endometrial Ca (unopposed) |
| Progestins | Progesterone, MPA, levonorgestrel | MHT, contraception, ART | Mood changes, irregular bleeding, thrombosis |
| Combined OC | Ethinyl estradiol + progestin | Contraception | VTE, migraine with aura |
| GnRH agonists | Leuprolide, goserelin | Prostate Ca, endometriosis | Flare effect, osteoporosis |
| GnRH antagonists | Degarelix, elagolix | Prostate Ca, endo | Osteoporosis (no flare) |
| SERMs | Tamoxifen, raloxifene | Breast Ca, osteoporosis | VTE; tamoxifen → endometrial Ca |
| SERDs | Fulvestrant | ER+ breast Ca | IM only; injection site reactions |
| Aromatase inhibitors | Anastrozole, letrozole, exemestane | Postmeno breast Ca | Arthralgia, osteoporosis |
| Androgens | Testosterone (various forms) | Male hypogonadism | Polycythemia, virilization, suppressed spermatogenesis |
| Antiandrogens | Enzalutamide, abiraterone, finasteride | Prostate Ca, BPH, hirsutism | Abiraterone: mineralocorticoid excess |
| Mifepristone | Mifepristone | Medical abortion | Anti-glucocorticoid at high doses |
| PDE5 inhibitors | Sildenafil, tadalafil | Erectile dysfunction, PAH | Nitrate contraindication, priapism |
| Gonadotropins | FSH, hCG | ART, hypogonadism | OHSS |
Needle stick injury microbiology
| Source Status | Risk of Clinical Hepatitis | Risk of Seroconversion |
|---|---|---|
| HBsAg+ AND HBeAg+ (high viremia) | 22-31% | 37-62% |
| HBsAg+ but HBeAg- (low viremia) | 1-6% | 23-37% |
| HBsAg- | Negligible | Negligible |
| Route | Risk per Exposure |
|---|---|
| Percutaneous (needlestick) from known HIV+ source | ~0.3% |
| Mucous membrane splash | ~0.09% |
| Non-intact skin | <0.09% |
| Intact skin | Negligible |
| Pathogen | Notes |
|---|---|
| HDV (Hepatitis D) | Only infects HBsAg-positive individuals (requires HBV co-infection); PEP same as HBV |
| HTLV-1/2 | Rare occupational transmission reported |
| Malaria | Rare case reports |
| Syphilis | Theoretical; Treponema pallidum very fragile outside host |
| Tetanus | Especially with soil/dirt-contaminated needles found in community settings |
| Exposed Worker's Status | Source HBsAg+ | Source HBsAg- | Source Unknown |
|---|---|---|---|
| Unvaccinated | HBIG × 1 dose (0.06 mL/kg IM) + start HBV vaccine series | Start HBV vaccine series | Start HBV vaccine series |
| Vaccinated - known responder (anti-HBs ≥10 mIU/mL) | No treatment | No treatment | No treatment |
| Vaccinated - known non-responder | HBIG × 1 + start vaccine series OR HBIG × 2 doses | No treatment | HBIG × 1 + start vaccine series or HBIG × 2 |
| Vaccinated - antibody response unknown | Test anti-HBs: if <10 → HBIG × 1 + vaccine booster; if ≥10 → no treatment | No treatment | Test anti-HBs; if <10 → vaccine booster; recheck in 1-2 weeks |
Tenofovir disoproxil fumarate (TDF) / Emtricitabine (FTC) [Truvada] + Dolutegravir (or Raltegravir)
- 3-drug regimen
- Duration: 28 days (4 weeks)
| Component | Drug | Class |
|---|---|---|
| Backbone | TDF/FTC (Truvada) or TAF/FTC (Descovy) | NRTI |
| Third agent (preferred) | Dolutegravir (DTG) | Integrase inhibitor |
| Third agent (alternative) | Raltegravir (RAL) | Integrase inhibitor |
| Feature | HBV | HCV | HIV |
|---|---|---|---|
| Per-needlestick risk | 22-62% (HBeAg+) | ~1.8% | ~0.3% |
| Environmental stability | Survives 7 days dry | Hours-days | Minutes-hours |
| Vaccine available | Yes (highly effective) | No | No |
| PEP available | Yes (HBIG + vaccine) | No | Yes (ARV, 28 days) |
| Chronicity if infected | 5-10% (adults) | 55-85% | 100% (lifelong) |
| Cure possible | Functional cure (HBsAg loss) | Yes (DAAs, >95% SVR) | No (suppression only) |