mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
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.
49 lines
2.1 KiB
SQL
49 lines
2.1 KiB
SQL
-- 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;
|