mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
fix(insights): plot the trends row and log fevers in the reader's units
Two more surfaces carried a fixed unit. The insights trends row labelled weight in kg and body temperature in °C for every reader, and plotted walking distance as raw metres under a km label; its trend caption said the same. A single transformed series there now takes unit, scale and offset from the preference, and the caption describes the converted series against a converted noise floor. The illness day sheet and timeline showed and took a fever in °C. The fever is now seeded, typed and shown in the reader's temperature unit and stored in °C as before. Refs #1067
This commit is contained in:
1 parent
d6f79c8bef
commit
bc2d60cce8
16 files changed
+229
-38
No files matched your search
@@ -106,13 +106,31 @@ describe("computeTrendDescriptor", () => {
|
||||
});
|
||||
|
||||
describe("numericDescriptorCopy", () => {
|
||||
it("states a preference-bound slot in the unit the caller resolves", () => {
|
||||
const d = computeTrendDescriptor(series([181.6, 178.5]), {
|
||||
absoluteFloor: 0.66,
|
||||
relativeFloor: 0.01,
|
||||
decimals: 1,
|
||||
})!;
|
||||
const copy = numericDescriptorCopy("weight", d, (type) =>
|
||||
type === "WEIGHT" ? "lb" : "?",
|
||||
);
|
||||
expect(copy?.params.unit).toBe(" lb");
|
||||
expect(TREND_SLOT_DESCRIPTOR_META.distance.unitType).toBe(
|
||||
"WALKING_RUNNING_DISTANCE",
|
||||
);
|
||||
expect(TREND_SLOT_DESCRIPTOR_META.body_temp.unitType).toBe(
|
||||
"BODY_TEMPERATURE",
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves the rising template with a signed delta and spaced unit", () => {
|
||||
const d = computeTrendDescriptor(series([120, 128]), {
|
||||
absoluteFloor: 2,
|
||||
relativeFloor: 0.02,
|
||||
decimals: 0,
|
||||
})!;
|
||||
const copy = numericDescriptorCopy("bp", d);
|
||||
const copy = numericDescriptorCopy("bp", d, () => "unused");
|
||||
expect(copy?.key).toBe("insights.trendDescriptor.rising");
|
||||
expect(copy?.params.delta).toBe("+8");
|
||||
expect(copy?.params.unit).toBe(" mmHg");
|
||||
@@ -124,7 +142,7 @@ describe("numericDescriptorCopy", () => {
|
||||
relativeFloor: 0.05,
|
||||
decimals: 0,
|
||||
})!;
|
||||
const copy = numericDescriptorCopy("steps", d);
|
||||
const copy = numericDescriptorCopy("steps", d, () => "unused");
|
||||
expect(copy?.key).toBe("insights.trendDescriptor.rising");
|
||||
expect(copy?.params.unit).toBe("");
|
||||
});
|
||||
@@ -135,14 +153,14 @@ describe("numericDescriptorCopy", () => {
|
||||
relativeFloor: 0.01,
|
||||
decimals: 1,
|
||||
})!;
|
||||
const copy = numericDescriptorCopy("weight", d);
|
||||
const copy = numericDescriptorCopy("weight", d, () => "kg");
|
||||
expect(copy?.key).toBe("insights.trendDescriptor.falling");
|
||||
expect(copy?.params.delta).toBe("−1.4");
|
||||
});
|
||||
|
||||
it("returns null for a slot with no numeric meta (mood)", () => {
|
||||
const d = computeTrendDescriptor(series([3, 4]), MOOD_DESCRIPTOR_CONFIG)!;
|
||||
expect(numericDescriptorCopy("mood", d)).toBeNull();
|
||||
expect(numericDescriptorCopy("mood", d, () => "")).toBeNull();
|
||||
});
|
||||
|
||||
it("covers every numeric slot in the descriptor meta", () => {
|
||||
@@ -154,7 +172,7 @@ describe("numericDescriptorCopy", () => {
|
||||
relativeFloor: 0,
|
||||
decimals: 1,
|
||||
})!;
|
||||
expect(numericDescriptorCopy(metric, d)).not.toBeNull();
|
||||
expect(numericDescriptorCopy(metric, d, () => "u")).not.toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,7 +51,12 @@ export interface TrendChartConfig {
|
||||
types: string[];
|
||||
/** Recharts stroke colours, positionally matched to `types`. */
|
||||
colors: string[];
|
||||
/** Display unit (chart legend / tooltip). */
|
||||
/**
|
||||
* Display unit (chart legend / tooltip) for a metric no preference
|
||||
* changes. A single-series slot whose type has a display transform
|
||||
* (weight, distance, body temperature) carries none: the renderer takes
|
||||
* the unit, scale and offset from the reader's preference instead.
|
||||
*/
|
||||
unit?: string;
|
||||
/** Y-axis unit label when it differs from the value unit. */
|
||||
yAxisUnit?: string;
|
||||
@@ -114,7 +119,6 @@ export const TREND_CHART_CONFIG: Record<
|
||||
kind: "health-chart",
|
||||
types: ["WEIGHT"],
|
||||
colors: ["var(--chart-1)"],
|
||||
unit: "kg",
|
||||
titleKey: "charts.weight",
|
||||
captionKey: "insights.trendsRow.caption.weight",
|
||||
annotationKey: "weight",
|
||||
@@ -212,8 +216,6 @@ export const TREND_CHART_CONFIG: Record<
|
||||
kind: "health-chart",
|
||||
types: ["WALKING_RUNNING_DISTANCE"],
|
||||
colors: ["var(--chart-4)"],
|
||||
unit: "km",
|
||||
yAxisUnit: "km",
|
||||
titleKey: "charts.distance",
|
||||
captionKey: "insights.trendsRow.caption.distance",
|
||||
detailHref: "/insights/walking-distance",
|
||||
@@ -234,8 +236,6 @@ export const TREND_CHART_CONFIG: Record<
|
||||
kind: "health-chart",
|
||||
types: ["BODY_TEMPERATURE"],
|
||||
colors: ["var(--destructive)"],
|
||||
unit: "°C",
|
||||
yAxisUnit: "°C",
|
||||
titleKey: "charts.bodyTemperature",
|
||||
captionKey: "insights.trendsRow.caption.body_temp",
|
||||
detailHref: "/insights/body-temperature",
|
||||
|
||||
@@ -140,8 +140,17 @@ export function computeTrendDescriptor(
|
||||
* dedicated copy rather than the numeric `{delta} {unit}` template.
|
||||
*/
|
||||
export interface TrendSlotDescriptorMeta {
|
||||
/** Display unit interpolated into the numeric descriptor template. */
|
||||
unit: string;
|
||||
/**
|
||||
* Display unit interpolated into the numeric descriptor template, for a
|
||||
* metric no preference changes.
|
||||
*/
|
||||
unit?: string;
|
||||
/**
|
||||
* For a metric whose unit follows the reader's preference: the
|
||||
* measurement type the caller resolves the unit of (and converts the
|
||||
* series and the noise floor through) before describing it.
|
||||
*/
|
||||
unitType?: string;
|
||||
config: TrendDescriptorConfig;
|
||||
}
|
||||
|
||||
@@ -154,7 +163,7 @@ export const TREND_SLOT_DESCRIPTOR_META: Record<
|
||||
config: { absoluteFloor: 2, relativeFloor: 0.02, decimals: 0 },
|
||||
},
|
||||
weight: {
|
||||
unit: "kg",
|
||||
unitType: "WEIGHT",
|
||||
config: { absoluteFloor: 0.3, relativeFloor: 0.01, decimals: 1 },
|
||||
},
|
||||
pulse: {
|
||||
@@ -186,7 +195,7 @@ export const TREND_SLOT_DESCRIPTOR_META: Record<
|
||||
config: { absoluteFloor: 1, relativeFloor: 0.1, decimals: 0 },
|
||||
},
|
||||
distance: {
|
||||
unit: "km",
|
||||
unitType: "WALKING_RUNNING_DISTANCE",
|
||||
config: { absoluteFloor: 0.3, relativeFloor: 0.05, decimals: 1 },
|
||||
},
|
||||
vo2_max: {
|
||||
@@ -194,7 +203,7 @@ export const TREND_SLOT_DESCRIPTOR_META: Record<
|
||||
config: { absoluteFloor: 0.5, relativeFloor: 0.02, decimals: 1 },
|
||||
},
|
||||
body_temp: {
|
||||
unit: "°C",
|
||||
unitType: "BODY_TEMPERATURE",
|
||||
config: { absoluteFloor: 0.2, relativeFloor: 0.005, decimals: 1 },
|
||||
},
|
||||
};
|
||||
@@ -231,9 +240,12 @@ export interface TrendDescriptorCopy {
|
||||
export function numericDescriptorCopy(
|
||||
metric: string,
|
||||
descriptor: TrendDescriptor,
|
||||
/** Resolves a `unitType` slot's unit under the reader's preference. */
|
||||
unitFor: (type: string) => string,
|
||||
): TrendDescriptorCopy | null {
|
||||
const meta = TREND_SLOT_DESCRIPTOR_META[metric];
|
||||
if (!meta) return null;
|
||||
const unit = meta.unitType ? unitFor(meta.unitType) : (meta.unit ?? "");
|
||||
|
||||
// Signed, formatted magnitude (e.g. "+8", "-1.4"). `stable` still
|
||||
// carries its small delta so the caption can read "stable (±0 …)" if a
|
||||
@@ -255,7 +267,7 @@ export function numericDescriptorCopy(
|
||||
// reads "({delta}{unit})" → "(+8 mmHg)" with a unit, "(+1200)"
|
||||
// without one — no dangling space inside the parens for unit-less
|
||||
// metrics (steps, flights).
|
||||
unit: meta.unit ? ` ${meta.unit}` : "",
|
||||
unit: unit ? ` ${unit}` : "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user