Files
HealthLog/src/lib/analytics/score/modules.ts
T
Marc-André Bombeck c60689affd Take the fitness pillar out of the health score
It could never score. The schema records VO2max but cannot tell a
measured test apart from a device estimate, so every fitness row reached
the scorer unproven and was refused - the pillar has never produced a
value on any account. Listing it on the score settings page as a thing
you can choose, with a line admitting it does nothing, was chrome over a
switch that changed no number.

So it leaves the catalogue: no pillar id, no domain, no scorer, no row,
no wire field. A stored selection that still names it reads back fine -
the resolver has always filtered stored ids against the catalogue and
drops what it no longer knows, so nobody's saved recipe throws or is
discarded over it.

That also fixes the page claiming unsaved changes forever. The save
stripped the fitness pillar out of the selection on its way to the
server, so the answer that came back could never match the form that
sent it, and 'All pillars' left the save button lit no matter how often
you pressed it.
2026-08-01 10:51:01 +02:00

81 lines
3.0 KiB
TypeScript

/**
* v1.35.0 — which modules put data in front of which score pillar.
*
* Until this release the mapping existed only as ternaries inside the
* reader's `availablePillars` expression, where nothing could read it
* and nothing could test it. It is named here because two questions
* that used to have one answer now have two, and confusing them is how
* a person's number moves without them asking for it:
*
* - the CONFIG says which pillars a person counts toward their score;
* - a MODULE says whether that pillar's data is being recorded at all.
*
* The score takes the intersection. A module that is off cannot be
* overridden by a selection, because turning the module off closes the
* data path itself; and a pillar left out of the config stays out
* however much data flows into it.
*
* `GLYCAEMIA` appears under two modules on purpose: it scores off
* fasting glucose OR HbA1c, so either module alone keeps it fed. That
* was the `glucose || labs` ternary, and it is the entry that would be
* lost by assuming one module per pillar.
*
* `BLOOD_PRESSURE`, `ACTIVITY` and `ADIPOSITY` are absent from the map,
* which is the same thing the reader has always done: they read core
* measurement domains that carry no module switch, so no module can
* withdraw their data. Absent means ungated, not forgotten — the test
* beside this file pins the three so a silent addition to the map cannot
* quietly start gating one of them.
*/
import { SCORE_PILLAR_IDS, type ScorePillarId } from "./types";
/**
* The modules the score reader is handed. Every key here gates at least
* one pillar; a module that gates nothing has no reason to be passed.
*/
export interface ScoreReaderModules {
glucose: boolean;
labs: boolean;
sleep: boolean;
mentalHealth: boolean;
}
export type ScoreModuleKey = keyof ScoreReaderModules;
/** Which pillars each module feeds. Registry ids, checked by the tests. */
export const MODULE_SCORE_PILLARS: Record<
ScoreModuleKey,
readonly ScorePillarId[]
> = {
glucose: ["GLYCAEMIA"],
labs: ["GLYCAEMIA", "LIPIDS"],
sleep: ["SLEEP"],
mentalHealth: ["WELLBEING"],
};
const MODULE_KEYS = Object.keys(MODULE_SCORE_PILLARS) as ScoreModuleKey[];
/** Pillars whose data any module can withdraw. Derived, never restated. */
export const MODULE_GATED_PILLARS: ReadonlySet<ScorePillarId> = new Set(
MODULE_KEYS.flatMap((key) => MODULE_SCORE_PILLARS[key]),
);
/**
* The registry-ordered pillars whose data is actually being recorded,
* given the account's modules. This is availability, not choice: it says
* nothing about what the person wants counted, only about what there
* could be data for.
*/
export function pillarsWithModuleData(
modules: ScoreReaderModules,
): ScorePillarId[] {
const fed = new Set<ScorePillarId>();
for (const key of MODULE_KEYS) {
if (!modules[key]) continue;
for (const id of MODULE_SCORE_PILLARS[key]) fed.add(id);
}
return SCORE_PILLAR_IDS.filter(
(id) => !MODULE_GATED_PILLARS.has(id) || fed.has(id),
);
}