mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(workouts): sanitize WHOOP zone enrichment
This commit is contained in:
1 parent
89fefce7ac
commit
d2f6bf33eb
2 files changed
+68
-3
No files matched your search
@@ -89,7 +89,11 @@ describe("pickCanonicalWorkoutRows", () => {
|
||||
avgHeartRate: 146,
|
||||
maxHeartRate: 181,
|
||||
metadata: {
|
||||
zoneDurations: { zone_one_milli: 60_000 },
|
||||
zoneDurations: {
|
||||
zone_one_milli: 60_000,
|
||||
nestedSecret: { mustNotFlow: true },
|
||||
zone_six_milli: 90_000,
|
||||
},
|
||||
whoopWorkoutStrain: 12.3,
|
||||
},
|
||||
};
|
||||
@@ -107,6 +111,11 @@ describe("pickCanonicalWorkoutRows", () => {
|
||||
zoneDurations: { zone_one_milli: 60_000 },
|
||||
},
|
||||
});
|
||||
expect(
|
||||
(picked.metadata as Record<string, unknown>).zoneDurations,
|
||||
).toEqual({
|
||||
zone_one_milli: 60_000,
|
||||
});
|
||||
expect(picked.metadata).not.toHaveProperty("whoopWorkoutStrain");
|
||||
expect(apple).toEqual({
|
||||
id: "apple",
|
||||
@@ -149,6 +158,32 @@ describe("pickCanonicalWorkoutRows", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects malformed WHOOP zone durations instead of partially copying them", () => {
|
||||
const result = pickCanonicalWorkoutRows<RowFixture>([
|
||||
{
|
||||
id: "apple",
|
||||
startedAt: new Date("2026-06-03T06:30:00Z"),
|
||||
sportType: "running",
|
||||
source: "APPLE_HEALTH",
|
||||
metadata: { owner: "apple" },
|
||||
},
|
||||
{
|
||||
id: "whoop",
|
||||
startedAt: new Date("2026-06-03T06:30:30Z"),
|
||||
sportType: "running",
|
||||
source: "WHOOP",
|
||||
metadata: {
|
||||
zoneDurations: {
|
||||
zone_one_milli: 60_000,
|
||||
zone_two_milli: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result[0].metadata).toEqual({ owner: "apple" });
|
||||
});
|
||||
|
||||
it("does not enrich across sport or fixed-window boundaries", () => {
|
||||
const apple: RowFixture = {
|
||||
id: "apple",
|
||||
|
||||
@@ -77,13 +77,43 @@ function jsonRecord(value: unknown): Record<string, unknown> | null {
|
||||
: null;
|
||||
}
|
||||
|
||||
const WHOOP_ZONE_DURATION_KEYS = [
|
||||
"zone_zero_milli",
|
||||
"zone_one_milli",
|
||||
"zone_two_milli",
|
||||
"zone_three_milli",
|
||||
"zone_four_milli",
|
||||
"zone_five_milli",
|
||||
] as const;
|
||||
|
||||
function sanitizedWhoopZoneDurations(
|
||||
metadata: unknown,
|
||||
): Record<string, number> | null {
|
||||
const raw = jsonRecord(jsonRecord(metadata)?.zoneDurations);
|
||||
if (!raw) return null;
|
||||
|
||||
const sanitized: Record<string, number> = {};
|
||||
for (const key of WHOOP_ZONE_DURATION_KEYS) {
|
||||
const value = raw[key];
|
||||
if (value === undefined) continue;
|
||||
if (
|
||||
typeof value !== "number" ||
|
||||
!Number.isFinite(value) ||
|
||||
value < 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
sanitized[key] = value;
|
||||
}
|
||||
return Object.keys(sanitized).length > 0 ? sanitized : null;
|
||||
}
|
||||
|
||||
function enrichFromWhoopTwin<T extends WorkoutPickerRow>(
|
||||
winner: T,
|
||||
donor: T,
|
||||
): T {
|
||||
const winnerMetadata = jsonRecord(winner.metadata);
|
||||
const donorMetadata = jsonRecord(donor.metadata);
|
||||
const donorZones = donorMetadata?.zoneDurations;
|
||||
const donorZones = sanitizedWhoopZoneDurations(donor.metadata);
|
||||
const needsZones =
|
||||
donorZones != null && winnerMetadata?.zoneDurations == null;
|
||||
const needsAvg =
|
||||
|
||||
Reference in new issue
Block a user