feat(anamnesis): serve the life circumstances, record AUDIT-C, and keep both off the closed legacy lists

This commit is contained in:
Marc-André Bombeck committed 2026-10-16 00:54:38 +02:00
1 parent 1e7fc08f57
commit f2be772eaf
28 files changed
+2131 -220

No files matched your search

+31 -12
View File
@@ -64,7 +64,9 @@ import {
} from "@/lib/validations/about-me";
import {
DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
type HealthProfileAiSection,
HEALTH_PROFILE_AI_SECTIONS,
aboutMeAiSections,
type StoredHealthProfileAiSection,
} from "@/lib/validations/health-profile-facts";
const PUT_RATE_LIMIT = 30;
@@ -101,9 +103,13 @@ export const GET = apiHandler(async () => {
}),
getAiCapability("aboutMeQuestions"),
]);
const aiIncludedSections =
(row?.aiIncludedSections as HealthProfileAiSection[] | undefined) ??
DEFAULT_HEALTH_PROFILE_AI_SECTIONS;
// The eight sections this wire has always carried. The life-circumstance
// sections (v1.43) live in the same column but are switched and read
// through `/api/anamnesis/life-circumstances`; older app builds decode this
// list closed.
const aiIncludedSections = aboutMeAiSections(
row?.aiIncludedSections ?? DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
);
// A stored set may be model-written. While the capability is unavailable
// an outstanding set is swapped for the deterministic hints; an answered
// (empty) set stays empty.
@@ -201,7 +207,7 @@ export const PUT = apiHandler(async (req: Request) => {
conditionsEncrypted?: Uint8Array<ArrayBuffer> | null;
allergiesEncrypted?: Uint8Array<ArrayBuffer> | null;
coachFocusEncrypted?: Uint8Array<ArrayBuffer> | null;
aiIncludedSections?: HealthProfileAiSection[];
aiIncludedSections?: StoredHealthProfileAiSection[];
} = {};
const aboutMePayload = encryptOptional(parsed.data.aboutMe);
if (aboutMePayload !== undefined) {
@@ -219,9 +225,6 @@ export const PUT = apiHandler(async (req: Request) => {
if (coachFocusPayload !== undefined) {
update.coachFocusEncrypted = coachFocusPayload;
}
if (parsed.data.aiIncludedSections !== undefined) {
update.aiIncludedSections = parsed.data.aiIncludedSections;
}
const fieldLengths: Record<string, number> = {
...(text !== undefined ? { aboutMe: text.length } : {}),
...(parsed.data.conditions !== undefined
@@ -240,6 +243,23 @@ export const PUT = apiHandler(async (req: Request) => {
// commit while an hour-fresh advisor payload still references excluded
// profile content.
const persistence = await prisma.$transaction(async (tx) => {
// A PUT here replaces the eight sections it lists and leaves the
// life-circumstance switches (v1.43) exactly as stored.
if (parsed.data.aiIncludedSections !== undefined) {
const stored = await tx.userHealthProfile.findUnique({
where: { userId: user.id },
select: { aiIncludedSections: true },
});
update.aiIncludedSections = [
...parsed.data.aiIncludedSections,
...(stored?.aiIncludedSections ?? []).filter(
(section) =>
!(HEALTH_PROFILE_AI_SECTIONS as readonly string[]).includes(
section,
),
),
];
}
let updatedAtIso: string;
let savedProfileUpdatedAt: Date | null = null;
@@ -334,10 +354,9 @@ export const PUT = apiHandler(async (req: Request) => {
select: { aiIncludedSections: true },
}),
]);
const aiIncludedSections = (controlRow?.aiIncludedSections as
HealthProfileAiSection[] | undefined) ?? [
...DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
];
const aiIncludedSections = aboutMeAiSections(
controlRow?.aiIncludedSections ?? DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
);
const aiContext = filterSelfContextForAi(ctx, aiIncludedSections);
const aiContextIsEmpty =
aiContext.aboutMe === null &&