Files
HealthLog/prisma/migrations/0109_v1103_strain_trimp_cache/migration.sql
T
Marc-André Bombeck f1ca0e28eb feat(insights): anchor STRAIN to the user's own training load
Map day-total Banister TRIMP to 0-100 against a personal reference -- the
EWMA-smoothed 75th percentile of the user's own training-day TRIMP over a
42-day chronic window -- instead of the fixed population anchor that pinned a
deconditioned or chronic-condition user near 0 regardless of their effort.
Below a 7-training-day cold-start floor the engine falls back to the
population anchor, recorded as anchor: population with lower confidence; at
or above it the personal anchor takes over. Always writes a score row -- no
new null gate. The anchor is derived from the TRIMP input, never the 0-100
output, so there is no circular feedback; rest-day zeros stay out of the
training-day distribution.

Add a server-internal per-(user, day) strain_trimp_cache (day-total TRIMP +
running EWMA reference + anchor) so the chronic window reads cheap cached
values instead of re-integrating 42 days of HR series each night. The cache
is not a Measurement -- it stays out of every user-facing surface, the
doctor PDF, and the FHIR bundle by construction. No backfill: the nightly
idempotent recompute populates the cache forward and self-heals, falling
back to the population anchor until enough personal history accrues.

Keep the Banister/Morton/Tanaka citations and the doctor-PDF exclusion; the
honesty copy now states the scale is relative to the user's typical effort,
with a general reference during cold start.
2026-06-03 19:31:16 +02:00

49 lines
2.1 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- v1.10.3 — Strain personal-anchor cache.
--
-- A server-internal per-(user, day) cache that lets the nightly Strain engine
-- anchor the 0–100 map to the user's OWN recent training-day load (an
-- EWMA-smoothed P75 of day-total TRIMP over a 42-day chronic window) instead
-- of the fixed population reference. Persisting the day-total TRIMP + the
-- running EWMA reference means the chronic window reads cheap cached values
-- rather than re-integrating 42 days of HR series every night.
--
-- Purely additive: one new table, no enum changes, no backfill. The nightly
-- idempotent recompute populates the cache forward; until a user accrues
-- enough personal training history the engine falls back to the population
-- anchor, so behaviour matches today until the cache fills in — a clean,
-- self-healing rollout.
--
-- NOT a Measurement: never read through the derived registry, never on a
-- user-facing surface, never in the doctor PDF or FHIR bundle, never ingested
-- from a client. A dedicated table keeps it server-internal and out of every
-- MeasurementType completeness wall.
--
-- Reversibility: forward-only; dropping the table loses only the cache, and
-- the nightly job rebuilds it (falling back to the population anchor in the
-- interim).
CREATE TABLE IF NOT EXISTS "strain_trimp_cache" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"day" TEXT NOT NULL,
"day_trimp" DOUBLE PRECISION NOT NULL,
"ref_personal" DOUBLE PRECISION NOT NULL,
"anchor" TEXT NOT NULL,
"training_days" INTEGER NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "strain_trimp_cache_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "strain_trimp_cache_user_id_day_key"
ON "strain_trimp_cache" ("user_id", "day");
CREATE INDEX IF NOT EXISTS "strain_trimp_cache_user_id_day_idx"
ON "strain_trimp_cache" ("user_id", "day" DESC);
ALTER TABLE "strain_trimp_cache"
ADD CONSTRAINT "strain_trimp_cache_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users" ("id")
ON DELETE CASCADE ON UPDATE CASCADE;