mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(mood): let the Coach read the deviation, and publish the resolved forecast
The snapshot gains the day's two readings with the age of the second one, through the same reader the page uses, so the page and the assistant cannot disagree about what today's comparison is. The freshness rule is the shared one: a comparison about the day before yesterday carries the date it is about and an instruction not to state it as today. Absence is a present field rather than a missing key — a model told "there is none yet" says so, and a model told nothing estimates one. The wording rule travels with the values, because a model handed a number and no instruction writes the sentence its training suggests, and about a forecast that sentence is a causal one. The contract publishes the resolved value: the expected figure, the band, the count, the deviation against the band, the age, and the contributions that carried weight. Nothing is left for a client to re-derive — two clients deriving the same figure about somebody's health record is how they start disagreeing about it. The description carries the observational wording and the display rule: the count goes wherever the value goes. The end-to-end proof runs against real Postgres and reads the table rather than a return value: nothing written a day short of the threshold, a banded and counted row on the day it crosses, the provisional label dropped at sixty days, the rows cleared when the module goes off, the rows cleared when entries are deleted back below the threshold, and an older pass's row replaced rather than left beside a fresh one. A delegate holding only the mind section is refused, and the assertion is on the response bytes.
This commit is contained in:
1 parent
f48afed82d
commit
c33f7f9f6a
6 files changed
+899
-1
No files matched your search
@@ -8357,6 +8357,28 @@ paths:
|
||||
"403": *a6
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/mood/prognosis:
|
||||
get:
|
||||
tags:
|
||||
- Mood
|
||||
summary: The day's own rating and what the account's days imply (v1.38)
|
||||
description: "The newest stored comparison for the account, with the person's own rating for that day beside it. Written
|
||||
nightly by a job and never by a request: the value cannot be overwritten, which is what keeps the two readings
|
||||
separate. Absence is explicit (`present: false` with a reason and a count) and is a normal answer, not an error
|
||||
— the comparison refuses when the history does not support one. Whole-record read: the fit takes the day's
|
||||
context AND the sleep, activity and recovery figures the owning modules hold, and it names them, so a
|
||||
section-scoped grant is refused rather than served a filtered list. Gated: `module.disabled` 403 when the mood
|
||||
module is off."
|
||||
responses:
|
||||
"200":
|
||||
description: The day's two readings, or an explained absence.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/MoodPrognosisEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/mood-entries/bulk:
|
||||
post:
|
||||
tags:
|
||||
@@ -32171,6 +32193,184 @@ components:
|
||||
- generatedAt
|
||||
additionalProperties: false
|
||||
description: Per-analyte change between the two most-recent numeric lab panels. Neutral framing, never a diagnosis.
|
||||
MoodPrognosisEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/MoodPrognosis"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
MoodPrognosis:
|
||||
anyOf:
|
||||
- type: object
|
||||
properties:
|
||||
present:
|
||||
type: boolean
|
||||
const: true
|
||||
date:
|
||||
type: string
|
||||
description: The local day this is about, `YYYY-MM-DD`.
|
||||
predicted:
|
||||
type: number
|
||||
description: The expected pleasantness (A1) for that day, 0-10, on the account's own past pattern. A counterfactual,
|
||||
never a measurement — and never to be displayed without `n` and the band.
|
||||
ciLow:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
ciHigh:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
description: The band, from the validation residuals. It widens when the model fits badly, so a wide band is the signal
|
||||
to present the value cautiously.
|
||||
n:
|
||||
type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
description: "Complete days the fit used. Display it WHEREVER `predicted` is displayed: a value built from 30 days and
|
||||
one built from 300 look identical otherwise."
|
||||
modelVersion:
|
||||
type: string
|
||||
computedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
ageDays:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
- type: "null"
|
||||
description: Whole days between that day and the reader's today.
|
||||
current:
|
||||
type: boolean
|
||||
description: Whether it is fresh enough to back a present-tense sentence. False means the copy must name the day it is
|
||||
about.
|
||||
provisional:
|
||||
type: boolean
|
||||
description: "True below 60 days of history: label it as provisional."
|
||||
stage:
|
||||
type: string
|
||||
enum:
|
||||
- provisional
|
||||
- regular
|
||||
- seasonal
|
||||
entries:
|
||||
type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
description: Days of history behind the ladder.
|
||||
seasonalUnlocked:
|
||||
type: boolean
|
||||
selfAssessment:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
- type: "null"
|
||||
description: "The person's own rating for that day, stored exactly as they gave it. It LEADS: it is the primary value on
|
||||
any surface that shows both, and it is never merged with `predicted`."
|
||||
deviation:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- within
|
||||
- above
|
||||
- below
|
||||
- type: "null"
|
||||
description: Where the self-assessment sat against the BAND, not against `predicted`. `within` means the day went as the
|
||||
account's own past days suggest.
|
||||
deviationAmount:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: "null"
|
||||
description: How far outside the band, in scale points. `0` when inside.
|
||||
contributions:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/MoodPrognosisContribution"
|
||||
required:
|
||||
- present
|
||||
- date
|
||||
- predicted
|
||||
- ciLow
|
||||
- ciHigh
|
||||
- n
|
||||
- modelVersion
|
||||
- computedAt
|
||||
- ageDays
|
||||
- current
|
||||
- provisional
|
||||
- stage
|
||||
- entries
|
||||
- seasonalUnlocked
|
||||
- selfAssessment
|
||||
- deviation
|
||||
- deviationAmount
|
||||
- contributions
|
||||
additionalProperties: false
|
||||
- type: object
|
||||
properties:
|
||||
present:
|
||||
type: boolean
|
||||
const: false
|
||||
reason:
|
||||
type: string
|
||||
enum:
|
||||
- no-output-yet
|
||||
- learning-phase
|
||||
- no-pattern
|
||||
description: "`no-output-yet`: too few days for anything. `learning-phase`: enough to describe, not to compare.
|
||||
`no-pattern`: enough days, nothing steady enough in them. None of the three is an error and none is a
|
||||
zero."
|
||||
entries:
|
||||
type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
nextThreshold:
|
||||
anyOf:
|
||||
- type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
- type: "null"
|
||||
description: Days at which the next step unlocks, when a count is missing.
|
||||
required:
|
||||
- present
|
||||
- reason
|
||||
- entries
|
||||
- nextThreshold
|
||||
additionalProperties: false
|
||||
description: "Two separate readings of one day: what the person recorded, and what a comparison with their own past days
|
||||
would have expected. They are never merged — the distance between them is the finding, and a single blended
|
||||
figure destroys it. The comparison is a regularised regression over the account's own history: observational
|
||||
only, never causal. Any copy built on it must state the value as a counterfactual (a value around X would have
|
||||
been expected), must carry `n`, and must not say that anything caused anything."
|
||||
MoodPrognosisContribution:
|
||||
type: object
|
||||
properties:
|
||||
feature:
|
||||
type: string
|
||||
description: "Stable feature identifier: `dimension:a2`, `context:workLoad`, `context:workStatus=off` or `linked:steps`.
|
||||
A client that does not recognise one skips it — an older row can name a feature a later model dropped."
|
||||
contribution:
|
||||
type: number
|
||||
description: "Signed: how far this feature moved the expected value away from the account's own average day. Ordered by
|
||||
absolute size, largest first."
|
||||
required:
|
||||
- feature
|
||||
- contribution
|
||||
additionalProperties: false
|
||||
BulkMoodResponseEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user