Local-first Flutter app that identifies the single behavioral change most likely to extend lifespan using hazard-based modeling. Features: - Risk engine with hazard ratios from meta-analyses - 50 countries mapped to 4 mortality groups - 6 modifiable factors: smoking, alcohol, sleep, activity, driving, work hours - SQLite local storage (no cloud, no accounts) - Muted clinical UI theme - 23 unit tests for risk engine Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
90 lines
2.5 KiB
Dart
90 lines
2.5 KiB
Dart
import 'enums.dart';
|
||
|
||
class LifespanDelta {
|
||
final int lowMonths;
|
||
final int highMonths;
|
||
final Confidence confidence;
|
||
|
||
const LifespanDelta({
|
||
required this.lowMonths,
|
||
required this.highMonths,
|
||
required this.confidence,
|
||
});
|
||
|
||
int get midpointMonths => ((lowMonths + highMonths) / 2).round();
|
||
|
||
String get rangeDisplay {
|
||
if (lowMonths <= 0 && highMonths <= 0) return '0';
|
||
if (lowMonths == highMonths) return '$lowMonths';
|
||
return '$lowMonths–$highMonths';
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'lowMonths': lowMonths,
|
||
'highMonths': highMonths,
|
||
'confidence': confidence.name,
|
||
};
|
||
|
||
factory LifespanDelta.fromJson(Map<String, dynamic> json) => LifespanDelta(
|
||
lowMonths: json['lowMonths'] as int,
|
||
highMonths: json['highMonths'] as int,
|
||
confidence: Confidence.values.byName(json['confidence'] as String),
|
||
);
|
||
}
|
||
|
||
class RankedFactor {
|
||
final String behaviorKey;
|
||
final String displayName;
|
||
final LifespanDelta delta;
|
||
|
||
const RankedFactor({
|
||
required this.behaviorKey,
|
||
required this.displayName,
|
||
required this.delta,
|
||
});
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'behaviorKey': behaviorKey,
|
||
'displayName': displayName,
|
||
'delta': delta.toJson(),
|
||
};
|
||
|
||
factory RankedFactor.fromJson(Map<String, dynamic> json) => RankedFactor(
|
||
behaviorKey: json['behaviorKey'] as String,
|
||
displayName: json['displayName'] as String,
|
||
delta: LifespanDelta.fromJson(json['delta'] as Map<String, dynamic>),
|
||
);
|
||
}
|
||
|
||
class CalculationResult {
|
||
final List<RankedFactor> rankedFactors;
|
||
final String modelVersion;
|
||
final DateTime calculatedAt;
|
||
|
||
const CalculationResult({
|
||
required this.rankedFactors,
|
||
required this.modelVersion,
|
||
required this.calculatedAt,
|
||
});
|
||
|
||
RankedFactor? get dominantFactor =>
|
||
rankedFactors.isNotEmpty ? rankedFactors.first : null;
|
||
|
||
RankedFactor? get secondaryFactor =>
|
||
rankedFactors.length > 1 ? rankedFactors[1] : null;
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'rankedFactors': rankedFactors.map((f) => f.toJson()).toList(),
|
||
'modelVersion': modelVersion,
|
||
'calculatedAt': calculatedAt.toIso8601String(),
|
||
};
|
||
|
||
factory CalculationResult.fromJson(Map<String, dynamic> json) =>
|
||
CalculationResult(
|
||
rankedFactors: (json['rankedFactors'] as List<dynamic>)
|
||
.map((f) => RankedFactor.fromJson(f as Map<String, dynamic>))
|
||
.toList(),
|
||
modelVersion: json['modelVersion'] as String,
|
||
calculatedAt: DateTime.parse(json['calculatedAt'] as String),
|
||
);
|
||
}
|