mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
`hasEntryUnitConversion` and `canonicalValueToEntry` shipped beside the hours-to-minutes conversion for an editing surface that does not exist and has no ticket. Neither has a caller, so both were only two more things to keep true, and the dead-code gate is right to say so. The module comment claimed the inverse was there so a future surface could not land on a different answer. It says what is actually true now: one direction, one place. The git history keeps the rest if that surface ever arrives.
68 lines
3.0 KiB
TypeScript
68 lines
3.0 KiB
TypeScript
/**
|
|
* Where the entry form asks for one unit and the column stores another.
|
|
*
|
|
* This is not the metric/imperial display transform in `display-transform.ts`:
|
|
* that one follows a preference and is the identity for anyone on metric. This
|
|
* one holds regardless of preference, because the storage unit was chosen for
|
|
* the sync sources rather than for the person typing.
|
|
*
|
|
* Sleep is the case. `SLEEP_DURATION` is stored in minutes — every provider
|
|
* delivers minutes, and a per-stage segment is only a few of them — while a
|
|
* person logging a night by hand thinks in hours and the field says so. The
|
|
* form sent the typed number straight through, so a night entered as 7.5 was
|
|
* stored as seven and a half MINUTES and read back on the list as eight
|
|
* minutes of sleep. Nothing rejected it: seven and a half minutes is inside
|
|
* the column's plausibility band, because a single sleep STAGE really can be
|
|
* that short.
|
|
*
|
|
* The storage unit stays minutes. The conversion belongs at the entry
|
|
* boundary, in one place, so a second surface that starts taking sleep by hand
|
|
* has one answer to reach for rather than its own.
|
|
*
|
|
* Only the direction that exists is here. An inverse and a "does this type
|
|
* convert" predicate shipped alongside it at first, written for an editing
|
|
* surface that does not exist and has no ticket; they were nothing but two
|
|
* more things to keep true. The git history holds them if that surface ever
|
|
* arrives.
|
|
*/
|
|
|
|
export const MINUTES_PER_HOUR = 60;
|
|
|
|
/**
|
|
* Entry unit → canonical unit multiplier, per measurement type. A type absent
|
|
* from this table enters in the unit it is stored in.
|
|
*/
|
|
const ENTRY_TO_CANONICAL_FACTOR: Readonly<Record<string, number>> = {
|
|
SLEEP_DURATION: MINUTES_PER_HOUR,
|
|
};
|
|
|
|
/**
|
|
* Convert a value as typed in the entry form to the unit the column stores.
|
|
*
|
|
* The result is rounded to the nearest whole canonical unit for a converted
|
|
* type: 7.3 hours is 438 minutes, and floating-point multiplication would
|
|
* otherwise store 437.99999999999994 for it. An unconverted type is returned
|
|
* untouched, so no existing metric changes shape.
|
|
*/
|
|
export function entryValueToCanonical(type: string, entered: number): number {
|
|
const factor = ENTRY_TO_CANONICAL_FACTOR[type];
|
|
if (factor === undefined || !Number.isFinite(entered)) return entered;
|
|
return Math.round(entered * factor);
|
|
}
|
|
|
|
/**
|
|
* Read a number out of an entry field, accepting the decimal comma.
|
|
*
|
|
* Most of the application's readers are German, and "7,5" is how they write
|
|
* seven and a half. `parseFloat` reads that as 7 and discards the rest without
|
|
* complaint — the worst shape of wrong, since the entry looks accepted.
|
|
* Returns `null` for anything that is not a number, so the caller decides what
|
|
* an empty field means rather than inheriting a `NaN`.
|
|
*/
|
|
export function parseDecimalEntry(raw: string): number | null {
|
|
const trimmed = raw.trim().replace(",", ".");
|
|
if (trimmed === "") return null;
|
|
const parsed = Number(trimmed);
|
|
return Number.isFinite(parsed) ? parsed : null;
|
|
}
|