// Personal targets the check engine trusts (H3). A target outside these ranges
// is almost always a typing slip, 30 for 3.0, and would silence alerts: the
// targets API refuses it, and the check ignores one saved before this rule
// (and says so). Ranges are in the test's catalogue unit. Signed off with the
// rules on 15 October 2026, unchanged since the review pack (src/domain/checks/signoff.ts).
import { findLabTest, normalizeLabValue } from './catalog.js';
export interface TargetRange { min: number; max: number }
export const PLAUSIBLE_TARGETS: Readonly<Record<string, { low?: TargetRange; high?: TargetRange }>> = {
inr: { low: { min: 1.5, max: 3.5 }, high: { min: 2, max: 4.5 } },
};
/**
* Results the check engine will act on (N16). One outside these limits is
* almost always a typing slip (a potassium of 55 for 5.5) and is named as not
* looking right instead of raising an alarm. In the catalogue unit. Signed
* off with the rules on 15 October 2026, unchanged since the review pack.
*/
export const PLAUSIBLE_RESULTS: Readonly<Record<string, TargetRange>> = {
potassium: { min: 1.5, max: 10 },
inr: { min: 0.5, max: 20 },
egfr: { min: 1, max: 200 },
};
/** Whether a result is outside the limits the check engine trusts. */
export function implausibleResult(code: string, value: number): boolean {
const range = PLAUSIBLE_RESULTS[code];
return Boolean(range && (value < range.min || value > range.max));
}
export type TargetProblem =
| { kind: 'out_of_range'; field: 'low' | 'high'; min: number; max: number }
| { kind: 'low_above_high'; field: 'low' };
/** Why a personal target can't be trusted, or null when it can (or the test has no ranges set). */
export function targetProblem(code: string, target: { low: number | null; high: number | null; unit?: string | null }): TargetProblem | null {
const ranges = PLAUSIBLE_TARGETS[code];
if (!ranges) return null;
const inCatalogueUnit = (value: number) => normalizeLabValue(code, value, target.unit ?? null)?.value ?? value;
for (const field of ['low', 'high'] as const) {
const value = target[field];
const range = ranges[field];
if (value === null || value === undefined || !range) continue;
const comparable = inCatalogueUnit(value);
if (comparable < range.min || comparable > range.max) return { kind: 'out_of_range', field, min: range.min, max: range.max };
}
if (target.low !== null && target.high !== null && target.low > target.high) return { kind: 'low_above_high', field: 'low' };
return null;
}
/** "The INR target high must be between 2 and 4.5." */
export function targetProblemMessage(code: string, problem: TargetProblem): string {
const name = findLabTest(code)?.name ?? code;
return problem.kind === 'out_of_range'
? `The ${name} target ${problem.field} must be between ${problem.min} and ${problem.max}`
: 'low must not be above high';
}