From c8733f5342686e16c5fe38d61aefa6ef32b1d860 Mon Sep 17 00:00:00 2001 From: Fanboynz Date: Mon, 17 Aug 2026 14:25:56 +1200 Subject: [PATCH] fix(storage): count ALL decoded raw streams in the footprint (was 8 of 14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `storageStats()` (Swift) and `storageRowCounts()` (Android) hand-listed a subset of the decoded raw stream tables, so the Test-Centre "decoded rows" footprint under-reported. Swift summed 8 of 14, Android 9 of 14; both omitted `ppgHrSample`, `sleepStateSample`, `ppgWaveformSample`, `rawImuSample`, `v18AuxSample` (Swift also `stepSample`) — and ppgHr/ppgWaveform/rawImu can each bank a lot of rows on a WHOOP 4.0. Count all 14 accumulating raw tables on both platforms, matching the canonical `TimestampHeal.rawTables` purge list (Swift now iterates that list rather than a fixed expression, so a future raw table can't silently drop out; comments on both sides point at the canonical list). Extended `ReadTests` to seed one row in every stream (incl. rawImu) and assert 18 decoded rows across all 14 tables. Diagnostic-only; no scoring/stored-data/BLE/UI-logic change. --- .../WhoopStore/Sources/WhoopStore/Reads.swift | 25 ++++++++++++------- .../Tests/WhoopStoreTests/ReadTests.swift | 21 ++++++++++++---- .../src/main/java/com/noop/data/WhoopDao.kt | 8 ++++++ .../java/com/noop/data/WhoopRepository.kt | 6 +++++ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Packages/WhoopStore/Sources/WhoopStore/Reads.swift b/Packages/WhoopStore/Sources/WhoopStore/Reads.swift index 4db71d74bd..1b3c551038 100644 --- a/Packages/WhoopStore/Sources/WhoopStore/Reads.swift +++ b/Packages/WhoopStore/Sources/WhoopStore/Reads.swift @@ -348,18 +348,25 @@ extension WhoopStore { /// Aggregate storage footprint: total decoded rows, raw batch count, total raw byteSize. public func storageStats() async throws -> (decodedRows: Int, rawBatches: Int, rawBytes: Int) { try syncRead { db in - let hr = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM hrSample") ?? 0 - let rr = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM rrInterval") ?? 0 - let ev = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM event") ?? 0 - let bat = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM battery") ?? 0 - let spo2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM spo2Sample") ?? 0 - let skin = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM skinTempSample") ?? 0 - let resp = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM respSample") ?? 0 - let grav = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM gravitySample") ?? 0 + // The COMPLETE set of accumulating decoded raw streams — KEEP IN SYNC with + // `TimestampHeal.rawTables` (its per-timestamp purge is the canonical list) and the Android + // `WhoopRepository.storageRowCounts`. Summed by iterating the list rather than a hand-written + // expression, because the old fixed sum silently under-reported: it omitted stepSample, + // ppgHrSample, sleepStateSample, ppgWaveformSample, rawImuSample and v18AuxSample — and a 4.0 + // with PPG (ppgHrSample/ppgWaveformSample) or IMU capture (rawImuSample) banks millions of rows. + // Table names are compile-time constants (never user input), so the interpolation is safe. + let rawTables = ["hrSample", "rrInterval", "event", "battery", + "spo2Sample", "skinTempSample", "respSample", "gravitySample", + "stepSample", "ppgHrSample", "sleepStateSample", "ppgWaveformSample", + "rawImuSample", "v18AuxSample"] + var decoded = 0 + for t in rawTables { + decoded += try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM \(t)") ?? 0 + } let batches = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM rawBatch") ?? 0 let bytes = try Int.fetchOne(db, sql: "SELECT COALESCE(SUM(byteSize), 0) FROM rawBatch") ?? 0 - return (hr + rr + ev + bat + spo2 + skin + resp + grav, batches, bytes) + return (decoded, batches, bytes) } } } diff --git a/Packages/WhoopStore/Tests/WhoopStoreTests/ReadTests.swift b/Packages/WhoopStore/Tests/WhoopStoreTests/ReadTests.swift index e2757a5244..c3d008c5dd 100644 --- a/Packages/WhoopStore/Tests/WhoopStoreTests/ReadTests.swift +++ b/Packages/WhoopStore/Tests/WhoopStoreTests/ReadTests.swift @@ -256,22 +256,33 @@ final class ReadTests: XCTestCase { func testStorageStats() async throws { let store = try await seeded() - // Add one of each biometric stream so the count proves all 8 tables are summed. + // Add one of each decoded raw stream so the count proves ALL of them are summed — including the + // ones the old hand-listed footprint omitted (stepSample, sleepStateSample, ppgHrSample, + // ppgWaveformSample, v18AuxSample, and rawImuSample below). _ = try await store.insert( Streams(spo2: [SpO2Sample(ts: 400, red: 1, ir: 2)], skinTemp: [SkinTempSample(ts: 400, raw: 930)], resp: [RespSample(ts: 400, raw: 3073)], - gravity: [GravitySample(ts: 400, x: 0.1, y: 0.2, z: 0.3)]), + gravity: [GravitySample(ts: 400, x: 0.1, y: 0.2, z: 0.3)], + steps: [StepSample(ts: 400, counter: 5)], + sleepState: [SleepStateSample(ts: 400, state: 1)], + ppgHr: [PpgHrSample(ts: 400, bpm: 60, conf: 0.9)], + ppgWaveform: [PpgWaveformSample(ts: 400, samples: [1, 2, 3])], + v18Aux: [V18AuxSample(ts: 400, slotValues: [1, 2])]), deviceId: "dev1") + _ = try await store.insertRawImu(deviceId: "dev1", + rows: [(ts: 400, cols: [Int16(1), Int16(2)])], + retentionRows: 1000) try await store.enqueueRawBatch( RawBatchMeta(batchId: "b1", deviceId: "dev1", clockRef: ClockRef(device: 0, wall: 0), capturedAt: 1, startTs: 0, endTs: 0, frameCount: 1, byteSize: 4), frames: [[0xAA, 0x00, 0x01, 0x02]]) let stats = try await store.storageStats() - // dev1: 3 hr + 2 rr + 1 event + 1 battery + 1 spo2 + 1 skinTemp + 1 resp + 1 gravity = 11 - // other: 1 hr = 1 → 12 decoded rows across all 8 tables. - XCTAssertEqual(stats.decodedRows, 12) + // dev1: 3 hr + 2 rr + 1 event + 1 battery + 1 spo2 + 1 skinTemp + 1 resp + 1 gravity + // + 1 step + 1 sleepState + 1 ppgHr + 1 ppgWaveform + 1 v18Aux + 1 rawImu = 17 + // other: 1 hr = 1 → 18 decoded rows across all 14 raw tables. + XCTAssertEqual(stats.decodedRows, 18) XCTAssertEqual(stats.rawBatches, 1) XCTAssertEqual(stats.rawBytes, 4) } diff --git a/android/app/src/main/java/com/noop/data/WhoopDao.kt b/android/app/src/main/java/com/noop/data/WhoopDao.kt index f7bad1d25b..d4f07d2299 100644 --- a/android/app/src/main/java/com/noop/data/WhoopDao.kt +++ b/android/app/src/main/java/com/noop/data/WhoopDao.kt @@ -871,6 +871,14 @@ interface WhoopDao : DeviceRegistryDao { @Query("SELECT COUNT(*) FROM spo2Sample") suspend fun countSpo2(): Int @Query("SELECT COUNT(*) FROM skinTempSample") suspend fun countSkinTemp(): Int @Query("SELECT COUNT(*) FROM stepSample") suspend fun countSteps(): Int + // The remaining accumulating decoded raw streams, so the Test-Centre footprint counts ALL of them + // (keep in sync with Swift storageStats / TimestampHeal's raw-table list). ppgHrSample (#156 v26 + // PPG-derived HR), ppgWaveformSample (raw v26 optical waveform) and rawImuSample can each bank a lot. + @Query("SELECT COUNT(*) FROM ppgHrSample") suspend fun countPpgHr(): Int + @Query("SELECT COUNT(*) FROM sleepStateSample") suspend fun countSleepState(): Int + @Query("SELECT COUNT(*) FROM ppgWaveformSample") suspend fun countPpgWaveform(): Int + @Query("SELECT COUNT(*) FROM rawImuSample") suspend fun countRawImu(): Int + @Query("SELECT COUNT(*) FROM v18AuxSample") suspend fun countV18Aux(): Int @Query("SELECT COUNT(*) FROM respSample") suspend fun countResp(): Int @Query("SELECT COUNT(*) FROM gravitySample") suspend fun countGravity(): Int diff --git a/android/app/src/main/java/com/noop/data/WhoopRepository.kt b/android/app/src/main/java/com/noop/data/WhoopRepository.kt index 0da19c5e18..7c29c3ca8f 100644 --- a/android/app/src/main/java/com/noop/data/WhoopRepository.kt +++ b/android/app/src/main/java/com/noop/data/WhoopRepository.kt @@ -1456,6 +1456,12 @@ class WhoopRepository( "battery" to dao.countBattery(), "spo2" to dao.countSpo2(), "skinTemp" to dao.countSkinTemp(), "steps" to dao.countSteps(), "resp" to dao.countResp(), "gravity" to dao.countGravity(), + // The rest of the accumulating decoded raw streams, so the Test-Centre footprint counts ALL of + // them (keep in sync with Swift storageStats / TimestampHeal's raw-table list). ppgHr/ppgWaveform + // /rawImu can each be large. + "ppgHr" to dao.countPpgHr(), "sleepState" to dao.countSleepState(), + "ppgWaveform" to dao.countPpgWaveform(), "rawImu" to dao.countRawImu(), + "v18Aux" to dao.countV18Aux(), ) }.getOrDefault(emptyMap())