mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
One boolean on the composite: true when the account's resolved composition differs from the composition its defaults would resolve to today, both narrowed by the same modules. Decided once in the score reader, from the same config and module map the composition came from, so no client interprets a config blob to answer it. This is not hasSelection. An account that opened the surface and kept every pillar has written a selection and is not configured; an account whose modules alone narrow the set has authored nothing and is not configured either.
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import type { Derived } from "@/lib/insights/derived/types";
|
|
|
|
import { computeActivityPillar } from "./activity";
|
|
import { computeAdiposityPillar } from "./adiposity";
|
|
import { computeBloodPressurePillar } from "./blood-pressure";
|
|
import { computeComposite } from "./composite";
|
|
import { computeFitnessPillar } from "./fitness";
|
|
import { computeGlycaemiaPillar } from "./glycaemia";
|
|
import { computeLipidsPillar } from "./lipids";
|
|
import { computeSleepPillar } from "./sleep";
|
|
import {
|
|
SCORE_PILLAR_DOMAINS,
|
|
SCORE_PILLAR_IDS,
|
|
SCORE_VERSION,
|
|
type CompositeValue,
|
|
type HealthScoreReport,
|
|
type PillarInputs,
|
|
type ScorePillarId,
|
|
type ScorePillarResult,
|
|
type WeightGoalValue,
|
|
} from "./types";
|
|
import { computeWellbeingPillar } from "./wellbeing";
|
|
|
|
export interface ComputeHealthScoreInput {
|
|
asOf: Date;
|
|
availablePillars: ScorePillarId[];
|
|
/**
|
|
* Whether `availablePillars` is an authored recipe rather than the
|
|
* account's defaults, resolved by `resolveScoreConfigured`. Rides to
|
|
* the composite unchanged; nothing here re-derives it.
|
|
*/
|
|
configured: boolean;
|
|
pillars: PillarInputs;
|
|
weightGoal: Derived<WeightGoalValue>;
|
|
delta?: number | null;
|
|
deltaReason?: HealthScoreReport["deltaReason"];
|
|
}
|
|
|
|
export function computeHealthScore(
|
|
input: ComputeHealthScoreInput,
|
|
): HealthScoreReport {
|
|
const byId: Record<ScorePillarId, ScorePillarResult> = {
|
|
BLOOD_PRESSURE: {
|
|
id: "BLOOD_PRESSURE",
|
|
domain: SCORE_PILLAR_DOMAINS.BLOOD_PRESSURE,
|
|
result: computeBloodPressurePillar(input.pillars.BLOOD_PRESSURE),
|
|
},
|
|
GLYCAEMIA: {
|
|
id: "GLYCAEMIA",
|
|
domain: SCORE_PILLAR_DOMAINS.GLYCAEMIA,
|
|
result: computeGlycaemiaPillar(input.pillars.GLYCAEMIA),
|
|
},
|
|
ACTIVITY: {
|
|
id: "ACTIVITY",
|
|
domain: SCORE_PILLAR_DOMAINS.ACTIVITY,
|
|
result: computeActivityPillar(input.pillars.ACTIVITY),
|
|
},
|
|
SLEEP: {
|
|
id: "SLEEP",
|
|
domain: SCORE_PILLAR_DOMAINS.SLEEP,
|
|
result: computeSleepPillar(input.pillars.SLEEP),
|
|
},
|
|
ADIPOSITY: {
|
|
id: "ADIPOSITY",
|
|
domain: SCORE_PILLAR_DOMAINS.ADIPOSITY,
|
|
result: computeAdiposityPillar(input.pillars.ADIPOSITY),
|
|
},
|
|
WELLBEING: {
|
|
id: "WELLBEING",
|
|
domain: SCORE_PILLAR_DOMAINS.WELLBEING,
|
|
result: computeWellbeingPillar(input.pillars.WELLBEING),
|
|
},
|
|
FITNESS: {
|
|
id: "FITNESS",
|
|
domain: SCORE_PILLAR_DOMAINS.FITNESS,
|
|
result: computeFitnessPillar(input.pillars.FITNESS),
|
|
},
|
|
LIPIDS: {
|
|
id: "LIPIDS",
|
|
domain: SCORE_PILLAR_DOMAINS.LIPIDS,
|
|
result: computeLipidsPillar(input.pillars.LIPIDS),
|
|
},
|
|
};
|
|
const available = new Set(input.availablePillars);
|
|
const pillars = SCORE_PILLAR_IDS.filter((id) => available.has(id)).map(
|
|
(id) => byId[id],
|
|
);
|
|
return {
|
|
composite: computeComposite({
|
|
pillars,
|
|
availablePillars: input.availablePillars,
|
|
asOf: input.asOf,
|
|
configured: input.configured,
|
|
}),
|
|
pillars,
|
|
delta: input.delta ?? null,
|
|
deltaReason: input.deltaReason ?? "no_previous_window",
|
|
scoreVersion: SCORE_VERSION,
|
|
weightGoal: input.weightGoal,
|
|
algorithmNotice: null,
|
|
};
|
|
}
|
|
|
|
export type { CompositeValue, HealthScoreReport };
|
|
export * from "./types";
|