fix(anamnesis): one newest AUDIT-C entry, scored against the wording offered

The stored language is the wording the read route offers the account;
a request naming another wording, or a takenAt more than five minutes
ahead, is refused. Latest, offerDue, the Coach block and the doctor
report all read the newest entry whose answers open.

The old one-word alcohol pattern gives way to an AUDIT-C entry in the
Coach block, and the alcohol section lists it with a remove action.
This commit is contained in:
Marc-André Bombeck committed 2026-10-16 05:36:42 +02:00
1 parent e9a0b3d959
commit b99b70127d
17 files changed
+515 -87

No files matched your search

@@ -396,6 +396,79 @@ describe("AUDIT-C", () => {
});
});
it("scores against the wording offered and refuses another one or a future date", async () => {
// An English account is shown the English wording; a German cut-off for
// it would be the stricter S3 table applied to questions never asked.
const man = await makeUser({ locale: "en", gender: "MALE" });
await signIn(man.id);
const wrong = await postAuditC({
items: [2, 1, 1],
locale: "de",
source: "IOS",
});
expect(wrong.status).toBe(422);
const { result } = await data<{
result: { locale: string; cutoff: { source: string } | null };
}>(await postAuditC({ items: [2, 1, 1], source: "IOS" }), 201);
expect(result.locale).toBe("en");
expect(result.cutoff?.source).toBe("BRADLEY_2007");
// French has no validated wording, so the English one is offered and
// stored, whatever the account language says.
const french = await makeUser({ locale: "fr", gender: "MALE" });
await signIn(french.id);
const fr = await data<{ result: { locale: string } }>(
await postAuditC({ items: [0, 0, 1], locale: "fr", source: "WEB" }),
201,
);
expect(fr.result.locale).toBe("en");
const future = await postAuditC({
items: [0, 0, 0],
source: "WEB",
takenAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
});
expect(future.status).toBe(422);
const skew = await postAuditC({
items: [0, 0, 0],
source: "WEB",
takenAt: new Date(Date.now() + 60 * 1000).toISOString(),
});
expect(skew.status).toBe(201);
});
it("reads latest and the offer from the newest entry whose answers open", async () => {
const user = await makeUser();
await signIn(user.id);
const older = await data<{ result: { id: string } }>(
await postAuditC({
items: [1, 0, 0],
source: "WEB",
takenAt: "2025-01-10T09:00:00.000Z",
}),
201,
);
// A newer entry this host cannot open (its key is gone).
const sealed = await getPrismaClient().mentalHealthAssessment.create({
data: {
userId: user.id,
instrument: "AUDIT_C",
locale: "en",
responsesEncrypted: new Uint8Array([9, 9, 9, 9]),
totalScore: 11,
severityBand: "unbanded",
takenAt: new Date(),
source: "WEB",
},
});
const life = await readLife();
expect(life.auditC.latest?.id).toBe(older.result.id);
// Timed from the result shown, not from the entry nobody can read.
expect(life.auditC.offerDue).toBe(true);
// Still listed, so it can be removed.
expect(life.auditC.history.map((h) => h.id)).toContain(sealed.id);
});
it("soft-deletes an entry once, and only an AUDIT-C entry of the record", async () => {
const user = await makeUser();
await signIn(user.id);