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>
7.4 KiB
7.4 KiB
Add Months
Local-first Flutter app that identifies the single behavioral change most likely to extend lifespan using hazard-based modeling.
Quick Start
flutter pub get
flutter test test/risk_engine/ # 23 unit tests
flutter run # Debug mode
flutter run --release -d <device> # Release build
Architecture
lib/
├── main.dart # App entry, routing logic
├── theme.dart # Muted clinical color palette
├── models/
│ ├── enums.dart # Sex, SmokingStatus, AlcoholLevel, etc.
│ ├── user_profile.dart # Age, sex, country, height, weight, diagnoses
│ ├── behavioral_inputs.dart # Modifiable behaviors
│ └── result.dart # LifespanDelta, RankedFactor, CalculationResult
├── risk_engine/
│ ├── hazard_ratios.dart # HR constants from meta-analyses
│ ├── mortality_tables.dart # 50 countries → 4 mortality groups
│ └── calculator.dart # Core ranking algorithm
├── screens/
│ ├── welcome_screen.dart # Onboarding
│ ├── baseline_screen.dart # Demographics, BMI, conditions
│ ├── behavioral_screen.dart # Modifiable factors input
│ └── results_screen.dart # Dominant challenge display
└── storage/
└── local_storage.dart # SQLite persistence
Core Principles
- Local-first: All data on device, no cloud, no accounts, no analytics
- Evidence-based: Hazard ratios from peer-reviewed meta-analyses
- Privacy: Delete All Data = full wipe including encryption keys
- Neutral tone: "Exposure", "Factor", "Estimated gain" — no moral language
Risk Engine
Hazard Ratio Model
Combined HR = Smoking × Alcohol × Sleep × Activity × BMI × Driving × WorkHours
Capped at 4.0 to prevent unrealistic compounding.
Key Hazard Ratios
| Factor | Level | HR |
|---|---|---|
| Smoking | Never | 1.0 |
| Former | 1.3 | |
| Current (<10/day) | 1.8 | |
| Current (10-20/day) | 2.2 | |
| Current (>20/day) | 2.8 | |
| Alcohol | None/Light | 1.0 |
| Moderate (8-14/wk) | 1.1 | |
| Heavy (15-21/wk) | 1.3 | |
| Very Heavy (21+/wk) | 1.6 | |
| Sleep | 7-8 hrs | 1.0 |
| 6-7 hrs | 1.05 | |
| <6 hrs | 1.15 | |
| >8 hrs | 1.10 | |
| + Inconsistent | ×1.05 | |
| Activity | High | 1.0 |
| Moderate | 1.05 | |
| Light | 1.15 | |
| Sedentary | 1.4 | |
| BMI | 18.5-25 | 1.0 |
| 25-30 | 1.1 | |
| 30-35 | 1.2 | |
| 35-40 | 1.4 | |
| 40+ | 1.8 | |
| Driving | <50 mi/wk | 1.0 |
| 50-150 | 1.02 | |
| 150-300 | 1.04 | |
| 300+ | 1.08 | |
| Work Hours | <40 | 1.0 |
| 40-55 | 1.05 | |
| 55-70 | 1.15 | |
| 70+ | 1.3 |
Existing Conditions (Non-modifiable)
| Condition | HR Multiplier |
|---|---|
| Cardiovascular | 1.5 |
| Diabetes | 1.4 |
| Cancer (active) | 2.0 |
| COPD | 1.6 |
| Hypertension | 1.2 |
Delta Calculation
// Simplified Gompertz-style approximation
rawDeltaYears = baselineYears × (1 - modifiedHR/currentHR) × 0.3
// Convert to months with uncertainty range
lowMonths = rawDeltaYears × 12 × 0.6
highMonths = rawDeltaYears × 12 × 1.4
Ranking Algorithm
- For each modifiable behavior:
- Compute HR with behavior set to optimal
- Calculate delta months gained
- Sort by midpoint delta descending
- Filter out behaviors already at optimal
- Return ranked list with confidence levels
Confidence Levels
| Factor | Confidence | Rationale |
|---|---|---|
| Smoking | High | Extremely well-documented |
| Alcohol (heavy) | High | Strong epidemiological data |
| Physical Activity | High | Large meta-analyses |
| BMI (extreme) | High | Well-established |
| Sleep | Moderate | Growing evidence, some confounding |
| Work Hours | Moderate | Decent studies, cultural variation |
| Driving | Emerging | Harder to isolate, regional variation |
Mortality Tables
Country Groups
| Group | LE at Birth (M) | Countries |
|---|---|---|
| A | 81 | Japan, Switzerland, Singapore, Spain, Italy, Australia, Iceland, Israel, Sweden, France, South Korea, Norway |
| B | 77 | USA, UK, Germany, Canada, Netherlands, Belgium, Austria, Finland, Ireland, New Zealand, Denmark, Portugal, Czech Republic, Poland, Chile, Costa Rica, Cuba, UAE, Qatar, Taiwan |
| C | 72 | China, Brazil, Mexico, Russia, Turkey, Argentina, Colombia, Thailand, Vietnam, Malaysia, Iran, Saudi Arabia, Egypt, Ukraine, Romania, Hungary, Peru, Philippines |
| D | 65 | India, Indonesia, South Africa, Pakistan, Bangladesh, Nigeria, Kenya, Ghana, Ethiopia, Myanmar, Nepal, Cambodia |
Female LE = Male LE + 4.5 years
Remaining Life Expectancy
// Survivors have higher LE than birth cohort suggests
survivorBonus = currentAge × 0.15 // capped at 5
remainingLE = (leAtBirth - currentAge) + survivorBonus
Storage
SQLite Schema
CREATE TABLE user_data (
key TEXT PRIMARY KEY,
value TEXT NOT NULL, -- JSON
updated_at INTEGER NOT NULL
)
Stored Keys
profile: UserProfile JSONbehaviors: BehavioralInputs JSONlastResult: CalculationResult JSON
Delete All Data
await db.delete('user_data'); // Wipes all rows
UI Theme
Colors (Muted Clinical)
primary: #4A90A4 // Muted teal
primaryDark: #2D6073
primaryLight: #7BB8CC
surface: #F8FAFB
textPrimary: #1A2B33
textSecondary: #5A6B73
success: #4A9A7C
warning: #B8934A
error: #A45A5A
Typography
- Headlines: SF Pro Display style, tight letter-spacing
- Body: 16px, 1.5 line height
- Labels: 600 weight
Testing
# Run all risk engine tests
flutter test test/risk_engine/
# 23 tests covering:
# - Hazard ratios for each behavior
# - Mortality table lookups
# - Combined HR calculation
# - Ranking algorithm
# - Confidence assignments
# - Existing conditions impact
Widget tests require SQLite mocking — integration test on device.
App Icon
Generated programmatically: muted teal tree on white background.
dart run tool/generate_icon.dart
dart run flutter_launcher_icons
Model Versioning
const modelVersion = '1.0';
Stored with each calculation result. Future updates can show: "Results updated under model v1.1"
Screen Flow
Welcome → Baseline → Behavioral → Results
↑ ↓
└────── Recalculate ───┘
Results screen shows:
- Dominant Challenge (largest gain)
- Estimated Gain range (e.g., "36-60 months")
- Confidence level (High/Moderate/Emerging)
- Secondary factor
- All other factors (if any)
- Delete All Data button
Key Design Decisions
- BMI is baseline only — affects calculation but not shown as a "challenge"
- Cigarettes/day — slider with haptic at 20 (one pack), max 40
- Country — full dropdown (50 countries), mapped internally to groups
- No gamification — no streaks, badges, or progress tracking
- No notifications — user controls when to recalculate
Dependencies
dependencies:
sqflite: ^2.3.0 # Local database
path: ^1.8.3 # Path utilities
flutter_secure_storage: # Encryption key storage (future)
dev_dependencies:
flutter_launcher_icons: ^0.14.1
Future Enhancements (Out of MVP Scope)
- Partner mode (compare two profiles)
- Export PDF summary
- Drug use factor
- Diet quality factor
- Stress/mental health factor
- Location-based mortality refinement
- Longitudinal tracking