mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(measurements): add ANS_CHARGE and CARDIO_LOAD measurement types
Polar Nightly Recharge and Training Load Pro carry two native signals with no existing home: the HRV-based autonomic-nervous-system charge (distinct from the 1-6 recovery band that maps to RECOVERY_SCORE) and the device-native cardio-load strain figure (distinct from WHOOP DAY_STRAIN and the COMPUTED STRAIN_SCORE). Add both to the canonical enum (migration 0166) and wire them through the validation enum, canonical units, plausibility ranges, category cluster, chart-token allowlist, PR-direction resolver, and the measurement- list label/icon/colour maps, with labels across all six locales. Both are device-derived composites, so they read no PR direction and stay out of the clinical vitals PDF, matching the WHOOP-native score classes. Regenerate the OpenAPI contract for the two new enum members.
This commit is contained in:
1 parent
44eed87e89
commit
fd39580ee6
16 files changed
+117
-1
No files matched your search
@@ -114,6 +114,9 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record<string, string> = {
|
||||
AVERAGE_HEART_RATE: "measurements.typeAverageHeartRate",
|
||||
MAX_HEART_RATE: "measurements.typeMaxHeartRate",
|
||||
SLEEP_DISTURBANCE_COUNT: "measurements.typeSleepDisturbanceCount",
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
ANS_CHARGE: "measurements.typeAnsCharge",
|
||||
CARDIO_LOAD: "measurements.typeCardioLoad",
|
||||
};
|
||||
|
||||
export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
|
||||
@@ -225,6 +228,11 @@ export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
|
||||
AVERAGE_HEART_RATE: HeartPulse,
|
||||
MAX_HEART_RATE: Heart,
|
||||
SLEEP_DISTURBANCE_COUNT: Waves,
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
// HeartPulse reads as the autonomic-charge signal; Gauge carries the
|
||||
// cardio-load strain figure (same family as DAY_STRAIN).
|
||||
ANS_CHARGE: HeartPulse,
|
||||
CARDIO_LOAD: Gauge,
|
||||
};
|
||||
|
||||
export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
|
||||
@@ -326,4 +334,8 @@ export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
|
||||
AVERAGE_HEART_RATE: "bg-chart-3/20 text-chart-3",
|
||||
MAX_HEART_RATE: "bg-chart-3/20 text-chart-3",
|
||||
SLEEP_DISTURBANCE_COUNT: "bg-chart-2/20 text-chart-2",
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
// chart-1 (strain/score family) for both, matching DAY_STRAIN.
|
||||
ANS_CHARGE: "bg-chart-1/20 text-chart-1",
|
||||
CARDIO_LOAD: "bg-chart-1/20 text-chart-1",
|
||||
};
|
||||
@@ -90,10 +90,13 @@ const EXPECTED_TYPES = [
|
||||
"AVERAGE_HEART_RATE",
|
||||
"MAX_HEART_RATE",
|
||||
"SLEEP_DISTURBANCE_COUNT",
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
"ANS_CHARGE",
|
||||
"CARDIO_LOAD",
|
||||
] as const;
|
||||
|
||||
describe("measurementTypeEnum coverage", () => {
|
||||
it("exposes the 63 canonical measurement types", () => {
|
||||
it("exposes the 65 canonical measurement types", () => {
|
||||
expect([...measurementTypeEnum.options].sort()).toEqual(
|
||||
[...EXPECTED_TYPES].sort(),
|
||||
);
|
||||
@@ -215,6 +218,13 @@ describe("measurementTypeEnum coverage", () => {
|
||||
"AVERAGE_HEART_RATE",
|
||||
"MAX_HEART_RATE",
|
||||
"SLEEP_DISTURBANCE_COUNT",
|
||||
// v1.17.1 — Polar Nightly Recharge + Training Load Pro components. ANS
|
||||
// charge and Cardio Load are device-derived recovery / strain composites,
|
||||
// not measured clinical vitals; they surface on their own Insights cluster
|
||||
// with a "descriptive, not clinical" framing and never belong in the
|
||||
// clinical vitals PDF. See doctor-report-pdf-core.ts.
|
||||
"ANS_CHARGE",
|
||||
"CARDIO_LOAD",
|
||||
]);
|
||||
|
||||
it("doctor-report PDF vital types cover the canonical enum minus documented exclusions", () => {
|
||||
|
||||
@@ -115,6 +115,11 @@ export const ALLOWED_CHART_TOKENS = [
|
||||
"metric:AVERAGE_HEART_RATE",
|
||||
"metric:MAX_HEART_RATE",
|
||||
"metric:SLEEP_DISTURBANCE_COUNT",
|
||||
// v1.17.1 — Polar-native recovery / strain components. Continuous daily
|
||||
// series, so each carries a `metric:<TYPE>` token and renders through the
|
||||
// generic chart renderer alongside the WHOOP-native score classes.
|
||||
"metric:ANS_CHARGE",
|
||||
"metric:CARDIO_LOAD",
|
||||
] as const;
|
||||
|
||||
export type ChartToken = (typeof ALLOWED_CHART_TOKENS)[number];
|
||||
|
||||
@@ -60,6 +60,12 @@ const MEASUREMENT_TYPES_WITHOUT_HK_COUNTERPART = new Set<MeasurementType>([
|
||||
"AVERAGE_HEART_RATE",
|
||||
"MAX_HEART_RATE",
|
||||
"SLEEP_DISTURBANCE_COUNT",
|
||||
// v1.17.1 — Polar Nightly Recharge + Training Load Pro components. These
|
||||
// ingest server-side from the Polar AccessLink API (source = POLAR), never
|
||||
// from HealthKit: Apple ships no identifier for Polar's autonomic charge or
|
||||
// its device-native cardio-load strain figure. No HK mapping by design.
|
||||
"ANS_CHARGE",
|
||||
"CARDIO_LOAD",
|
||||
]);
|
||||
|
||||
describe("APPLE_HEALTH_TYPE_MAP", () => {
|
||||
|
||||
@@ -188,6 +188,12 @@ export const MEASUREMENT_CATEGORIES: ReadonlyMap<
|
||||
["AVERAGE_HEART_RATE", "cardiovascular"],
|
||||
["MAX_HEART_RATE", "cardiovascular"],
|
||||
["SLEEP_DISTURBANCE_COUNT", "sleep"],
|
||||
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
// ANS charge + Cardio Load are device-native recovery / strain composites,
|
||||
// so they join the scores cluster alongside DAY_STRAIN.
|
||||
["ANS_CHARGE", "scores"],
|
||||
["CARDIO_LOAD", "scores"],
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -176,6 +176,13 @@ export function getPRDirection(
|
||||
// WALKING_HEART_RATE_AVERAGE case above. Defer to a null PR direction.
|
||||
case "AVERAGE_HEART_RATE":
|
||||
case "MAX_HEART_RATE":
|
||||
// v1.17.1 — Polar-native recovery / strain components. ANS_CHARGE is a
|
||||
// baseline-relative autonomic deviation (homeostatic, not a goal axis) and
|
||||
// CARDIO_LOAD is a device-derived cardiovascular-load composite — neither a
|
||||
// higher nor a lower value is an achievement, so they stay null like the
|
||||
// other device-derived scores (HRV_RMSSD / DAY_STRAIN).
|
||||
case "ANS_CHARGE":
|
||||
case "CARDIO_LOAD":
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,13 @@ export const measurementTypeEnum = z.enum([
|
||||
"AVERAGE_HEART_RATE",
|
||||
"MAX_HEART_RATE",
|
||||
"SLEEP_DISTURBANCE_COUNT",
|
||||
// v1.17.1 — Polar Nightly Recharge + Training Load Pro components ingested
|
||||
// server-side as `source = POLAR`. ANS_CHARGE is the HRV-based autonomic
|
||||
// charge (distinct from the 1–6 recovery band that maps to RECOVERY_SCORE);
|
||||
// CARDIO_LOAD is Polar's device-native cardiovascular-strain figure (distinct
|
||||
// from WHOOP DAY_STRAIN and the COMPUTED STRAIN_SCORE).
|
||||
"ANS_CHARGE",
|
||||
"CARDIO_LOAD",
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -312,6 +319,9 @@ const unitMap: Record<string, string> = {
|
||||
MAX_HEART_RATE: "bpm",
|
||||
// Per-night sleep disturbance tally — a plain integer count.
|
||||
SLEEP_DISTURBANCE_COUNT: "count",
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
ANS_CHARGE: "score",
|
||||
CARDIO_LOAD: "score",
|
||||
};
|
||||
|
||||
export function getUnitForType(type: string): string {
|
||||
@@ -487,6 +497,13 @@ const VALUE_RANGES: Record<string, { min: number; max: number }> = {
|
||||
// Per-night sleep disturbance count — 0 is an undisturbed night; 200 is a
|
||||
// generous ceiling over any plausible severely-fragmented night.
|
||||
SLEEP_DISTURBANCE_COUNT: { min: 0, max: 200 },
|
||||
// ── v1.17.1 — Polar Nightly Recharge + Training Load Pro components ──
|
||||
// ANS charge is a baseline-relative autonomic deviation that can run
|
||||
// negative; a generous symmetric ±100 band covers any plausible reading.
|
||||
// Cardio Load is a non-negative cumulative-strain figure; 1000 is a generous
|
||||
// ceiling over any plausible single-day training load.
|
||||
ANS_CHARGE: { min: -100, max: 100 },
|
||||
CARDIO_LOAD: { min: 0, max: 1000 },
|
||||
};
|
||||
|
||||
export function validateMeasurementRange(
|
||||
|
||||
Reference in new issue
Block a user