diff --git a/src/__tests__/baselines/test-as-never.json b/src/__tests__/baselines/test-as-never.json index 9500eeb7d..ef945bea3 100644 --- a/src/__tests__/baselines/test-as-never.json +++ b/src/__tests__/baselines/test-as-never.json @@ -1,7 +1,7 @@ { "$comment": "as never casts in tests, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", "count": 523, - "total": 5464, + "total": 5459, "entries": { "__tests__/api/auth/login/mfa-branch.test.ts": 10, "__tests__/api/auth/mfa/verify.test.ts": 9, @@ -385,7 +385,7 @@ "lib/import/__tests__/record-import-route.test.ts": 1, "lib/insights/__tests__/background-reasoning-wire.test.ts": 2, "lib/insights/__tests__/biomarker-status.test.ts": 23, - "lib/insights/__tests__/blood-pressure-status.test.ts": 32, + "lib/insights/__tests__/blood-pressure-status.test.ts": 27, "lib/insights/__tests__/bmi-status.test.ts": 21, "lib/insights/__tests__/briefing-provider-budget.test.ts": 2, "lib/insights/__tests__/comprehensive-briefing-reroll.test.ts": 1, diff --git a/src/__tests__/baselines/version-stamp-comments.json b/src/__tests__/baselines/version-stamp-comments.json index fe190cc51..be9a1f451 100644 --- a/src/__tests__/baselines/version-stamp-comments.json +++ b/src/__tests__/baselines/version-stamp-comments.json @@ -1,7 +1,7 @@ { "$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.", "count": 2889, - "total": 9212, + "total": 9210, "entries": { "__tests__/account-payload-consumer-guard.test.ts": 2, "__tests__/acting-account-boundary-guard.test.ts": 7, @@ -291,7 +291,7 @@ "app/api/insights/bmi-status/route.ts": 2, "app/api/insights/breathing-screening/route.ts": 2, "app/api/insights/cards/__tests__/route.test.ts": 2, - "app/api/insights/cards/route.ts": 9, + "app/api/insights/cards/route.ts": 8, "app/api/insights/chat/__tests__/dialog-harness.ts": 1, "app/api/insights/chat/__tests__/golden-transcripts.test.ts": 1, "app/api/insights/chat/__tests__/route-activity.test.ts": 1, @@ -2203,7 +2203,7 @@ "lib/insights/exclude-filter.ts": 1, "lib/insights/feature-blocks.ts": 5, "lib/insights/feature-cache.ts": 2, - "lib/insights/features.ts": 34, + "lib/insights/features.ts": 33, "lib/insights/general-status.ts": 5, "lib/insights/glp1-plateau.ts": 2, "lib/insights/graded-series.ts": 3, diff --git a/src/app/api/insights/cards/route.ts b/src/app/api/insights/cards/route.ts index 04779c3ba..87cd8ee77 100644 --- a/src/app/api/insights/cards/route.ts +++ b/src/app/api/insights/cards/route.ts @@ -19,7 +19,7 @@ import { prisma } from "@/lib/db"; import { summarize, type DataPoint } from "@/lib/analytics/trends"; import { probeRollupCoverage } from "@/lib/rollups/measurement-coverage"; import { readDayMeanSeries } from "@/lib/insights/derived/baseline"; -import { getBpTargets } from "@/lib/analytics/bp-targets"; +import { resolveEffectiveBpTargets } from "@/lib/analytics/effective-range"; import { isBpReadingInTarget } from "@/lib/analytics/bp-in-target"; import { generateAlerts, @@ -72,6 +72,17 @@ interface InsightCard { export const dynamic = "force-dynamic"; +/** + * The profile fields BMI and the BP band read: the person's own BP target + * where one is set, the age default otherwise. + */ +const PROFILE_SELECT = { + heightCm: true, + dateOfBirth: true, + gender: true, + thresholdsJson: true, +} as const; + export const GET = apiHandler(async () => { // v1.37.0 — MANAGE-level read: computed over the whole record, with no // provider anywhere on the path. @@ -85,7 +96,7 @@ export const GET = apiHandler(async () => { await Promise.all([ prisma.user.findUnique({ where: { id: user.id }, - select: { heightCm: true, dateOfBirth: true }, + select: PROFILE_SELECT, }), // WEIGHT + BP are manual entries (low volume, and BP needs exact- // timestamp pairing below for `bpPctInTarget`), so a bounded raw read @@ -168,14 +179,14 @@ export const GET = apiHandler(async () => { } let bpPctInTarget: number | null = null; - const bpTargets = getBpTargets(dbUser?.dateOfBirth ?? null); + const bpTargets = + dbUser && resolveEffectiveBpTargets(dbUser, dbUser.thresholdsJson); if (bpTargets) { const sysData = byType("BLOOD_PRESSURE_SYS"); const diaData = byType("BLOOD_PRESSURE_DIA"); const pairs = pairByTimestamp(sysData, diaData, 5 * 60_000); if (pairs.length > 0) { - // v1.4.16 — one-sided ceiling semantics with hypotension - // floor. See lib/analytics/bp-in-target.ts. + // One-sided ceiling with a hypotension floor (`bp-in-target.ts`). const inTarget = pairs.filter((p) => isBpReadingInTarget(p.a, p.b, bpTargets), ).length; diff --git a/src/lib/insights/__tests__/blood-pressure-status.test.ts b/src/lib/insights/__tests__/blood-pressure-status.test.ts index 43891fcb2..872190a88 100644 --- a/src/lib/insights/__tests__/blood-pressure-status.test.ts +++ b/src/lib/insights/__tests__/blood-pressure-status.test.ts @@ -99,6 +99,28 @@ beforeEach(() => { vi.mocked(prisma.$queryRaw).mockResolvedValue([] as never); }); +/** The record row and the readings one generation reads, nothing else on file. */ +function seed( + records: Array<{ type: string; value: number; measuredAt: Date }>, + user: Record = { dateOfBirth: null }, +) { + vi.mocked(prisma.user.findUnique).mockResolvedValue(user as never); + vi.mocked(prisma.measurement.findMany).mockResolvedValue(records as never); + vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never); + vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue( + [] as never, + ); + vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never); +} + +/** The snapshot the model was handed, parsed out of the user prompt. */ +async function snapshotFor() { + const captured: { userPrompt: string | null } = { userPrompt: null }; + stubCompletion('{"summary":"OK"}', captured); + await generateBloodPressureStatusForUser("user-1", { locale: "en" }); + return JSON.parse(captured.userPrompt!.match(/\{[\s\S]*\}/)![0]); +} + describe("generateBloodPressureStatusForUser — graded payload", () => { it("emits a graded {recent, weekly, monthly} per-metric series, not the full daily array", async () => { const now = new Date(); @@ -121,23 +143,8 @@ describe("generateBloodPressureStatusForUser — graded payload", () => { }); } - vi.mocked(prisma.user.findUnique).mockResolvedValue({ - dateOfBirth: null, - } as never); - vi.mocked(prisma.measurement.findMany).mockResolvedValue(records as never); - vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never); - vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue( - [] as never, - ); - vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never); - - const captured: { userPrompt: string | null } = { userPrompt: null }; - stubCompletion('{"summary":"OK"}', captured); - - await generateBloodPressureStatusForUser("user-1", { locale: "en" }); - - const match = captured.userPrompt!.match(/\{[\s\S]*\}/); - const snapshot = JSON.parse(match![0]); + seed(records); + const snapshot = await snapshotFor(); const sys = snapshot.bloodPressure.systolic.series; expect(sys).toHaveProperty("recent"); @@ -170,18 +177,10 @@ describe("generateBloodPressureStatusForUser — graded payload", () => { describe("generateBloodPressureStatusForUser — timeout/error never persists", () => { it("serves the fallback without writing a cache row on timeout", async () => { - vi.mocked(prisma.user.findUnique).mockResolvedValue({ - dateOfBirth: null, - } as never); - vi.mocked(prisma.measurement.findMany).mockResolvedValue([ + seed([ { type: "BLOOD_PRESSURE_SYS", value: 132, measuredAt: new Date() }, { type: "BLOOD_PRESSURE_DIA", value: 84, measuredAt: new Date() }, - ] as never); - vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never); - vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue( - [] as never, - ); - vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never); + ]); vi.mocked(runStatusCompletion).mockResolvedValue({ kind: "timeout", @@ -346,3 +345,36 @@ describe("generateBloodPressureStatusForUser — no rate without an expectation" expect(names).toContain("Amlodipine"); }); }); + +describe("generateBloodPressureStatusForUser — the person's own target", () => { + const readings = [1, 2, 3].flatMap((daysAgo) => { + const measuredAt = new Date(Date.now() - daysAgo * dayMs); + return [ + { type: "BLOOD_PRESSURE_SYS", value: 135, measuredAt }, + { type: "BLOOD_PRESSURE_DIA", value: 85, measuredAt }, + ]; + }); + + it("hands the model the own target and the share of days inside it", async () => { + seed(readings, { + heightCm: 180, + dateOfBirth: new Date("1980-01-01"), + gender: "MALE", + thresholdsJson: { + BLOOD_PRESSURE_SYS: { min: 110, max: 140 }, + BLOOD_PRESSURE_DIA: { min: 70, max: 90 }, + }, + }); + const targets = (await snapshotFor()).bloodPressure.targets; + expect(targets.systolic).toEqual({ min: 110, max: 140 }); + expect(targets.diastolic).toEqual({ min: 70, max: 90 }); + expect(targets.inTargetPctLast30DailyPoints).toBe(100); + }); + + it("falls back to the age default without one", async () => { + seed(readings, { dateOfBirth: new Date("1980-01-01") }); + const targets = (await snapshotFor()).bloodPressure.targets; + expect(targets.systolic.max).toBeLessThan(135); + expect(targets.inTargetPctLast30DailyPoints).toBe(0); + }); +}); diff --git a/src/lib/insights/__tests__/features.test.ts b/src/lib/insights/__tests__/features.test.ts index 0a1b36456..fbdd0fdd6 100644 --- a/src/lib/insights/__tests__/features.test.ts +++ b/src/lib/insights/__tests__/features.test.ts @@ -422,6 +422,39 @@ describe("extractFeatures — weight target direction (#1006)", () => { }); }); +describe("extractFeatures — blood pressure against the person's own target", () => { + // 135/85 on three days: above the age default's ceiling, inside a target + // the person's doctor agreed (up to 140/90). + const bpRows = [1, 2, 3].flatMap((daysAgo) => { + const measuredAt = new Date(Date.now() - daysAgo * DAY_MS); + return [ + { ...measurementRow("BLOOD_PRESSURE_SYS", 135, daysAgo), measuredAt }, + { ...measurementRow("BLOOD_PRESSURE_DIA", 85, daysAgo), measuredAt }, + ]; + }); + + it("counts readings inside the own target as in target", async () => { + prismaMock.user.findUnique.mockResolvedValue({ + heightCm: 180, + dateOfBirth: new Date("1980-01-01"), + gender: "MALE", + thresholdsJson: { + BLOOD_PRESSURE_SYS: { min: 110, max: 140 }, + BLOOD_PRESSURE_DIA: { min: 70, max: 90 }, + }, + }); + prismaMock.measurement.findMany.mockResolvedValue(bpRows); + const f = await extractFeatures("user-1", false); + expect(f.bloodPressure?.pctInTarget).toBe(100); + }); + + it("grades against the age default when no target is stored", async () => { + prismaMock.measurement.findMany.mockResolvedValue(bpRows); + const f = await extractFeatures("user-1", false); + expect(f.bloodPressure?.pctInTarget).toBe(0); + }); +}); + describe("extractFeatures — v1.22 labs briefing block", () => { it("leaves the labs out of the briefing with the labs module off", async () => { labsModule.on = false; diff --git a/src/lib/insights/__tests__/pulse-status.test.ts b/src/lib/insights/__tests__/pulse-status.test.ts index e593ba9b8..9489df5de 100644 --- a/src/lib/insights/__tests__/pulse-status.test.ts +++ b/src/lib/insights/__tests__/pulse-status.test.ts @@ -152,7 +152,10 @@ describe("generatePulseStatusForUser — graded payload", () => { }); describe("generatePulseStatusForUser — A2 resting-target in-target %", () => { - it("scores RESTING_HEART_RATE against the resting band, ignoring workout PULSE", async () => { + /** A clean resting 72 bpm for 30 days beside a workout-polluted PULSE. */ + async function restingSnapshot( + user: Record = { dateOfBirth: null, gender: null }, + ) { const now = new Date(); // PULSE polluted with a heavy workout burst (would tank the in-target // % if scored against the resting band). @@ -181,10 +184,7 @@ describe("generatePulseStatusForUser — A2 resting-target in-target %", () => { }); } - vi.mocked(prisma.user.findUnique).mockResolvedValue({ - dateOfBirth: null, - gender: null, - } as never); + vi.mocked(prisma.user.findUnique).mockResolvedValue(user as never); // pulse-status reads PULSE first, then RESTING_HEART_RATE. The latest // 365 PULSE readings reach back less than 30 days here, so the PULSE // read takes the 30-day window as a second query. @@ -199,14 +199,28 @@ describe("generatePulseStatusForUser — A2 resting-target in-target %", () => { stubCompletion('{"summary":"OK"}', captured); await generatePulseStatusForUser("user-resting", { locale: "en" }); + return JSON.parse(captured.userPrompt!.match(/\{[\s\S]*\}/)![0]); + } - const match = captured.userPrompt!.match(/\{[\s\S]*\}/); - const snapshot = JSON.parse(match![0]); + it("scores RESTING_HEART_RATE against the resting band, ignoring workout PULSE", async () => { + const snapshot = await restingSnapshot(); // Resting 72 sits inside the 60-100 band → 100 % in target, NOT the // ~0 % the workout-polluted PULSE stream would have produced. expect(snapshot.pulse.target.inTargetPctLast30DailyPoints).toBe(100); }); + it("scores the resting series against the person's own target where one is set", async () => { + const snapshot = await restingSnapshot({ + heightCm: null, + dateOfBirth: null, + gender: null, + thresholdsJson: { PULSE: { min: 50, max: 65 } }, + }); + expect(snapshot.pulse.target).toMatchObject({ greenMin: 50, greenMax: 65 }); + // Resting 72 is above the own ceiling of 65 on every day. + expect(snapshot.pulse.target.inTargetPctLast30DailyPoints).toBe(0); + }); + it("falls back to a low-percentile PULSE proxy when no resting rows exist", async () => { const now = new Date(); // Each day: mostly resting reads ~72 + a workout burst ~150. The diff --git a/src/lib/insights/blood-pressure-status.ts b/src/lib/insights/blood-pressure-status.ts index 566e9cc3f..50ba6fc31 100644 --- a/src/lib/insights/blood-pressure-status.ts +++ b/src/lib/insights/blood-pressure-status.ts @@ -5,7 +5,8 @@ import { getBloodPressureUserPrompt, } from "@/lib/ai/prompts/blood-pressure"; import { computeSteadyRun } from "@/lib/insights/assessment-context"; -import { getBpTargets } from "@/lib/analytics/bp-targets"; +import type { BpTargets } from "@/lib/analytics/bp-targets"; +import { resolveEffectiveBpTargets } from "@/lib/analytics/effective-range"; import { isBpReadingInTarget } from "@/lib/analytics/bp-in-target"; import { significantPearsonCorrelation, @@ -131,14 +132,21 @@ async function buildBloodPressureSnapshot( const user = await prisma.user.findUnique({ where: { id: userId }, select: { + heightCm: true, dateOfBirth: true, + gender: true, + thresholdsJson: true, }, }); const { measurements, now, weightSeries, sysSeries, diaSeries } = await readBpChannels(userId, userTz); - const bpTargets = getBpTargets(user?.dateOfBirth ?? null); + // The person's own target where one is set, the age default otherwise: + // the same band the chart and the dashboard tile paint. + const bpTargets = user + ? resolveEffectiveBpTargets(user, user.thresholdsJson) + : null; const { pairedBloodPressure, bpInTargetPctLast30DailyPoints } = pairBloodPressure(sysSeries.daily, diaSeries.daily, bpTargets, now, userTz); @@ -387,7 +395,7 @@ async function readBpChannels(userId: string, userTz: string) { function pairBloodPressure( sysDaily: DailyBucket[], diaDaily: DailyBucket[], - bpTargets: ReturnType, + bpTargets: BpTargets | null, now: Date, userTz: string, ) { diff --git a/src/lib/insights/comprehensive-overview/build.ts b/src/lib/insights/comprehensive-overview/build.ts index a96e8b751..212cba021 100644 --- a/src/lib/insights/comprehensive-overview/build.ts +++ b/src/lib/insights/comprehensive-overview/build.ts @@ -14,7 +14,7 @@ import { prisma } from "@/lib/db"; import { probeProviderPresence } from "@/lib/ai/provider"; import type { DataSummary } from "@/lib/analytics/trends"; import { summarize } from "@/lib/analytics/trends"; -import { getBpTargets } from "@/lib/analytics/bp-targets"; +import { resolveEffectiveBpTargets } from "@/lib/analytics/effective-range"; import { computeBpInTargetPct } from "@/lib/analytics/bp-in-target"; import { classifyBMI, @@ -43,10 +43,18 @@ export type ComprehensiveOverviewBody = Awaited< ReturnType >; -/** BMI and the 30-day BP classification against the record's profile. */ +/** + * BMI, the 30-day BP classification, and the BP band: the person's own target + * where one is set, the age default otherwise. + */ function classifyProfile( summaries: Record, - dbUser: { heightCm: number | null; dateOfBirth: Date | null } | null, + dbUser: { + heightCm: number | null; + dateOfBirth: Date | null; + gender: string | null; + thresholdsJson: unknown; + } | null, ) { let bmi: number | null = null; let bmiClassification = null; @@ -57,7 +65,9 @@ function classifyProfile( } let bpClassification = null; - const bpTargets = getBpTargets(dbUser?.dateOfBirth ?? null); + const bpTargets = dbUser + ? resolveEffectiveBpTargets(dbUser, dbUser.thresholdsJson) + : null; if ( summaries.BLOOD_PRESSURE_SYS?.avg30 && summaries.BLOOD_PRESSURE_DIA?.avg30 @@ -75,12 +85,14 @@ export async function buildComprehensiveResponse(user: OverviewRecord) { const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); const userTz = user.timezone || DEFAULT_TIMEZONE; - // Height + DOB drive BMI + BP targets. + // Height drives BMI; the BP band is the own target or the age default. const dbUser = await prisma.user.findUnique({ where: { id: userId }, select: { heightCm: true, dateOfBirth: true, + gender: true, + thresholdsJson: true, }, }); diff --git a/src/lib/insights/features.ts b/src/lib/insights/features.ts index 2ac921ee6..83d0dd81d 100644 --- a/src/lib/insights/features.ts +++ b/src/lib/insights/features.ts @@ -32,7 +32,6 @@ import { type SleepStageRow, } from "@/lib/analytics/sleep-night"; import { loadUserSourcePriority } from "@/lib/rollups/measurement-read"; -import { getBpTargets } from "@/lib/analytics/bp-targets"; import { isBpReadingInTarget } from "@/lib/analytics/bp-in-target"; import { READING_SPREAD } from "@/lib/stats/spread"; import { @@ -65,7 +64,10 @@ import type { RollupGranularity, } from "@/generated/prisma/client"; -import { resolveWeightTargetOverride } from "@/lib/analytics/effective-range"; +import { + resolveEffectiveBpTargets, + resolveWeightTargetOverride, +} from "@/lib/analytics/effective-range"; import { buildWeightTargetFeature, type WeightTargetFeature, @@ -1174,7 +1176,8 @@ export async function extractFeatures( ? await resolveUserTimezone(userId) : DEFAULT_TIMEZONE; - const bpTargets = getBpTargets(user?.dateOfBirth ?? null); + const bpTargets = + user && resolveEffectiveBpTargets(user, user.thresholdsJson); // Compute overall data span const oldestMeasurement = @@ -1287,11 +1290,8 @@ export async function extractFeatures( const sysVal = sysByTime.get(dia.measuredAt.getTime()); if (sysVal === undefined) continue; pairedCount++; - // v1.4.16 — one-sided ceiling semantics with hypotension - // floor. See lib/analytics/bp-in-target.ts. - if (isBpReadingInTarget(sysVal, dia.value, bpTargets)) { - inTargetCount++; - } + // One-sided ceiling with a hypotension floor (`bp-in-target.ts`). + if (isBpReadingInTarget(sysVal, dia.value, bpTargets)) inTargetCount++; } pctInTarget = pairedCount > 0 diff --git a/src/lib/insights/general-status.ts b/src/lib/insights/general-status.ts index 2857bcb24..0a585e720 100644 --- a/src/lib/insights/general-status.ts +++ b/src/lib/insights/general-status.ts @@ -5,7 +5,8 @@ import { getGeneralStatusSystemPrompt, getGeneralStatusUserPrompt, } from "@/lib/ai/prompts/general-status"; -import { getBpTargets } from "@/lib/analytics/bp-targets"; +import type { BpTargets } from "@/lib/analytics/bp-targets"; +import { resolveEffectiveBpTargets } from "@/lib/analytics/effective-range"; import { isBpReadingInTarget } from "@/lib/analytics/bp-in-target"; import { getNoKeyGeneralStatusText } from "@/lib/insights/no-key-fallbacks"; import { measurementTypeEnum } from "@/lib/validations/measurement"; @@ -71,7 +72,10 @@ async function buildGeneralSnapshot( const user = await prisma.user.findUnique({ where: { id: userId }, select: { + heightCm: true, dateOfBirth: true, + gender: true, + thresholdsJson: true, }, }); @@ -148,7 +152,10 @@ async function buildGeneralSnapshot( ); const moodMean = moodSummary?.mean ?? null; - const bpTargets = getBpTargets(user?.dateOfBirth ?? null); + // The person's own target where one is set, the age default otherwise. + const bpTargets = user + ? resolveEffectiveBpTargets(user, user.thresholdsJson) + : null; const bpInTargetLast30Days = bpTargets ? bpInTargetLast30DailyPoints(dailyByType, bpTargets) : null; @@ -314,7 +321,7 @@ async function readAdherenceSeries(userId: string, now: Date, userTz: string) { */ function bpInTargetLast30DailyPoints( dailyByType: ReadonlyMap>, - bpTargets: NonNullable>, + bpTargets: BpTargets, ): number | null { const sysDaily = dailyByType.get("BLOOD_PRESSURE_SYS")?.daily ?? []; const diaMap = new Map( diff --git a/src/lib/insights/pulse-status.ts b/src/lib/insights/pulse-status.ts index 31ee348d6..fca511739 100644 --- a/src/lib/insights/pulse-status.ts +++ b/src/lib/insights/pulse-status.ts @@ -10,6 +10,10 @@ import { getAgeFromDateOfBirth, getPersonalizedPulseTarget, } from "@/lib/analytics/pulse-targets"; +import { + getEffectiveRange, + resolveThresholdOverride, +} from "@/lib/analytics/effective-range"; import { resolveRestingPulseSeries } from "@/lib/analytics/resting-pulse"; import { getNoKeyPulseStatusText } from "@/lib/insights/no-key-fallbacks"; import { applyPayloadBudget } from "@/lib/insights/bucket-series"; @@ -73,8 +77,10 @@ async function buildPulseSnapshot( const user = await prisma.user.findUnique({ where: { id: userId }, select: { + heightCm: true, dateOfBirth: true, gender: true, + thresholdsJson: true, }, }); @@ -150,11 +156,17 @@ async function buildPulseSnapshot( ); const moodMean = moodSummary?.mean ?? null; - const pulseAge = getAgeFromDateOfBirth(user?.dateOfBirth ?? null); - const pulseTarget = getPersonalizedPulseTarget( - pulseAge, - binaryReferenceSex(user?.gender), - ); + // The person's own resting-pulse target where one is set, the age and sex + // default otherwise. + const ownPulse = resolveThresholdOverride(user?.thresholdsJson, "PULSE"); + const pulseTarget = + (user && ownPulse + ? getEffectiveRange("PULSE", user, { PULSE: ownPulse }).range + : null) ?? + getPersonalizedPulseTarget( + getAgeFromDateOfBirth(user?.dateOfBirth ?? null), + binaryReferenceSex(user?.gender), + ); const inTargetPctLast30DailyPoints = restingInTargetPct({ restingMeasurements,