pharmacy-mcp / clinical_calculators.py
Chris McMaster
Updates, improvements, new ADR features
f32824f
"""
Clinical Calculator Suite - Phase 1.2 MCP Development
Implements common clinical calculations for pharmacist workflow
"""
import logging
from typing import Dict, Any
logger = logging.getLogger(__name__)
def cockcroft_gault_creatinine_clearance(
age: int,
weight_kg: float,
serum_creatinine_mg_dl: float,
is_female: bool = False
) -> Dict[str, Any]:
"""
Calculate creatinine clearance using Cockcroft-Gault equation.
Args:
age: Patient age in years
weight_kg: Weight in kilograms
serum_creatinine_mg_dl: Serum creatinine in mg/dL
is_female: True if patient is female
Returns:
Dict with calculated creatinine clearance and interpretation
"""
# Enhanced input validation with specific error messages
if age <= 0:
raise ValueError(f"Age must be positive, got {age}")
if age > 120:
raise ValueError(f"Age seems unrealistic, got {age}")
if weight_kg <= 0:
raise ValueError(f"Weight must be positive, got {weight_kg}")
if weight_kg > 500:
raise ValueError(f"Weight seems unrealistic, got {weight_kg}")
if serum_creatinine_mg_dl <= 0:
raise ValueError(f"Serum creatinine must be positive, got {serum_creatinine_mg_dl}")
if serum_creatinine_mg_dl > 20:
raise ValueError(f"Serum creatinine seems unrealistic, got {serum_creatinine_mg_dl}")
clearance = ((140 - age) * weight_kg) / (72 * serum_creatinine_mg_dl)
if is_female:
clearance *= 0.85
if clearance >= 90:
stage = "Normal or high"
category = "G1"
elif clearance >= 60:
stage = "Mildly decreased"
category = "G2"
elif clearance >= 45:
stage = "Mild to moderately decreased"
category = "G3a"
elif clearance >= 30:
stage = "Moderately to severely decreased"
category = "G3b"
elif clearance >= 15:
stage = "Severely decreased"
category = "G4"
else:
stage = "Kidney failure"
category = "G5"
return {
"creatinine_clearance_ml_min": round(clearance, 1),
"kidney_function_stage": stage,
"gfr_category": category,
"formula_used": "Cockcroft-Gault",
"requires_dose_adjustment": clearance < 60,
"patient_info": {
"age": age,
"weight_kg": weight_kg,
"serum_creatinine_mg_dl": serum_creatinine_mg_dl,
"is_female": is_female
}
}
def ckd_epi_egfr(
age: int,
serum_creatinine_mg_dl: float,
is_female: bool = False,
is_black: bool = False
) -> Dict[str, Any]:
"""
Calculate estimated GFR using CKD-EPI equation.
Args:
age: Patient age in years
serum_creatinine_mg_dl: Serum creatinine in mg/dL
is_female: True if patient is female
is_black: True if patient is Black
Returns:
Dict with calculated eGFR and interpretation
"""
if age <= 0 or serum_creatinine_mg_dl <= 0:
raise ValueError("Age and creatinine must be positive")
if is_female:
kappa = 0.7
alpha = -0.329
if serum_creatinine_mg_dl <= kappa:
alpha = -0.411
else:
kappa = 0.9
alpha = -0.411
if serum_creatinine_mg_dl <= kappa:
alpha = -0.302
scr_kappa_ratio = serum_creatinine_mg_dl / kappa
if serum_creatinine_mg_dl <= kappa:
egfr = 141 * (scr_kappa_ratio ** alpha) * (0.993 ** age)
else:
egfr = 141 * (scr_kappa_ratio ** -1.209) * (0.993 ** age)
if is_female:
egfr *= 1.018
if is_black:
egfr *= 1.159
if egfr >= 90:
stage = "Normal or high"
category = "G1"
elif egfr >= 60:
stage = "Mildly decreased"
category = "G2"
elif egfr >= 45:
stage = "Mild to moderately decreased"
category = "G3a"
elif egfr >= 30:
stage = "Moderately to severely decreased"
category = "G3b"
elif egfr >= 15:
stage = "Severely decreased"
category = "G4"
else:
stage = "Kidney failure"
category = "G5"
return {
"egfr_ml_min_1_73m2": round(egfr, 1),
"kidney_function_stage": stage,
"gfr_category": category,
"formula_used": "CKD-EPI",
"requires_dose_adjustment": egfr < 60,
"patient_info": {
"age": age,
"serum_creatinine_mg_dl": serum_creatinine_mg_dl,
"is_female": is_female,
"is_black": is_black
}
}
def child_pugh_score(
bilirubin_mg_dl: float,
albumin_g_dl: float,
inr: float,
ascites: str,
encephalopathy: str
) -> Dict[str, Any]:
"""
Calculate Child-Pugh score for liver function assessment.
Args:
bilirubin_mg_dl: Total bilirubin in mg/dL
albumin_g_dl: Serum albumin in g/dL
inr: International Normalized Ratio
ascites: 'none', 'mild', or 'moderate-severe'
encephalopathy: 'none', 'grade-1-2', or 'grade-3-4'
Returns:
Dict with Child-Pugh score, class, and interpretation
"""
score = 0
if bilirubin_mg_dl < 2:
score += 1
elif bilirubin_mg_dl <= 3:
score += 2
else:
score += 3
if albumin_g_dl > 3.5:
score += 1
elif albumin_g_dl >= 2.8:
score += 2
else:
score += 3
if inr < 1.7:
score += 1
elif inr <= 2.3:
score += 2
else:
score += 3
ascites_lower = ascites.lower()
if 'none' in ascites_lower:
score += 1
elif 'mild' in ascites_lower:
score += 2
else:
score += 3
encephalopathy_lower = encephalopathy.lower()
if 'none' in encephalopathy_lower:
score += 1
elif 'grade-1-2' in encephalopathy_lower or '1-2' in encephalopathy_lower:
score += 2
else:
score += 3
if score <= 6:
child_class = "A"
mortality_1yr = "< 10%"
mortality_2yr = "< 15%"
perioperative_mortality = "10%"
elif score <= 9:
child_class = "B"
mortality_1yr = "10-20%"
mortality_2yr = "20-30%"
perioperative_mortality = "30%"
else:
child_class = "C"
mortality_1yr = "> 20%"
mortality_2yr = "> 35%"
perioperative_mortality = "50%"
return {
"child_pugh_score": score,
"child_pugh_class": child_class,
"one_year_mortality": mortality_1yr,
"two_year_mortality": mortality_2yr,
"perioperative_mortality": perioperative_mortality,
"requires_dose_adjustment": child_class in ["B", "C"],
"severe_impairment": child_class == "C",
"components": {
"bilirubin_mg_dl": bilirubin_mg_dl,
"albumin_g_dl": albumin_g_dl,
"inr": inr,
"ascites": ascites,
"encephalopathy": encephalopathy
}
}
def bmi_calculator(
weight_kg: float,
height_cm: float
) -> Dict[str, Any]:
"""
Calculate Body Mass Index and provide interpretation.
Args:
weight_kg: Weight in kilograms
height_cm: Height in centimeters
Returns:
Dict with BMI and weight category
"""
if weight_kg <= 0 or height_cm <= 0:
raise ValueError("Weight and height must be positive")
height_m = height_cm / 100
bmi = weight_kg / (height_m ** 2)
if bmi < 18.5:
category = "Underweight"
risk = "Increased risk of malnutrition"
elif bmi < 25:
category = "Normal weight"
risk = "Low risk"
elif bmi < 30:
category = "Overweight"
risk = "Increased risk"
elif bmi < 35:
category = "Obesity Class I"
risk = "High risk"
elif bmi < 40:
category = "Obesity Class II"
risk = "Very high risk"
else:
category = "Obesity Class III"
risk = "Extremely high risk"
return {
"bmi": round(bmi, 1),
"category": category,
"health_risk": risk,
"weight_kg": weight_kg,
"height_cm": height_cm
}
def ideal_body_weight(
height_cm: float,
is_male: bool = True
) -> Dict[str, Any]:
"""
Calculate Ideal Body Weight using Devine formula.
Args:
height_cm: Height in centimeters
is_male: True if patient is male
Returns:
Dict with ideal body weight
"""
if height_cm <= 0:
raise ValueError("Height must be positive")
height_inches = height_cm / 2.54
if is_male:
ibw_kg = 50 + 2.3 * (height_inches - 60)
else:
ibw_kg = 45.5 + 2.3 * (height_inches - 60)
ibw_kg = max(ibw_kg, 30)
return {
"ideal_body_weight_kg": round(ibw_kg, 1),
"height_cm": height_cm,
"is_male": is_male,
"formula_used": "Devine"
}
def adjusted_body_weight(
actual_weight_kg: float,
ideal_weight_kg: float,
correction_factor: float = 0.4
) -> Dict[str, Any]:
"""
Calculate Adjusted Body Weight for obese patients.
Args:
actual_weight_kg: Actual body weight in kg
ideal_weight_kg: Ideal body weight in kg
correction_factor: Correction factor (default 0.4)
Returns:
Dict with adjusted body weight
"""
if actual_weight_kg <= 0 or ideal_weight_kg <= 0:
raise ValueError("Weights must be positive")
if actual_weight_kg <= ideal_weight_kg * 1.2:
adjusted_weight = actual_weight_kg
adjustment_needed = False
else:
adjusted_weight = ideal_weight_kg + correction_factor * (actual_weight_kg - ideal_weight_kg)
adjustment_needed = True
return {
"adjusted_body_weight_kg": round(adjusted_weight, 1),
"actual_weight_kg": actual_weight_kg,
"ideal_weight_kg": ideal_weight_kg,
"correction_factor": correction_factor,
"adjustment_needed": adjustment_needed,
"percent_above_ideal": round(((actual_weight_kg / ideal_weight_kg) - 1) * 100, 1)
}
def creatinine_conversion(
creatinine_value: float,
from_unit: str,
to_unit: str
) -> Dict[str, Any]:
"""
Convert creatinine between mg/dL and μmol/L.
Args:
creatinine_value: Creatinine value to convert
from_unit: 'mg_dl' or 'umol_l'
to_unit: 'mg_dl' or 'umol_l'
Returns:
Dict with converted value
"""
if creatinine_value <= 0:
raise ValueError("Creatinine value must be positive")
conversion_factor = 88.42
if from_unit == to_unit:
converted_value = creatinine_value
elif from_unit == 'mg_dl' and to_unit == 'umol_l':
converted_value = creatinine_value * conversion_factor
elif from_unit == 'umol_l' and to_unit == 'mg_dl':
converted_value = creatinine_value / conversion_factor
else:
raise ValueError("Invalid units. Use 'mg_dl' or 'umol_l'")
return {
"original_value": creatinine_value,
"original_unit": from_unit,
"converted_value": round(converted_value, 2),
"converted_unit": to_unit,
"conversion_factor": conversion_factor
}
def dosing_weight_recommendation(
actual_weight_kg: float,
height_cm: float,
is_male: bool = True
) -> Dict[str, Any]:
"""
Recommend appropriate weight for dosing calculations.
Args:
actual_weight_kg: Actual body weight in kg
height_cm: Height in centimeters
is_male: True if patient is male
Returns:
Dict with dosing weight recommendation
"""
ibw_result = ideal_body_weight(height_cm, is_male)
ibw = ibw_result["ideal_body_weight_kg"]
bmi_result = bmi_calculator(actual_weight_kg, height_cm)
bmi = bmi_result["bmi"]
if actual_weight_kg <= ibw * 1.2:
dosing_weight = actual_weight_kg
recommendation = "Use actual body weight"
rationale = "Patient weight is within 20% of ideal body weight"
elif bmi >= 30:
adj_weight_result = adjusted_body_weight(actual_weight_kg, ibw)
dosing_weight = adj_weight_result["adjusted_body_weight_kg"]
recommendation = "Use adjusted body weight"
rationale = "Patient is obese (BMI ≥ 30); adjusted weight recommended for most drugs"
else:
dosing_weight = actual_weight_kg
recommendation = "Use actual body weight (consider drug-specific guidelines)"
rationale = "Patient is overweight but not obese; actual weight typically appropriate"
return {
"recommended_dosing_weight_kg": dosing_weight,
"recommendation": recommendation,
"rationale": rationale,
"actual_weight_kg": actual_weight_kg,
"ideal_weight_kg": ibw,
"bmi": bmi,
"bmi_category": bmi_result["category"]
}