mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
fix(compliance): carry endedAt into the streak and per-window timelines
calculateCompliance and buildTimelineForWindow built the cadence engine's context field by field and left out endedAt, so a dose slot after End course counted as missed in the streak and the timeline while the rate left it out. Both now take the context from one builder, endedAt is required on ComplianceMedicationContext and CadenceEngineContext, and the course anchor guard holds every builder of either type to naming it. Refs #1280
This commit is contained in:
1 parent
8fd1d543a6
commit
8b66f647cb
9 files changed
+217
-35
No files matched your search
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
|
||||
"count": 351,
|
||||
"total": 113619,
|
||||
"count": 350,
|
||||
"total": 113407,
|
||||
"entries": {
|
||||
"app/achievements/page.tsx :: AchievementsPage": 206,
|
||||
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
|
||||
@@ -257,7 +257,6 @@
|
||||
"lib/ai/local-client.ts :: generateCompletionStream": 294,
|
||||
"lib/ai/openai-client.ts :: generateCompletion": 335,
|
||||
"lib/analytics/bp-in-target-fast-path.ts :: computeFromRollups": 203,
|
||||
"lib/analytics/compliance/display.ts :: calculateCompliance": 212,
|
||||
"lib/analytics/correlations-fast-path.ts :: computeCorrelationHypothesesFastPath": 256,
|
||||
"lib/analytics/intraday-pulse-io.ts :: loadIntradayPulse": 206,
|
||||
"lib/analytics/score/reader.ts :: computeUserHealthScore": 324,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
|
||||
"count": 2897,
|
||||
"total": 9272,
|
||||
"total": 9270,
|
||||
"entries": {
|
||||
"__tests__/account-payload-consumer-guard.test.ts": 2,
|
||||
"__tests__/acting-account-boundary-guard.test.ts": 7,
|
||||
@@ -1828,7 +1828,7 @@
|
||||
"lib/analytics/compliance/__tests__/schedule-compliance-select.test.ts": 1,
|
||||
"lib/analytics/compliance/adapters.ts": 6,
|
||||
"lib/analytics/compliance/cycle.ts": 5,
|
||||
"lib/analytics/compliance/display.ts": 18,
|
||||
"lib/analytics/compliance/display.ts": 16,
|
||||
"lib/analytics/compliance/dose-status.ts": 4,
|
||||
"lib/analytics/compliance/ledger.ts": 7,
|
||||
"lib/analytics/compliance/occurrences.ts": 9,
|
||||
|
||||
@@ -21,6 +21,14 @@
|
||||
* when its block names `endedAt` or spreads a window that carries it
|
||||
* (`...window` from `mintWindow`, `...medicationWindow(row)`).
|
||||
*
|
||||
* The compliance side builds its context in two more shapes,
|
||||
* `ComplianceMedicationContext` and the `CadenceEngineContext` the timelines
|
||||
* hand the engine, and both dropped `endedAt` once: the streak and the
|
||||
* per-window timeline copied the context field by field and counted the dose
|
||||
* after an ended course as missed. Both types now require the field, and the
|
||||
* last leg holds every builder of either to naming it, and the timelines to
|
||||
* the one builder.
|
||||
*
|
||||
* Limit, stated: a literal passed straight to the engine without the
|
||||
* `RecurrenceContext` annotation is invisible here. Every medication path
|
||||
* today goes through one of the annotated builders below.
|
||||
@@ -29,7 +37,7 @@ import { readdirSync } from "node:fs";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readSourceText } from "./helpers/source-files";
|
||||
import { readSource, readSourceText } from "./helpers/source-files";
|
||||
|
||||
const SRC = resolve(__dirname, "..");
|
||||
const ROOT = resolve(SRC, "..");
|
||||
@@ -142,3 +150,53 @@ describe("the rolling course anchor is opt-in", () => {
|
||||
expect(declaring).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
/** A typed literal or a typed return of either compliance context. */
|
||||
const COMPLIANCE_CONTEXT_DECLARATION =
|
||||
/(\)\s*:\s*(?:CadenceEngineContext|ComplianceMedicationContext)\s*\{|:\s*(?:CadenceEngineContext|ComplianceMedicationContext)(?:\s*\|\s*undefined)?\s*=\s*[^;{]*\{)/g;
|
||||
|
||||
function complianceContextBuilders(): Array<{ file: string; block: string }> {
|
||||
const out: Array<{ file: string; block: string }> = [];
|
||||
for (const file of sourceFiles(SRC)) {
|
||||
const text = readSourceText(file);
|
||||
for (const m of text.matchAll(COMPLIANCE_CONTEXT_DECLARATION)) {
|
||||
const open = m.index! + m[0].length - 1;
|
||||
out.push({ file: relative(ROOT, file), block: blockAt(text, open) });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe("the compliance contexts carry the instant a course was ended", () => {
|
||||
it("builds the engine's context in one place, which names endedAt", () => {
|
||||
const found = complianceContextBuilders();
|
||||
// The two builders: the medication context and the engine's view of it.
|
||||
expect(found.map((b) => b.file).sort()).toEqual([
|
||||
"src/lib/analytics/compliance/display.ts",
|
||||
"src/lib/analytics/compliance/types.ts",
|
||||
]);
|
||||
for (const b of found) {
|
||||
expect(b.block, b.file).toMatch(/\bendedAt\s*:/);
|
||||
}
|
||||
// The matcher sees each shape the timelines used to write.
|
||||
const shapes = [
|
||||
"const e: CadenceEngineContext = { a: 1 };",
|
||||
"const e: CadenceEngineContext | undefined = ctx ? { a: 1 } : undefined;",
|
||||
"function f(): ComplianceMedicationContext {\n return {};\n}",
|
||||
];
|
||||
for (const shape of shapes) {
|
||||
expect([...shape.matchAll(COMPLIANCE_CONTEXT_DECLARATION)]).toHaveLength(
|
||||
1,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("has both timelines take the engine context from that builder", () => {
|
||||
const display = readSource("src/lib/analytics/compliance/display.ts", [
|
||||
"function cadenceEngineContextOf(",
|
||||
"function buildTimelineForWindow(",
|
||||
]);
|
||||
const uses = display.match(/cadenceEngineContextOf\(ctx\)/g) ?? [];
|
||||
expect(uses.length).toBe(2);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user