Fix decision-engine override behavior: sleep/pms return GENTLE per spec
Some checks failed
CI / quality (push) Failing after 28s
Deploy / deploy (push) Successful in 1m40s

The spec (decision-engine.md lines 93-94) clearly states:
- sleep override -> GENTLE
- pms override -> GENTLE

But the implementation was returning REST for all overrides. This fix:
- Updates decision-engine.ts to use OVERRIDE_DECISIONS with correct status/reason/icon per override type
- Updates tests to expect GENTLE for sleep and pms overrides
- Aligns implementation with specification

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-12 23:11:47 +00:00
parent 262c28d9bd
commit 0ea8e2f2b5
3 changed files with 36 additions and 14 deletions

View File

@@ -128,7 +128,7 @@ describe("getTrainingDecision (algorithmic rules)", () => {
});
describe("getDecisionWithOverrides", () => {
describe("override types force REST", () => {
describe("override types force appropriate decisions", () => {
it("flare override forces REST", () => {
const data = createHealthyData();
const overrides: OverrideType[] = ["flare"];
@@ -145,20 +145,20 @@ describe("getDecisionWithOverrides", () => {
expect(result.reason).toContain("stress");
});
it("sleep override forces REST", () => {
it("sleep override forces GENTLE (per spec)", () => {
const data = createHealthyData();
const overrides: OverrideType[] = ["sleep"];
const result = getDecisionWithOverrides(data, overrides);
expect(result.status).toBe("REST");
expect(result.status).toBe("GENTLE");
expect(result.reason).toContain("sleep");
});
it("pms override forces REST", () => {
it("pms override forces GENTLE (per spec)", () => {
const data = createHealthyData();
const overrides: OverrideType[] = ["pms"];
const result = getDecisionWithOverrides(data, overrides);
expect(result.status).toBe("REST");
expect(result.reason).toContain("pms");
expect(result.status).toBe("GENTLE");
expect(result.reason).toContain("PMS");
});
});

View File

@@ -6,11 +6,31 @@ import type { DailyData, Decision, OverrideType } from "@/types";
// Override priority order - checked before algorithmic rules
const OVERRIDE_PRIORITY: OverrideType[] = ["flare", "stress", "sleep", "pms"];
const OVERRIDE_REASONS: Record<OverrideType, string> = {
flare: "Hashimoto's flare - rest required",
stress: "High stress override - rest required",
sleep: "Poor sleep override - rest required",
pms: "pms override - rest required",
// Override decisions per spec: flare/stress -> REST, sleep/pms -> GENTLE
const OVERRIDE_DECISIONS: Record<
OverrideType,
{ status: "REST" | "GENTLE"; reason: string; icon: string }
> = {
flare: {
status: "REST",
reason: "Hashimoto's flare - rest required",
icon: "🛑",
},
stress: {
status: "REST",
reason: "High stress override - rest required",
icon: "🛑",
},
sleep: {
status: "GENTLE",
reason: "Poor sleep override - gentle activity only",
icon: "🟡",
},
pms: {
status: "GENTLE",
reason: "PMS override - gentle activity only",
icon: "🟡",
},
};
export function getTrainingDecision(data: DailyData): Decision {
@@ -81,10 +101,11 @@ export function getDecisionWithOverrides(
// Check overrides first, in priority order: flare > stress > sleep > pms
for (const override of OVERRIDE_PRIORITY) {
if (overrides.includes(override)) {
const overrideDecision = OVERRIDE_DECISIONS[override];
const decision: Decision = {
status: "REST",
reason: OVERRIDE_REASONS[override],
icon: "🛑",
status: overrideDecision.status,
reason: overrideDecision.reason,
icon: overrideDecision.icon,
};
decisionEngineCallsTotal.inc({ decision: decision.status });
return decision;