import type { Derived } from "@/lib/insights/derived/types"; import { userDayKey } from "@/lib/tz/format"; import type { ActivityPillarInput, PillarValue } from "./types"; import { clampScore, coverage, failedPillar, insufficientPillar, mean, okPillar, standardDeviation, } from "./shared"; export const ACTIVITY_WINDOW_DAYS = 28; export const ACTIVITY_MIN_DAYS = 21; export const OLDER_ADULT_AGE = 60; export const YOUNGER_STEP_PLATEAU = 10_000; export const OLDER_STEP_PLATEAU = 8_000; export function computeActivityPillar( input: ActivityPillarInput, ): Derived { if (input.readFailed) { return failedPillar({ id: "ACTIVITY", asOf: input.asOf, windowDays: ACTIVITY_WINDOW_DAYS, }); } const asOfDay = userDayKey(input.asOf, input.timezone); const sinceDay = userDayKey( new Date(input.asOf.getTime() - (ACTIVITY_WINDOW_DAYS - 1) * 86_400_000), input.timezone, ); const points = input.days.filter( (point) => point.day <= asOfDay && point.day >= sinceDay, ); if (points.length < ACTIVITY_MIN_DAYS) { return insufficientPillar({ id: "ACTIVITY", source: input.source, asOf: input.asOf, windowDays: ACTIVITY_WINDOW_DAYS, requiredInputs: ACTIVITY_MIN_DAYS, presentInputs: points.length, historyDays: points.length, missing: [`${Math.max(0, ACTIVITY_MIN_DAYS - points.length)} days`], reason: input.days.length > 0 ? "below_day_floor_or_stale" : "not_tracked", }); } const values = points.map((point) => point.value); const observed = mean(values); const plateau = input.ageYears != null && input.ageYears >= OLDER_ADULT_AGE ? OLDER_STEP_PLATEAU : YOUNGER_STEP_PLATEAU; const score = clampScore((observed / plateau) * 100); const noiseFloor = Math.max( 1, Math.round(((0.5 * standardDeviation(values)) / plateau) * 100), ); const latestDay = [...points].sort((a, b) => b.day.localeCompare(a.day))[0]; return okPillar({ id: "ACTIVITY", source: input.source, asOf: input.asOf, windowDays: ACTIVITY_WINDOW_DAYS, coverage: coverage({ requiredInputs: ACTIVITY_MIN_DAYS, presentInputs: ACTIVITY_MIN_DAYS, historyDays: points.length, }), value: { score, observed: { value: Math.round(observed), unit: "steps/day", label: `${Math.round(observed).toLocaleString("en-US")} steps/day`, asOf: `${latestDay.day}T00:00:00.000Z`, sources: input.sources, }, reference: { kind: "guideline-band", low: 0, high: plateau, label: `benefit plateaus by ${plateau.toLocaleString("en-US")} steps/day`, source: "Paluch 2022", }, noiseFloor, deltaEligible: true, deltaIdentity: "ACTIVITY_STEPS", }, }); }