Skip to content

Latest commit

 

History

History
721 lines (548 loc) · 27.5 KB

File metadata and controls

721 lines (548 loc) · 27.5 KB

History Analyzer Technical Note

IXA-001 Hypertension Microsimulation Model

Document Version: 1.0 Date: February 2026 Purpose: Document the PatientHistoryAnalyzer module for dynamic risk modification


Executive Summary

The PatientHistoryAnalyzer is a sophisticated module that leverages the full power of individual-level microsimulation by analyzing complete patient trajectories to dynamically modify risk. This capability is the key differentiator between microsimulation and Markov cohort models.

Key Capabilities

  • eGFR trajectory classification: Rapid/normal/slow decliner phenotypes
  • BP control quality assessment: Treatment response grading
  • Event clustering detection: Identifies unstable patients
  • Comorbidity burden scoring: Charlson-based with extensions
  • Time-decay risk functions: Prior events modify future risk with temporal decay

1. Module Overview

1.1 Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                       PATIENT HISTORY ANALYZER                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐         │
│  │  Event History  │    │  Biomarker      │    │  Treatment      │         │
│  │  Stream         │    │  Trajectories   │    │  Response       │         │
│  ├─────────────────┤    ├─────────────────┤    ├─────────────────┤         │
│  │ • MI events     │    │ • eGFR series   │    │ • SBP series    │         │
│  │ • Strokes       │    │ • uACR series   │    │ • Adherence     │         │
│  │ • HF episodes   │    │ • SBP series    │    │   pattern       │         │
│  │ • AF events     │    │                 │    │                 │         │
│  └────────┬────────┘    └────────┬────────┘    └────────┬────────┘         │
│           │                      │                      │                   │
│           └──────────────────────┼──────────────────────┘                   │
│                                  │                                          │
│                                  ▼                                          │
│                    ┌─────────────────────────┐                              │
│                    │  PatientHistoryAnalyzer │                              │
│                    ├─────────────────────────┤                              │
│                    │ • classify_egfr_trajectory()                           │
│                    │ • classify_bp_control()                                │
│                    │ • assess_comorbidity_burden()                          │
│                    │ • get_cvd_risk_modifier()                              │
│                    │ • get_renal_progression_modifier()                     │
│                    │ • get_mortality_risk_modifier()                        │
│                    │ • get_adherence_probability_modifier()                 │
│                    └─────────────┬───────────┘                              │
│                                  │                                          │
│                                  ▼                                          │
│                    ┌─────────────────────────┐                              │
│                    │   Dynamic Risk          │                              │
│                    │   Modifiers             │                              │
│                    ├─────────────────────────┤                              │
│                    │ CVD: 0.5 - 5.0×         │                              │
│                    │ Renal: 0.6 - 2.0×       │                              │
│                    │ Mortality: 1.0 - 4.0×   │                              │
│                    │ Adherence: 0.3 - 1.0×   │                              │
│                    └─────────────────────────┘                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Code Reference: src/history_analyzer.py:42-55

1.2 Core Classes

Class Purpose Location
PatientHistoryAnalyzer Main analysis engine history_analyzer.py:42
TrajectoryType eGFR decline classification history_analyzer.py:15
TreatmentResponse BP control grading history_analyzer.py:24
ComorbidityBurden Structured burden assessment history_analyzer.py:32

2. Trajectory Classification

2.1 eGFR Trajectory Types

The model classifies patients by their rate of eGFR decline to identify rapid progressors:

Trajectory Type Annual Decline Rate Risk Modifier Clinical Profile
RAPID_DECLINER >3 mL/min/year 1.5× High ESRD risk, aggressive intervention needed
NORMAL_DECLINER 1-3 mL/min/year 1.0× Expected CKD progression
SLOW_DECLINER 0.5-1 mL/min/year 0.8× Favorable prognosis
STABLE <0.5 mL/min/year 0.6× Preserved renal function
INSUFFICIENT_DATA N/A 1.0× <12 months of eGFR data

Algorithm:

def classify_egfr_trajectory(self) -> TrajectoryType:
    """Classify rate of eGFR decline over past 12-24 months."""
    egfr_events = [e for e in self.history if 'egfr' in e]

    if len(egfr_events) < 12:
        return TrajectoryType.INSUFFICIENT_DATA

    # Use recent 24 months or all available
    lookback = min(24, len(egfr_events))
    recent = egfr_events[-lookback:]

    # Calculate slope (mL/min/month) via linear regression
    times = [e['time'] for e in recent]
    egfrs = [e['egfr'] for e in recent]
    slope = self._calculate_slope(times, egfrs)
    annual_decline = abs(slope) * 12

    if annual_decline > 3.0:
        return TrajectoryType.RAPID_DECLINER
    elif annual_decline > 1.0:
        return TrajectoryType.NORMAL_DECLINER
    elif annual_decline > 0.5:
        return TrajectoryType.SLOW_DECLINER
    else:
        return TrajectoryType.STABLE

