# Renal Dose Adjustment Tool — Design Document

**Date:** 2026-03-07
**Status:** Approved
**Context:** Supports PIBIC Study 1 (eGFR-based renal dose adjustment validation)

## Purpose

A single deterministic MCP tool that looks up FDA/guideline-approved renal dose adjustments for ~20 commonly renally-adjusted medications. Accepts either CrCl or eGFR as input, returns structured dosing recommendations with per-drug DOI traceability.

## Architecture: Approach B — JSON Drug Database + Thin Calculator

Drug data lives in a structured JSON file. The calculator loads it, looks up the drug, matches the renal tier, and returns a `ClinicalResult`.

### Files

| File | Purpose |
|------|---------|
| `src/open_medicine/mcp/calculators/data/renal_dose_adjustments.json` | Drug database (~20 drugs) |
| `src/open_medicine/mcp/calculators/renal_dose_adjustment.py` | Calculator logic |
| `src/open_medicine/mcp/registry.py` | Registration (one new entry) |
| `tests/test_renal_dose_adjustment.py` | Unit + schema + boundary tests |
| `tests/comparative_validation/test_bounds.py` | Hypothesis fuzz test (append) |

## Drug Coverage (Initial Release)

- **Antibiotics:** Vancomycin, Gentamicin, Levofloxacin, Cefepime, Meropenem, Piperacillin-Tazobactam
- **Analgesics/Neuro:** Gabapentin, Pregabalin, Morphine
- **Cardiovascular:** Digoxin, Metformin, Atenolol, Sotalol
- **Antifungals:** Fluconazole
- **Antivirals:** Acyclovir, Valacyclovir
- **Other:** Allopurinol, Methotrexate, Colchicine, Lithium

Anticoagulants excluded — already have dedicated individual calculators.

## Input

```python
class RenalMetric(str, Enum):
    CRCL = "crcl"
    EGFR = "egfr"

class RenalDoseAdjustmentParams(BaseModel):
    drug_name: str = Field(..., description="Generic drug name (e.g., 'vancomycin', 'gabapentin')")
    renal_value: float = Field(..., description="Renal function value (CrCl in mL/min or eGFR in mL/min/1.73m2)")
    renal_metric: RenalMetric = Field(..., description="Which renal metric is provided: 'crcl' or 'egfr'")
```

## Output (ClinicalResult.value)

```python
{
    # Drug identification
    "drug_name": "vancomycin",
    "brand_names": ["Vancocin"],
    "drug_class": "antibiotic",

    # What the patient provided
    "renal_input": {
        "metric": "crcl",
        "value": 25.0,
        "unit": "mL/min"
    },

    # Metric match validation
    "label_renal_metric": "crcl",
    "metric_match": True,
    "metric_mismatch_warning": None,

    # Adjustment result
    "renal_category": "Severe impairment (CrCl 10-29 mL/min)",
    "normal_dose": "15-20 mg/kg IV q8-12h",
    "adjusted_dose": "15-20 mg/kg IV q24-48h; dose by levels",
    "adjustment_type": "interval_extension",
    # One of: no_adjustment | dose_reduction | interval_extension
    #       | contraindicated | use_with_caution

    # Safety metadata
    "requires_tdm": True,
    "monitoring_parameters": ["Trough or AUC/MIC 400-600"],
    "dialysis_note": "Redose guided by levels post-HD",
    "hepatic_interaction_flag": False,
    "warnings": ["Target AUC/MIC 400-600 per ASHP/IDSA 2020"]
}
```

## JSON Drug Database Schema

