fix(coach): keep GLP-1 dose changes before the lookback limit out

The GLP-1 block sent every dose change with its decrypted note whatever
the lookback limit said. Dose changes are entries, so only those inside
the limit reach the prompt; the dose in force stays as the current dose.
This commit is contained in:
Marc-André Bombeck committed 2026-10-05 08:34:27 +02:00
1 parent 308e154978
commit 8dc34c7691
2 files changed
+73 -4

No files matched your search

@@ -332,3 +332,66 @@ describe("buildGlp1SnapshotBlock injection days in the user's zone", () => {
});
});
});
describe("buildGlp1SnapshotBlock under the lookback limit", () => {
it("leaves dose changes before the limit out and keeps the current dose", async () => {
const now = new Date("2026-10-04T12:00:00.000Z");
prismaMock.medication.findMany.mockResolvedValue([
fakeMedication({
doseChanges: [
{
doseValue: 2.5,
doseUnit: "mg",
effectiveFrom: new Date("2026-06-01T00:00:00.000Z"),
note: "Nausea in the first week",
},
{
doseValue: 5,
doseUnit: "mg",
effectiveFrom: new Date("2026-08-01T00:00:00.000Z"),
note: "Stepped up after review",
},
],
}),
]);
const out = await buildGlp1SnapshotBlock("user-1", now, "UTC", {
window: "last30days",
days: 30,
});
const med = out?.medications[0];
// The current dose is therapy state, not history.
expect(med?.currentDose).toMatchObject({ value: 5, since: "2026-08-01" });
// Both changes lie before the 30-day floor: neither they nor their
// notes reach the prompt.
expect(med?.doseHistory).toEqual([]);
expect(JSON.stringify(out)).not.toContain("Nausea");
expect(JSON.stringify(out)).not.toContain("Stepped up");
});
it("keeps the dose changes inside the limit", async () => {
const now = new Date("2026-10-04T12:00:00.000Z");
prismaMock.medication.findMany.mockResolvedValue([
fakeMedication({
doseChanges: [
{
doseValue: 5,
doseUnit: "mg",
effectiveFrom: new Date("2026-08-01T00:00:00.000Z"),
note: null,
},
{
doseValue: 7.5,
doseUnit: "mg",
effectiveFrom: new Date("2026-09-20T00:00:00.000Z"),
note: null,
},
],
}),
]);
const out = await buildGlp1SnapshotBlock("user-1", now, "UTC", {
window: "last30days",
days: 30,
});
expect(out?.medications[0].doseHistory.map((d) => d.value)).toEqual([7.5]);
});
});
+10 -4
View File
@@ -225,9 +225,10 @@ export async function buildGlp1SnapshotBlock(
now: Date = new Date(),
tz: string = DEFAULT_TIMEZONE,
/**
* The Coach's lookback limit. The therapy plan (dose, titration, pens) is
* current state and stays; the last injection and the side-effect tags are
* entries and are read from inside the limit only.
* The Coach's lookback limit. The therapy plan (current dose, schedule,
* pens) is current state and stays; the last injection, the side-effect
* tags and the dose history (each change with its note) are entries and
* are read from inside the limit only.
*/
reach: CoachHistoryReach = UNBOUNDED_REACH,
): Promise<Glp1SnapshotBlock | null> {
@@ -309,7 +310,12 @@ export async function buildGlp1SnapshotBlock(
const genericSafe = sanitizeForPrompt(generic, 80);
const latestChange = med.doseChanges[med.doseChanges.length - 1] ?? null;
const doseHistory: DoseHistoryEntry[] = med.doseChanges.map((dc) => {
// A dose change is an entry like any other: one made before the limit
// is left out with its note. The dose in force stays as `currentDose`.
const changesInReach = med.doseChanges.filter(
(dc) => floor === null || dc.effectiveFrom.getTime() >= floor.getTime(),
);
const doseHistory: DoseHistoryEntry[] = changesInReach.map((dc) => {
const decryptedNote = readNote(dc.noteEncrypted, dc.note);
return {
value: dc.doseValue,