mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(insights): keep chronic conditions out of the symptom series fill
The symptom-severity series fills healthy days with zero across each illness episode's span. A chronic condition has no end, so its span covered the whole window and the correlation engine read a near-constant run of zeros. Only episodes that can put the account into Rest Mode (`REST_MODE_EPISODE_WHERE`) carry a span now.
This commit is contained in:
1 parent
ac86199808
commit
9ffcdea312
2 files changed
+86
-3
No files matched your search
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* A chronic condition does not zero-fill the symptom-severity series.
|
||||
*
|
||||
* The series fills healthy days with 0 across an episode's span. A chronic
|
||||
* condition has no end, so its span covered the whole window and the engine
|
||||
* correlated a near-constant run of zeros. Only episodes that can put the
|
||||
* account into Rest Mode carry a span; the fake store below honours the
|
||||
* lifecycle filter the way Postgres would.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type Episode = {
|
||||
id: string;
|
||||
lifecycle: string;
|
||||
onsetAt: Date;
|
||||
resolvedAt: Date | null;
|
||||
};
|
||||
const store: { episodes: Episode[] } = { episodes: [] };
|
||||
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: {
|
||||
illnessEpisode: {
|
||||
findMany: vi.fn(
|
||||
async (args: { where: { lifecycle?: { not: string } } }) =>
|
||||
store.episodes.filter(
|
||||
(e) =>
|
||||
args.where.lifecycle === undefined ||
|
||||
e.lifecycle !== args.where.lifecycle.not,
|
||||
),
|
||||
),
|
||||
},
|
||||
illnessDayLog: { findMany: vi.fn(async () => []) },
|
||||
},
|
||||
}));
|
||||
|
||||
import { fetchSymptomSeries } from "../correlation-channel-series";
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000;
|
||||
const since = new Date(Date.now() - 60 * DAY);
|
||||
|
||||
beforeEach(() => {
|
||||
store.episodes = [];
|
||||
});
|
||||
|
||||
describe("fetchSymptomSeries", () => {
|
||||
it("has no series for a chronic condition alone", async () => {
|
||||
store.episodes = [
|
||||
{
|
||||
id: "chronic",
|
||||
lifecycle: "CHRONIC_ONGOING",
|
||||
onsetAt: new Date(Date.now() - 900 * DAY),
|
||||
resolvedAt: null,
|
||||
},
|
||||
];
|
||||
const series = await fetchSymptomSeries("user-1", "UTC", since);
|
||||
expect(series.points).toEqual([]);
|
||||
});
|
||||
|
||||
it("fills only the acute episode's span beside a chronic one", async () => {
|
||||
store.episodes = [
|
||||
{
|
||||
id: "chronic",
|
||||
lifecycle: "CHRONIC_ONGOING",
|
||||
onsetAt: new Date(Date.now() - 900 * DAY),
|
||||
resolvedAt: null,
|
||||
},
|
||||
{
|
||||
id: "cold",
|
||||
lifecycle: "ACUTE",
|
||||
onsetAt: new Date(Date.now() - 20 * DAY),
|
||||
resolvedAt: new Date(Date.now() - 14 * DAY),
|
||||
},
|
||||
];
|
||||
const series = await fetchSymptomSeries("user-1", "UTC", since);
|
||||
expect(series.points.length).toBeGreaterThanOrEqual(6);
|
||||
expect(series.points.length).toBeLessThanOrEqual(8);
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@
|
||||
* fabricating a constant.
|
||||
*/
|
||||
import { prisma } from "@/lib/db";
|
||||
import { REST_MODE_EPISODE_WHERE } from "@/lib/illness/rest-mode";
|
||||
import {
|
||||
buildComplianceMedicationContext,
|
||||
COURSES_COMPLIANCE_SELECT,
|
||||
@@ -152,9 +153,9 @@ export async function fetchComplianceSeries(
|
||||
* v1.21.0 (FDREXTEND) — build the user's SYMPTOM_SEVERITY daily series in the
|
||||
* `outcome` role (callers that need the behaviour role re-tag the returned
|
||||
* series — the points are role-invariant). Reads every in-window illness episode
|
||||
* + its day-logs; the builder zero-fills healthy days ONLY across real episode
|
||||
* spans, so a user with no episodes yields an empty series that degrades to
|
||||
* absent.
|
||||
* that is not a chronic condition + its day-logs; the builder zero-fills healthy
|
||||
* days ONLY across those spans, so a user with no such episode yields an empty
|
||||
* series that degrades to absent.
|
||||
*/
|
||||
export async function fetchSymptomSeries(
|
||||
userId: string,
|
||||
@@ -165,11 +166,15 @@ export async function fetchSymptomSeries(
|
||||
const episodes = await prisma.illnessEpisode.findMany({
|
||||
// An episode overlaps the window when it onset before `now` and either is
|
||||
// ongoing or resolved at/after the window start.
|
||||
// A chronic condition has no end, so its span would zero-fill every day
|
||||
// of the window and hand the engine a near-constant series. Only the
|
||||
// episodes that can put the account into Rest Mode carry a span.
|
||||
where: {
|
||||
userId,
|
||||
deletedAt: null,
|
||||
onsetAt: { lte: now },
|
||||
OR: [{ resolvedAt: null }, { resolvedAt: { gte: since } }],
|
||||
...REST_MODE_EPISODE_WHERE,
|
||||
},
|
||||
select: { id: true, onsetAt: true, resolvedAt: true },
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user