mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
perf(coach): bound buildCoachSnapshot to last 90 days
Every POST /api/insights/chat called extractFeatures(userId, false),
which ran prisma.measurement.findMany({ where: { userId } }) with no
date filter — fetching every measurement the user had ever recorded.
For power users with multi-year Withings imports that's tens of
thousands of rows on every Coach turn.
Add an optional sinceDays bound to extractFeatures (default unchanged
= full history; analytics dashboard + insights/generate keep their
existing behaviour) and have the Coach snapshot pass sinceDays: 90.
The Coach already only narrates the last7 / last30 / last90 windows,
so the 90-day floor preserves every metric the snapshot surfaces.
Fixes Senior-HIGH-3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e632e26854
commit
249607d0b2
2 files changed
+26
-3
No files matched your search
@@ -25,11 +25,21 @@ export interface CoachSnapshotResult {
|
|||||||
* `includeRaw=false` because the Coach replies are conversational and
|
* `includeRaw=false` because the Coach replies are conversational and
|
||||||
* the user is asking the model — they should never depend on raw
|
* the user is asking the model — they should never depend on raw
|
||||||
* measurement timestamps that the privacy mode controls.
|
* measurement timestamps that the privacy mode controls.
|
||||||
|
*
|
||||||
|
* Bounded to the last 90 days. Coach narrates the same windows the
|
||||||
|
* Daily Briefing renders (last7 / last30 / last90); fetching every
|
||||||
|
* historical measurement on each turn was unbounded I/O for power
|
||||||
|
* users with multi-year Withings imports. The 90-day floor is the
|
||||||
|
* widest window any Coach metric currently consumes.
|
||||||
*/
|
*/
|
||||||
|
const COACH_SNAPSHOT_WINDOW_DAYS = 90;
|
||||||
|
|
||||||
export async function buildCoachSnapshot(
|
export async function buildCoachSnapshot(
|
||||||
userId: string,
|
userId: string,
|
||||||
): Promise<CoachSnapshotResult> {
|
): Promise<CoachSnapshotResult> {
|
||||||
const features = await extractFeatures(userId, false);
|
const features = await extractFeatures(userId, false, {
|
||||||
|
sinceDays: COACH_SNAPSHOT_WINDOW_DAYS,
|
||||||
|
});
|
||||||
|
|
||||||
// Trim down to the metrics the Coach narrates. extractFeatures
|
// Trim down to the metrics the Coach narrates. extractFeatures
|
||||||
// returns more (sleep, steps, etc.) — the Coach surface keeps the
|
// returns more (sleep, steps, etc.) — the Coach surface keeps the
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ function computeHistoricalComparison(
|
|||||||
export async function extractFeatures(
|
export async function extractFeatures(
|
||||||
userId: string,
|
userId: string,
|
||||||
includeRaw: boolean,
|
includeRaw: boolean,
|
||||||
|
options: { sinceDays?: number } = {},
|
||||||
): Promise<AggregatedFeatures | RawFeatures> {
|
): Promise<AggregatedFeatures | RawFeatures> {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
@@ -268,9 +269,21 @@ export async function extractFeatures(
|
|||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Fetch ALL measurements (not just 30 days) for full temporal context
|
// Fetch measurements. Default = ALL (full temporal context for the
|
||||||
|
// dashboard / insights generator). Callers that only consume the
|
||||||
|
// ≤90-day windows (Coach snapshot per turn) pass `sinceDays: 90` so
|
||||||
|
// the per-turn I/O stays bounded — `findMany({ where: { userId } })`
|
||||||
|
// is unbounded by user-history size and gets paid once per Coach turn
|
||||||
|
// for power users with multi-year Withings imports.
|
||||||
|
const sinceDays = options.sinceDays;
|
||||||
|
const sinceCutoff =
|
||||||
|
typeof sinceDays === "number" && sinceDays > 0
|
||||||
|
? new Date(now - sinceDays * 24 * 60 * 60 * 1000)
|
||||||
|
: null;
|
||||||
const measurements = await prisma.measurement.findMany({
|
const measurements = await prisma.measurement.findMany({
|
||||||
where: { userId },
|
where: sinceCutoff
|
||||||
|
? { userId, measuredAt: { gte: sinceCutoff } }
|
||||||
|
: { userId },
|
||||||
orderBy: { measuredAt: "asc" },
|
orderBy: { measuredAt: "asc" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user