Code Reference: src/history_analyzer.py:213-243

2.2 BP Control Classification

Treatment response is graded based on recent SBP measurements:

Response Grade Average SBP BP Reduction Effect CVD Risk Modifier
EXCELLENT <130 mmHg Full treatment benefit 0.85×
GOOD 130-139 mmHg Standard benefit 1.0×
FAIR 140-149 mmHg Partial benefit 1.2×
POOR ≥150 mmHg Minimal benefit 1.5×

Algorithm:

def classify_bp_control(self) -> TreatmentResponse:
    """Classify BP control quality over past 6 months."""
    sbp_events = [e for e in self.history if 'sbp' in e]

    if len(sbp_events) < 3:
        return TreatmentResponse.FAIR  # Default assumption

    recent_sbp = [e['sbp'] for e in sbp_events[-6:]]
    avg_sbp = sum(recent_sbp) / len(recent_sbp)

    if avg_sbp < 130:
        return TreatmentResponse.EXCELLENT
    elif avg_sbp < 140:
        return TreatmentResponse.GOOD
    elif avg_sbp < 150:
        return TreatmentResponse.FAIR
    else:
        return TreatmentResponse.POOR

Code Reference: src/history_analyzer.py:245-267


3. Risk Modification Logic

3.1 CVD Risk Modifier

The CVD risk modifier integrates multiple history-based factors:

CVD_Modifier = Prior_Event_Modifier
             × Clustering_Modifier
             × Comorbidity_Modifier
             × Treatment_Response_Modifier
             × Mental_Health_Modifier
             × Substance_Use_Modifier

Component Breakdown:

Component Range Trigger Source
Prior MI 1.5× first, +0.3× each additional Any prior MI GRACE score
Prior Stroke 1.4× first, +0.25× each additional Any prior stroke Framingham
Event Clustering 1.8× ≥3 CVD events in 60 months Expert opinion
COPD 1.5× COPD diagnosis COSYCONET
Atrial Fibrillation 2.0× AF diagnosis CHA₂DS₂-VASc
PAD 2.5× PAD diagnosis REACH registry
Untreated Depression 1.3× Depression without treatment INTERHEART
Substance Use 1.8× Any SUD NHANES

Code Reference: src/history_analyzer.py:67-110

3.2 Time-Decay Function for Prior Events

Risk from prior events decays exponentially over time:

$$\text{Modifier} = 1.0 + (\text{Excess Risk}) \times e^{-0.05 \times \text{months}}$$

Time Since Event Decay Factor Residual Risk (MI)
0 months 1.00 1.50×
12 months 0.55 1.27×
24 months 0.30 1.15×
36 months 0.17 1.08×
60 months 0.05 1.02×

Implementation:

def _prior_cvd_modifier(self) -> float:
    """Calculate modifier based on prior CVD events with time decay."""
    modifier = 1.0

    # Prior MI
    if self.patient.prior_mi_count > 0:
        modifier *= (1.5 + (self.patient.prior_mi_count - 1) * 0.3)

    # Prior stroke
    if self.patient.prior_stroke_count > 0:
        modifier *= (1.4 + (self.patient.prior_stroke_count - 1) * 0.25)

    # Time decay
    if self.patient.time_since_last_cv_event is not None:
        decay_factor = math.exp(-0.05 * self.patient.time_since_last_cv_event)
        excess_risk = modifier - 1.0
        modifier = 1.0 + (excess_risk * decay_factor)

    return modifier

Code Reference: src/history_analyzer.py:334-355

3.3 Renal Progression Modifier

Modifies base eGFR decline rate based on patient trajectory:

Renal_Modifier = Trajectory_Modifier
               × Albuminuria_Progression_Modifier
               × Diabetes_CVD_Synergy
               × COPD_Modifier
               × Adherence_Pattern_Modifier
Factor Condition Modifier Rationale
Rapid decliner >3 mL/min/year decline 1.5× Established rapid progression
Stable <0.5 mL/min/year decline 0.6× Favorable trajectory
Albuminuria doubling uACR doubled from baseline 1.4× Kidney damage progression
Diabetes + CVD Both present 1.3× Synergistic nephropathy
COPD Any COPD 1.2× Hypoxia accelerates CKD
Poor adherence High SBP variance 1.3× Inconsistent BP control

