diff --git a/src/lib/ai/coach/learn-catalog.ts b/src/lib/ai/coach/learn-catalog.ts index b1c3f38cf..73d3a9e00 100644 --- a/src/lib/ai/coach/learn-catalog.ts +++ b/src/lib/ai/coach/learn-catalog.ts @@ -100,11 +100,16 @@ export const LEARN_GUIDES: readonly LearnGuide[] = [ */ export function learnCatalogPromptBlock(): string { const lines = LEARN_GUIDES.map((g) => `- ${g.topic}: ${g.url}`).join("\n"); + // Look the example guide up by slug, not by array index, so reordering the + // catalog above cannot silently change the prompt example. + const exampleGuide = + LEARN_GUIDES.find((g) => g.slug === "resting-heart-rate") ?? + LEARN_GUIDES[0]; return [ "LEARN GUIDES (public articles you MAY link when genuinely helpful):", lines, "Only link a URL from this list — never invent a /learn URL or article", "content. Reference at most one guide, and only when it adds real value,", - 'e.g. "more on this: ' + LEARN_GUIDES[1].url + '".', + 'e.g. "more on this: ' + exampleGuide.url + '".', ].join("\n"); } diff --git a/src/lib/ai/coach/snapshot.ts b/src/lib/ai/coach/snapshot.ts index 5879d4188..4eba2238f 100644 --- a/src/lib/ai/coach/snapshot.ts +++ b/src/lib/ai/coach/snapshot.ts @@ -408,12 +408,13 @@ async function buildCoarseTimelineTail( // "fall back on miss" contract the rest of the snapshot honours). let series: Awaited>; try { - // `skipRecentDaily` — the snapshot already holds the raw 0–14d rows from - // its own window read, so the tiered builder skips that duplicate raw - // round-trip and returns only the coarse MONTH/YEAR bands + anomalies. + // `coarseOnly` — the snapshot already holds the raw 0–14d rows from its + // own window read and this tail consumes only the MONTH/YEAR bands + + // anomaly envelope, so the builder skips the raw read AND the DAY/WEEK + // band reads it would otherwise discard. series = await buildTieredSeries(userId, type, { now: now.getTime(), - skipRecentDaily: true, + coarseOnly: true, }); } catch { return undefined; diff --git a/src/lib/rollups/tiered-context.ts b/src/lib/rollups/tiered-context.ts index accf0d0c3..e8d1abba7 100644 --- a/src/lib/rollups/tiered-context.ts +++ b/src/lib/rollups/tiered-context.ts @@ -124,6 +124,16 @@ export interface BuildTieredSeriesOptions { * MONTH/YEAR bands + anomaly envelope; avoids a duplicate raw round-trip. */ skipRecentDaily?: boolean; + /** + * v1.18.7 — skip the raw 0–14d read AND the DAY (14–30d) / WEEK (30–90d) + * band reads, returning only the MONTH / YEAR bands plus the anomaly + * envelope. For the Coach's `buildCoarseTimelineTail`, which consumes only + * month/year + anomalies; the skipped near-term bands would otherwise be + * read and discarded. The anomaly window-stats then span the coarse bands + * only, which is the right baseline for the deep-history tail. Implies + * `skipRecentDaily`. + */ + coarseOnly?: boolean; } interface RollupBucket { @@ -280,13 +290,19 @@ export async function buildTieredSeries( await ensureUserRollupsFresh(userId); } + const coarseOnly = options.coarseOnly ?? false; + const emptyBand = Promise.resolve([]); const [recentDaily, dayBand, weekBand, monthBand, yearBand] = await Promise.all([ - options.skipRecentDaily + options.skipRecentDaily || coarseOnly ? Promise.resolve([]) : readRecentDaily(userId, type, now), - readBand(userId, type, "DAY", TIERED_BANDS.dayUntil, TIERED_BANDS.rawDays, now), - readBand(userId, type, "WEEK", TIERED_BANDS.weekUntil, TIERED_BANDS.dayUntil, now), + coarseOnly + ? emptyBand + : readBand(userId, type, "DAY", TIERED_BANDS.dayUntil, TIERED_BANDS.rawDays, now), + coarseOnly + ? emptyBand + : readBand(userId, type, "WEEK", TIERED_BANDS.weekUntil, TIERED_BANDS.dayUntil, now), readBand(userId, type, "MONTH", TIERED_BANDS.monthUntil, TIERED_BANDS.weekUntil, now), readBand(userId, type, "YEAR", TIERED_BANDS.yearUntil, TIERED_BANDS.monthUntil, now), ]);