perf(coach): read only coarse bands for the deep-history tail

Add a coarseOnly option to buildTieredSeries that skips 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. The Coach snapshot's
coarse timeline tail consumes only month/year + anomalies, so the
near-term bands were read and discarded every turn; it now passes
coarseOnly. The briefing path is unchanged. Look the learn-guide prompt
example up by slug rather than by array index so reordering the catalog
cannot silently change the example link.
This commit is contained in:
Marc-André Bombeck committed 2026-06-19 20:29:02 +02:00
1 parent ebb9d0c607
commit d2007f2f37
3 files changed
+30 -8

No files matched your search

+6 -1
View File
@@ -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");
}
+5 -4
View File
@@ -408,12 +408,13 @@ async function buildCoarseTimelineTail(
// "fall back on miss" contract the rest of the snapshot honours).
let series: Awaited<ReturnType<typeof buildTieredSeries>>;
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;
+19 -3
View File
@@ -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<RollupBucket[]>([]);
const [recentDaily, dayBand, weekBand, monthBand, yearBand] =
await Promise.all([
options.skipRecentDaily
options.skipRecentDaily || coarseOnly
? Promise.resolve<TieredRawPoint[]>([])
: 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),
]);