import { describe, it, expect } from "vitest"; import { getEffectiveRange, getAllEffectiveRanges, METRIC_BOUNDS, resolveWeightTargetOverride, resolveEffectiveBpTargets, type UserProfileForRange, type ThresholdOverridesJson, } from "../effective-range"; const baseProfile: UserProfileForRange = { heightCm: 180, dateOfBirth: new Date("1985-01-01"), gender: "MALE", }; describe("getEffectiveRange", () => { it("returns computed default when no override is set", () => { const result = getEffectiveRange("WEIGHT", baseProfile, null); expect(result.isOverride).toBe(false); expect(result.range).not.toBeNull(); // BMI 18.5..24.9 at 1.80m ≈ 59.94..80.68 expect(result.range!.greenMin).toBeCloseTo(59.94, 1); expect(result.range!.greenMax).toBeCloseTo(80.68, 1); }); it("returns null range when a prerequisite profile field is missing", () => { const result = getEffectiveRange( "WEIGHT", { heightCm: null, dateOfBirth: null, gender: null }, null, ); expect(result.range).toBeNull(); expect(result.default).toBeNull(); }); it("uses override when present", () => { const overrides: ThresholdOverridesJson = { WEIGHT: { min: 70, max: 85 }, }; const result = getEffectiveRange("WEIGHT", baseProfile, overrides); expect(result.isOverride).toBe(true); expect(result.range!.greenMin).toBe(70); expect(result.range!.greenMax).toBe(85); // Orange wings should be non-degenerate expect(result.range!.orangeMin).toBeLessThan(70); expect(result.range!.orangeMax).toBeGreaterThan(85); }); it("returns the pristine default alongside the override", () => { const overrides: ThresholdOverridesJson = { WEIGHT: { min: 70, max: 85 }, }; const result = getEffectiveRange("WEIGHT", baseProfile, overrides); expect(result.default).not.toBeNull(); expect(result.default!.greenMin).toBeCloseTo(59.94, 1); }); it("has bounds for every supported metric", () => { for (const metric of Object.keys(METRIC_BOUNDS) as Array< keyof typeof METRIC_BOUNDS >) { const result = getEffectiveRange(metric, baseProfile, null); expect(result.bounds).toEqual(METRIC_BOUNDS[metric]); } }); it("has sensible blood glucose defaults in fasting context", () => { const result = getEffectiveRange( "BLOOD_GLUCOSE_FASTING", baseProfile, null, ); expect(result.range!.greenMin).toBe(70); expect(result.range!.greenMax).toBe(99); expect(result.range!.orangeMax).toBe(125); // pre-diabetes upper }); it("has different defaults for postprandial vs fasting", () => { const fasting = getEffectiveRange( "BLOOD_GLUCOSE_FASTING", baseProfile, null, ); const post = getEffectiveRange( "BLOOD_GLUCOSE_POSTPRANDIAL", baseProfile, null, ); expect(post.range!.greenMax).toBeGreaterThan(fasting.range!.greenMax); }); }); // The resolver narrows the stored three-value sex to the two a sex-split // reference table has rows for, and OTHER / "no answer" must land on the // neutral band rather than on either side. Nothing pinned that before, so a // resolver that quietly stopped reading the profile's sex — or read it and // picked a default side — went unnoticed. describe("sex-split defaults", () => { const withGender = (gender: string | null) => getEffectiveRange("BODY_FAT", { ...baseProfile, gender }, null).range!; it("gives each recorded side its own body-fat band", () => { expect(withGender("MALE").greenMin).toBe(14); expect(withGender("MALE").greenMax).toBe(24); expect(withGender("FEMALE").greenMin).toBe(21); expect(withGender("FEMALE").greenMax).toBe(31); }); it("gives OTHER and an unanswered profile the same neutral band", () => { const neutral = { greenMin: 17, greenMax: 27 }; expect(withGender("OTHER")).toMatchObject(neutral); expect(withGender(null)).toMatchObject(neutral); }); }); describe("getAllEffectiveRanges", () => { it("returns an entry for every metric", () => { const ranges = getAllEffectiveRanges(baseProfile, null); const keys = Object.keys(ranges); expect(keys).toContain("WEIGHT"); expect(keys).toContain("BLOOD_GLUCOSE_FASTING"); expect(keys).toContain("TOTAL_BODY_WATER"); expect(keys).toContain("BONE_MASS"); expect(keys).toContain("OXYGEN_SATURATION"); expect(keys.length).toBeGreaterThanOrEqual(14); }); }); describe("resolveEffectiveBpTargets", () => { it("merges the user's systolic and diastolic target overrides", () => { expect( resolveEffectiveBpTargets(baseProfile, { BLOOD_PRESSURE_SYS: { min: 115, max: 125 }, BLOOD_PRESSURE_DIA: { min: 65, max: 75 }, }), ).toEqual({ sysLow: 115, sysHigh: 125, diaLow: 65, diaHigh: 75, }); }); it("uses the evidence default for an axis without an override", () => { expect( resolveEffectiveBpTargets(baseProfile, { BLOOD_PRESSURE_SYS: { min: 115, max: 125 }, }), ).toEqual({ sysLow: 115, sysHigh: 125, diaLow: 70, diaHigh: 79, }); }); it("rejects malformed target pairs without losing valid defaults", () => { expect( resolveEffectiveBpTargets(baseProfile, { BLOOD_PRESSURE_SYS: { min: 130, max: 120 }, }), ).toEqual({ sysLow: 120, sysHigh: 129, diaLow: 70, diaHigh: 79, }); }); }); // Audit-2026-05-07 / phase P0 / closes audit C-15: TOTAL_BODY_WATER and // BONE_MASS lacked threshold definitions. Severity logic returned `nominal` // for any value, so users would see "all healthy" regardless of input. describe("body composition thresholds", () => { it("TOTAL_BODY_WATER has a sensible non-null green band", () => { const result = getEffectiveRange("TOTAL_BODY_WATER", baseProfile, null); expect(result.range).not.toBeNull(); expect(result.range!.greenMin).toBeGreaterThan(0); expect(result.range!.greenMax).toBeGreaterThan(result.range!.greenMin); expect(result.range!.orangeMin).toBeLessThan(result.range!.greenMin); expect(result.range!.orangeMax).toBeGreaterThan(result.range!.greenMax); }); it("BONE_MASS has a sensible non-null green band", () => { const result = getEffectiveRange("BONE_MASS", baseProfile, null); expect(result.range).not.toBeNull(); expect(result.range!.greenMin).toBeGreaterThan(0); expect(result.range!.greenMax).toBeGreaterThan(result.range!.greenMin); }); it("respects user override for body water", () => { const overrides: ThresholdOverridesJson = { TOTAL_BODY_WATER: { min: 30, max: 45 }, }; const result = getEffectiveRange( "TOTAL_BODY_WATER", baseProfile, overrides, ); expect(result.isOverride).toBe(true); expect(result.range!.greenMin).toBe(30); expect(result.range!.greenMax).toBe(45); }); it("METRIC_BOUNDS for body composition match VALUE_RANGES in validation", () => { expect(METRIC_BOUNDS.TOTAL_BODY_WATER).toEqual({ min: 5, max: 100, unit: "kg", }); expect(METRIC_BOUNDS.BONE_MASS).toEqual({ min: 0.5, max: 8, unit: "kg" }); }); }); // Audit-2026-05-07 / v1.3.3: SpO2 (pulse oximetry) added as a single-value // metric with lower-only severity (saturation cannot exceed 100% by definition). // Defaults follow BTS Guideline 2017 (target 94–98%) and ATS clinical practice // (≥95% at rest is healthy). Below 92% triggers orange band; below 88% in // real-world clinical literature is the ER threshold. describe("OXYGEN_SATURATION thresholds", () => { it("has a non-null green band centred on the BTS healthy range", () => { const result = getEffectiveRange("OXYGEN_SATURATION", baseProfile, null); expect(result.range).not.toBeNull(); expect(result.range!.greenMin).toBe(95); expect(result.range!.greenMax).toBe(100); }); it("collapses upper orange wing to greenMax (no upper concern)", () => { const result = getEffectiveRange("OXYGEN_SATURATION", baseProfile, null); expect(result.range!.orangeMax).toBe(result.range!.greenMax); }); it("respects user override (e.g. COPD baseline 88-92)", () => { const overrides: ThresholdOverridesJson = { OXYGEN_SATURATION: { min: 88, max: 92 }, }; const result = getEffectiveRange( "OXYGEN_SATURATION", baseProfile, overrides, ); expect(result.isOverride).toBe(true); expect(result.range!.greenMin).toBe(88); expect(result.range!.greenMax).toBe(92); }); it("clamps override orangeMax to physiological 100% (no impossible saturations)", () => { // Without the bounds clamp this used to emit orangeMax = 100.75. const overrides: ThresholdOverridesJson = { OXYGEN_SATURATION: { min: 95, max: 100 }, }; const result = getEffectiveRange( "OXYGEN_SATURATION", baseProfile, overrides, ); expect(result.range!.orangeMax).toBeLessThanOrEqual(100); expect(result.range!.orangeMin).toBeGreaterThanOrEqual(50); }); it("METRIC_BOUNDS plausibility floor is 50% (incompatible-with-life below)", () => { expect(METRIC_BOUNDS.OXYGEN_SATURATION).toEqual({ min: 50, max: 100, unit: "%", }); }); }); /** * v1.34 — `User.thresholdsJson` is untyped JSON and v1.34 gave the weight * target three new consumers at once (the Health Score, the dashboard bands, * the PR direction). One narrowing beats three hand-rolled casts, and a * malformed blob must read as "no target", never as a target of NaN. */ describe("resolveWeightTargetOverride", () => { it("reads a well-formed weight override", () => { expect( resolveWeightTargetOverride({ WEIGHT: { min: 74, max: 78 } }), ).toEqual({ min: 74, max: 78 }); }); it("returns null for a blob with no weight entry", () => { expect(resolveWeightTargetOverride(null)).toBeNull(); expect(resolveWeightTargetOverride({})).toBeNull(); expect( resolveWeightTargetOverride({ BLOOD_PRESSURE_SYS: { min: 1, max: 2 } }), ).toBeNull(); }); it("rejects a malformed pair instead of passing it through", () => { expect(resolveWeightTargetOverride({ WEIGHT: { min: 74 } })).toBeNull(); expect( resolveWeightTargetOverride({ WEIGHT: { min: "74", max: "78" } }), ).toBeNull(); expect( resolveWeightTargetOverride({ WEIGHT: { min: NaN, max: 78 } }), ).toBeNull(); // An inverted or collapsed band is not a band. expect( resolveWeightTargetOverride({ WEIGHT: { min: 80, max: 70 } }), ).toBeNull(); expect( resolveWeightTargetOverride({ WEIGHT: { min: 75, max: 75 } }), ).toBeNull(); }); it("survives a non-object blob", () => { expect(resolveWeightTargetOverride("nope")).toBeNull(); expect(resolveWeightTargetOverride(42)).toBeNull(); expect(resolveWeightTargetOverride(undefined)).toBeNull(); }); });