mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
feat(anamnesis): serve the life circumstances, record AUDIT-C, and keep both off the closed legacy lists
This commit is contained in:
1 parent
1e7fc08f57
commit
f2be772eaf
28 files changed
+2131
-220
No files matched your search
@@ -60,6 +60,7 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
|
||||
analytics: ["analytics.invalid_query"],
|
||||
anamnesis: [
|
||||
"anamnesis.auditC.invalid",
|
||||
"anamnesis.auditC.notFound",
|
||||
"anamnesis.auditC.rateLimited",
|
||||
"anamnesis.fact.conflict",
|
||||
"anamnesis.fact.currentExists",
|
||||
|
||||
@@ -13,12 +13,15 @@ import {
|
||||
} from "@/lib/validations/about-me";
|
||||
import {
|
||||
healthProfileAiSectionSchema,
|
||||
healthProfileFactCorrectionSchema,
|
||||
healthProfileFactDtoSchema,
|
||||
healthProfileFactsResponseSchema,
|
||||
healthProfileFactWriteSchema,
|
||||
removedHealthProfileFactSchema,
|
||||
} from "@/lib/validations/health-profile-facts";
|
||||
import {
|
||||
factCorrectionSchema,
|
||||
factWriteSchema,
|
||||
lifeFactRevisionSchema,
|
||||
removedFactSchema,
|
||||
} from "@/lib/validations/life-circumstances";
|
||||
import {
|
||||
emergencyProfileDtoSchema,
|
||||
emergencyProfileUpdateSchema,
|
||||
@@ -1526,7 +1529,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Insights"],
|
||||
summary: "Read effective-dated anamnesis facts",
|
||||
description:
|
||||
"Returns the current value and immutable revision history for the caller's smoking status, alcohol pattern and shift schedule. Values are decrypted server-side; unreadable ciphertext returns `value: null` with `unreadable: true`.",
|
||||
"Returns the current value and immutable revision history for the caller's smoking status, alcohol pattern and shift schedule, and only those three kinds: the life-circumstance kinds are read through `GET /api/anamnesis/life-circumstances`. Values are decrypted server-side; unreadable ciphertext returns `value: null` with `unreadable: true`.",
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
"200": {
|
||||
@@ -1547,12 +1550,12 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Insights"],
|
||||
summary: "Create an anamnesis fact",
|
||||
description:
|
||||
"Creates the first current revision for one closed fact kind. The answer is encrypted before persistence. A second current value for the same kind returns 409; corrections use the revision endpoint.",
|
||||
"Creates the first current revision for one fact kind. The answer is encrypted before persistence. The three closed kinds take their string value and answer the closed revision shape; the six life-circumstance kinds (v1.43, send only when `features.lifeCircumstances` is true) take an object with `schema: 1` and answer a `LifeFactRevision`. Optional `validFrom` (a calendar day) dates the answer back. A second current value for the same kind returns 409; corrections use the revision endpoint.",
|
||||
parameters: [idempotencyKeyParameter],
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": { schema: healthProfileFactWriteSchema },
|
||||
"application/json": { schema: factWriteSchema },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
@@ -1562,7 +1565,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
healthProfileFactDtoSchema,
|
||||
z.union([healthProfileFactDtoSchema, lifeFactRevisionSchema]),
|
||||
"CreateAnamnesisFactResponse",
|
||||
),
|
||||
},
|
||||
@@ -1578,7 +1581,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
tags: ["Insights"],
|
||||
summary: "Correct a current anamnesis fact",
|
||||
description:
|
||||
"Closes the current revision at the correction instant and creates a successor. Prior ciphertext and its half-open validity interval remain in history.",
|
||||
"Closes the current revision at the correction instant (or at the optional `validFrom` day, never earlier than the revision it replaces) and creates a successor. The value takes the stored kind's form: a string for the closed kinds, an object with `schema: 1` for the life-circumstance kinds; any other is 422 `anamnesis.fact.invalidValue`. Prior ciphertext and its half-open validity interval remain in history.",
|
||||
parameters: [
|
||||
{
|
||||
name: "id",
|
||||
@@ -1590,7 +1593,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": { schema: healthProfileFactCorrectionSchema },
|
||||
"application/json": { schema: factCorrectionSchema },
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
@@ -1599,7 +1602,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
healthProfileFactDtoSchema,
|
||||
z.union([healthProfileFactDtoSchema, lifeFactRevisionSchema]),
|
||||
"CorrectAnamnesisFactResponse",
|
||||
),
|
||||
},
|
||||
@@ -1629,7 +1632,7 @@ export const coachPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
removedHealthProfileFactSchema,
|
||||
removedFactSchema,
|
||||
"RemoveAnamnesisFactResponse",
|
||||
),
|
||||
},
|
||||
|
||||
@@ -14,8 +14,8 @@ import { z } from "zod/v4";
|
||||
import {
|
||||
AUDIT_C_CUTOFF_SOURCES,
|
||||
LIFE_AI_SECTIONS,
|
||||
LIFE_FACT_KINDS,
|
||||
LIFE_SECTION_KEYS,
|
||||
lifeFactRevisionSchema,
|
||||
auditCRequestSchema,
|
||||
lifeAiSectionsPatchSchema,
|
||||
lifeConfirmSchema,
|
||||
@@ -26,32 +26,14 @@ import {
|
||||
errorEnvelope,
|
||||
idempotencyKeyParameter,
|
||||
idempotentWrite,
|
||||
notImplementedResponse,
|
||||
recordRefusal,
|
||||
stdResponses,
|
||||
} from "./shared";
|
||||
|
||||
const lifeFactRevision = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
kind: z.enum(["SMOKING_STATUS", "SHIFT_SCHEDULE", ...LIFE_FACT_KINDS]),
|
||||
value: z
|
||||
.unknown()
|
||||
.describe(
|
||||
"`SMOKING_STATUS` and `SHIFT_SCHEDULE` carry their string value; every other kind an object with `schema: 1` whose shape is fixed per kind (see the life-circumstances contract). `null` when `unreadable`.",
|
||||
),
|
||||
unreadable: z
|
||||
.boolean()
|
||||
.describe("The sealed value could not be opened on this host."),
|
||||
validFrom: z.iso.datetime({ offset: true }),
|
||||
validUntil: z.null(),
|
||||
provenance: z.enum(["USER_REPORTED", "USER_CORRECTION"]),
|
||||
createdAt: z.iso.datetime({ offset: true }),
|
||||
})
|
||||
.meta({
|
||||
id: "LifeFactRevision",
|
||||
description: "The current revision of one life-circumstance fact.",
|
||||
});
|
||||
const lifeFactRevision = lifeFactRevisionSchema.meta({
|
||||
id: "LifeFactRevision",
|
||||
description: "The current revision of one life-circumstance fact.",
|
||||
});
|
||||
|
||||
const auditCCutoff = z.object({
|
||||
value: z.number().int(),
|
||||
@@ -210,7 +192,6 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
...notImplementedResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -249,7 +230,6 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...stdResponses,
|
||||
...notImplementedResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -279,7 +259,6 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
...notImplementedResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -295,6 +274,7 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: { "application/json": { schema: auditCRequest } },
|
||||
},
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
...idempotentWrite(),
|
||||
"200": {
|
||||
description: "A replay of an entry already stored.",
|
||||
@@ -319,7 +299,6 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
...notImplementedResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -330,6 +309,7 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
description: "Soft delete. 404 `anamnesis.auditC.notFound`.",
|
||||
requestParams: { path: z.object({ id: z.string() }) },
|
||||
responses: {
|
||||
...recordRefusal(),
|
||||
"200": {
|
||||
description: "The removed entry.",
|
||||
content: {
|
||||
@@ -349,7 +329,6 @@ export const lifeCircumstancesPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
content: { "application/json": { schema: errorEnvelope } },
|
||||
},
|
||||
...stdResponses,
|
||||
...notImplementedResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user