mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
feat(score): compute a health score from the data a person actually has
Three distinct areas of health stops being the price of admission and becomes what it always described: a recommendation. `evaluateScoreBreadth` returns a tier instead of a pass/fail — `full` at three areas or more, `partial` at two, `minimal` at one — and refuses only when not one pillar came back usable, with the reason `no_usable_data`. The arithmetic does not change with the tier. Same mean, same worst-pillar band drag, same noise floor. A one-area score of 82 is an honest 82 that rests on one area, and discounting it for the pillars somebody does not track would have been a new invented target, which is the thing this score spent two releases removing. The physiological requirement becomes a labelling fact rather than a veto. An account whose only readable pillar is activity or wellbeing gets its number with `physiological: false` on the basis block; the old refusal told that person nothing they could not already see. `SCORE_MIN_ELIGIBLE_DOMAINS` is now `SCORE_RECOMMENDED_DOMAINS` and still feeds `deriveCoverage`, so the coverage fraction and the confidence blend keep telling the truth with no new machinery: a one-area score lands at low confidence through the path that was already there. The fraction used to be a floor every scored account had cleared; it is a real moving number now. `SCORE_VERSION` 3 -> 4 and `SCORE_ALGORITHM_CHANGED_AT` moves to the release date. Both are method-identity duties: deltas suppress as `algorithm_changed` across the straddle window, the notice key rotates so each account is told once, and stored days stamp 4. No v3 row is rewritten and no v3 number changes meaning. The settings write follows the same rule rather than a stricter one. It refused two-area and non-physiological selections on the ground that they could not produce a score; they can, so refusing the write would be the two layers disagreeing about the one question they share. The empty selection is still refused, under a single reason `no_pillars_selected`, and the seven message catalogues trade the two old refusal sentences for one. Also fixes the stale `./series.ts` reference in the config header — that module was deleted as dead in v1.35; the columns the sentence describes are still written by `./record.ts`.
This commit is contained in:
1 parent
f0ccd9db38
commit
4ad00406d3
13 files changed
+185
-82
No files matched your search
@@ -19,15 +19,17 @@
|
||||
*
|
||||
* Two things this route is responsible for beyond storing a list.
|
||||
*
|
||||
* **It refuses a selection that cannot produce a score.** Three distinct
|
||||
* areas of health and at least one physical measurement, or the
|
||||
* composite does not exist. Saving such a selection would leave the
|
||||
* person with a settings page that says one thing and a score that has
|
||||
* silently vanished, so the write is refused with a sentence they can
|
||||
* act on. The scorer applies the same rule again at read time through
|
||||
* the same function, deliberately: a config written before a rule change
|
||||
* or edited outside the app still meets an honest refusal rather than a
|
||||
* missing number.
|
||||
* **It refuses a selection that cannot produce a score.** As of v1.38
|
||||
* that is one selection: the empty one. Three areas of health is what
|
||||
* the score recommends, not what it demands — a two-area or one-area
|
||||
* recipe now saves and produces a score that says on its face how broad
|
||||
* it is, so refusing the write would refuse something the scorer is
|
||||
* perfectly willing to compute. Taking every pillar out is different in
|
||||
* kind: there is nothing left to average, and storing that recipe would
|
||||
* leave the person with a settings page that says one thing and a score
|
||||
* that has silently vanished. The scorer applies the same rule again at
|
||||
* read time through the same function, deliberately, so a config edited
|
||||
* outside the app meets an honest refusal rather than a missing number.
|
||||
*
|
||||
* **It evicts the score caches.** The person just changed what the
|
||||
* number means; serving the old one for the next hour would make the
|
||||
@@ -82,14 +84,11 @@ const healthScoreConfigPatchSchema = z
|
||||
|
||||
/**
|
||||
* Why a selection cannot produce a score, said the way a person would
|
||||
* say it. The cardiometabolic sentence is the one that surprises people:
|
||||
* blood pressure, glucose and cholesterol are three pillars but one area.
|
||||
* say it. One entry, because one selection cannot: the empty one.
|
||||
*/
|
||||
const BREADTH_REFUSAL = {
|
||||
measured_physiological_domain_required:
|
||||
"A health score needs at least one physical measurement. Keep one of blood pressure, glucose, sleep, body shape or cholesterol in your selection, alongside whatever else you choose.",
|
||||
three_domains_required:
|
||||
"A health score needs at least three different areas of health. Blood pressure, glucose and cholesterol all describe the same area, so a selection made only of those counts as one.",
|
||||
no_pillars_selected:
|
||||
"A health score needs at least one area of health to score. Keep one selected — the score will say which areas it is built from.",
|
||||
} as const;
|
||||
|
||||
export const GET = apiHandler(async () => {
|
||||
|
||||
Reference in new issue
Block a user