health.dose-weight-based@1.0.0
impl/typescript.ts
3,533 bytes · the TypeScript implementation · view raw
import { type WeightBasedDose } from "./health_dose_weight_based_types.ts";
function checkRange(name: string, value: number, low: number, high: number): void {
if (!Number.isInteger(value) || value < low || value > high) {
throw new RangeError(`${name} must be a whole number from ${low} to ${high}, received ${value}`);
}
}
/** num / den rounded half-up, for num >= 0 and den > 0. */
function halfUp(num: bigint, den: bigint): bigint {
return (2n * num + den) / (2n * den);
}
/** num / den rounded half away from zero, for den > 0. */
function halfAwayFromZero(num: bigint, den: bigint): bigint {
const magnitude = halfUp(num < 0n ? -num : num, den);
return num < 0n ? -magnitude : magnitude;
}
/**
* A weight-based dose, capped at the maximum single dose and turned into a
* volume the measuring device can show. Every quantity is a whole number of
* micrograms, microlitres or grams and every step is an exact fraction, so a
* 0.1 mL step never drifts to 0.30000000000000004 mL. The volume is the
* nearest whole number of increments, a tie going down, and never one that
* would deliver more than the maximum single dose.
*/
export function weightBasedDose(
weightGrams: number,
dosePerKgMicrograms: number,
maxSingleDoseMicrograms: number | null,
strengthMicrograms: number,
strengthVolumeMicrolitres: number,
measuringIncrementMicrolitres: number,
): WeightBasedDose {
checkRange("weightGrams", weightGrams, 200, 500000);
checkRange("dosePerKgMicrograms", dosePerKgMicrograms, 1, 1000000);
if (maxSingleDoseMicrograms !== null) checkRange("maxSingleDoseMicrograms", maxSingleDoseMicrograms, 1, 100000000);
checkRange("strengthMicrograms", strengthMicrograms, 1, 100000000);
checkRange("strengthVolumeMicrolitres", strengthVolumeMicrolitres, 1, 1000000);
checkRange("measuringIncrementMicrolitres", measuringIncrementMicrolitres, 1, 100000);
const perKg = BigInt(weightGrams) * BigInt(dosePerKgMicrograms); // micrograms × 1000
const max = maxSingleDoseMicrograms === null ? null : BigInt(maxSingleDoseMicrograms);
const capped = max !== null && perKg > max * 1000n;
// The target dose as the exact fraction targetNum / targetDen micrograms.
const targetNum = capped ? (max as bigint) : perKg;
const targetDen = capped ? 1n : 1000n;
const strength = BigInt(strengthMicrograms);
const volume = BigInt(strengthVolumeMicrolitres);
const increment = BigInt(measuringIncrementMicrolitres);
// Increments needed: target × volume / (strength × increment), nearest, a tie down.
const stepsNum = targetNum * volume;
const stepsDen = targetDen * strength * increment;
let steps = stepsNum / stepsDen;
if (2n * (stepsNum % stepsDen) > stepsDen) steps += 1n;
// Rounding up to the nearest step must not take the dose over the maximum.
if (max !== null && steps * increment * strength > max * volume) steps -= 1n;
if (steps === 0n) {
throw new RangeError("dose is less than one measuring increment; use a more dilute product or a finer measure");
}
const deliveredNum = steps * increment * strength; // micrograms × volume
return {
calculatedMicrograms: Number(halfUp(perKg, 1000n)),
cappedAtMaximum: capped,
targetMicrograms: Number(halfUp(targetNum, targetDen)),
volumeMicrolitres: Number(steps * increment),
deliveredMicrograms: Number(halfUp(deliveredNum, volume)),
differenceBasisPoints: Number(
halfAwayFromZero((deliveredNum * targetDen - targetNum * volume) * 10000n, targetNum * volume),
),
};
}