diff --git a/lib/compute/accepted_naps.dart b/lib/compute/accepted_naps.dart index 3fa991f3a..3229ec665 100644 --- a/lib/compute/accepted_naps.dart +++ b/lib/compute/accepted_naps.dart @@ -5,7 +5,31 @@ import 'nap_edits.dart'; void composeAcceptedNaps(Map bundle, List edits) { final old = bundle['naps'] as Map?; if (old == null && edits.isEmpty) return; - final accepted = applyNapEdits(const [], edits); + final sleep = bundle['sleep']; + final metric = sleep is Map ? sleep['window'] : null; + final window = metric is Map ? metric['value'] : null; + final onset = window is Map ? window['onset_ms'] : null; + final offset = window is Map ? window['offset_ms'] : null; + final hasMainWindow = + onset is num && + offset is num && + onset.isFinite && + offset.isFinite && + offset > onset; + // A corrected main window can absorb an older detector's nap snapshot. + // Suppress only fully contained snapshots, retaining the stored entry for + // a later correction. Partial overlaps keep their recorded duration: the + // snapshot has no stages to apportion sleep inside/outside the main window. + // Manual/confirmed decisions still win. + final accepted = applyNapEdits(const [], edits) + .where( + (nap) => + nap['source'] != 'legacy' || + !hasMainWindow || + (nap['start'] as num) * 1000 < onset || + (nap['end'] as num) * 1000 > offset, + ) + .toList(); // Whether the detector judged the day, persisted so a later compose cannot // turn an unjudged day into a measured zero once its logged naps are gone. // Rows from before the flag: an unjudged day only carried a value when the diff --git a/lib/compute/derivation_engine.dart b/lib/compute/derivation_engine.dart index fba1cc9e6..22a517742 100644 --- a/lib/compute/derivation_engine.dart +++ b/lib/compute/derivation_engine.dart @@ -1810,7 +1810,14 @@ import 'vendor_sleep.dart'; // store `skin_temp_ambient_r` and the illness watch leaves out WHOOP 4 nights // whose skin temp tracks ambient (|r| >= 0.8, or unmeasured). One-shot, // raw-free: days derived before v106 drop naps overlapping the next night. -const int kAlgoVersion = 113; +// 113 → 114: analytics sleep-continuation fix. Main-sleep selection +// shares the detector's inclusive 90-minute continuation limit, retaining +// second sleep blocks after hour-long awakenings; gaps stay wake/unobserved. +// Legacy nap snapshots overlapping the corrected main window no longer add +// the same sleep twice. Explicit nap confirmations and restorations survive. +// 114 → 115: cap grouped sleep at 16 hours and preserve legacy naps that +// only partially overlap the main window; only full containment is absorbed. +const int kAlgoVersion = 115; /// The sibling SHAs this version was derived against, asserted against /// pubspec.yaml in test/db_serve_version_and_reads_test.dart. /// @@ -1996,7 +2003,7 @@ const int kAlgoVersion = 113; // REPIN @ 77fe373: analytics main, #95 merge (readiness autonomic // merge + spread calibration + capped re-centring + robust lnRMSSD // centre), for v112. -const String kAnalyticsPin = '77fe373eb819fda235f80610e898a4bc20a7a249'; +const String kAnalyticsPin = '302332bbe74d82b9bd10d08f93f8a058a3108123'; // Repinned to analytics main's tip, which carries BOTH PR #72 (hrv_freq // Welch gap guard) and PR #73 (overreachingConjunction rhr quantum guard) — // the two independent kAlgoVersion bumps above (93 and 94). Verified both diff --git a/lib/data/db.dart b/lib/data/db.dart index ed954c51b..763608951 100644 --- a/lib/data/db.dart +++ b/lib/data/db.dart @@ -12103,10 +12103,17 @@ class LocalDb { if (restoreSource != null) { // Removing a rejection means "Put it back". Keep its original asleep // minutes and confidence even after raw recordings have expired. + // This is now an explicit decision, even for a legacy snapshot: a + // later main-window correction must not suppress it as old detection. + restoreSource = 'confirmed'; + final restored = Map.from(jsonDecode( + restorePayload ?? rows.single['payload_json'] as String) as Map); + restored['source'] = restoreSource; + restorePayload = jsonEncode(restored); await tx.update('sleep_nap', { 'source': restoreSource, 'created_at': DateTime.now().millisecondsSinceEpoch ~/ 1000, - 'payload_json': ?restorePayload, + 'payload_json': restorePayload, }, where: 'day_id = ? AND start_ts = ?', whereArgs: [dayId, startTs]); } else { await tx.delete('sleep_nap', where: 'day_id = ? AND start_ts = ?', whereArgs: [dayId, startTs]); diff --git a/pubspec.lock b/pubspec.lock index d7f845952..94cf5bae4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -937,17 +937,17 @@ packages: dependency: "direct main" description: path: "." - ref: 77fe373eb819fda235f80610e898a4bc20a7a249 - resolved-ref: 77fe373eb819fda235f80610e898a4bc20a7a249 - url: "https://github.com/OpenStrap/analytics.git" + ref: "302332bbe74d82b9bd10d08f93f8a058a3108123" + resolved-ref: "302332bbe74d82b9bd10d08f93f8a058a3108123" + url: "https://github.com/DropTabl/analytics.git" source: git version: "1.0.0" openstrap_protocol: dependency: "direct main" description: path: "." - ref: 89080bcfac7767b3a871fd3a7d9307f5f30dad26 - resolved-ref: 89080bcfac7767b3a871fd3a7d9307f5f30dad26 + ref: "89080bcfac7767b3a871fd3a7d9307f5f30dad26" + resolved-ref: "89080bcfac7767b3a871fd3a7d9307f5f30dad26" url: "https://github.com/OpenStrap/protocol.git" source: git version: "1.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3501cd10e..86c8971b4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -265,7 +265,10 @@ dependencies: ref: 89080bcfac7767b3a871fd3a7d9307f5f30dad26 openstrap_analytics: git: - url: https://github.com/OpenStrap/analytics.git + # Temporary reviewed pin for OpenStrap/analytics#96. Before release, + # restore the upstream URL and pin its merge SHA in all three locations: + # this ref, pubspec.lock, and kAnalyticsPin. + url: https://github.com/DropTabl/analytics.git # analytics main @ #34 merge. Two hops in one: #32 (the HR-onset bypass # for low-limb-swing cardio) had already merged and this pin was still # sitting on its PR-branch head, and #34 lands the sleep-stager rewrite. @@ -512,7 +515,9 @@ dependencies: # analytics main @ 77fe373, the #95 merge (tree-identical to its head # 608c46f): readiness autonomic merge + spread calibration + capped # re-centring + robust lnRMSSD centre, for kAlgoVersion 112. - ref: 77fe373eb819fda235f80610e898a4bc20a7a249 + # Preserve interrupted main sleep with the detector's + # 90-minute continuation limit; awake/unobserved gaps stay excluded. + ref: 302332bbe74d82b9bd10d08f93f8a058a3108123 # BLE — flutter_blue_plus is the maintained cross-platform GATT client. flutter_blue_plus: ^1.36.8 diff --git a/test/accepted_naps_test.dart b/test/accepted_naps_test.dart new file mode 100644 index 000000000..83ae32131 --- /dev/null +++ b/test/accepted_naps_test.dart @@ -0,0 +1,102 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:openstrap_edge/compute/accepted_naps.dart'; +import 'package:openstrap_edge/compute/nap_edits.dart'; + +Map bundle({bool mainSleep = true}) => { + if (mainSleep) + 'sleep': { + 'window': { + 'value': {'onset_ms': 0, 'offset_ms': 8 * 3600 * 1000}, + }, + }, + 'naps': {'value': >[], 'assessment_complete': true}, + 'sleep_periods': { + 'assessment_complete': true, + 'periods': [ + if (mainSleep) {'is_main': true, 'duration_min': 360}, + ], + }, +}; + +NapEdit nap(int start, int end, {String source = 'legacy'}) => NapEdit( + kind: NapEditKind.added, + startSec: start, + endSec: end, + snapshot: source == 'manual' ? null : {'source': source, 'duration_min': 120}, +); + +void main() { + test('a legacy continuation is not also credited as a nap', () { + final day = bundle(); + final edit = nap(6 * 3600, 8 * 3600); + composeAcceptedNaps(day, [edit]); + + expect(day['naps']['value'], isEmpty); + expect(day['scalars']['nap_min'], 0); + expect(day['sleep_periods']['periods'], hasLength(1)); + expect(day['sleep_periods']['total_asleep_min'], 360); + composeAcceptedNaps(day, [edit]); + expect(day['scalars']['nap_min'], 0); + expect(day['sleep_periods']['total_asleep_min'], 360); + + // Keep the stored decision: a later correction that removes the main + // window can expose this original snapshot again. + final withoutMain = bundle(mainSleep: false); + composeAcceptedNaps(withoutMain, [edit]); + expect(withoutMain['naps']['value'], hasLength(1)); + expect(withoutMain['scalars']['nap_min'], 120); + }); + + for (final bounds in [ + (-3600, 3600), + (7 * 3600, 9 * 3600), + (-3600, 9 * 3600), + ]) { + test( + 'a partially overlapping legacy nap keeps its recorded duration: $bounds', + () { + final day = bundle(); + final edit = nap(bounds.$1, bounds.$2); + composeAcceptedNaps(day, [edit]); + expect(day['naps']['value'], hasLength(1)); + expect(day['scalars']['nap_min'], 120); + expect(day['sleep_periods']['total_asleep_min'], 480); + composeAcceptedNaps(day, [edit]); + expect(day['scalars']['nap_min'], 120); + expect(day['sleep_periods']['total_asleep_min'], 480); + }, + ); + } + + test('legacy naps adjoining the main window stay separate', () { + final day = bundle(); + composeAcceptedNaps(day, [nap(-7200, 0), nap(8 * 3600, 10 * 3600)]); + expect(day['naps']['value'], hasLength(2)); + expect(day['scalars']['nap_min'], 240); + expect(day['sleep_periods']['total_asleep_min'], 600); + }); + + for (final window in [ + {}, + {'onset_ms': 1000, 'offset_ms': 0}, + {'onset_ms': 'unknown', 'offset_ms': 8 * 3600 * 1000}, + ]) { + test('invalid main window preserves the legacy nap: $window', () { + final day = bundle(); + day['sleep'] = { + 'window': {'value': window}, + }; + composeAcceptedNaps(day, [nap(6 * 3600, 8 * 3600)]); + expect(day['naps']['value'], hasLength(1)); + }); + } + + for (final source in ['manual', 'confirmed']) { + test('a corrected window preserves a $source nap decision', () { + final day = bundle(); + composeAcceptedNaps(day, [nap(6 * 3600, 8 * 3600, source: source)]); + expect(day['naps']['value'], hasLength(1)); + expect(day['scalars']['nap_min'], 120); + }); + } +} diff --git a/test/activity_review_regression_test.dart b/test/activity_review_regression_test.dart index c19f34d80..ceeb45ab6 100644 --- a/test/activity_review_regression_test.dart +++ b/test/activity_review_regression_test.dart @@ -484,7 +484,7 @@ void main() { expect(restored['confidence'], 0.7); expect( restored['source'], - source == 'confirmed' ? 'confirmed' : 'legacy', + 'confirmed', ); expect(await store.pending(), isEmpty); expect(await db.query('decoded_onehz'), isEmpty); @@ -513,7 +513,7 @@ void main() { (await LocalDb.dayResult(day))!['payload_json'] as String, ); final restored = (bundle['naps']['value'] as List).single as Map; - expect(restored['source'], 'legacy'); + expect(restored['source'], 'confirmed'); expect(restored['start'], start); expect(bundle['scalars']['nap_min'], 30); }); diff --git a/test/activity_review_test.dart b/test/activity_review_test.dart index fff563977..7ba776ae5 100644 --- a/test/activity_review_test.dart +++ b/test/activity_review_test.dart @@ -61,11 +61,13 @@ void main() { int? version, List naps = const [], String? source = 'band', + Map? mainWindow, }) async { await LocalDb.putDayResult( dayId: day, algoVersion: version ?? kAlgoVersion, payloadJson: jsonEncode({ + if (mainWindow != null) 'sleep': {'window': {'value': mainWindow}}, 'scalars': {'rhr': 53, 'readiness': 74, 'nap_min': 0}, 'naps': {'value': naps, 'count': naps.length}, 'sleep_periods': { @@ -492,6 +494,65 @@ void main() { expect(await store.pending(), isEmpty); }); + test('a corrected main window excludes a captured legacy nap on every write', () async { + await db.update('activity_review_meta', {'activated_at': now}); + await dayResult(version: kAlgoVersion - 1, naps: [nap()], source: null); + await dayResult(mainWindow: { + 'onset_ms': (start - 6 * 3600) * 1000, + 'offset_ms': (start + 3600) * 1000, + }); + Future check() async { + final saved = jsonDecode((await LocalDb.dayResult(day))!['payload_json'] as String); + expect(saved['naps']['value'], isEmpty); + expect(saved['scalars']['nap_min'], 0); + expect(saved['sleep_periods']['total_asleep_min'], 420); + expect((await LocalDb.metricSeries('nap_min')).single['value'], 0); + expect((await LocalDb.napEdits(day)).single['source'], 'legacy'); + } + await check(); + // The durable review path has no raw input and must compose identically. + await ActivityStore.markDayChanged(db, day); + await LocalDb.applyPendingActivityReviews(); + await check(); + }); + + test('a partially overlapping captured nap survives persistence and review', () async { + await db.update('activity_review_meta', {'activated_at': now}); + await dayResult(version: kAlgoVersion - 1, naps: [nap()], source: null); + await dayResult(mainWindow: { + 'onset_ms': (start - 6 * 3600) * 1000, + 'offset_ms': (start + 900) * 1000, + }); + Future check() async { + final saved = jsonDecode((await LocalDb.dayResult(day))!['payload_json'] as String); + expect(saved['naps']['value'], hasLength(1)); + expect(saved['scalars']['nap_min'], 24); + expect(saved['sleep_periods']['total_asleep_min'], 444); + expect((await LocalDb.metricSeries('nap_min')).single['value'], 24); + expect((await LocalDb.napEdits(day)).single['source'], 'legacy'); + } + await check(); + await ActivityStore.markDayChanged(db, day); + await LocalDb.applyPendingActivityReviews(); + await check(); + }); + + test('putting a legacy nap back records an explicit confirmation', () async { + await db.update('activity_review_meta', {'activated_at': now}); + await dayResult(version: kAlgoVersion - 1, naps: [nap()], source: null); + await dayResult(mainWindow: { + 'onset_ms': (start - 6 * 3600) * 1000, + 'offset_ms': (start + 3600) * 1000, + }); + await LocalDb.putNapEdit(dayId: day, startTs: start, endTs: start + 1800, source: 'rejected'); + await LocalDb.deleteNapEdit(day, start); + await LocalDb.applyPendingActivityReviews(); + final saved = jsonDecode((await LocalDb.dayResult(day))!['payload_json'] as String); + expect((await LocalDb.napEdits(day)).single['source'], 'confirmed'); + expect(saved['naps']['value'], hasLength(1)); + expect(saved['scalars']['nap_min'], 24); + }); + test('older refresh cannot clear a newer durable review job', () async { final s = await detectNap(); await store.discard(s); diff --git a/test/fixtures/whoop_freeze/gen4_minus0400.json b/test/fixtures/whoop_freeze/gen4_minus0400.json index 82c4b8122..d16459411 100644 --- a/test/fixtures/whoop_freeze/gen4_minus0400.json +++ b/test/fixtures/whoop_freeze/gen4_minus0400.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -24902,7 +24902,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49595,7 +49595,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73355,7 +73355,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73363,7 +73363,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73371,7 +73371,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73381,7 +73381,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -74589,7 +74589,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 240, @@ -75797,7 +75797,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40, diff --git a/test/fixtures/whoop_freeze/gen4_plus0530.json b/test/fixtures/whoop_freeze/gen4_plus0530.json index 071bd2e28..31416877c 100644 --- a/test/fixtures/whoop_freeze/gen4_plus0530.json +++ b/test/fixtures/whoop_freeze/gen4_plus0530.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -24902,7 +24902,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49595,7 +49595,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73355,7 +73355,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73363,7 +73363,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73371,7 +73371,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73381,7 +73381,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -74589,7 +74589,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 240, @@ -75797,7 +75797,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40, diff --git a/test/fixtures/whoop_freeze/gen4_utc.json b/test/fixtures/whoop_freeze/gen4_utc.json index 985e1bd8f..17a866510 100644 --- a/test/fixtures/whoop_freeze/gen4_utc.json +++ b/test/fixtures/whoop_freeze/gen4_utc.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -24902,7 +24902,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49595,7 +49595,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73355,7 +73355,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73363,7 +73363,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73371,7 +73371,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen4", "coverage_devices": "", @@ -73381,7 +73381,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -74589,7 +74589,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 239, @@ -75797,7 +75797,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40, diff --git a/test/fixtures/whoop_freeze/gen5_minus0400.json b/test/fixtures/whoop_freeze/gen5_minus0400.json index 6c4a2008d..305786e80 100644 --- a/test/fixtures/whoop_freeze/gen5_minus0400.json +++ b/test/fixtures/whoop_freeze/gen5_minus0400.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -25080,7 +25080,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49946,7 +49946,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73868,7 +73868,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73876,7 +73876,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73884,7 +73884,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73894,7 +73894,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -75102,7 +75102,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 240, @@ -76310,7 +76310,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40, diff --git a/test/fixtures/whoop_freeze/gen5_plus0530.json b/test/fixtures/whoop_freeze/gen5_plus0530.json index 3109bf0c1..e53da5827 100644 --- a/test/fixtures/whoop_freeze/gen5_plus0530.json +++ b/test/fixtures/whoop_freeze/gen5_plus0530.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -25080,7 +25080,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49946,7 +49946,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73868,7 +73868,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73876,7 +73876,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73884,7 +73884,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73894,7 +73894,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -75102,7 +75102,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 240, @@ -76310,7 +76310,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40, diff --git a/test/fixtures/whoop_freeze/gen5_utc.json b/test/fixtures/whoop_freeze/gen5_utc.json index 781e9ee03..5531fbc1a 100644 --- a/test/fixtures/whoop_freeze/gen5_utc.json +++ b/test/fixtures/whoop_freeze/gen5_utc.json @@ -2,7 +2,7 @@ "day_result": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-13", "day_confidence": 0.6, @@ -25080,7 +25080,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-14", "day_confidence": 0.6, @@ -49946,7 +49946,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "date": "2026-07-15", "day_confidence": 0.6, @@ -73868,7 +73868,7 @@ "metric_series_version": [ { "date": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73876,7 +73876,7 @@ }, { "date": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73884,7 +73884,7 @@ }, { "date": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "source": "band", "device_family": "gen5", "coverage_devices": "", @@ -73894,7 +73894,7 @@ "wake_day_features": [ { "day_id": "2026-07-13", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-13", "active_min": 90, @@ -75102,7 +75102,7 @@ }, { "day_id": "2026-07-14", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-14", "active_min": 239, @@ -76310,7 +76310,7 @@ }, { "day_id": "2026-07-15", - "algo_version": 113, + "algo_version": 115, "payload_json": { "day_id": "2026-07-15", "active_min": 40,