feat(ai): answer the status notes with a null note when AI is unavailable

The status family (the five metric cards, medication compliance, the
generic metric status and the biomarker assessment) asked the retired
switch and the `insights` module and refused with 403. The note is model
text and the card around it is data, so they are mixed reads now: while
the `statusText` capability is available the note is served and warmed
as before, with `ai` beside it; otherwise the answer is 200 with the
note null, `preparing: false`, `hasProvider` as provider presence only
and `ai` naming the reason, and neither the cache nor the warm queue is
touched. The generic metric status still reports `insufficient` from the
data. The mood card keeps its mood module gate.

Kept apart so it can be held back while the shipped iPhone app's
rendering of a 200 without a note is confirmed.

Refs #975
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 09:30:23 +02:00
1 parent 9f1d1341f9
commit 1f6a13e2f7
19 files changed
+621 -207

No files matched your search

+40 -16
View File
@@ -74,6 +74,46 @@ export const AI_ROUTES: Readonly<Record<string, AiRouteEntry>> = {
capabilities: ["aboutMeQuestions"],
why: "The profile is data; only model-written follow-up questions follow the capability.",
},
"src/app/api/insights/biomarker-assessment/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/blood-pressure-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/bmi-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/medication-compliance-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/metric-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/mood-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/pulse-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/insights/weight-status/route.ts": {
kind: "mixed",
capabilities: ["statusText"],
why: "Status family: the card is data, the note inside it follows the capability (200 with a null note otherwise).",
},
"src/app/api/workouts/[id]/route.ts": {
kind: "mixed",
capabilities: ["workoutInsights"],
@@ -165,22 +205,6 @@ export const PENDING_ROUTES: Readonly<Record<string, string>> = {
"Attaching a document to a conversation: moves to `coach` and `documentAi` with the document-extraction routes.",
"src/app/api/insights/chat/[id]/attachments/[documentId]/route.ts":
"Detaching a document: removal of one's own data, stays ungated once the document routes move.",
"src/app/api/insights/biomarker-assessment/route.ts":
"Status family: moves to `statusText` (200 with null text) in its own change.",
"src/app/api/insights/blood-pressure-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/bmi-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/medication-compliance-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/metric-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/mood-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/pulse-status/route.ts":
"Status family: moves to `statusText` in its own change.",
"src/app/api/insights/weight-status/route.ts":
"Status family: moves to `statusText` in its own change.",
};
/** The trees in which every route has to be classified above. */