mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
Merge remote-tracking branch 'origin/main' into fix/settings-ekg-polish
This commit is contained in:
commit
4c2dfa57b4
138 files changed
+12046
-137
No files matched your search
@@ -0,0 +1,58 @@
|
||||
-- v1.38 — the expected value of a day, beside the day's own rating.
|
||||
--
|
||||
-- One row per account per local day, holding what the account's own past days
|
||||
-- say a day like this one usually looks like. It is a second reading of a day,
|
||||
-- never a correction of the first: the self-assessment on `mood_entries` is
|
||||
-- stored exactly as the person set it and nothing here touches it. The
|
||||
-- distance between the two is the whole point of having both.
|
||||
--
|
||||
-- Its own table rather than four more columns on `mood_entries`, and the
|
||||
-- reason is a permission rather than a shape: a value the user must not be
|
||||
-- able to overwrite has no business sitting in the row their own edit form
|
||||
-- writes. One writer, one table.
|
||||
--
|
||||
-- Not carried in a disaster-recovery backup. Every value here is recomputable
|
||||
-- from `mood_entries` + `mood_contexts` and the measurement tables, and the
|
||||
-- nightly job rebuilds it. The verdict is written down in `DERIVED_MODELS`
|
||||
-- (`src/lib/export/backup-plan.ts`) where the completeness guard reads it,
|
||||
-- so a restore that comes back without these rows is doing what it was told.
|
||||
--
|
||||
-- No CHECK constraints on the ranges, matching `mood_contexts`: the bounds are
|
||||
-- server-side, and a later change to a scale must not strand rows written
|
||||
-- under the old one.
|
||||
|
||||
CREATE TABLE "mood_predictions" (
|
||||
"user_id" TEXT NOT NULL,
|
||||
-- YYYY-MM-DD in the account's own zone, the same key `mood_entries.date`
|
||||
-- carries, so the two readings of one day join without a second timezone
|
||||
-- derivation.
|
||||
"date" TEXT NOT NULL,
|
||||
|
||||
-- The expected pleasantness (A1), 0-10, and the band around it. The band
|
||||
-- comes from the rolling-origin validation residuals, so it widens when
|
||||
-- the model is bad instead of staying confidently narrow.
|
||||
"predicted" DOUBLE PRECISION NOT NULL,
|
||||
"ci_low" DOUBLE PRECISION,
|
||||
"ci_high" DOUBLE PRECISION,
|
||||
-- Complete days the fit was built from. Displayed wherever the value is.
|
||||
"n" INTEGER NOT NULL,
|
||||
|
||||
-- Bumped when the feature set or the fit changes, so a re-fit is legible
|
||||
-- in the stored rows rather than silently changing what they mean.
|
||||
"model_version" TEXT NOT NULL,
|
||||
-- JSON array of { feature, contribution }: the standardised coefficient
|
||||
-- times that day's standardised value. Stored so the explanation line is
|
||||
-- generated from what the model actually used.
|
||||
"features" TEXT NOT NULL,
|
||||
"computed_at" TIMESTAMPTZ(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "mood_predictions_pkey" PRIMARY KEY ("user_id", "date")
|
||||
);
|
||||
|
||||
-- The account cascade every other user-scoped table here carries. The primary
|
||||
-- key already leads with `user_id`, so the per-account sweep the job and the
|
||||
-- wipe both run needs no second index.
|
||||
ALTER TABLE "mood_predictions"
|
||||
ADD CONSTRAINT "mood_predictions_user_id_fkey"
|
||||
FOREIGN KEY ("user_id") REFERENCES "users"("id")
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -751,6 +751,7 @@ model User {
|
||||
googleHealthOAuthStates GoogleHealthOAuthState[]
|
||||
moodEntries MoodEntry[]
|
||||
moodContexts MoodContext[]
|
||||
moodPredictions MoodPrediction[]
|
||||
mentalHealthAssessments MentalHealthAssessment[]
|
||||
moodTagsCustom MoodTag[] @relation("UserMoodTags")
|
||||
moodTagCategoriesCustom MoodTagCategory[] @relation("UserMoodTagCategories")
|
||||
@@ -4242,6 +4243,68 @@ model MoodContext {
|
||||
@@map("mood_contexts")
|
||||
}
|
||||
|
||||
/// What the account's own past days say a day like this one usually looks
|
||||
/// like — one row per local day, written by a job and never by a person.
|
||||
///
|
||||
/// Four things about this table that a later reader would otherwise have to
|
||||
/// infer, and would infer wrongly:
|
||||
///
|
||||
/// 1. **Written by the `mood-prognosis-refresh` job only. Never by a route,
|
||||
/// and the user cannot overwrite it.** The self-assessment on `MoodEntry`
|
||||
/// and the value here are two separate readings of the same day and they
|
||||
/// stay separate: the deviation between them is the entire finding, and a
|
||||
/// surface that let somebody correct the prognosis into agreement would
|
||||
/// destroy it. Two tables make that structural rather than a convention,
|
||||
/// and `src/__tests__/mood-prediction-write-surface-guard.test.ts` holds
|
||||
/// the writer set to one file.
|
||||
/// 2. **`features` holds the evidence, not a hint.** It is the JSON list of
|
||||
/// the standardised contributions the fit actually used for that day, so
|
||||
/// the "what the model weighted" line is generated from what happened
|
||||
/// rather than recomputed at render time against a model that may since
|
||||
/// have been re-fitted.
|
||||
/// 3. **Deliberately not in the disaster-recovery backup.** It is a derived
|
||||
/// artefact, recomputable in full from `MoodEntry` + `MoodContext` and the
|
||||
/// linked modules, and the nightly job rebuilds it after a restore. The
|
||||
/// verdict is written down in `DERIVED_MODELS`
|
||||
/// (`src/lib/export/backup-plan.ts`), where the completeness guard reads
|
||||
/// it, so this is a decision rather than an omission. It IS in the wipe
|
||||
/// plan: a derived row is still a statement about somebody's days.
|
||||
/// 4. **`n` and the band are part of the value.** A consumer that renders
|
||||
/// `predicted` without `ciLow` / `ciHigh` / `n` is a defect: a forecast
|
||||
/// built from thirty days and one built from three hundred look identical
|
||||
/// on screen and mean very different things.
|
||||
model MoodPrediction {
|
||||
userId String @map("user_id")
|
||||
/// YYYY-MM-DD in the account's own zone — the same day key and the same
|
||||
/// anchoring `MoodEntry.date` carries, so the two readings of a day join
|
||||
/// without a second timezone derivation.
|
||||
date String
|
||||
|
||||
/// The expected pleasantness (A1) for that day, 0-10, on the account's own
|
||||
/// past pattern. A counterfactual, never a measurement.
|
||||
predicted Float
|
||||
/// The band around it, from the rolling-origin validation residuals. Null
|
||||
/// only when the validation produced no residuals at all, which the write
|
||||
/// path treats as "no forecast" rather than as a bare point estimate.
|
||||
ciLow Float? @map("ci_low")
|
||||
ciHigh Float? @map("ci_high")
|
||||
/// Complete days the fit was built from. Shown wherever `predicted` is.
|
||||
n Int
|
||||
|
||||
/// Bumped whenever the feature set or the fit changes, so a re-fit is
|
||||
/// legible in the stored rows instead of silently changing their meaning.
|
||||
modelVersion String @map("model_version")
|
||||
/// JSON array of `{ feature, contribution }` — the standardised coefficient
|
||||
/// times that day's standardised value, per feature.
|
||||
features String
|
||||
computedAt DateTime @map("computed_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([userId, date])
|
||||
@@map("mood_predictions")
|
||||
}
|
||||
|
||||
// ─── Mood Tag Taxonomy (v1.8.5 W6-tax) ────────────────────────
|
||||
//
|
||||
// Structured, icon-bearing tag catalog modelled on the standalone mood
|
||||
|
||||
Reference in new issue
Block a user