app/test/risk_engine/calculator_test.dart
John Mizerek 40275bcd0c Add lifestyle factors screen with diet, social, stress tracking
- Split behavioral inputs into two screens (Habits + Lifestyle)
- Added 5 new modifiable factors: diet quality, processed food,
  drug use, social connection, and stress level
- Updated hazard ratios for all new factors based on meta-analyses
- Model version bumped to 1.1
- Simplified welcome screen with clearer value proposition
- Updated tests for expanded behavioral model

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-21 09:22:48 -08:00

244 lines
8 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:add_months/models/models.dart';
import 'package:add_months/risk_engine/risk_engine.dart';
void main() {
group('Hazard Ratios', () {
test('smoking current produces HR > 1.8', () {
expect(getSmokingHR(SmokingStatus.current, 10), greaterThanOrEqualTo(1.8));
});
test('smoking heavy (>20/day) produces highest HR', () {
final lightHR = getSmokingHR(SmokingStatus.current, 5);
final moderateHR = getSmokingHR(SmokingStatus.current, 15);
final heavyHR = getSmokingHR(SmokingStatus.current, 25);
expect(heavyHR, greaterThan(moderateHR));
expect(moderateHR, greaterThan(lightHR));
expect(heavyHR, equals(2.8));
});
test('never smoker has HR of 1.0', () {
expect(getSmokingHR(SmokingStatus.never, 0), equals(1.0));
});
test('sedentary activity has higher HR than active', () {
expect(
getActivityHR(ActivityLevel.sedentary),
greaterThan(getActivityHR(ActivityLevel.high)),
);
});
test('very heavy alcohol has highest HR', () {
expect(getAlcoholHR(AlcoholLevel.veryHeavy), equals(1.6));
expect(getAlcoholHR(AlcoholLevel.none), equals(1.0));
});
test('short sleep has higher HR than optimal', () {
expect(getSleepHR(5.0, true), greaterThan(getSleepHR(7.5, true)));
});
test('inconsistent sleep adds to HR', () {
expect(getSleepHR(7.0, false), greaterThan(getSleepHR(7.0, true)));
});
});
group('Mortality Tables', () {
test('Japan is in Group A', () {
expect(getCountryGroup('Japan'), equals(MortalityGroup.groupA));
});
test('United States is in Group B', () {
expect(getCountryGroup('United States'), equals(MortalityGroup.groupB));
});
test('female LE is higher than male', () {
final maleLE =
getLifeExpectancyAtBirth(MortalityGroup.groupB, Sex.male);
final femaleLE =
getLifeExpectancyAtBirth(MortalityGroup.groupB, Sex.female);
expect(femaleLE, greaterThan(maleLE));
});
test('remaining LE decreases with age', () {
final le30 = getRemainingLifeExpectancy(30, Sex.male, 'United States');
final le50 = getRemainingLifeExpectancy(50, Sex.male, 'United States');
expect(le30, greaterThan(le50));
});
test('getSupportedCountries returns sorted list', () {
final countries = getSupportedCountries();
expect(countries, isNotEmpty);
expect(countries.first, equals('Argentina'));
expect(countries, contains('United States'));
});
});
group('Calculator', () {
final healthyProfile = UserProfile(
age: 40,
sex: Sex.male,
country: 'United States',
heightCm: 175,
weightKg: 75,
);
final optimalInputs = BehavioralInputs.optimal;
final unhealthyInputs = BehavioralInputs(
smoking: SmokingStatus.current,
cigarettesPerDay: 20,
alcohol: AlcoholLevel.heavy,
sleepHours: 5.0,
sleepConsistent: false,
activity: ActivityLevel.sedentary,
diet: DietQuality.poor,
processedFood: ProcessedFoodLevel.daily,
drugUse: DrugUse.regular,
social: SocialConnection.isolated,
stress: StressLevel.chronic,
driving: DrivingExposure.veryHigh,
workHours: WorkHoursLevel.extreme,
);
test('optimal inputs produce low combined HR', () {
final hr = computeCombinedHazard(optimalInputs, 22.0);
expect(hr, closeTo(1.0, 0.1));
});
test('unhealthy inputs produce high combined HR', () {
final hr = computeCombinedHazard(unhealthyInputs, 35.0);
expect(hr, greaterThan(3.0));
});
test('combined HR is capped at 4.0', () {
final hr = computeCombinedHazard(unhealthyInputs, 45.0);
expect(hr, lessThanOrEqualTo(4.0));
});
test('ranking puts smoking above driving for heavy smoker', () {
final smokerInputs = BehavioralInputs(
smoking: SmokingStatus.current,
cigarettesPerDay: 20,
alcohol: AlcoholLevel.none,
sleepHours: 7.5,
sleepConsistent: true,
activity: ActivityLevel.high,
diet: DietQuality.excellent,
processedFood: ProcessedFoodLevel.rarely,
drugUse: DrugUse.none,
social: SocialConnection.strong,
stress: StressLevel.low,
driving: DrivingExposure.veryHigh,
workHours: WorkHoursLevel.normal,
);
final result = calculateRankedFactors(healthyProfile, smokerInputs);
expect(result.rankedFactors, isNotEmpty);
expect(result.dominantFactor?.behaviorKey, equals('smoking'));
});
test('optimal profile returns empty factors', () {
final result = calculateRankedFactors(healthyProfile, optimalInputs);
expect(result.rankedFactors, isEmpty);
});
test('result includes model version', () {
final result = calculateRankedFactors(healthyProfile, unhealthyInputs);
expect(result.modelVersion, equals('1.1'));
});
test('sedentary person sees activity as factor', () {
final sedentaryInputs = BehavioralInputs(
smoking: SmokingStatus.never,
cigarettesPerDay: 0,
alcohol: AlcoholLevel.none,
sleepHours: 7.5,
sleepConsistent: true,
activity: ActivityLevel.sedentary,
diet: DietQuality.excellent,
processedFood: ProcessedFoodLevel.rarely,
drugUse: DrugUse.none,
social: SocialConnection.strong,
stress: StressLevel.low,
driving: DrivingExposure.low,
workHours: WorkHoursLevel.normal,
);
final result = calculateRankedFactors(healthyProfile, sedentaryInputs);
expect(result.rankedFactors, isNotEmpty);
expect(result.dominantFactor?.behaviorKey, equals('activity'));
});
test('delta ranges are reasonable', () {
final result = calculateRankedFactors(healthyProfile, unhealthyInputs);
for (final factor in result.rankedFactors) {
// Low should be less than or equal to high
expect(factor.delta.lowMonths, lessThanOrEqualTo(factor.delta.highMonths));
// Months should be reasonable (not hundreds of years)
expect(factor.delta.highMonths, lessThan(240)); // < 20 years
}
});
});
group('Existing Conditions', () {
test('cardiovascular disease increases diagnosis HR', () {
final hr = getDiagnosisHR({Diagnosis.cardiovascular});
expect(hr, equals(1.5));
});
test('multiple conditions compound', () {
final singleHR = getDiagnosisHR({Diagnosis.diabetes});
final multiHR = getDiagnosisHR({Diagnosis.diabetes, Diagnosis.hypertension});
expect(multiHR, greaterThan(singleHR));
});
test('conditions reduce effective baseline', () {
final healthyProfile = UserProfile(
age: 50,
sex: Sex.male,
country: 'United States',
heightCm: 175,
weightKg: 80,
diagnoses: {},
);
final unhealthyProfile = healthyProfile.copyWith(
diagnoses: {Diagnosis.cardiovascular, Diagnosis.diabetes},
);
final sedentaryInputs = BehavioralInputs(
smoking: SmokingStatus.never,
cigarettesPerDay: 0,
alcohol: AlcoholLevel.none,
sleepHours: 7.5,
sleepConsistent: true,
activity: ActivityLevel.sedentary,
diet: DietQuality.excellent,
processedFood: ProcessedFoodLevel.rarely,
drugUse: DrugUse.none,
social: SocialConnection.strong,
stress: StressLevel.low,
driving: DrivingExposure.low,
workHours: WorkHoursLevel.normal,
);
final healthyResult = calculateRankedFactors(healthyProfile, sedentaryInputs);
final unhealthyResult = calculateRankedFactors(unhealthyProfile, sedentaryInputs);
// Unhealthy person has less to gain (lower baseline)
if (healthyResult.dominantFactor != null && unhealthyResult.dominantFactor != null) {
expect(
unhealthyResult.dominantFactor!.delta.highMonths,
lessThan(healthyResult.dominantFactor!.delta.highMonths),
);
}
});
});
}