import 'dart:math'; import '../models/models.dart'; import 'hazard_ratios.dart'; import 'mortality_tables.dart'; const String modelVersion = '1.0'; /// Maximum combined hazard ratio (prevents unrealistic compounding). const double _maxCombinedHR = 4.0; /// Damping factor for delta calculation (conservative estimate). const double _dampingFactor = 0.3; /// Uncertainty range multipliers (±20% around midpoint). const double _lowMultiplier = 0.8; const double _highMultiplier = 1.2; /// Calculate combined hazard ratio from behavioral inputs. double computeCombinedHazard(BehavioralInputs inputs, double bmi) { double hr = 1.0; hr *= getSmokingHR(inputs.smoking, inputs.cigarettesPerDay); hr *= getAlcoholHR(inputs.alcohol); hr *= getSleepHR(inputs.sleepHours, inputs.sleepConsistent); hr *= getActivityHR(inputs.activity); hr *= getBmiHR(bmi); hr *= getDrivingHR(inputs.driving); hr *= getWorkHoursHR(inputs.workHours); return min(hr, _maxCombinedHR); } /// Calculate lifespan delta when modifying a behavior to optimal. LifespanDelta _computeDelta( double baselineYears, double currentHR, double modifiedHR, String behaviorKey, ) { if (currentHR <= modifiedHR) { // No improvement possible or already optimal return LifespanDelta( lowMonths: 0, highMonths: 0, confidence: getConfidenceForBehavior(behaviorKey), ); } // Delta years ≈ baselineYears × (1 - modifiedHR/currentHR) × dampingFactor final rawDeltaYears = baselineYears * (1 - modifiedHR / currentHR) * _dampingFactor; // Convert to months with uncertainty range final midpointMonths = rawDeltaYears * 12; final lowMonths = (midpointMonths * _lowMultiplier).round(); final highMonths = (midpointMonths * _highMultiplier).round(); return LifespanDelta( lowMonths: max(0, lowMonths), highMonths: max(0, highMonths), confidence: getConfidenceForBehavior(behaviorKey), ); } /// Get modified inputs with a specific behavior set to optimal. BehavioralInputs _setToOptimal(BehavioralInputs inputs, String behaviorKey) { switch (behaviorKey) { case 'smoking': return inputs.copyWith( smoking: SmokingStatus.never, cigarettesPerDay: 0, ); case 'alcohol': return inputs.copyWith(alcohol: AlcoholLevel.none); case 'sleep': return inputs.copyWith(sleepHours: 7.5, sleepConsistent: true); case 'activity': return inputs.copyWith(activity: ActivityLevel.high); case 'driving': return inputs.copyWith(driving: DrivingExposure.low); case 'workHours': return inputs.copyWith(workHours: WorkHoursLevel.normal); default: return inputs; } } /// Check if a behavior is already at optimal level. bool _isOptimal(BehavioralInputs inputs, String behaviorKey) { switch (behaviorKey) { case 'smoking': return inputs.smoking == SmokingStatus.never; case 'alcohol': return inputs.alcohol == AlcoholLevel.none || inputs.alcohol == AlcoholLevel.light; case 'sleep': return inputs.sleepHours >= 7 && inputs.sleepHours <= 8 && inputs.sleepConsistent; case 'activity': return inputs.activity == ActivityLevel.high; case 'driving': return inputs.driving == DrivingExposure.low; case 'workHours': return inputs.workHours == WorkHoursLevel.normal; default: return true; } } /// List of modifiable behavior keys. const _modifiableBehaviors = [ 'smoking', 'alcohol', 'sleep', 'activity', 'driving', 'workHours', ]; /// Calculate ranked factors for a user profile and behavioral inputs. CalculationResult calculateRankedFactors( UserProfile profile, BehavioralInputs inputs, ) { // Get baseline remaining life expectancy final baselineYears = getRemainingLifeExpectancy( profile.age, profile.sex, profile.country, ); // Apply existing condition modifiers (reduces baseline) final conditionHR = getDiagnosisHR(profile.diagnoses); final adjustedBaselineYears = baselineYears / conditionHR; // Calculate current combined HR from behaviors final currentHR = computeCombinedHazard(inputs, profile.bmi); // Calculate delta for each modifiable behavior final factors = []; for (final behaviorKey in _modifiableBehaviors) { // Skip if already optimal if (_isOptimal(inputs, behaviorKey)) continue; // Compute HR with this behavior set to optimal final modifiedInputs = _setToOptimal(inputs, behaviorKey); final modifiedHR = computeCombinedHazard(modifiedInputs, profile.bmi); final delta = _computeDelta( adjustedBaselineYears, currentHR, modifiedHR, behaviorKey, ); // Only include if there's meaningful gain (> 1 month) if (delta.highMonths >= 1) { factors.add(RankedFactor( behaviorKey: behaviorKey, displayName: getDisplayName(behaviorKey), delta: delta, )); } } // Sort by midpoint delta descending factors.sort((a, b) => b.delta.midpointMonths.compareTo(a.delta.midpointMonths)); return CalculationResult( rankedFactors: factors, modelVersion: modelVersion, calculatedAt: DateTime.now(), ); }