fix(mood): say which age governs a claim about a dimension

The snapshot stamps every metric block with an asOf built from the
newest reading in it, which for mood is the newest ENTRY on the 1-5
axis. Somebody who taps a face each morning and opens the sliders once
a week therefore carries a block stamped as current and a stress
reading six days old, and a reader taking the block stamp as covering
the dimensions states last week's answer as today's.

The block now names the precedence in one line beside the dimensions,
and each dimension keeps its own age. Present only when there are
dimensions to explain.
This commit is contained in:
Marc-André Bombeck committed 2026-08-08 02:57:40 +02:00
1 parent 0a39d50b05
commit 4db0f3a62a
2 files changed
+54 -2

No files matched your search

+39 -1
View File
@@ -187,6 +187,27 @@ async function buildMoodDimensionFeatures(
return blocks.length > 0 ? blocks : undefined;
}
/**
* The dimensions block and the note that says how to read it beside the
* block-level `asOf`. Absent entirely when nobody has answered a dimension,
* so an account that only ever taps a face carries neither key.
*/
async function buildMoodDimensionBlock(userId: string): Promise<{
dimensions?: MoodDimensionFeature[];
dimensionsAsOfNote?: string;
}> {
const dimensions = await buildMoodDimensionFeatures(userId);
if (!dimensions) return {};
return {
dimensions,
dimensionsAsOfNote:
"The block-level asOf describes the newest mood ENTRY on the 1-5 axis. " +
"For the five dimensions below, each carries its own newestDaysAgo and " +
"current, and those are the ones that govern what may be said about " +
"them today; they are often older than the entry itself.",
};
}
export interface AggregatedFeatures {
weight?: {
latest: number;
@@ -295,6 +316,23 @@ export interface AggregatedFeatures {
* given one age for the pair narrates the stale one as today's.
*/
dimensions?: MoodDimensionFeature[];
/**
* Says which age governs which claim, because two of them ride in the
* same block and they disagree by design.
*
* `annotateSnapshotFreshness` stamps every metric block with an `asOf`
* built from `coverage.newestDaysAgo`, which for mood is the age of the
* newest ENTRY — the 1-5 axis. Somebody who taps a face each morning and
* opens the sliders once a week has `asOf.daysAgo` of 0 and a stress
* reading six days old, so a reader taking the block-level stamp as
* covering the dimensions would state last week's answer as today's.
* That is the exact sentence this milestone exists to prevent, and it is
* cheaper to name the precedence than to leave two true numbers looking
* like a contradiction.
*
* Present only alongside `dimensions`.
*/
dimensionsAsOfNote?: string;
};
sleep?: {
avg7: number | null;
@@ -1189,7 +1227,7 @@ export async function extractFeatures(
latest: moodLatestScore,
trend30,
totalEntries: moodTotalEntries,
dimensions: await buildMoodDimensionFeatures(userId),
...(await buildMoodDimensionBlock(userId)),
coverage: {
count: moodTotalEntries,
spanDays,
@@ -122,6 +122,18 @@ describe("level-A blocks reach the feature set with their own ages", () => {
// arriving pre-flipped.
expect(a2.inverse).toBe(true);
expect(a2.count).toBe(1);
// The block-level asOf the snapshot stamps on top of this comes from
// coverage.newestDaysAgo — the newest ENTRY, which is today, because a
// face was tapped this morning. Read as covering the dimensions it would
// make the six-day-old stress look current, so the block says which age
// governs which claim.
expect(features.mood?.coverage.newestDaysAgo).toBe(0);
expect(
features.mood?.dimensionsAsOfNote,
"the block carries two ages that disagree and nothing says which one governs the dimensions",
).toBeTruthy();
expect(features.mood?.dimensionsAsOfNote).toContain("newestDaysAgo");
});
it("never ships a latest without an age beside it, and names the scale", async () => {
@@ -157,8 +169,10 @@ describe("level-A blocks reach the feature set with their own ages", () => {
// The mood aggregate is still there — the five-point axis is unaffected.
expect(features.mood).toBeDefined();
expect(features.mood?.latest).toBe(4);
// But nothing claims a self-state that was never recorded.
// But nothing claims a self-state that was never recorded — and no note
// explaining a block that is not there.
expect(features.mood?.dimensions).toBeUndefined();
expect(features.mood?.dimensionsAsOfNote).toBeUndefined();
});
it("leaves the legacy scale line untouched", async () => {