mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(jobs): hand the reaction line its new datum in the reader's units
The reaction line read the arrived weight with its stored unit (kg) and a workout distance in metres, whatever the reader had chosen, so the sentence written about the new reading could quote kilograms to a reader on pounds. The evidence now converts the reading and the distance into the reader's units before the prompt is built. Refs #1067
This commit is contained in:
1 parent
7cb1b5ba05
commit
43be8ebd43
2 files changed
+107
-8
No files matched your search
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* The datum a reaction line reacts to is handed to the model in the reader's
|
||||
* units, so the line cannot quote kilograms to a reader on pounds.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const measurementFindMany = vi.fn();
|
||||
const workoutFindFirst = vi.fn();
|
||||
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: {
|
||||
measurement: {
|
||||
findMany: (...a: unknown[]) => measurementFindMany(...a),
|
||||
},
|
||||
workout: { findFirst: (...a: unknown[]) => workoutFindFirst(...a) },
|
||||
},
|
||||
}));
|
||||
|
||||
import { loadArrivalEvidence } from "../reaction-line";
|
||||
|
||||
const row = { occurredAt: new Date("2026-09-30T07:00:00Z"), refId: "w1" };
|
||||
const imperial = {
|
||||
timezone: "UTC",
|
||||
sourcePriorityJson: null,
|
||||
unitPreference: "imperial",
|
||||
glucoseUnit: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
measurementFindMany.mockReset();
|
||||
workoutFindFirst.mockReset();
|
||||
});
|
||||
|
||||
describe("loadArrivalEvidence — the reader's units", () => {
|
||||
it("states a new weight in pounds for an imperial reader", async () => {
|
||||
measurementFindMany.mockResolvedValue([{ type: "WEIGHT", value: 80 }]);
|
||||
const evidence = await loadArrivalEvidence(
|
||||
{ userId: "u1", kind: "weight", localDate: "2026-09-30", revision: "r" },
|
||||
row,
|
||||
imperial,
|
||||
);
|
||||
expect(evidence).toBe("- Newly arrived reading: WEIGHT 176.4 lb.");
|
||||
});
|
||||
|
||||
it("states blood pressure in mmHg for every reader", async () => {
|
||||
measurementFindMany.mockResolvedValue([
|
||||
{ type: "BLOOD_PRESSURE_DIA", value: 80 },
|
||||
{ type: "BLOOD_PRESSURE_SYS", value: 125 },
|
||||
]);
|
||||
const evidence = await loadArrivalEvidence(
|
||||
{
|
||||
userId: "u1",
|
||||
kind: "blood_pressure",
|
||||
localDate: "2026-09-30",
|
||||
revision: "r",
|
||||
},
|
||||
row,
|
||||
imperial,
|
||||
);
|
||||
expect(evidence).toContain("BLOOD_PRESSURE_SYS 125 mmHg.");
|
||||
});
|
||||
|
||||
it("states a workout distance in miles, not metres", async () => {
|
||||
measurementFindMany.mockResolvedValue([]);
|
||||
workoutFindFirst.mockResolvedValue({
|
||||
sportType: "running",
|
||||
durationSec: 1800,
|
||||
totalDistanceM: 5000,
|
||||
totalEnergyKcal: 300,
|
||||
avgHeartRate: 150,
|
||||
});
|
||||
const evidence = await loadArrivalEvidence(
|
||||
{ userId: "u1", kind: "workout", localDate: "2026-09-30", revision: "r" },
|
||||
row,
|
||||
imperial,
|
||||
);
|
||||
expect(evidence).toContain("distance 3.11 mi;");
|
||||
expect(evidence).not.toContain("metres");
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,12 @@
|
||||
* The deterministic digest adds only the same computed context rendered next
|
||||
* to the reaction.
|
||||
*/
|
||||
import {
|
||||
applyDisplayTransform,
|
||||
getQuantityTransform,
|
||||
getReadingTransform,
|
||||
resolveUnitPreferences,
|
||||
} from "@/lib/measurements/display-transform";
|
||||
import type { Job } from "pg-boss";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
@@ -140,11 +146,19 @@ export function sanitiseReactionLine(
|
||||
* intentionally a narrow, labels-and-numbers-only projection: no notes or
|
||||
* other free text can enter the provider prompt through this path.
|
||||
*/
|
||||
async function loadArrivalEvidence(
|
||||
export async function loadArrivalEvidence(
|
||||
job: ReactionLineJob,
|
||||
row: { occurredAt: Date; refId: string | null },
|
||||
user: { timezone: string; sourcePriorityJson: unknown },
|
||||
user: {
|
||||
timezone: string;
|
||||
sourcePriorityJson: unknown;
|
||||
unitPreference: string | null;
|
||||
glucoseUnit: string | null;
|
||||
},
|
||||
): Promise<string> {
|
||||
// The line is written in the reader's units, so the new datum it reacts to
|
||||
// is handed over in them.
|
||||
const units = resolveUnitPreferences(user);
|
||||
if (job.kind === "weight" || job.kind === "blood_pressure") {
|
||||
const types =
|
||||
job.kind === "weight"
|
||||
@@ -157,15 +171,15 @@ async function loadArrivalEvidence(
|
||||
measuredAt: row.occurredAt,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: { type: true, value: true, unit: true },
|
||||
select: { type: true, value: true },
|
||||
orderBy: { type: "asc" },
|
||||
});
|
||||
if (readings.length > 0) {
|
||||
return readings
|
||||
.map(
|
||||
(reading) =>
|
||||
`- Newly arrived reading: ${reading.type} ${reading.value} ${reading.unit}.`,
|
||||
)
|
||||
.map((reading) => {
|
||||
const transform = getReadingTransform(reading.type, units);
|
||||
return `- Newly arrived reading: ${reading.type} ${applyDisplayTransform(reading.value, transform)} ${transform.displayUnit}.`;
|
||||
})
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
@@ -241,7 +255,12 @@ async function loadArrivalEvidence(
|
||||
},
|
||||
});
|
||||
if (workout) {
|
||||
return `- Newly arrived workout: ${workout.sportType}; duration ${workout.durationSec} seconds; distance ${workout.totalDistanceM ?? "not reported"} metres; energy ${workout.totalEnergyKcal ?? "not reported"} kcal; average heart rate ${workout.avgHeartRate ?? "not reported"} bpm.`;
|
||||
const distance = getQuantityTransform("distance", units.system);
|
||||
const distanceText =
|
||||
workout.totalDistanceM == null
|
||||
? "not reported"
|
||||
: `${applyDisplayTransform(workout.totalDistanceM, distance)} ${distance.displayUnit}`;
|
||||
return `- Newly arrived workout: ${workout.sportType}; duration ${workout.durationSec} seconds; distance ${distanceText}; energy ${workout.totalEnergyKcal ?? "not reported"} kcal; average heart rate ${workout.avgHeartRate ?? "not reported"} bpm.`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user