mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(doctor-report): resolve recovery by night in the user timezone
Pass the report's timezone into the canonical recovery resolution so the PDF buckets a WHOOP-native row and its computed proxy onto the same physiological night — the proxy is stamped the day that ended while WHOOP stamps the wake morning — instead of a raw calendar day. Aligns the printed recovery summary with the tile, the coach, and the companion feed.
This commit is contained in:
1 parent
5831143e26
commit
5b88401935
2 files changed
+18
-9
No files matched your search
@@ -14,11 +14,14 @@ describe("summariseCanonicalRecovery", () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("prefers the WHOOP-native row over the COMPUTED proxy per day", () => {
|
||||
// The same day has a low proxy AND a high native row. The PDF must read the
|
||||
// native value, not blend both into one average.
|
||||
it("prefers the WHOOP-native row over the COMPUTED proxy per night", () => {
|
||||
// ONE physiological night (wake morning Jun 1) carries both rows on their
|
||||
// real, off-by-one stamps: WHOOP stamps the wake morning (Jun 1), the
|
||||
// COMPUTED proxy stamps the day that ended (May 31 noon). The resolver
|
||||
// shifts the proxy onto the wake day so they collapse to one night, and the
|
||||
// PDF reads the native value, not a blend.
|
||||
const summary = summariseCanonicalRecovery([
|
||||
m("RECOVERY_SCORE", 50, "2026-06-01T12:00:00Z", "COMPUTED"),
|
||||
m("RECOVERY_SCORE", 50, "2026-05-31T12:00:00Z", "COMPUTED"),
|
||||
m("RECOVERY_SCORE", 80, "2026-06-01T06:00:00Z", "WHOOP"),
|
||||
]);
|
||||
expect(summary).not.toBeNull();
|
||||
@@ -30,14 +33,18 @@ describe("summariseCanonicalRecovery", () => {
|
||||
expect(summary!.count).toBe(1);
|
||||
});
|
||||
|
||||
it("summarises across days using one canonical row each", () => {
|
||||
it("summarises across nights using one canonical row each", () => {
|
||||
// Night A (wake Jun 1): WHOOP 80 + its COMPUTED proxy 40 (stamped May 31
|
||||
// noon) collapse to the native 80. Night B (wake Jun 2): a COMPUTED-only
|
||||
// proxy 60, stamped Jun 1 noon, stands alone.
|
||||
const summary = summariseCanonicalRecovery([
|
||||
m("RECOVERY_SCORE", 60, "2026-06-02T12:00:00Z", "COMPUTED"),
|
||||
m("RECOVERY_SCORE", 60, "2026-06-01T12:00:00Z", "COMPUTED"),
|
||||
m("RECOVERY_SCORE", 80, "2026-06-01T06:00:00Z", "WHOOP"),
|
||||
m("RECOVERY_SCORE", 40, "2026-06-01T12:00:00Z", "COMPUTED"),
|
||||
m("RECOVERY_SCORE", 40, "2026-05-31T12:00:00Z", "COMPUTED"),
|
||||
]);
|
||||
expect(summary!.count).toBe(2);
|
||||
// Canonical set: 06-02 COMPUTED 60, 06-01 WHOOP 80 (proxy 40 dropped).
|
||||
// Canonical set: night B COMPUTED 60 (latest stamp), night A WHOOP 80
|
||||
// (proxy 40 dropped).
|
||||
expect(summary!.latest).toBe(60);
|
||||
expect(summary!.min).toBe(60);
|
||||
expect(summary!.max).toBe(80);
|
||||
|
||||
@@ -252,6 +252,7 @@ interface RecoveryMeasurementRow {
|
||||
*/
|
||||
export function summariseCanonicalRecovery(
|
||||
measurements: readonly RecoveryMeasurementRow[],
|
||||
timezone?: string | null,
|
||||
): WellnessScoreSummary | null {
|
||||
const recovery = measurements.filter((m) => m.type === "RECOVERY_SCORE");
|
||||
if (recovery.length === 0) return null;
|
||||
@@ -261,6 +262,7 @@ export function summariseCanonicalRecovery(
|
||||
measuredAt: m.measuredAt,
|
||||
source: m.source,
|
||||
})),
|
||||
timezone,
|
||||
);
|
||||
if (canonical.length === 0) return null;
|
||||
// `resolveCanonicalRecovery` returns rows newest-first; the report wants the
|
||||
@@ -996,7 +998,7 @@ export async function collectDoctorReportData(
|
||||
// the SAME value the tile + iOS feed show — one number, one engine.
|
||||
const wellnessScoreSummaries = WELLNESS_SCORE_REPORT_TYPES.flatMap((type) => {
|
||||
if (type === "RECOVERY_SCORE") {
|
||||
const summary = summariseCanonicalRecovery(measurements);
|
||||
const summary = summariseCanonicalRecovery(measurements, reportTz);
|
||||
return summary ? [summary] : [];
|
||||
}
|
||||
const s = stats[type];
|
||||
|
||||
Reference in new issue
Block a user