feat(insights): let the usage level decide the background AI work

The level now sets, per record: the status-card regeneration gap after a
sync (frugal 24 h, standard the operator's gap, full 1 h) and after a hand
entry (frugal 6 h, otherwise 1 h); the status snapshot cap (10 000 compact
characters on frugal, 14 000 above); background reasoning for the briefing
and the period narratives (off on frugal); the daily briefing's form (a
short form on frugal that keeps every field and at most one
recommendation, the full contract on standard and full); and whether the
automatic document read may act (frugal reads on request only, the stored
switch is kept).

A deterministic simulation of a watch-synced day through the invalidator
counts 122 ingest-driven status generations on full (the v1.42 behaviour),
25 on standard and 2 on frugal, plus the nightly warm on each.

Refs #1217
This commit is contained in:
Marc-André Bombeck committed 2026-10-15 22:55:40 +02:00
1 parent d3770b9010
commit 74dcc67ef7
23 files changed
+840 -83

No files matched your search

+13
View File
@@ -94,6 +94,11 @@ import { resolveServerLocale } from "@/lib/i18n/server-locale";
import { normalizeLocale } from "@/lib/insights/status-shared";
import { hasUsableStatusProvider } from "@/lib/insights/status-provider";
import { hashInsightSnapshot } from "@/lib/insights/snapshot-hash";
import {
applyBriefingShortForm,
briefingFormForRecord,
buildBriefingShortFormBlock,
} from "@/lib/insights/briefing-short-form";
import { featuresInReaderUnits } from "@/lib/insights/features-units";
import { resolveUnitPreferences } from "@/lib/measurements/display-transform";
import { enqueueForceWarm } from "@/lib/jobs/insight-pregenerate-shared";
@@ -753,6 +758,10 @@ export const POST = apiHandler((request: NextRequest) =>
locale,
await resolveUserTimezone(userId),
);
// v1.43 (#1217) — the short form on the `frugal` usage level; the full
// contract on every other level.
const briefingForm = await briefingFormForRecord(userId);
userPrompt += buildBriefingShortFormBlock(locale, briefingForm);
// v1.12.7 (B5) — inject the curated SOURCES block for the metric sections
// this briefing actually carries, so a normative claim can cite a real
@@ -1083,6 +1092,9 @@ export const POST = apiHandler((request: NextRequest) =>
errorCode: "insights.generate.profileScopeChanged",
});
}
if (briefingForm === "short") {
insights = applyBriefingShortForm(insights);
}
const stored = withBriefingGeneratedAt(insights, new Date());
const committed = await prisma.user.updateMany({
where: {
@@ -1104,6 +1116,7 @@ export const POST = apiHandler((request: NextRequest) =>
aboutMe: aboutMe ?? null,
comparisonBaseline: comparisonSnapshot?.baseline ?? "none",
generationLocale: locale,
...(briefingForm === "short" ? { briefingForm } : {}),
}),
},
});