import 'dart:math'; import '../models/models.dart'; import 'hazard_ratios.dart'; import 'mortality_tables.dart'; const String modelVersion = '1.1'; /// 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; // Screen 1 factors hr *= getSmokingHR(inputs.smoking, inputs.cigarettesPerDay); hr *= getAlcoholHR(inputs.alcohol); hr *= getSleepHR(inputs.sleepHours, inputs.sleepConsistent); hr *= getActivityHR(inputs.activity); hr *= getBmiHR(bmi); // Screen 2 factors hr *= getDietHR(inputs.diet); hr *= getProcessedFoodHR(inputs.processedFood); hr *= getDrugUseHR(inputs.drugUse); hr *= getSocialHR(inputs.social); hr *= getStressHR(inputs.stress); 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) { return LifespanDelta( lowMonths: 0, highMonths: 0, confidence: getConfidenceForBehavior(behaviorKey), ); } final rawDeltaYears = baselineYears * (1 - modifiedHR / currentHR) * _dampingFactor; 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 'diet': return inputs.copyWith(diet: DietQuality.excellent); case 'processedFood': return inputs.copyWith(processedFood: ProcessedFoodLevel.rarely); case 'drugUse': return inputs.copyWith(drugUse: DrugUse.none); case 'social': return inputs.copyWith(social: SocialConnection.strong); case 'stress': return inputs.copyWith(stress: StressLevel.low); 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 'diet': return inputs.diet == DietQuality.excellent; case 'processedFood': return inputs.processedFood == ProcessedFoodLevel.rarely; case 'drugUse': return inputs.drugUse == DrugUse.none; case 'social': return inputs.social == SocialConnection.strong; case 'stress': return inputs.stress == StressLevel.low; 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', 'diet', 'processedFood', 'drugUse', 'social', 'stress', 'driving', 'workHours', ]; /// Calculate ranked factors for a user profile and behavioral inputs. CalculationResult calculateRankedFactors( UserProfile profile, BehavioralInputs inputs, ) { final baselineYears = getRemainingLifeExpectancy( profile.age, profile.sex, profile.country, ); final conditionHR = getDiagnosisHR(profile.diagnoses); final adjustedBaselineYears = baselineYears / conditionHR; final currentHR = computeCombinedHazard(inputs, profile.bmi); final factors = []; for (final behaviorKey in _modifiableBehaviors) { if (_isOptimal(inputs, behaviorKey)) continue; final modifiedInputs = _setToOptimal(inputs, behaviorKey); final modifiedHR = computeCombinedHazard(modifiedInputs, profile.bmi); final delta = _computeDelta( adjustedBaselineYears, currentHR, modifiedHR, behaviorKey, ); if (delta.highMonths >= 1) { factors.add(RankedFactor( behaviorKey: behaviorKey, displayName: getDisplayName(behaviorKey), delta: delta, )); } } factors.sort( (a, b) => b.delta.midpointMonths.compareTo(a.delta.midpointMonths)); return CalculationResult( rankedFactors: factors, modelVersion: modelVersion, calculatedAt: DateTime.now(), ); }