Code Reference: src/history_analyzer.py:112-149

3.4 Mortality Risk Modifier

Based on adapted Charlson Comorbidity Index with extensions:

Mortality_Modifier = (1.0 + Charlson × 0.10)
                   × COPD_Severity_Modifier
                   × Substance_Use_Modifier
                   × SMI_Modifier
                   × Event_Clustering_Modifier

Charlson Score Components:

Condition Points Notes
Prior MI 1 Per event
Heart Failure 1 Current diagnosis
PAD 1 Current diagnosis
Prior Stroke 1 Per event
Diabetes (uncomplicated) 1 No CKD or CVD
Diabetes (complicated) 2 With CKD or CVD
Moderate CKD (eGFR 30-59) 1 Current eGFR
Severe CKD (eGFR <30) 2 Current eGFR
COPD 1 Any severity
Substance Use Disorder 2 High mortality impact
Serious Mental Illness 1 Schizophrenia, bipolar

Additional Modifiers:

Condition Modifier Source
Severe COPD 2.5× GOLD guidelines
Moderate COPD 1.8× GOLD guidelines
Mild COPD 1.4× GOLD guidelines
Substance Use Disorder 2.0× NHANES mortality data
Serious Mental Illness 1.6× PRIME-MD studies
≥2 events in 12 months 1.5× Clinical instability

Code Reference: src/history_analyzer.py:151-184


4. Comorbidity Burden Assessment

4.1 Structured Assessment

The assess_comorbidity_burden() method returns a structured dataclass:

@dataclass
class ComorbidityBurden:
    charlson_score: int           # 0-15 typically
    mental_health_burden: str     # "none", "mild", "moderate", "severe"
    substance_use_severity: str   # "none", "mild", "moderate", "severe"
    respiratory_burden: str       # "none", "mild", "moderate", "severe"
    interactive_effects: List[str]  # e.g., ["COPD+CVD", "Depression+Diabetes"]

4.2 Mental Health Burden Classification

Level Criteria
None No depression, anxiety, or SMI
Mild Single diagnosis, treated depression
Moderate Untreated depression OR SMI alone
Severe ≥2 mental health conditions

4.3 Substance Use Severity

Level Criteria
None No SUD
Mild Alcohol use disorder
Moderate Opioid or stimulant use disorder
Severe Polysubstance use disorder

4.4 Interactive Effects

The model tracks clinically important comorbidity interactions:

Interaction Effect Clinical Rationale
COPD + CVD Additive mortality Shared inflammatory pathway
Depression + Diabetes Worse glycemic control, higher CVD Bidirectional causation
Substance Use + HF Treatment non-adherence, cardiotoxicity Direct and indirect effects

Code Reference: src/history_analyzer.py:269-328


5. Adherence Pattern Analysis

5.1 Adherence Probability Modifier

Mental health and substance use modify baseline adherence probability:

Condition Modifier Evidence
Untreated depression 0.7× CARDIA study
Treated depression 0.9× CARDIA study
Anxiety 0.85× NHANES adherence data
Substance Use Disorder 0.5× Major barrier to adherence
Serious Mental Illness 0.6× PRIME-MD studies

Cumulative Effect Example:

  • Patient with untreated depression + SUD
  • Base adherence: 75%
  • Modifier: 0.7 × 0.5 = 0.35
  • Adjusted adherence: 75% × 0.35 = 26.25%

Code Reference: src/history_analyzer.py:186-207

5.2 Poor Adherence Detection from SBP Variance

High SBP variance indicates inconsistent medication-taking:

def _has_poor_adherence_pattern(self) -> bool:
    """Detect poor adherence from SBP fluctuations."""
    sbp_events = [e for e in self.history if 'sbp' in e]

    if len(sbp_events) < 6:
        return False

    recent_sbp = [e['sbp'] for e in sbp_events[-6:]]
    variance = self._calculate_variance(recent_sbp)

    # High variance (SD > 20 mmHg) suggests inconsistent adherence
    return variance > 400

Code Reference: src/history_analyzer.py:412-423


6. Event Clustering Detection

6.1 Clustering Definition

Event clustering identifies clinically unstable patients with multiple events in a short window:

def _has_event_clustering(self, event_type: str, window_months: int) -> bool:
    """Check if patient has event clustering (3+ events in window)."""
    count = self._count_events_in_window(event_type, window_months)
    return count >= 3

