Files
HealthLog/src/lib/insights/briefing-generated-at.ts
T
Marc-André Bombeck 491d542f58 fix(daily): judge the briefing's day by when its text was written
The check that keeps an earlier day's briefing off the Today surfaces
read the cache row's insightsCachedAt. Every warm moves that column,
including the unchanged-hash short-circuit that keeps yesterday's text
and only refreshes the stamp, so text written the day before passed as
today's after the nightly warm.

The cached payload now carries briefingGeneratedAt, written only where
new text is written: a full generation in the warm and in the
on-demand route, and the daily paragraph re-roll. The stamp refresh
leaves it untouched. briefingForToday reads that moment, and a payload
without it counts as not from today, so the hero falls back to its own
lead until the next generation.
2026-10-05 21:57:49 +02:00

46 lines
1.8 KiB
TypeScript

/**
* When the cached briefing's content was written, as opposed to when the
* cache row was last touched.
*
* `User.insightsCachedAt` moves on every warm, including the content-hash
* short-circuit that keeps yesterday's text and only refreshes the stamp.
* A "is this briefing from today?" check read off that column therefore
* passed for text written the day before. The moment the content itself was
* produced rides inside the cached payload instead, written only where new
* text is written: a full generation (the warm and the on-demand route) and
* the daily paragraph re-roll. The plain stamp refresh leaves it alone.
*
* A payload without the field predates it, and its age is unknown: the
* readers treat it as not from today, which is the safe side (the hero falls
* back to its own lead and a warm is asked for).
*/
/** The top-level key on the cached insight payload. */
export const BRIEFING_GENERATED_AT_KEY = "briefingGeneratedAt";
/** The payload with the generation moment set. */
export function withBriefingGeneratedAt<T extends object>(
payload: T,
at: Date,
): T & { [BRIEFING_GENERATED_AT_KEY]: string } {
return { ...payload, [BRIEFING_GENERATED_AT_KEY]: at.toISOString() };
}
/**
* The generation moment of a cached payload (an object or its stored JSON
* text), or null when it carries none or does not parse.
*/
export function readBriefingGeneratedAt(payload: unknown): string | null {
let value: unknown = payload;
if (typeof payload === "string") {
try {
value = JSON.parse(payload);
} catch {
return null;
}
}
if (value === null || typeof value !== "object") return null;
const at = (value as Record<string, unknown>)[BRIEFING_GENERATED_AT_KEY];
return typeof at === "string" && !Number.isNaN(Date.parse(at)) ? at : null;
}