mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
refactor(measurements): hoist cumulative metric-key mapping into shared module
The analytics route carried an inline MeasurementType → SourcePriorityMetricKey switch next to the cumulative-day-sum bucket-and-sum. Adding a new cumulative metric required updates in three places — the SourcePriorityMetricKey enum, the route-level switch, and CUMULATIVE_HK_TYPES — with no shared test to pin the drift. Same shape as the H-2 / W10-4 H3 finding the previous commit closed for the cumulative-type set. Move the lookup next to CUMULATIVE_DAY_SUM_TYPES in src/lib/measurements/cumulative-day-sum.ts and add vitest assertions: every CUMULATIVE_HK_TYPES member resolves either to a real SourcePriorityMetricKey or to null (the picker's pass-through escape hatch for types like TIME_IN_DAYLIGHT). The route now imports the helper instead of duplicating the switch.
This commit is contained in:
1 parent
3154c1bc36
commit
0750b97845
3 files changed
+73
-24
No files matched your search
@@ -14,42 +14,19 @@ import type {
|
||||
} from "@/generated/prisma/client";
|
||||
import { measurementTypeEnum } from "@/lib/validations/measurement";
|
||||
import { pickCanonicalSourceRows } from "@/lib/analytics/source-priority";
|
||||
import type { SourcePriorityMetricKey } from "@/lib/validations/source-priority";
|
||||
import { ensureUserRollupsFresh } from "@/lib/measurements/rollups";
|
||||
import { probeRollupCoverage } from "@/lib/measurements/rollup-coverage";
|
||||
import { computeBpInTargetFastPath } from "@/lib/analytics/bp-in-target-fast-path";
|
||||
import { computeUserHealthScoreFastPath } from "@/lib/analytics/health-score-fast-path";
|
||||
import { computeCorrelationHypothesesFastPath } from "@/lib/analytics/correlations-fast-path";
|
||||
import {
|
||||
cumulativeMetricKey,
|
||||
isCumulativeDaySumType,
|
||||
pickCumulativeDaySum,
|
||||
} from "@/lib/measurements/cumulative-day-sum";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* v1.4.36 W4c — cumulative MeasurementType → SourcePriorityMetricKey
|
||||
* for `pickCanonicalSourceRows`. Returns `null` for types without a
|
||||
* dedicated priority ladder (e.g. TIME_IN_DAYLIGHT), which fall
|
||||
* through the picker's "no ladder" pass-through branch.
|
||||
*/
|
||||
function cumulativeMetricKey(
|
||||
type: MeasurementType,
|
||||
): SourcePriorityMetricKey | null {
|
||||
switch (type) {
|
||||
case "ACTIVITY_STEPS":
|
||||
return "steps";
|
||||
case "ACTIVE_ENERGY_BURNED":
|
||||
return "activeEnergy";
|
||||
case "WALKING_RUNNING_DISTANCE":
|
||||
return "walkingRunningDistance";
|
||||
case "FLIGHTS_CLIMBED":
|
||||
return "flightsClimbed";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.4.33 C1 — pull `?slice=…` from either a NextRequest (the
|
||||
* route's production wrapper) or a plain `Request` (the integration
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { SOURCE_PRIORITY_METRIC_KEYS } from "@/lib/validations/source-priority";
|
||||
import { CUMULATIVE_HK_TYPES } from "../apple-health-mapping";
|
||||
import {
|
||||
CUMULATIVE_DAY_SUM_TYPES,
|
||||
cumulativeMetricKey,
|
||||
isCumulativeDaySumType,
|
||||
pickCumulativeDaySum,
|
||||
} from "../cumulative-day-sum";
|
||||
@@ -119,3 +121,38 @@ describe("CUMULATIVE_DAY_SUM_TYPES / CUMULATIVE_HK_TYPES parity", () => {
|
||||
expect(new Set(CUMULATIVE_DAY_SUM_TYPES)).toEqual(CUMULATIVE_HK_TYPES);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cumulativeMetricKey", () => {
|
||||
// v1.4.37 W10 — pinned mapping from MeasurementType →
|
||||
// SourcePriorityMetricKey for the cumulative analytics fast-path.
|
||||
// Every member of CUMULATIVE_HK_TYPES must resolve either to a real
|
||||
// SourcePriorityMetricKey (so the canonical-source picker fires) or
|
||||
// to `null` (so the picker's no-ladder pass-through branch runs).
|
||||
// A new cumulative HK type without an explicit map entry falls
|
||||
// through to the default branch (returns null) — that's the
|
||||
// intended escape hatch for types like TIME_IN_DAYLIGHT that have
|
||||
// no clinical competitor; pin the audit here so the operator is
|
||||
// forced to think about it.
|
||||
it("returns a known SourcePriorityMetricKey or null for every CUMULATIVE_HK_TYPES member", () => {
|
||||
const validKeys = new Set<string>(SOURCE_PRIORITY_METRIC_KEYS);
|
||||
for (const type of CUMULATIVE_HK_TYPES) {
|
||||
const key = cumulativeMetricKey(type);
|
||||
if (key !== null) {
|
||||
expect(validKeys.has(key)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("explicitly maps the four metrics that own a SourcePriorityMetricKey ladder", () => {
|
||||
expect(cumulativeMetricKey("ACTIVITY_STEPS")).toBe("steps");
|
||||
expect(cumulativeMetricKey("ACTIVE_ENERGY_BURNED")).toBe("activeEnergy");
|
||||
expect(cumulativeMetricKey("WALKING_RUNNING_DISTANCE")).toBe(
|
||||
"walkingRunningDistance",
|
||||
);
|
||||
expect(cumulativeMetricKey("FLIGHTS_CLIMBED")).toBe("flightsClimbed");
|
||||
});
|
||||
|
||||
it("returns null for TIME_IN_DAYLIGHT (no clinical competitor today)", () => {
|
||||
expect(cumulativeMetricKey("TIME_IN_DAYLIGHT")).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -63,6 +63,7 @@ export function pickCumulativeDaySum<T extends CumulativeRow>(
|
||||
}
|
||||
|
||||
import type { MeasurementType } from "@/generated/prisma/client";
|
||||
import type { SourcePriorityMetricKey } from "@/lib/validations/source-priority";
|
||||
import { CUMULATIVE_HK_TYPES } from "./apple-health-mapping";
|
||||
|
||||
/**
|
||||
@@ -96,3 +97,37 @@ export function isCumulativeDaySumType(
|
||||
): type is CumulativeDaySumType {
|
||||
return (CUMULATIVE_DAY_SUM_TYPES as readonly string[]).includes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* v1.4.36 W4c / v1.4.37 W10 — cumulative MeasurementType →
|
||||
* SourcePriorityMetricKey lookup for `pickCanonicalSourceRows`.
|
||||
*
|
||||
* Returns `null` for types without a dedicated priority ladder
|
||||
* (e.g. TIME_IN_DAYLIGHT, which lacks a clinical-grade competitor
|
||||
* to Apple Health for daylight minutes today). The picker treats a
|
||||
* `null` ladder as the "no priority — pass through every row"
|
||||
* branch, so the bucket-and-sum runs over every source.
|
||||
*
|
||||
* Hoisted out of `src/app/api/analytics/route.ts` so adding a new
|
||||
* cumulative metric is a single-line edit here rather than a
|
||||
* route-level switch that drifts from the `SourcePriorityMetricKey`
|
||||
* enum + `CUMULATIVE_HK_TYPES` set. A parity test pins the contract:
|
||||
* every member of `CUMULATIVE_HK_TYPES` either resolves to a real
|
||||
* `SourcePriorityMetricKey` or is explicitly mapped to `null`.
|
||||
*/
|
||||
export function cumulativeMetricKey(
|
||||
type: MeasurementType,
|
||||
): SourcePriorityMetricKey | null {
|
||||
switch (type) {
|
||||
case "ACTIVITY_STEPS":
|
||||
return "steps";
|
||||
case "ACTIVE_ENERGY_BURNED":
|
||||
return "activeEnergy";
|
||||
case "WALKING_RUNNING_DISTANCE":
|
||||
return "walkingRunningDistance";
|
||||
case "FLIGHTS_CLIMBED":
|
||||
return "flightsClimbed";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user