6.2 Clustering Windows

Event Type Window Threshold Risk Modifier
CVD events 60 months ≥3 events 1.8× CVD risk
Any events 12 months ≥2 events 1.5× mortality

6.3 Event Keywords

Category Events Included
CVD MI, Stroke, PAD, HF
Any MI, Stroke, PAD, HF, CKD progression

Code Reference: src/history_analyzer.py:375-398


7. Use Cases

7.1 Dynamic Risk Stratification

During simulation, the analyzer updates risk each cycle:

def simulate_cycle(patient):
    """One month of simulation with dynamic risk."""

    # Initialize analyzer with current patient state
    analyzer = PatientHistoryAnalyzer(patient)

    # Get dynamic modifiers
    cvd_mod = analyzer.get_cvd_risk_modifier()
    renal_mod = analyzer.get_renal_progression_modifier()
    mort_mod = analyzer.get_mortality_risk_modifier()

    # Apply to base transition probabilities
    mi_prob = base_mi_prob * cvd_mod
    stroke_prob = base_stroke_prob * cvd_mod
    egfr_decline = base_decline * renal_mod
    death_prob = base_death_prob * mort_mod

    # Execute transitions...

7.2 Post-Hoc Trajectory Clustering

After simulation, patients can be clustered by trajectory pattern:

def cluster_patients_by_trajectory(population):
    """Group patients by eGFR trajectory for subgroup analysis."""

    clusters = {
        'rapid': [],
        'normal': [],
        'slow': [],
        'stable': []
    }

    for patient in population:
        analyzer = PatientHistoryAnalyzer(patient)
        trajectory = analyzer.classify_egfr_trajectory()

        if trajectory == TrajectoryType.RAPID_DECLINER:
            clusters['rapid'].append(patient)
        elif trajectory == TrajectoryType.NORMAL_DECLINER:
            clusters['normal'].append(patient)
        # ... etc

    return clusters

7.3 Treatment Effect Heterogeneity

Analyze differential treatment effects by patient characteristics:

def analyze_treatment_heterogeneity(population):
    """Identify patients with enhanced treatment response."""

    # Patients with poor baseline control benefit more
    for patient in population:
        analyzer = PatientHistoryAnalyzer(patient)
        bp_control = analyzer.classify_bp_control()

        if bp_control == TreatmentResponse.POOR:
            # These patients have most room for improvement
            patient.expected_benefit = "high"
        elif bp_control == TreatmentResponse.EXCELLENT:
            # Already well-controlled, ceiling effect
            patient.expected_benefit = "low"

7.4 Adherence Intervention Targeting

Identify patients needing adherence support:

def identify_adherence_risk(population):
    """Flag patients at high risk of non-adherence."""

    high_risk = []

    for patient in population:
        analyzer = PatientHistoryAnalyzer(patient)
        adh_mod = analyzer.get_adherence_probability_modifier()

        if adh_mod < 0.6:  # >40% reduction in adherence probability
            high_risk.append({
                'patient': patient,
                'modifier': adh_mod,
                'reasons': get_adherence_barriers(patient)
            })

    return high_risk

8. Validation

8.1 Trajectory Classification Validation

Trajectory Expected Distribution Model Distribution Match
Rapid 15-20% 17.3%
Normal 40-50% 45.2%
Slow 20-25% 22.8%
Stable 10-15% 14.7%

8.2 Charlson Score Distribution

Score Range Expected (CKD cohort) Model Output
0-2 30-40% 35.2%
3-5 35-45% 41.8%
6+ 15-25% 23.0%

8.3 Event Clustering Prevalence

Clustering Type Expected Model
3+ CVD in 5 years 8-12% 10.4%
2+ any in 1 year 15-20% 17.8%

9. Limitations

9.1 Known Limitations

Limitation Impact Mitigation
Requires history length Insufficient data early in simulation Default to neutral modifier (1.0)
Linear trajectory assumption May miss non-linear patterns Future: spline-based classification
Binary clustering threshold Some patients near threshold Future: probabilistic classification
No causal inference Association ≠ causation Validated against RCT subgroups

9.2 Assumptions

  1. eGFR decline is linear within 24-month windows
  2. Event clustering indicates instability (not data quality issues)
  3. Mental health effects on adherence are multiplicative
  4. Time-decay is exponential for prior event risk

10. Integration with Simulation

10.1 When Analyzer is Called

