mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
fix(visits): cap the window before a visit at the longest report
The window before a booked visit started at the last matching visit however far back it lay, while the health-report export covers 730 days and fell back to its 90-day default for anything longer. The card then offered a report the export did not build. The window now stops at the export's limit, read from one shared constant, and preparation.capped says when it did; the web says "The last two years" for it. The export clamps an over-long explicit range to the last 730 days before its end instead of falling back to 90 days, so a custom range or a linked window cannot shrink silently either, and its day count forgives the hour a clock change adds, so two years of local days count as 730.
This commit is contained in:
1 parent
f39598f2e7
commit
da3d29424d
22 files changed
+282
-32
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
import { REPORT_MAX_RANGE_DAYS } from "@/lib/doctor-report-range";
|
||||
import { daysBetweenDateKeys, shiftDateKey } from "@/lib/tz/format";
|
||||
import {
|
||||
PREPARATION_DEFAULT_DAYS,
|
||||
resolvePreparation,
|
||||
@@ -76,6 +78,7 @@ describe("the window before a booked visit", () => {
|
||||
practitionerName: "Skin practice",
|
||||
specialty: "Dermatology",
|
||||
},
|
||||
capped: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,6 +106,7 @@ describe("the window before a booked visit", () => {
|
||||
to: "2026-10-23",
|
||||
basis: "default",
|
||||
since: null,
|
||||
capped: false,
|
||||
});
|
||||
expect(PREPARATION_DEFAULT_DAYS).toBe(90);
|
||||
});
|
||||
@@ -152,6 +156,35 @@ describe("the window before a booked visit", () => {
|
||||
expect(prep.since?.id).toBe("derm-a");
|
||||
});
|
||||
|
||||
it("caps a window older than a report covers, still naming its visit", () => {
|
||||
const derm = (id: string, at: string) =>
|
||||
visit(id, at, {
|
||||
kind: "SPECIALIST",
|
||||
practitionerId: "p-derm",
|
||||
name: "Skin practice",
|
||||
specialty: "Dermatology",
|
||||
});
|
||||
const prep = resolve(next, [derm("old", "2023-04-03T09:00:00.000Z")]);
|
||||
expect(prep).toMatchObject({
|
||||
from: shiftDateKey("2026-10-23", -(REPORT_MAX_RANGE_DAYS - 1)),
|
||||
to: "2026-10-23",
|
||||
basis: "practitioner",
|
||||
capped: true,
|
||||
since: { id: "old", occurredAt: "2023-04-03T09:00:00.000Z" },
|
||||
});
|
||||
expect(daysBetweenDateKeys(prep.from, prep.to) + 1).toBe(
|
||||
REPORT_MAX_RANGE_DAYS,
|
||||
);
|
||||
// One day inside the limit is left as it is.
|
||||
const edge = shiftDateKey("2026-10-23", -(REPORT_MAX_RANGE_DAYS - 1));
|
||||
expect(
|
||||
resolve(next, [derm("edge", `${edge}T09:00:00.000Z`)]),
|
||||
).toMatchObject({ from: edge, capped: false });
|
||||
expect(
|
||||
resolve(next, [derm("over", `${shiftDateKey(edge, -1)}T09:00:00.000Z`)]),
|
||||
).toMatchObject({ from: edge, capped: true });
|
||||
});
|
||||
|
||||
it("only counts visits before the booked one", () => {
|
||||
const later = visit("later", "2026-10-24T09:00:00.000Z", {
|
||||
practitionerId: "p-derm",
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
* is no better a start than the default.
|
||||
*
|
||||
* The window starts on the matched visit's own day: a dose changed at that
|
||||
* visit belongs to what the next one should know.
|
||||
* visit belongs to what the next one should know. It covers at most
|
||||
* {@link REPORT_MAX_RANGE_DAYS} days, the longest report the export
|
||||
* produces: a matched visit further back moves `from` to the first day of
|
||||
* that stretch and sets `capped`, so the report offered for the visit is the
|
||||
* one the export builds.
|
||||
*
|
||||
* Resolved on the server, published on the visit list, never recomputed by a
|
||||
* client. A person can still change both dates on the report.
|
||||
@@ -34,7 +38,8 @@
|
||||
* Pure and client-safe; the read lives in `preparation-read.ts`.
|
||||
*/
|
||||
import type { EncounterKind } from "@/generated/prisma/client";
|
||||
import { shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
import { REPORT_MAX_RANGE_DAYS } from "@/lib/doctor-report-range";
|
||||
import { daysBetweenDateKeys, shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
|
||||
/** The window when no earlier visit matches the booked one. */
|
||||
export const PREPARATION_DEFAULT_DAYS = 90;
|
||||
@@ -72,8 +77,17 @@ export interface EncounterPreparationDTO {
|
||||
to: string;
|
||||
/** Which rule chose `from`. */
|
||||
basis: PreparationBasis;
|
||||
/** The visit `from` is the day of; null on `default`. */
|
||||
/**
|
||||
* The visit the window is measured from; null on `default`. `from` is its
|
||||
* day unless `capped`.
|
||||
*/
|
||||
since: PreparationAnchorDTO | null;
|
||||
/**
|
||||
* True when `since` lies further back than a report covers and `from` was
|
||||
* moved to the first day of the last {@link REPORT_MAX_RANGE_DAYS} days
|
||||
* before `to`.
|
||||
*/
|
||||
capped: boolean;
|
||||
}
|
||||
|
||||
/** The fields the rule reads, off a booked visit or an earlier one. */
|
||||
@@ -140,10 +154,14 @@ export function resolvePreparation(args: {
|
||||
for (const [basis, matches] of rules) {
|
||||
const hit = earlier.find(matches);
|
||||
if (!hit) continue;
|
||||
const day = userDayKey(hit.occurredAt, tz);
|
||||
// Both end days count: `from` to `to` spans at most the report's days.
|
||||
const capped = daysBetweenDateKeys(day, to) > REPORT_MAX_RANGE_DAYS - 1;
|
||||
return {
|
||||
from: userDayKey(hit.occurredAt, tz),
|
||||
from: capped ? shiftDateKey(to, -(REPORT_MAX_RANGE_DAYS - 1)) : day,
|
||||
to,
|
||||
basis,
|
||||
capped,
|
||||
since: {
|
||||
id: hit.id,
|
||||
occurredAt: hit.occurredAt.toISOString(),
|
||||
@@ -158,5 +176,6 @@ export function resolvePreparation(args: {
|
||||
to,
|
||||
basis: "default",
|
||||
since: null,
|
||||
capped: false,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user