mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
Show the answers behind a screening score
Refs A3-8. The per-item PHQ-9 / GAD-7 / WHO-5 / SCI answers have been stored encrypted since the assessments shipped, and nothing ever read them back — the clinical detail (item 9 included) was invisible. A new by-id read decrypts them for whoever holds the mind read grant (the same grant that already serves the mood diary's prose and the item-9 flag), degrades to an honest items-unavailable state on any decrypt or shape problem, and the history rows gain an expandable breakdown rendering the official instrument texts the check-in flow already carries — nothing retranslated, no alarm styling on item 9 beyond what the house already does. Rides along: the guard inventories learn the branch's new routes (frozen counts moved with reasons), the reminder-engine test harnesses carry the ledger delegate, and the custom-metric restore toast keys sit in their right namespace in all six locales.
This commit is contained in:
1 parent
651d37b494
commit
90bad17cc8
23 files changed
+1036
-34
No files matched your search
@@ -4,11 +4,12 @@
|
||||
*
|
||||
* Part of the OpenAPI route table; aggregated in `./index.ts`. The request body
|
||||
* reuses the runtime Zod schema from `@/lib/validations/mental-health` so the
|
||||
* wire contract stays single-source. The response deliberately excludes the
|
||||
* raw item answers — only the server-authoritative total / band / safety flag
|
||||
* are exposed. The iOS client mirrors this DTO and MUST surface its own
|
||||
* crisis-resource card on a positive item-9 flag (never rely on server text
|
||||
* alone).
|
||||
* wire contract stays single-source. The list / create responses deliberately
|
||||
* exclude the raw item answers — only the server-authoritative total / band /
|
||||
* safety flag are exposed there; the per-administration detail read is the one
|
||||
* surface that returns the decrypted answers. The iOS client mirrors these
|
||||
* DTOs and MUST surface its own crisis-resource card on a positive item-9
|
||||
* flag (never rely on server text alone).
|
||||
*/
|
||||
import type { ZodOpenApiObject } from "zod-openapi";
|
||||
import { z } from "zod/v4";
|
||||
@@ -48,7 +49,19 @@ const assessmentRow = z
|
||||
.meta({
|
||||
id: "MentalHealthAssessment",
|
||||
description:
|
||||
"One completed screener administration. `severityBand` is a descriptive label for the screen (never a diagnosis). `item9Flagged` is true when the PHQ-9 self-harm item was answered > 0 — the client MUST show crisis resources when it is. Raw per-item answers are intentionally absent.",
|
||||
"One completed screener administration. `severityBand` is a descriptive label for the screen (never a diagnosis). `item9Flagged` is true when the PHQ-9 self-harm item was answered > 0 — the client MUST show crisis resources when it is. Raw per-item answers are intentionally absent from list rows; the by-id detail read returns them decrypted.",
|
||||
});
|
||||
|
||||
const assessmentDetail = assessmentRow
|
||||
.extend({
|
||||
items: z.array(z.number().int()).nullable(),
|
||||
functionalDifficulty: z.number().int().nullable(),
|
||||
itemsUnavailable: z.boolean(),
|
||||
})
|
||||
.meta({
|
||||
id: "MentalHealthAssessmentDetail",
|
||||
description:
|
||||
"One administration with its decrypted per-item answers, in the order the instrument presents its items (PHQ-9/GAD-7 answers 0–3, SCI 0–4, WHO-5 0–5). `functionalDifficulty` is the PHQ-9's unscored functional follow-up when it was answered. When the stored blob cannot be decrypted the read degrades honestly: `items` is null, `itemsUnavailable` is true, and the denormalised score fields still answer.",
|
||||
});
|
||||
|
||||
const crisisResource = z
|
||||
@@ -81,6 +94,10 @@ const listResponse = z
|
||||
.object({ assessments: z.array(assessmentRow) })
|
||||
.meta({ id: "ListMentalHealthAssessmentsResponse" });
|
||||
|
||||
const detailResponse = z
|
||||
.object({ assessment: assessmentDetail })
|
||||
.meta({ id: "GetMentalHealthAssessmentResponse" });
|
||||
|
||||
export const mentalHealthPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
"/api/mental-health/assessments": {
|
||||
get: {
|
||||
@@ -132,4 +149,28 @@ export const mentalHealthPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/mental-health/assessments/{id}": {
|
||||
get: {
|
||||
tags: ["Mental health"],
|
||||
summary: "Read one administration with its decrypted answers",
|
||||
description:
|
||||
"Returns a single screener administration including the decrypted per-item answers (and the PHQ-9's unscored functional follow-up when answered) — the one surface that reads them back. When the stored blob cannot be decrypted the read degrades to `items: null` + `itemsUnavailable: true` instead of failing; the total / band / flag still answer. A soft-deleted or foreign row answers 404.",
|
||||
requestParams: { path: z.object({ id: z.string() }) },
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
"200": {
|
||||
description: "The administration with its answer breakdown.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
detailResponse,
|
||||
"GetMentalHealthAssessmentEnvelope",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user