mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
feat(coach): name a read past the lookback limit on the trail
A read whose readings all lie past the lookback limit was reported on its step as outside_window, and its fetch entry carried no reason, so a client could not tell an empty window from data the limit keeps out. The step now carries reason outside_reach and the fetch entry mirrors it. The method line and the web trail keep the window sentence; the answer names the limit as before.
This commit is contained in:
1 parent
2e28d4ed4c
commit
f39598f2e7
17 files changed
+212
-33
No files matched your search
@@ -46,6 +46,8 @@ export interface DialogWorld {
|
||||
resultsWithheld: boolean;
|
||||
/** The provider's rounds, consumed in order. */
|
||||
script: ScriptRound[];
|
||||
/** The stored `User.coachPrefsJson`; null is the defaults. */
|
||||
coachPrefs: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export const world: DialogWorld = {
|
||||
@@ -56,6 +58,7 @@ export const world: DialogWorld = {
|
||||
storedProvenance: {},
|
||||
resultsWithheld: false,
|
||||
script: [],
|
||||
coachPrefs: null,
|
||||
};
|
||||
|
||||
/** Everything the scripted provider was sent and answered, in order. */
|
||||
@@ -412,6 +415,7 @@ export function resetDialog(): void {
|
||||
world.storedProvenance = {};
|
||||
world.resultsWithheld = false;
|
||||
world.script = [];
|
||||
world.coachPrefs = null;
|
||||
providerCalls.length = 0;
|
||||
messageSeq = 0;
|
||||
for (const fn of Object.values(m)) fn.mockReset();
|
||||
@@ -465,7 +469,7 @@ export const modules = {
|
||||
prisma: {
|
||||
user: {
|
||||
findUnique: async () => ({
|
||||
coachPrefsJson: null,
|
||||
coachPrefsJson: world.coachPrefs,
|
||||
displayName: null,
|
||||
aiResponseTimeoutSeconds: null,
|
||||
}),
|
||||
|
||||
@@ -171,6 +171,62 @@ describe("the live trail", () => {
|
||||
expect(interim).toBeLessThan(firstToken);
|
||||
});
|
||||
|
||||
it("names a read past the lookback limit on its step and its fetch entry", async () => {
|
||||
world.coachPrefs = { defaultWindow: "last7days" };
|
||||
world.script = [
|
||||
{ calls: [{ name: "get_day", args: { date: "2026-06-01" } }] },
|
||||
{ text: "That day lies beyond the lookback limit you set." },
|
||||
];
|
||||
const { status, frames } = await h.postTurn(post, {
|
||||
message: "What happened on 1 June?",
|
||||
locale: "en",
|
||||
});
|
||||
expect(status).toBe(200);
|
||||
const step = framesOf<{ step: { id: string; status: string } }>(
|
||||
frames,
|
||||
"step",
|
||||
)
|
||||
.map((f) => f.step)
|
||||
.at(-1);
|
||||
expect(step).toMatchObject({
|
||||
tool: "get_day",
|
||||
status: "empty",
|
||||
reason: "outside_reach",
|
||||
});
|
||||
const fetch = framesOf<{ activity: CoachActivity }>(frames, "activity")
|
||||
.map((f) => f.activity)
|
||||
.filter((a) => a.phase === "fetch")
|
||||
.at(-1);
|
||||
expect(fetch).toMatchObject({
|
||||
status: "empty",
|
||||
stepRef: step?.id,
|
||||
reason: "outside_reach",
|
||||
});
|
||||
});
|
||||
|
||||
it("names no reason on a fetch inside the limit", async () => {
|
||||
world.script = [
|
||||
{
|
||||
calls: [
|
||||
{
|
||||
name: "get_metric_table",
|
||||
args: { metric: "bp", window: "last30days" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ text: "Steady. result:r1" },
|
||||
];
|
||||
const { frames } = await h.postTurn(post, {
|
||||
message: "How was my blood pressure?",
|
||||
locale: "en",
|
||||
});
|
||||
const fetches = framesOf<{ activity: CoachActivity }>(frames, "activity")
|
||||
.map((f) => f.activity)
|
||||
.filter((a) => a.phase === "fetch");
|
||||
expect(fetches.length).toBeGreaterThan(0);
|
||||
for (const entry of fetches) expect(entry).not.toHaveProperty("reason");
|
||||
});
|
||||
|
||||
it("persists the structure on the provenance and the text in the trail", async () => {
|
||||
world.script = [
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user