The Lab feature enables users to conduct scientific N-of-1 experiments to test hypotheses about their wellbeing and productivity. Users can form hypotheses like "Does morning exercise improve my focus?" and systematically test them with proper baseline/intervention phases.
@JsonSerializable()
class Experiment {
final String id;
final String title; // "Does morning exercise improve focus?"
final String hypothesis; // "Morning exercise improves my afternoon focus scores"
final ExperimentStatus status; // draft, baseline, active, completed, abandoned
final ExperimentDesign design; // AB, baselineIntervention, etc.
// Variables
final InterventionVariable intervention; // What we're testing
final OutcomeVariable outcome; // What we're measuring (linked to PulseType)
// Configuration
final int baselineDays; // Days to collect baseline data (default: 7)
final int interventionDays; // Days to run intervention (default: 14)
final int minimumDataPoints; // Minimum entries needed for validity
// Timeline
final DateTime createdAt;
final DateTime? startedAt; // When baseline started
final DateTime? interventionStartedAt; // When intervention phase started
final DateTime? completedAt;
// Linked data
final String? linkedHabitId; // If intervention is tracked via habit
final String? linkedGoalId; // Optional goal this experiment supports
// Results (populated when complete)
final ExperimentResults? results;
// Notes and observations
final List<ExperimentNote> notes;
}
enum ExperimentStatus {
draft, // Not yet started
baseline, // Collecting baseline data
active, // Running intervention
analyzing, // Collecting final data
completed, // Finished with results
abandoned, // User stopped early
}
enum ExperimentDesign {
abTest, // Random on/off days
baselineIntervention, // First baseline, then intervention (most common)
reversal, // A-B-A design
multipleBaseline, // Staggered across behaviors
}@JsonSerializable()
class InterventionVariable {
final String id;
final String name; // "Morning exercise"
final String description; // "30 minutes of exercise before 9 AM"
final InterventionType type; // habit, custom, timeBased
final String? linkedHabitId; // Link to existing habit for tracking
final List<InterventionCondition>? conditions; // For A/B designs
}
enum InterventionType {
habit, // Tracked via habit completion
custom, // User manually logs
timeBased, // Time of day variation
}
@JsonSerializable()
class InterventionCondition {
final String id;
final String label; // "Exercise", "No Exercise"
final String description;
}@JsonSerializable()
class OutcomeVariable {
final String id;
final String name; // "Focus level"
final String pulseTypeName; // Links to PulseType.name (e.g., "Focus")
final MeasurementTiming timing; // When to measure
final int? specificHour; // If timing is specificTime
}
enum MeasurementTiming {
morning, // 6-10 AM
afternoon, // 12-4 PM
evening, // 6-10 PM
specificTime, // User-defined hour
anytime, // Any pulse entry counts
}@JsonSerializable()
class ExperimentEntry {
final String id;
final String experimentId;
final DateTime date;
final ExperimentPhase phase; // baseline, intervention
// Intervention data
final bool? interventionApplied; // Did they do the intervention?
final String? conditionId; // For A/B: which condition
final InterventionLog? interventionLog;
// Outcome data
final int? outcomeValue; // 1-5 scale from Pulse
final String? linkedPulseEntryId; // Reference to actual pulse entry
// Quality indicators
final bool isComplete; // Has both intervention + outcome
final String? notes; // Optional daily notes
}
enum ExperimentPhase {
baseline,
intervention,
followup,
}
@JsonSerializable()
class InterventionLog {
final DateTime? completedAt;
final int? duration; // Minutes, if applicable
final int? intensity; // 1-5 scale, if applicable
final String? notes;
}@JsonSerializable()
class ExperimentResults {
final String experimentId;
final DateTime analyzedAt;
// Descriptive statistics
final double baselineMean;
final double baselineStdDev;
final int baselineN;
final double interventionMean;
final double interventionStdDev;
final int interventionN;
// Effect measures
final double effectSize; // Cohen's d
final double percentChange; // ((intervention - baseline) / baseline) * 100
final EffectDirection direction; // improved, declined, noChange
// Statistical significance (simplified for non-statisticians)
final double confidenceLevel; // e.g., 0.85 = 85% confident
final SignificanceLevel significance; // high, moderate, low, insufficient
// Interpretation
final String summaryStatement; // AI-generated plain English summary
final List<String> caveats; // Data quality warnings
final List<String> suggestions; // What to try next
}
enum EffectDirection { improved, declined, noChange }
enum SignificanceLevel { high, moderate, low, insufficient }@JsonSerializable()
class ExperimentNote {
final String id;
final String experimentId;
final DateTime createdAt;
final String content;
final ExperimentNoteType type;
}
enum ExperimentNoteType {
observation, // General observation
confoundingFactor, // Something that might affect results
adjustment, // Change made to experiment
}Experiment
├── InterventionVariable
│ └── InterventionCondition[]
├── OutcomeVariable
│ └── → PulseType (by name)
├── ExperimentEntry[]
│ ├── InterventionLog
│ └── → PulseEntry (by id)
├── ExperimentResults
└── ExperimentNote[]
class ExperimentProvider extends ChangeNotifier {
final StorageService _storage = StorageService();
List<Experiment> _experiments = [];
bool _isLoading = false;
// Getters
List<Experiment> get experiments => _experiments;
List<Experiment> get activeExperiments =>
_experiments.where((e) => e.status == ExperimentStatus.active ||
e.status == ExperimentStatus.baseline).toList();
List<Experiment> get completedExperiments =>
_experiments.where((e) => e.status == ExperimentStatus.completed).toList();
bool get isLoading => _isLoading;
ExperimentProvider() {
_loadExperiments();
}
// CRUD operations
Future<void> addExperiment(Experiment experiment);
Future<void> updateExperiment(Experiment experiment);
Future<void> deleteExperiment(String id);
Experiment? getExperimentById(String id);
// Phase transitions
Future<void> startBaseline(String experimentId);
Future<void> startIntervention(String experimentId);
Future<void> completeExperiment(String experimentId, ExperimentResults results);
Future<void> abandonExperiment(String experimentId, String reason);
// Entry management
Future<void> addEntry(String experimentId, ExperimentEntry entry);
Future<void> updateEntry(String experimentId, ExperimentEntry entry);
List<ExperimentEntry> getEntriesForExperiment(String experimentId);
// Notes
Future<void> addNote(String experimentId, ExperimentNote note);
// Reload support
Future<void> reload();
}Statistical analysis and result generation.
class ExperimentAnalysisService {
/// Analyze experiment data and generate results
Future<ExperimentResults> analyzeExperiment({
required Experiment experiment,
required List<ExperimentEntry> entries,
});
/// Calculate Cohen's d effect size
double calculateEffectSize(List<int> baseline, List<int> intervention);
/// Calculate confidence level
double calculateConfidence(List<int> baseline, List<int> intervention);
/// Determine significance level
SignificanceLevel determineSignificance(double effectSize, double confidence, int n);
/// Check minimum data requirements
bool hasMinimumData(List<ExperimentEntry> entries, int minimumDataPoints);
/// Get data quality warnings
List<String> getDataQualityCaveats(List<ExperimentEntry> entries);
}AI-powered experiment suggestions.
class ExperimentSuggestionService {
/// Generate suggestions based on user data and patterns
Future<List<ExperimentSuggestion>> getSuggestions({
required List<Goal> goals,
required List<Habit> habits,
required List<PulseEntry> pulseHistory,
required List<JournalEntry> journalHistory,
});
/// Generate interpretation of results
Future<String> interpretResults({
required Experiment experiment,
required ExperimentResults results,
});
}
class ExperimentSuggestion {
final String title;
final String hypothesis;
final String interventionDescription;
final String outcomeDescription;
final String rationale;
final ExperimentDesign suggestedDesign;
}Add Lab as a card in the Wellness Dashboard under "Insights & Analytics":
WellnessDashboardScreen
└── "Lab - Personal Experiments" card
└── LabHomeScreen
LabHomeScreen
├── Active Experiments List
├── Quick Actions (New Experiment, View Completed)
├── CreateExperimentScreen (wizard)
│ ├── Step 1: Define Hypothesis
│ ├── Step 2: Choose Intervention
│ ├── Step 3: Choose Outcome Metric
│ └── Step 4: Configure Timeline
├── ExperimentDetailScreen
│ ├── Progress Overview
│ ├── Daily Entry Log
│ └── Results (when complete)
├── DailyExperimentEntryScreen
└── ExperimentResultsScreen
├── Statistical Summary
├── Visualization (charts)
└── AI Interpretation
| File | Purpose |
|---|---|
lib/screens/lab_home_screen.dart |
Main Lab dashboard |
lib/screens/create_experiment_screen.dart |
Multi-step creation wizard |
lib/screens/experiment_detail_screen.dart |
View/manage specific experiment |
lib/screens/experiment_results_screen.dart |
Detailed results |
lib/screens/daily_experiment_entry_screen.dart |
Daily logging |
Add to lib/services/reflection_function_schemas.dart:
static const Map<String, dynamic> createExperimentTool = {
'name': 'create_experiment',
'description': 'Creates a new personal experiment to test a hypothesis',
'input_schema': {
'type': 'object',
'properties': {
'title': {'type': 'string'},
'hypothesis': {'type': 'string'},
'interventionName': {'type': 'string'},
'outcomeName': {'type': 'string'},
'pulseTypeName': {'type': 'string'},
'baselineDays': {'type': 'integer'},
'interventionDays': {'type': 'integer'},
'linkedHabitId': {'type': 'string'},
},
'required': ['title', 'hypothesis', 'interventionName', 'outcomeName'],
},
};
static const Map<String, dynamic> suggestExperimentTool = {
'name': 'suggest_experiment',
'description': 'Suggests an experiment based on user patterns',
'input_schema': {...},
};
static const Map<String, dynamic> logExperimentEntryTool = {
'name': 'log_experiment_entry',
'description': 'Logs a daily entry for an active experiment',
'input_schema': {...},
};Add experiment data to AI context in context_management_service.dart.
// Cohen's d effect size
double calculateCohenD(List<int> baseline, List<int> intervention) {
final baselineMean = baseline.average;
final interventionMean = intervention.average;
final pooledStdDev = sqrt(
((baseline.length - 1) * variance(baseline) +
(intervention.length - 1) * variance(intervention)) /
(baseline.length + intervention.length - 2)
);
return (interventionMean - baselineMean) / pooledStdDev;
}
// Effect size interpretation
String interpretEffectSize(double d) {
if (d.abs() < 0.2) return 'negligible';
if (d.abs() < 0.5) return 'small';
if (d.abs() < 0.8) return 'medium';
return 'large';
}Transform statistical results into plain language:
- "Your focus improved by 18% on exercise days (medium effect)"
- "Based on 14 days of data, we're 85% confident this is a real effect"
- "Consider continuing this habit - the evidence suggests it helps"
Files to create:
lib/models/experiment.dartlib/models/intervention_variable.dartlib/models/outcome_variable.dartlib/models/experiment_entry.dartlib/models/experiment_results.dartlib/providers/experiment_provider.dart
Files to modify:
lib/services/storage_service.dart- Add experiment storagelib/services/backup_service.dart- Add export/importlib/main.dart- Register provider
Files to create:
lib/services/experiment_analysis_service.dart
Files to create:
lib/screens/lab_home_screen.dartlib/screens/create_experiment_screen.dartlib/screens/experiment_detail_screen.dart
Files to modify:
lib/screens/wellness_dashboard_screen.dart- Add Lab entry point
Files to create:
lib/screens/daily_experiment_entry_screen.dartlib/screens/experiment_results_screen.dartlib/widgets/experiment_card.dartlib/widgets/experiment_results_chart.dart
Files to create:
lib/services/experiment_suggestion_service.dart
Files to modify:
lib/services/reflection_function_schemas.dartlib/services/reflection_action_service.dartlib/services/context_management_service.dartlib/services/ai_service.dart
- Add provider tests
- Add analysis service tests
- Add schema validation tests
- UI polish
- Insufficient data: Show "Need X more days" instead of results
- Missed days: Calculate with available data, note in caveats
- No pulse entries: Prompt user to log outcome metric
- Habit not completed: Allow manual override for intervention logging
- Experiment abandoned: Save partial data for reference
- Confounding factors: Allow user to add notes about external factors
| Widget | Purpose |
|---|---|
lib/widgets/experiment_card.dart |
Summary card for list view |
lib/widgets/experiment_progress_indicator.dart |
Visual phase progress |
lib/widgets/experiment_entry_form.dart |
Daily logging form |
lib/widgets/experiment_results_chart.dart |
Before/after visualization |
lib/widgets/hypothesis_input_widget.dart |
Guided hypothesis builder |
lib/widgets/intervention_selector.dart |
Select habit or custom action |
lib/widgets/outcome_selector.dart |
Select pulse metric |
| Existing Feature | Integration |
|---|---|
| Pulse | Outcome metrics use existing PulseTypes |
| Habits | Interventions can be tracked via habit completion |
| Journal | Experiment notes link to journal entries |
| Goals | Experiments can be linked to goals |
| AI Mentor | Suggests experiments, interprets results |
| Check-ins | Can prompt for experiment data |
| Backup | Full export/import of experiment data |