Files
HealthLog/src/lib/insights/status-unavailable.ts
T
Marc-André Bombeck 1f6a13e2f7 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
2026-09-24 09:30:23 +02:00

71 lines
2.1 KiB
TypeScript

/**
* What a status route answers while the `statusText` capability is
* unavailable.
*
* The status family (`*-status`, `metric-status`, `biomarker-assessment`) is
* a mixed read: the card around the note is data, the note is model output.
* With the capability unavailable, for any reason, the route answers 200 with
* no note and says why:
*
* - `text: null`: stored model text is never served, however fresh.
* - `preparing: false`: nothing is warming, because nothing was enqueued.
* - `hasProvider`: provider presence and nothing else. A shipped native
* client renders "no provider" copy for `false`, so it must not be false
* because the operator switched notes off or consent is missing.
* - `ai`: the resolved state, so a client can say why without guessing.
*
* The route builds this instead of calling the generator, so no cache row is
* read and no generation is queued.
*/
import { probeProviderPresence } from "@/lib/ai/provider";
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
export interface UnavailableStatusBody {
hasProvider: boolean;
text: null;
cached: false;
updatedAt: null;
preparing: false;
ai: AiCapabilityState;
}
export async function unavailableStatusBody(
recordId: string,
ai: AiCapabilityState,
): Promise<UnavailableStatusBody> {
return {
hasProvider: await probeProviderPresence(recordId),
text: null,
cached: false,
updatedAt: null,
preparing: false,
ai,
};
}
/** The medication-compliance card's shape: a summary plus per-medication notes. */
export interface UnavailableComplianceStatusBody {
hasProvider: boolean;
summary: null;
medications: [];
cached: false;
updatedAt: null;
preparing: false;
ai: AiCapabilityState;
}
export async function unavailableComplianceStatusBody(
recordId: string,
ai: AiCapabilityState,
): Promise<UnavailableComplianceStatusBody> {
return {
hasProvider: await probeProviderPresence(recordId),
summary: null,
medications: [],
cached: false,
updatedAt: null,
preparing: false,
ai,
};
}