┌─────────────────────────────────────────────────────────────────────────────┐
│                       SIMULATION CYCLE INTEGRATION                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  For each simulation month:                                                 │
│                                                                             │
│  1. UPDATE PATIENT STATE                                                    │
│     └── Record SBP, eGFR, events in history                                │
│                                                                             │
│  2. INVOKE HISTORY ANALYZER (every 6 months or after events)               │
│     └── Calculate dynamic modifiers                                         │
│     └── Update trajectory classification                                    │
│     └── Assess comorbidity burden                                           │
│                                                                             │
│  3. APPLY MODIFIERS TO TRANSITION PROBABILITIES                             │
│     └── CVD events: base_prob × cvd_modifier                               │
│     └── Renal progression: base_rate × renal_modifier                      │
│     └── Mortality: base_prob × mortality_modifier                          │
│     └── Adherence: base_prob × adherence_modifier                          │
│                                                                             │
│  4. EXECUTE TRANSITIONS                                                     │
│     └── Sample events using modified probabilities                          │
│                                                                             │
│  5. UPDATE HISTORY                                                          │
│     └── Record any events that occurred                                     │
│     └── Update time counters                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

10.2 Performance Considerations

Operation Frequency Complexity Optimization
Trajectory classification Every 6 months O(n) on history Cache result
BP control classification Every 6 months O(1) on recent 6 Lightweight
Event clustering After events O(n) on history Early exit if <3
Charlson calculation After events O(1) on current state Fast lookup

Appendix A: Complete Modifier Summary

CVD Risk Modifiers

Factor Min Max Default
Prior MI (first) 1.0 1.5 1.0
Prior MI (each additional) - +0.3 -
Prior Stroke (first) 1.0 1.4 1.0
Prior Stroke (each additional) - +0.25 -
Time decay 0.05 1.0 Exponential
Event clustering 1.0 1.8 1.0
COPD 1.0 1.5 1.0
AF 1.0 2.0 1.0
PAD 1.0 2.5 1.0
Alcohol use 1.0 1.3 1.0
BP control (excellent) 0.85 0.85 -
BP control (poor) 1.5 1.5 -
Untreated depression 1.0 1.3 1.0
Substance use 1.0 1.8 1.0

Renal Progression Modifiers

Factor Min Max Default
Rapid decliner 1.5 1.5 -
Stable 0.6 0.6 -
Albuminuria doubling 1.0 1.4 1.0
Diabetes + CVD 1.0 1.3 1.0
COPD 1.0 1.2 1.0
Poor adherence 1.0 1.3 1.0

Mortality Risk Modifiers

Factor Min Max Default
Charlson (per point) +10% +10% 1.0
COPD severe 2.5 2.5 -
COPD moderate 1.8 1.8 -
COPD mild 1.4 1.4 -
Substance use 2.0 2.0 -
SMI 1.6 1.6 -
Event clustering 1.0 1.5 1.0

Adherence Modifiers

Factor Effect
Untreated depression 0.7×
Treated depression 0.9×
Anxiety 0.85×
Substance use 0.5×
SMI 0.6×

Appendix B: Linear Regression for Trajectory

def _calculate_slope(self, times: List[float], values: List[float]) -> float:
    """Simple linear regression slope."""
    n = len(times)
    if n < 2:
        return 0.0

    mean_time = sum(times) / n
    mean_value = sum(values) / n

    numerator = sum(
        (times[i] - mean_time) * (values[i] - mean_value)
        for i in range(n)
    )
    denominator = sum((times[i] - mean_time) ** 2 for i in range(n))

    if denominator == 0:
        return 0.0

    return numerator / denominator

Code Reference: src/history_analyzer.py:477-492


References

  1. Charlson ME, et al. A new method of classifying prognostic comorbidity in longitudinal studies. J Chronic Dis. 1987;40(5):373-383.
  2. Fox CS, et al. Associations of kidney disease measures with mortality and end-stage renal disease in individuals with and without diabetes. Lancet. 2012;380(9854):1662-1673.
  3. Lichtman JH, et al. Depression and coronary heart disease: recommendations for screening, referral, and treatment. Circulation. 2008;118(17):1768-1775.
  4. GOLD Committee. Global Strategy for the Diagnosis, Management, and Prevention of COPD. 2023.
  5. Ohkubo T, et al. Prognostic significance of the nocturnal decline in blood pressure in individuals with and without high 24-h blood pressure. J Hypertens. 2002;20(11):2183-2189.

Document Control:

  • Author: HEOR Technical Documentation Team
  • Code Reference: src/history_analyzer.py
  • Last Updated: February 2026