```json
{
  "vancomycin": {
    "drug_name": "vancomycin",
    "brand_names": ["Vancocin"],
    "drug_class": "antibiotic",
    "route": "IV",
    "label_renal_metric": "crcl",
    "normal_dose": "15-20 mg/kg IV q8-12h",
    "requires_tdm": true,
    "monitoring_parameters": ["Trough or AUC/MIC 400-600"],
    "hepatic_interaction_flag": false,
    "source_doi": "10.1093/cid/ciz895",
    "source_description": "Rybak MJ et al. ASHP/IDSA/SIDP Vancomycin Guidelines 2020.",
    "evidence_level": "Clinical Practice Guideline",
    "tiers": [
      {
        "min": 50, "max": null,
        "dose": "15-20 mg/kg IV q8-12h",
        "adjustment_type": "no_adjustment",
        "dialysis_note": null,
        "warnings": []
      },
      {
        "min": 30, "max": 49,
        "dose": "15-20 mg/kg IV q12h",
        "adjustment_type": "interval_extension",
        "dialysis_note": null,
        "warnings": ["Monitor trough levels closely"]
      },
      {
        "min": 10, "max": 29,
        "dose": "15-20 mg/kg IV q24-48h; dose by levels",
        "adjustment_type": "interval_extension",
        "dialysis_note": "Redose guided by levels post-HD",
        "warnings": ["Extended interval dosing", "Levels essential before redosing"]
      },
      {
        "min": 0, "max": 9,
        "dose": "15-20 mg/kg IV; redose by levels only",
        "adjustment_type": "use_with_caution",
        "dialysis_note": "Redose guided by levels post-HD",
        "warnings": ["Near-anuric; dose entirely by drug levels"]
      }
    ]
  }
}
```

### Schema Rules

- Tiers ordered descending by `min`, first match wins
- `max: null` means no upper bound
- Every drug has at least one tier
- `adjustment_type` is one of: `no_adjustment`, `dose_reduction`, `interval_extension`, `contraindicated`, `use_with_caution`
- `source_doi` is required and non-empty per drug

## Calculator Logic Flow

1. Normalize `drug_name` (lowercase, strip)
2. Look up in JSON — if not found, return `ClinicalResult` listing available drugs
3. Check `metric_match` (input metric vs `label_renal_metric`)
4. Walk tiers — find first where `min <= renal_value <= max` (null max = infinity)
5. Build structured `value` dict
6. Build `Evidence` from drug's `source_doi`
7. Return `ClinicalResult`

### Metric Mismatch Behavior

When the user provides eGFR but the drug label specifies CrCl (or vice versa), the tool:
- Still returns the lookup (using the value as-is against the tier thresholds)
- Sets `metric_match: False`
- Populates `metric_mismatch_warning` with an explicit explanation

### Drug Not Found Behavior

Returns a `ClinicalResult` with `adjustment_type: "drug_not_found"` and lists all supported drugs in the interpretation. No exception thrown.

## Registration

```python
"calculate_renal_dose_adjustment": RegisteredTool(
    description="Looks up FDA/guideline-approved renal dose adjustment for a drug based on CrCl or eGFR. "
                "Covers ~20 commonly renally-adjusted medications across antibiotics, analgesics, "
                "cardiovascular, antifungals, and antivirals.",
    pydantic_model=RenalDoseAdjustmentParams,
    execute_function=calculate_renal_dose_adjustment
)
```

## Testing Strategy

### Unit Tests (`tests/test_renal_dose_adjustment.py`)

1. Per-drug happy path — at least one renal value per drug, verify dose + adjustment_type + DOI
2. Tier boundary tests — exact boundary values (30, 29, 50, 49)
3. Contraindicated drugs — verify `adjustment_type: "contraindicated"` at low renal function
4. Metric mismatch — eGFR for CrCl-label drug, verify warning
5. Metric match — matching metric, verify no warning
6. Drug not found — graceful response listing available drugs
7. Case insensitivity — "Vancomycin", "VANCOMYCIN", "vancomycin" all resolve
8. DOI present — every result has non-empty DOI

### JSON Schema Validation Test

- All required fields present per drug
- At least one tier per drug
- Tiers cover 0 to infinity with no gaps
- No overlapping tier ranges
- `adjustment_type` is one of 5 allowed values
- `source_doi` is non-empty

### Hypothesis Bounds Test (append to `test_bounds.py`)

- Random renal values 0-200, random drug → always returns valid `ClinicalResult`, never throws

## Hallucination Prevention Design

1. **Per-drug DOI** — each drug traces to its own FDA label or guideline
2. **Metric mismatch flagging** — explicit warning when input metric differs from label metric
3. **No interpolation** — tier boundaries are inclusive, first-match wins
4. **No extrapolation** — if no tier matches, the tool says so rather than guessing
5. **Structured output** — agents get machine-readable data, not free-text to misinterpret
6. **Drug not found** — graceful enumeration rather than guessing at similar drug names
7. **Hepatic flag** — warns when hepatic function also affects dosing (agent should not ignore)
