mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
feat(ai): legacy insight payload detection with regenerate CTA
Cached insight blobs persisted before B5c shipped don't carry the new `rationale` field — the strict parser would reject them, and auto-regenerating on every cache-hit would burn rate-limit tokens silently. Instead the route now detects the legacy shape and surfaces a `legacyPayload: true` flag in the response so the UI can render a one-time "regenerate for new explainability features" CTA. User-initiated regeneration stays the trigger. `isLegacyInsightPayload(payload)` lives in `src/lib/ai/legacy-payload.ts` and is intentionally lenient: any rec missing rationale (or the v1.4.14/v1.4.15 string-only InsightResult shape) flags. Empty recommendations[] is NOT legacy — it's a legitimate refusal payload. The annotate() Wide-Event also carries `legacyPayload` on cached hits so the admin AI quality dashboard can chart the migration ratio over time. v1.4.17 can flip auto-eviction on once the ratio drops near zero. 7 new tests in `legacy-payload-detection.test.ts` pin the contract. Existing 223 AI tests still green; typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
13f1ae5c0b
commit
c8b30c112d
3 files changed
+194
-2
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
AllProvidersFailedError,
|
||||
runRawCompletionWithFallback,
|
||||
} from "@/lib/ai/provider-runner";
|
||||
import { isLegacyInsightPayload } from "@/lib/ai/legacy-payload";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { NextRequest } from "next/server";
|
||||
import { apiHandler, requireAuth } from "@/lib/api-handler";
|
||||
@@ -94,14 +95,20 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
) {
|
||||
try {
|
||||
const cached = JSON.parse(dbUser.insightsCachedText);
|
||||
// v1.4.16 B5c: detect legacy payloads (pre-rationale) so the UI
|
||||
// can show a "regenerate for new explainability features" CTA.
|
||||
// We don't auto-regenerate — that would burn rate-limit tokens
|
||||
// on a cache-hit silently. User-initiated only.
|
||||
const legacyPayload = isLegacyInsightPayload(cached);
|
||||
annotate({
|
||||
action: { name: "insights.generate" },
|
||||
meta: { cached: true },
|
||||
meta: { cached: true, legacyPayload },
|
||||
});
|
||||
return apiSuccess({
|
||||
insights: cached,
|
||||
cached: true,
|
||||
cachedAt: dbUser.insightsCachedAt,
|
||||
legacyPayload,
|
||||
});
|
||||
} catch {
|
||||
// Invalid cache, regenerate
|
||||
@@ -312,5 +319,5 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
},
|
||||
});
|
||||
|
||||
return apiSuccess({ insights, cached: false });
|
||||
return apiSuccess({ insights, cached: false, legacyPayload: false });
|
||||
});
|
||||
Reference in new issue
Block a user