From 18cde65af7a6bfddf020f62ca572a4b4f321c8c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 16:52:11 +0200 Subject: [PATCH] docs(openapi): register the six specialised insight status routes The generic metric-status route set the OpenAPI pattern for the assessment cards; retrofit the six specialised routes onto it. Add registry entries for /api/insights/{blood-pressure,pulse,weight,bmi,mood,medication-compliance}-status mirroring the metric-status envelope (hasProvider/text/cached/updatedAt plus the preparing + revalidating flags). Medication-compliance carries its richer summary + per-medication note array as its own schema. Regenerate the YAML. Co-Authored-By: Claude Opus 4.8 (1M context) (cherry picked from commit 91887a13b30e8978e91f615604163331bdc54950) --- docs/api/openapi.yaml | 366 ++++++++++++++++++++++++++++++++++++++ src/lib/openapi/routes.ts | 264 +++++++++++++++++++++++++++ 2 files changed, 630 insertions(+) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index b784f0af7..428463849 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -2035,6 +2035,174 @@ paths: "401": *a1 "422": *a2 "429": *a3 + /api/insights/blood-pressure-status: + get: + tags: + - Insights + summary: Blood-pressure assessment + description: "Data-driven plain-language assessment of the user's recent blood-pressure readings. Read-only: a cache + miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via + cookie or Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/BloodPressureStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 + /api/insights/pulse-status: + get: + tags: + - Insights + summary: Pulse assessment + description: "Data-driven plain-language assessment of the user's recent resting-pulse readings. Read-only: a cache miss + warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie + or Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/PulseStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 + /api/insights/weight-status: + get: + tags: + - Insights + summary: Weight assessment + description: "Data-driven plain-language assessment of the user's recent weight trend. Read-only: a cache miss warms a + generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or + Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/WeightStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 + /api/insights/bmi-status: + get: + tags: + - Insights + summary: BMI assessment + description: "Data-driven plain-language assessment of the user's body-mass index. Read-only: a cache miss warms a + generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or + Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/BmiStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 + /api/insights/mood-status: + get: + tags: + - Insights + summary: Mood assessment + description: "Data-driven plain-language assessment of the user's recent mood entries. Read-only: a cache miss warms a + generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or + Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/MoodStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 + /api/insights/medication-compliance-status: + get: + tags: + - Insights + summary: Medication-compliance assessment + description: "Data-driven plain-language assessment of the user's medication compliance — an overall `summary` plus a + per-medication note array. Read-only: a cache miss warms a generation out of band and serves the last-good + envelope meanwhile (stale-while-revalidate). Auth via cookie or Bearer." + parameters: + - in: query + name: locale + schema: + description: Optional UI-locale override; defaults to the session locale. + type: string + enum: + - de + - en + description: Optional UI-locale override; defaults to the session locale. + responses: + "200": + description: Compliance assessment envelope (fresh, cached, or preparing). + content: + application/json: + schema: + $ref: "#/components/schemas/MedicationComplianceStatusResponseEnvelope" + "401": *a1 + "422": *a2 + "429": *a3 /api/insights/metric-status: get: tags: @@ -6578,6 +6746,204 @@ components: - mean - sum additionalProperties: false + BloodPressureStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/InsightStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + InsightStatusResponse: + type: object + properties: + hasProvider: + type: boolean + description: False when the user has no usable AI provider — `text` then carries the generic no-key guidance. + text: + anyOf: + - type: string + - type: "null" + description: The assessment narrative (plain text, rendered as React text children). Null while a first generation is + preparing. + cached: + type: boolean + description: True when `text` is served from cache (incl. last-good). + updatedAt: + anyOf: + - type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + - type: "null" + description: When the served assessment was generated; null when none. + preparing: + description: True when a first assessment is being generated out of band and no prior text exists yet — the client polls + until it lands. + type: boolean + revalidating: + description: True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in + flight. The client keeps polling on `preparing || revalidating` (bounded) so the open card upgrades to the + warmed assessment without a remount. + type: boolean + required: + - hasProvider + - text + - cached + - updatedAt + additionalProperties: false + description: "Specialised per-metric assessment envelope (blood-pressure, pulse, weight, bmi, mood). Identical shape to + the generic metric-status card so the `InsightStatusCard` consumes it unchanged. Read-only + + stale-while-revalidate: a cache miss warms a generation out of band and serves the last-good text meanwhile." + PulseStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/InsightStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + WeightStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/InsightStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + BmiStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/InsightStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + MoodStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/InsightStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + MedicationComplianceStatusResponseEnvelope: + type: object + properties: + data: + $ref: "#/components/schemas/MedicationComplianceStatusResponse" + error: + type: "null" + meta: + type: object + properties: + requestId: + type: string + additionalProperties: false + required: + - data + - error + additionalProperties: false + MedicationComplianceStatusResponse: + type: object + properties: + hasProvider: + type: boolean + description: False when the user has no usable AI provider — `summary` then carries the generic no-key guidance. + summary: + anyOf: + - type: string + - type: "null" + description: The overall compliance narrative (plain text). Null while a first generation is preparing. + medications: + type: array + items: + $ref: "#/components/schemas/MedicationComplianceStatusItem" + description: Per-medication compliance notes. Empty while preparing or when no medication qualifies. + cached: + type: boolean + description: True when the envelope is served from cache (incl. last-good). + updatedAt: + anyOf: + - type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + - type: "null" + description: When the served assessment was generated; null when none. + preparing: + description: True when a first assessment is being generated out of band and no prior summary exists yet — the client + polls until it lands. + type: boolean + revalidating: + description: True when the envelope is served from last-good cache (stale-while-revalidate) while a fresh generation is + in flight. The client keeps polling on `preparing || revalidating` (bounded). + type: boolean + required: + - hasProvider + - summary + - medications + - cached + - updatedAt + additionalProperties: false + description: Medication-compliance assessment envelope. Unlike the other six specialised cards it carries a `summary` + plus a per-medication `text` array rather than a single `text` field. Read-only + stale-while-revalidate. + MedicationComplianceStatusItem: + type: object + properties: + medicationId: + type: string + description: The medication this note belongs to. + text: + type: string + description: Per-medication compliance note (plain text). + required: + - medicationId + - text + additionalProperties: false MetricStatusResponseEnvelope: type: object properties: diff --git a/src/lib/openapi/routes.ts b/src/lib/openapi/routes.ts index b93fded35..53217ae53 100644 --- a/src/lib/openapi/routes.ts +++ b/src/lib/openapi/routes.ts @@ -1175,6 +1175,119 @@ const metricStatusResponse = z "Generic per-metric assessment envelope. Identical shape to the seven specialised `*-status` cards so the `InsightStatusCard` consumes it unchanged. Read-only + stale-while-revalidate: a cache miss warms a generation out of band and serves the last-good text meanwhile.", }); +// The seven specialised `*-status` routes accept an optional locale +// override (the metric is fixed by the route path, unlike the generic +// metric-status route which carries it as a query field). +const insightStatusQuery = z + .object({ + locale: z + .enum(["de", "en"]) + .optional() + .describe("Optional UI-locale override; defaults to the session locale."), + }) + .meta({ id: "InsightStatusQuery" }); + +// Shared response shape for the five text-bearing specialised status +// routes (blood-pressure, pulse, weight, bmi, mood). Same envelope as +// the generic metric-status card minus the `insufficient` flag, which is +// metric-status-only. Read-only + stale-while-revalidate. +const insightStatusResponse = z + .object({ + hasProvider: z + .boolean() + .describe( + "False when the user has no usable AI provider — `text` then carries the generic no-key guidance.", + ), + text: z + .string() + .nullable() + .describe( + "The assessment narrative (plain text, rendered as React text children). Null while a first generation is preparing.", + ), + cached: z + .boolean() + .describe("True when `text` is served from cache (incl. last-good)."), + updatedAt: z.iso + .datetime({ offset: true }) + .nullable() + .describe("When the served assessment was generated; null when none."), + preparing: z + .boolean() + .optional() + .describe( + "True when a first assessment is being generated out of band and no prior text exists yet — the client polls until it lands.", + ), + revalidating: z + .boolean() + .optional() + .describe( + "True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in flight. The client keeps polling on `preparing || revalidating` (bounded) so the open card upgrades to the warmed assessment without a remount.", + ), + }) + .meta({ + id: "InsightStatusResponse", + description: + "Specialised per-metric assessment envelope (blood-pressure, pulse, weight, bmi, mood). Identical shape to the generic metric-status card so the `InsightStatusCard` consumes it unchanged. Read-only + stale-while-revalidate: a cache miss warms a generation out of band and serves the last-good text meanwhile.", + }); + +// The medication-compliance route carries a richer envelope than the +// other six: a `summary` narrative plus a per-medication `text` array, +// instead of a single `text` field. +const medicationComplianceStatusResponse = z + .object({ + hasProvider: z + .boolean() + .describe( + "False when the user has no usable AI provider — `summary` then carries the generic no-key guidance.", + ), + summary: z + .string() + .nullable() + .describe( + "The overall compliance narrative (plain text). Null while a first generation is preparing.", + ), + medications: z + .array( + z + .object({ + medicationId: z + .string() + .describe("The medication this note belongs to."), + text: z + .string() + .describe("Per-medication compliance note (plain text)."), + }) + .meta({ id: "MedicationComplianceStatusItem" }), + ) + .describe( + "Per-medication compliance notes. Empty while preparing or when no medication qualifies.", + ), + cached: z + .boolean() + .describe("True when the envelope is served from cache (incl. last-good)."), + updatedAt: z.iso + .datetime({ offset: true }) + .nullable() + .describe("When the served assessment was generated; null when none."), + preparing: z + .boolean() + .optional() + .describe( + "True when a first assessment is being generated out of band and no prior summary exists yet — the client polls until it lands.", + ), + revalidating: z + .boolean() + .optional() + .describe( + "True when the envelope is served from last-good cache (stale-while-revalidate) while a fresh generation is in flight. The client keeps polling on `preparing || revalidating` (bounded).", + ), + }) + .meta({ + id: "MedicationComplianceStatusResponse", + description: + "Medication-compliance assessment envelope. Unlike the other six specialised cards it carries a `summary` plus a per-medication `text` array rather than a single `text` field. Read-only + stale-while-revalidate.", + }); + const analyticsRangeQuery = z .object({ type: measurementTypeEnum.describe( @@ -2575,6 +2688,157 @@ export const openApiPaths: NonNullable = { }, }, }, + "/api/insights/blood-pressure-status": { + get: { + tags: ["Insights"], + summary: "Blood-pressure assessment", + description: + "Data-driven plain-language assessment of the user's recent blood-pressure readings. Read-only: a cache miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: "Assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + insightStatusResponse, + "BloodPressureStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, + "/api/insights/pulse-status": { + get: { + tags: ["Insights"], + summary: "Pulse assessment", + description: + "Data-driven plain-language assessment of the user's recent resting-pulse readings. Read-only: a cache miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: "Assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + insightStatusResponse, + "PulseStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, + "/api/insights/weight-status": { + get: { + tags: ["Insights"], + summary: "Weight assessment", + description: + "Data-driven plain-language assessment of the user's recent weight trend. Read-only: a cache miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: "Assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + insightStatusResponse, + "WeightStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, + "/api/insights/bmi-status": { + get: { + tags: ["Insights"], + summary: "BMI assessment", + description: + "Data-driven plain-language assessment of the user's body-mass index. Read-only: a cache miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: "Assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + insightStatusResponse, + "BmiStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, + "/api/insights/mood-status": { + get: { + tags: ["Insights"], + summary: "Mood assessment", + description: + "Data-driven plain-language assessment of the user's recent mood entries. Read-only: a cache miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: "Assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + insightStatusResponse, + "MoodStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, + "/api/insights/medication-compliance-status": { + get: { + tags: ["Insights"], + summary: "Medication-compliance assessment", + description: + "Data-driven plain-language assessment of the user's medication compliance — an overall `summary` plus a per-medication note array. Read-only: a cache miss warms a generation out of band and serves the last-good envelope meanwhile (stale-while-revalidate). Auth via cookie or Bearer.", + requestParams: { + query: insightStatusQuery, + }, + responses: { + "200": { + description: + "Compliance assessment envelope (fresh, cached, or preparing).", + content: { + "application/json": { + schema: dataEnvelope( + medicationComplianceStatusResponse, + "MedicationComplianceStatusResponseEnvelope", + ), + }, + }, + }, + ...stdResponses, + }, + }, + }, "/api/insights/metric-status": { get: { tags: ["Insights"],