mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
The dead-code sweep found the window type exported and unread. It was unread because the surface that offers the choice carried its own copy of the same three numbers: the chart could not import the calculator's, since that module reaches the database client two hops down and a client component pulling it in would ship Prisma to the browser. Both now come from the dimension table, which is pure by construction and which the chart already imported. One declaration of 7 / 30 / 90, read by the calculator that averages over them and by the control that offers them, so the two cannot drift.
136 lines
4.6 KiB
TypeScript
136 lines
4.6 KiB
TypeScript
/**
|
||
* The five level-A self-state dimensions of a mood entry.
|
||
*
|
||
* A mood entry has carried one 1–5 value (`MoodEntry.score`) since the first
|
||
* release. That value keeps its meaning and its scale — eleven surfaces read
|
||
* it on the assumption that it runs 1..5 — and the five dimensions below ride
|
||
* beside it as their own 0–10 columns. The two coexist by design: `score` is
|
||
* "how was the day, in one word", the dimensions are "how was the day, in the
|
||
* terms the day was actually made of".
|
||
*
|
||
* Each dimension is stored literally, exactly as the user set it against the
|
||
* anchors they read. A2 (stress) is inverse-oriented — a higher value means a
|
||
* worse day — and it is stored that way rather than flipped at write time. An
|
||
* analytics surface that needs "up is better" flips the sign through
|
||
* `inverse` on this table; nothing flips inline. The same rule already governs
|
||
* rated mood factors (`MoodTag.inverse`), and it exists because a stored value
|
||
* that disagrees with the label the user saw is unrecoverable afterwards.
|
||
*
|
||
* This module is pure: it holds i18n keys and column names, and imports
|
||
* neither Prisma nor the message bundles. The German wording behind every key
|
||
* lives in `messages/de.json` and its five siblings.
|
||
*/
|
||
|
||
/** Wire/DTO key of a level-A dimension. */
|
||
export type MoodDimensionKey = "a1" | "a2" | "a3" | "a4" | "a5";
|
||
|
||
/** `MoodEntry` column a level-A dimension is stored in. */
|
||
export type MoodDimensionColumn =
|
||
"moodA1" | "stressA2" | "energyA3" | "connectionA4" | "stabilityA5";
|
||
|
||
export interface MoodDimension {
|
||
/** Wire key (`a1`…`a5`) accepted by the create schema and echoed on reads. */
|
||
key: MoodDimensionKey;
|
||
/** Prisma field on `MoodEntry` this dimension is stored in. */
|
||
column: MoodDimensionColumn;
|
||
/** i18n key of the dimension's own name. */
|
||
labelKey: string;
|
||
/** i18n key of the wording at the bottom of the scale. */
|
||
lowAnchorKey: string;
|
||
/** i18n key of the wording at the top of the scale. */
|
||
highAnchorKey: string;
|
||
/**
|
||
* A higher stored value means a worse day. Exactly one dimension carries
|
||
* this today (A2, stress), and the unit test pins that count, so a second
|
||
* one cannot be flipped quietly.
|
||
*/
|
||
inverse: boolean;
|
||
min: 0;
|
||
max: 10;
|
||
}
|
||
|
||
/**
|
||
* The dimensions in capture order — the order the sliders render in and the
|
||
* order every per-dimension surface iterates. Concept order, not alphabetical:
|
||
* pleasantness first because it is the one the quick check-in already answers.
|
||
*/
|
||
export const MOOD_DIMENSIONS: readonly MoodDimension[] = [
|
||
{
|
||
key: "a1",
|
||
column: "moodA1",
|
||
labelKey: "mood.dimension.a1.label",
|
||
lowAnchorKey: "mood.dimension.a1.low",
|
||
highAnchorKey: "mood.dimension.a1.high",
|
||
inverse: false,
|
||
min: 0,
|
||
max: 10,
|
||
},
|
||
{
|
||
key: "a2",
|
||
column: "stressA2",
|
||
labelKey: "mood.dimension.a2.label",
|
||
lowAnchorKey: "mood.dimension.a2.low",
|
||
highAnchorKey: "mood.dimension.a2.high",
|
||
inverse: true,
|
||
min: 0,
|
||
max: 10,
|
||
},
|
||
{
|
||
key: "a3",
|
||
column: "energyA3",
|
||
labelKey: "mood.dimension.a3.label",
|
||
lowAnchorKey: "mood.dimension.a3.low",
|
||
highAnchorKey: "mood.dimension.a3.high",
|
||
inverse: false,
|
||
min: 0,
|
||
max: 10,
|
||
},
|
||
{
|
||
key: "a4",
|
||
column: "connectionA4",
|
||
labelKey: "mood.dimension.a4.label",
|
||
lowAnchorKey: "mood.dimension.a4.low",
|
||
highAnchorKey: "mood.dimension.a4.high",
|
||
inverse: false,
|
||
min: 0,
|
||
max: 10,
|
||
},
|
||
{
|
||
key: "a5",
|
||
column: "stabilityA5",
|
||
labelKey: "mood.dimension.a5.label",
|
||
lowAnchorKey: "mood.dimension.a5.low",
|
||
highAnchorKey: "mood.dimension.a5.high",
|
||
inverse: false,
|
||
min: 0,
|
||
max: 10,
|
||
},
|
||
] as const;
|
||
|
||
/**
|
||
* Windows the per-dimension trend reports, in days. Concept §10.1.
|
||
*
|
||
* Here rather than beside the calculator that averages over them, because the
|
||
* chart offering the choice needs the same three numbers and cannot import
|
||
* that module: it reaches Prisma two hops down, and a client component that
|
||
* pulled it in would drag the database client into the browser bundle. This
|
||
* module is pure by construction, which is what makes it the one place both
|
||
* ends can agree on.
|
||
*/
|
||
export const MOOD_DIMENSION_WINDOWS = [7, 30, 90] as const;
|
||
export type MoodDimensionWindow = (typeof MOOD_DIMENSION_WINDOWS)[number];
|
||
|
||
/** Lowest value any dimension accepts. */
|
||
export const MOOD_DIMENSION_MIN = 0;
|
||
/** Highest value any dimension accepts. */
|
||
export const MOOD_DIMENSION_MAX = 10;
|
||
|
||
const BY_KEY = new Map<string, MoodDimension>(
|
||
MOOD_DIMENSIONS.map((d) => [d.key, d]),
|
||
);
|
||
|
||
/** The dimension carrying this wire key, or `undefined` for anything else. */
|
||
export function getMoodDimension(key: string): MoodDimension | undefined {
|
||
return BY_KEY.get(key);
|
||
}
|