mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
test(anamnesis): drive the life circumstances, AUDIT-C and its backup through real Postgres and the manage matrix
This commit is contained in:
1 parent
88aa5ccacd
commit
4d2c8c2bbb
8 files changed
+718
-16
No files matched your search
@@ -16,7 +16,7 @@ import {
|
||||
correctLifeFact,
|
||||
removeHealthProfileFact,
|
||||
} from "@/lib/profile/health-facts";
|
||||
import { dayStartUtc } from "@/lib/profile/life-derived";
|
||||
import { dateOnlyAtNoonUtc } from "@/lib/tz/date-only";
|
||||
import {
|
||||
validateCorrectedFactValue,
|
||||
type HealthProfileFactKind,
|
||||
@@ -63,7 +63,7 @@ export const PATCH = apiHandler(
|
||||
// circumstances (v1.43, #614).
|
||||
const now = new Date();
|
||||
const validFrom = parsed.data.validFrom
|
||||
? dayStartUtc(parsed.data.validFrom)
|
||||
? dateOnlyAtNoonUtc(parsed.data.validFrom)
|
||||
: undefined;
|
||||
let corrected: { id: string; kind: string } | null;
|
||||
if (isLifeFactKind(current.kind)) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
createLifeFact,
|
||||
readHealthProfileFacts,
|
||||
} from "@/lib/profile/health-facts";
|
||||
import { dayStartUtc } from "@/lib/profile/life-derived";
|
||||
import { dateOnlyAtNoonUtc } from "@/lib/tz/date-only";
|
||||
import { factWriteSchema } from "@/lib/validations/life-circumstances";
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ async function postFact(request: NextRequest): Promise<Response> {
|
||||
|
||||
const now = new Date();
|
||||
const validFrom = parsed.data.validFrom
|
||||
? dayStartUtc(parsed.data.validFrom)
|
||||
? dateOnlyAtNoonUtc(parsed.data.validFrom)
|
||||
: undefined;
|
||||
try {
|
||||
const input = parsed.data;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
* Server-only — reads `@/lib/db`.
|
||||
*/
|
||||
import { prisma } from "@/lib/db";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
import { getEvent } from "@/lib/logging/context";
|
||||
import { decryptFromBytes, encryptToBytes } from "@/lib/ai/coach/bytes-codec";
|
||||
import {
|
||||
@@ -508,6 +509,8 @@ export interface LifeFactForPrompt {
|
||||
export interface AuditCForPrompt {
|
||||
totalScore: number;
|
||||
takenAt: Date;
|
||||
/** The zone the entry was taken in, for its calendar day. */
|
||||
tz: string;
|
||||
cutoff: number | null;
|
||||
aboveCutoff: boolean | null;
|
||||
}
|
||||
@@ -624,7 +627,7 @@ export function composeLifeCircumstancesText(
|
||||
lines.push(
|
||||
`${L.auditC}: ${fill(L.auditCValue, {
|
||||
score: auditC.totalScore,
|
||||
date: auditC.takenAt.toISOString().slice(0, 10),
|
||||
date: userDayKey(auditC.takenAt, auditC.tz),
|
||||
})}${verdict}`,
|
||||
);
|
||||
}
|
||||
@@ -645,12 +648,12 @@ async function loadAuditCForPrompt(
|
||||
const audit = await prisma.mentalHealthAssessment.findFirst({
|
||||
where: { userId, instrument: "AUDIT_C", deletedAt: null },
|
||||
orderBy: [{ takenAt: "desc" }, { id: "desc" }],
|
||||
select: { totalScore: true, takenAt: true, locale: true },
|
||||
select: { totalScore: true, takenAt: true, locale: true, tz: true },
|
||||
});
|
||||
if (!audit) return null;
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { gender: true, locale: true },
|
||||
select: { gender: true, locale: true, timezone: true },
|
||||
});
|
||||
const cutoff = auditCCutoff(
|
||||
audit.locale,
|
||||
@@ -660,6 +663,7 @@ async function loadAuditCForPrompt(
|
||||
return {
|
||||
totalScore: audit.totalScore,
|
||||
takenAt: audit.takenAt,
|
||||
tz: audit.tz ?? user?.timezone ?? "UTC",
|
||||
cutoff: cutoff?.value ?? null,
|
||||
aboveCutoff: cutoff ? audit.totalScore >= cutoff.value : null,
|
||||
};
|
||||
|
||||
@@ -62,11 +62,6 @@ export const LIFE_KIND_AI_SECTION: Readonly<
|
||||
export const REVIEW_AFTER_DAYS = 365;
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
|
||||
/** UTC midnight of a `YYYY-MM-DD` calendar day. */
|
||||
export function dayStartUtc(day: string): Date {
|
||||
return new Date(`${day}T00:00:00.000Z`);
|
||||
}
|
||||
|
||||
/** Whether an instant is at least `REVIEW_AFTER_DAYS` before `now`. */
|
||||
export function isReviewDue(at: Date | null, now: Date): boolean {
|
||||
return (
|
||||
|
||||
@@ -249,7 +249,7 @@ const validFromDay = z.iso
|
||||
.date()
|
||||
.optional()
|
||||
.describe(
|
||||
"The calendar day the answer holds from (UTC midnight). Omitted, it holds from now. Never later than today; an earlier day than the end of the previous answer of the same kind starts at that end instead.",
|
||||
"The calendar day the answer holds from, stored at noon UTC like every date-only value. Omitted, it holds from now. Never later than today; an earlier day than the end of the previous answer of the same kind starts at that end instead.",
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user