mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The per-metric target editor receives a glucose range already projected into the user's display unit (mmol/L) along with `unit="mmol/L"`, but it seeded, validated and saved that value verbatim. HealthLog stores glucose canonically in mg/dL, so a user on the mmol/L preference either had a valid target (e.g. 3.9–5.5 mmol/L) rejected by the 40–400 mg/dL bounds, or — when an override already existed — saw a mg/dL number rendered as if it were mmol/L and wrote it straight back. Add `mmolToMgdl` / `toCanonicalMgdl` (the inverse of `convertGlucose`) and teach the edit sheet to treat the display unit at its boundary: seed the persisted mg/dL override into the display unit, validate against bounds expressed in the display unit, and convert the typed value back to canonical mg/dL before the PUT. A no-op for every non-glucose metric and for users on the mg/dL preference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> (cherry picked from commit 0cc0361058a85f32d7135e5f2fd425c98f62d885)
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
/**
|
|
* Blood-glucose unit conversions.
|
|
*
|
|
* HealthLog stores glucose canonically in **mg/dL**. The UI can display in
|
|
* mmol/L for users who prefer that (SI-unit countries). Conversion factor is
|
|
* 18.0182 (per DGIM / DDG S3 guideline). We round to clinically meaningful
|
|
* precision: 0 fractional digits for mg/dL, 1 for mmol/L.
|
|
*/
|
|
|
|
export type GlucoseUnit = "mg/dL" | "mmol/L";
|
|
|
|
const MGDL_PER_MMOL = 18.0182;
|
|
|
|
export function mgdlToMmol(mgdl: number): number {
|
|
return Math.round((mgdl / MGDL_PER_MMOL) * 10) / 10;
|
|
}
|
|
|
|
export function mmolToMgdl(mmol: number): number {
|
|
return Math.round(mmol * MGDL_PER_MMOL);
|
|
}
|
|
|
|
export function convertGlucose(value: number, to: GlucoseUnit): number {
|
|
return to === "mmol/L" ? mgdlToMmol(value) : Math.round(value);
|
|
}
|
|
|
|
/**
|
|
* Convert a value the user typed in their display unit back to the
|
|
* canonical mg/dL HealthLog stores. The inverse of {@link convertGlucose}
|
|
* for the editor write path: a `5.5 mmol/L` target the user enters must
|
|
* persist as `99 mg/dL`, not as the literal `5.5`. A `mg/dL` display unit
|
|
* is already canonical, so it only rounds.
|
|
*/
|
|
export function toCanonicalMgdl(value: number, from: GlucoseUnit): number {
|
|
return from === "mmol/L" ? mmolToMgdl(value) : Math.round(value);
|
|
}
|
|
|
|
export function resolveGlucoseUnit(
|
|
userPreference: string | null | undefined,
|
|
): GlucoseUnit {
|
|
return userPreference === "mmol/L" ? "mmol/L" : "mg/dL";
|
|
}
|
|
|
|
/**
|
|
* Threshold metric key corresponding to a glucose context. Keeps the
|
|
* effective-range resolver the single source of truth.
|
|
*/
|
|
export function thresholdMetricForContext(
|
|
context: "FASTING" | "POSTPRANDIAL" | "RANDOM" | "BEDTIME",
|
|
):
|
|
| "BLOOD_GLUCOSE_FASTING"
|
|
| "BLOOD_GLUCOSE_POSTPRANDIAL"
|
|
| "BLOOD_GLUCOSE_RANDOM"
|
|
| "BLOOD_GLUCOSE_BEDTIME" {
|
|
switch (context) {
|
|
case "FASTING":
|
|
return "BLOOD_GLUCOSE_FASTING";
|
|
case "POSTPRANDIAL":
|
|
return "BLOOD_GLUCOSE_POSTPRANDIAL";
|
|
case "RANDOM":
|
|
return "BLOOD_GLUCOSE_RANDOM";
|
|
case "BEDTIME":
|
|
return "BLOOD_GLUCOSE_BEDTIME";
|
|
}
|
|
}
|