import '../models/habit.dart'; import '../models/tracker_entry.dart'; /// fn-weekly-minimum-ratio /// /// Computes the share of done check-ins in the last 7 days that used the /// habit's "minimum" dose variant. Useful for L2-conditional habits where the /// user picks a minimum option on bad-condition days. A high ratio is fine /// (the protocol's whole point), but it also signals the user is mostly /// running on minimum dose — a context the UI can surface. /// /// Returns 0.0 when there are no done entries in the window (no division by /// zero). class WeeklyMinimumRatio { final int totalDone; final int minimumUsed; final double ratio; // 0.0..1.0 final DateTime windowStart; // inclusive, YYYY-MM-DD == windowStart final DateTime windowEnd; // inclusive const WeeklyMinimumRatio({ required this.totalDone, required this.minimumUsed, required this.ratio, required this.windowStart, required this.windowEnd, }); } /// Pure function: caller resolves the variant rows and passes them in. /// /// - [entries] should already be filtered to the habit and to value=done. /// - [variantsById] maps variant_id → variant (only minimums need to be /// present, but a full map is fine). /// - [asOf] is treated as the inclusive end of the 7-day window. WeeklyMinimumRatio computeWeeklyMinimumRatio({ required Iterable entries, required Map variantsById, required DateTime asOf, }) { final end = DateTime(asOf.year, asOf.month, asOf.day); final start = end.subtract(const Duration(days: 6)); var totalDone = 0; var minimumUsed = 0; for (final e in entries) { if (e.value != TrackerValue.done) continue; final d = DateTime.parse(e.date); if (d.isBefore(start) || d.isAfter(end)) continue; totalDone += 1; final vId = e.variantId; if (vId == null) continue; final v = variantsById[vId]; if (v != null && v.isMinimum) minimumUsed += 1; } final ratio = totalDone == 0 ? 0.0 : minimumUsed / totalDone; return WeeklyMinimumRatio( totalDone: totalDone, minimumUsed: minimumUsed, ratio: ratio, windowStart: start, windowEnd: end, ); }