mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(day): show several readings of a type as their mean in the day's tiles
This commit is contained in:
1 parent
6ff24cf2cf
commit
88836452eb
13 files changed
+111
-11
No files matched your search
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 Eintrag",
|
||||
"countEntriesFew": "{count} Einträge",
|
||||
"countEntriesOther": "{count} Einträge",
|
||||
"meanOfReadingsOne": "Schnitt aus 1 Messung",
|
||||
"meanOfReadingsFew": "Schnitt aus {count} Messungen",
|
||||
"meanOfReadingsOther": "Schnitt aus {count} Messungen",
|
||||
"sinceLastVisit": {
|
||||
"title": "Seit dem letzten Besuch am {date}",
|
||||
"countOne": "1 Eintrag",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 entry",
|
||||
"countEntriesFew": "{count} entries",
|
||||
"countEntriesOther": "{count} entries",
|
||||
"meanOfReadingsOne": "Average of 1 reading",
|
||||
"meanOfReadingsFew": "Average of {count} readings",
|
||||
"meanOfReadingsOther": "Average of {count} readings",
|
||||
"sinceLastVisit": {
|
||||
"title": "Since the last visit on {date}",
|
||||
"countOne": "1 entry",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 entrada",
|
||||
"countEntriesFew": "{count} entradas",
|
||||
"countEntriesOther": "{count} entradas",
|
||||
"meanOfReadingsOne": "Media de 1 medición",
|
||||
"meanOfReadingsFew": "Media de {count} mediciones",
|
||||
"meanOfReadingsOther": "Media de {count} mediciones",
|
||||
"sinceLastVisit": {
|
||||
"title": "Desde la última visita el {date}",
|
||||
"countOne": "1 entrada",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 entrée",
|
||||
"countEntriesFew": "{count} entrées",
|
||||
"countEntriesOther": "{count} entrées",
|
||||
"meanOfReadingsOne": "Moyenne de 1 mesure",
|
||||
"meanOfReadingsFew": "Moyenne de {count} mesures",
|
||||
"meanOfReadingsOther": "Moyenne de {count} mesures",
|
||||
"sinceLastVisit": {
|
||||
"title": "Depuis la dernière visite le {date}",
|
||||
"countOne": "1 entrée",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 voce",
|
||||
"countEntriesFew": "{count} voci",
|
||||
"countEntriesOther": "{count} voci",
|
||||
"meanOfReadingsOne": "Media di 1 misurazione",
|
||||
"meanOfReadingsFew": "Media di {count} misurazioni",
|
||||
"meanOfReadingsOther": "Media di {count} misurazioni",
|
||||
"sinceLastVisit": {
|
||||
"title": "Dall’ultima visita del {date}",
|
||||
"countOne": "1 voce",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "기록 1개",
|
||||
"countEntriesFew": "기록 {count}개",
|
||||
"countEntriesOther": "기록 {count}개",
|
||||
"meanOfReadingsOne": "측정 1회 평균",
|
||||
"meanOfReadingsFew": "측정 {count}회 평균",
|
||||
"meanOfReadingsOther": "측정 {count}회 평균",
|
||||
"sinceLastVisit": {
|
||||
"title": "{date} 마지막 방문 이후",
|
||||
"countOne": "기록 1개",
|
||||
|
||||
@@ -12260,6 +12260,9 @@
|
||||
"countEntriesOne": "1 wpis",
|
||||
"countEntriesFew": "{count} wpisy",
|
||||
"countEntriesOther": "{count} wpisów",
|
||||
"meanOfReadingsOne": "Średnia z 1 pomiaru",
|
||||
"meanOfReadingsFew": "Średnia z {count} pomiarów",
|
||||
"meanOfReadingsOther": "Średnia z {count} pomiarów",
|
||||
"sinceLastVisit": {
|
||||
"title": "Od ostatniej wizyty {date}",
|
||||
"countOne": "1 wpis",
|
||||
|
||||
@@ -114,6 +114,10 @@ const TS_READERS: Record<string, Entry> = {
|
||||
via: "lib/ai/coach/snapshot-series.ts",
|
||||
passes: /buildDailyValueRows\([^)]*"PULSE"/,
|
||||
},
|
||||
"components/day/day-values-model.ts": {
|
||||
exempt:
|
||||
"means several readings of a type the day route sends one by one (weight, blood pressure, glucose); pulse arrives as one server-side day value (`dayValueShape` in lib/day/values.ts)",
|
||||
},
|
||||
"components/charts/health-chart.tsx": {
|
||||
exempt:
|
||||
"a rolling average over the points the chart draws: display smoothing, not a stated figure",
|
||||
|
||||
@@ -8,12 +8,12 @@ import {
|
||||
numberLinePositions,
|
||||
} from "../day-values-model";
|
||||
|
||||
function value(type: string, v = 1): DayValue {
|
||||
function value(type: string, v = 1, at = "2026-01-03T07:12:00.000Z"): DayValue {
|
||||
return {
|
||||
type,
|
||||
value: v,
|
||||
unit: "x",
|
||||
at: "2026-01-03T07:12:00.000Z",
|
||||
at,
|
||||
source: "MANUAL",
|
||||
band: null,
|
||||
};
|
||||
@@ -30,6 +30,30 @@ describe("curateDayValues", () => {
|
||||
expect(curated[0]!.values.map((v) => v.value)).toEqual([138, 88]);
|
||||
});
|
||||
|
||||
it("shows two blood pressure readings of a day as their mean, not four numbers", () => {
|
||||
const { curated } = curateDayValues([
|
||||
value("BLOOD_PRESSURE_SYS", 127, "2026-01-03T07:00:00.000Z"),
|
||||
value("BLOOD_PRESSURE_DIA", 86, "2026-01-03T07:00:00.000Z"),
|
||||
value("BLOOD_PRESSURE_SYS", 135, "2026-01-03T19:00:00.000Z"),
|
||||
value("BLOOD_PRESSURE_DIA", 87, "2026-01-03T19:00:00.000Z"),
|
||||
]);
|
||||
expect(curated).toHaveLength(1);
|
||||
const tile = curated[0]!;
|
||||
expect(tile.values.map((v) => v.type)).toEqual([
|
||||
"BLOOD_PRESSURE_SYS",
|
||||
"BLOOD_PRESSURE_DIA",
|
||||
]);
|
||||
expect(tile.values.map((v) => v.value)).toEqual([131, 86.5]);
|
||||
expect(tile.values[0]!.at).toBe("2026-01-03T19:00:00.000Z");
|
||||
expect(tile.readings).toBe(2);
|
||||
});
|
||||
|
||||
it("keeps a single reading as it is", () => {
|
||||
const { curated } = curateDayValues([value("WEIGHT", 80.4)]);
|
||||
expect(curated[0]!.values.map((v) => v.value)).toEqual([80.4]);
|
||||
expect(curated[0]!.readings).toBe(1);
|
||||
});
|
||||
|
||||
it("shows eight tiles in reading order and keeps the rest for All", () => {
|
||||
const types = [
|
||||
"WALKING_SPEED",
|
||||
|
||||
@@ -34,6 +34,7 @@ function Probe({
|
||||
const shown = formatTile({
|
||||
key: type,
|
||||
values: [{ type, value, unit, at: "", source: "", band: null }],
|
||||
readings: 1,
|
||||
});
|
||||
return (
|
||||
<span>
|
||||
|
||||
@@ -302,7 +302,7 @@ function ValueTile({
|
||||
tile: DayValueTile;
|
||||
selected: boolean;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
const { t, tCount } = useTranslations();
|
||||
const { formatTile } = useDayValueFormat();
|
||||
const shown = formatTile(tile);
|
||||
// The line plots the first row: systolic for a blood pressure.
|
||||
@@ -328,6 +328,14 @@ function ValueTile({
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
{tile.readings > 1 ? (
|
||||
<span
|
||||
data-slot="day-value-mean"
|
||||
className="text-muted-foreground truncate text-xs"
|
||||
>
|
||||
{tCount("day.meanOfReadings", tile.readings)}
|
||||
</span>
|
||||
) : null}
|
||||
{lead?.band ? (
|
||||
<>
|
||||
<NumberLine value={lead.value} band={lead.band} />
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
* Which of a day's values the view shows first, and how they pair up.
|
||||
*
|
||||
* Pure, so the curation can be pinned without rendering. The server sends one
|
||||
* row per type (canonical source, band already decided); this only orders the
|
||||
* rows and folds the two halves of a blood pressure into one tile, because a
|
||||
* reading of 138 over 88 is one thing to a person, not two.
|
||||
* row per reading for the types taken a few times a day (weight, blood
|
||||
* pressure, glucose) and one row per type otherwise, with the band already
|
||||
* decided. This orders the tiles, folds the two halves of a blood pressure
|
||||
* into one, because a reading of 138 over 88 is one thing to a person, not
|
||||
* two, and shows several readings of one type as their mean. The single
|
||||
* readings stay in the day's list of entries.
|
||||
*/
|
||||
import type { DayValue } from "@/lib/day/contract";
|
||||
|
||||
@@ -35,8 +38,42 @@ export const VALUE_PRIORITY: readonly string[] = [
|
||||
export interface DayValueTile {
|
||||
/** Stable key: the first type, `BLOOD_PRESSURE` for the pair. */
|
||||
key: string;
|
||||
/** The rows the tile shows, systolic before diastolic for the pair. */
|
||||
/**
|
||||
* The rows the tile shows, one per type, systolic before diastolic for the
|
||||
* pair. A type read more than once that day is its mean.
|
||||
*/
|
||||
values: DayValue[];
|
||||
/** How many readings the tile stands for; above 1 the value is a mean. */
|
||||
readings: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* One row per type: several readings of a type become their mean, at the
|
||||
* time of the last. The band is the type's, so any reading's will do.
|
||||
*/
|
||||
function meanPerType(rows: readonly DayValue[]): {
|
||||
values: DayValue[];
|
||||
readings: number;
|
||||
} {
|
||||
const byType = new Map<string, DayValue[]>();
|
||||
for (const row of rows) {
|
||||
const list = byType.get(row.type) ?? [];
|
||||
list.push(row);
|
||||
byType.set(row.type, list);
|
||||
}
|
||||
let readings = 0;
|
||||
const values: DayValue[] = [];
|
||||
for (const list of byType.values()) {
|
||||
readings = Math.max(readings, list.length);
|
||||
if (list.length === 1) {
|
||||
values.push(list[0]!);
|
||||
continue;
|
||||
}
|
||||
const last = list.reduce((a, b) => (b.at > a.at ? b : a));
|
||||
const mean = list.reduce((sum, r) => sum + r.value, 0) / list.length;
|
||||
values.push({ ...last, value: mean });
|
||||
}
|
||||
return { values, readings };
|
||||
}
|
||||
|
||||
/** The tile key a type belongs to. */
|
||||
@@ -54,12 +91,16 @@ export function curateDayValues(
|
||||
values: readonly DayValue[],
|
||||
focusTypes: readonly string[] = [],
|
||||
): { curated: DayValueTile[]; rest: DayValueTile[] } {
|
||||
const tiles = new Map<string, DayValueTile>();
|
||||
const grouped = new Map<string, DayValue[]>();
|
||||
for (const value of values) {
|
||||
const key = tileKeyOf(value.type);
|
||||
const tile = tiles.get(key) ?? { key, values: [] };
|
||||
tile.values.push(value);
|
||||
tiles.set(key, tile);
|
||||
const rows = grouped.get(key) ?? [];
|
||||
rows.push(value);
|
||||
grouped.set(key, rows);
|
||||
}
|
||||
const tiles = new Map<string, DayValueTile>();
|
||||
for (const [key, rows] of grouped) {
|
||||
tiles.set(key, { key, ...meanPerType(rows) });
|
||||
}
|
||||
for (const tile of tiles.values()) {
|
||||
if (tile.key === "BLOOD_PRESSURE") {
|
||||
|
||||
@@ -164,6 +164,7 @@ export function SinceLastVisit({
|
||||
band: null,
|
||||
},
|
||||
],
|
||||
readings: 1,
|
||||
});
|
||||
return `${label} ${shown.value}${shown.unit ? ` ${shown.unit}` : ""}`;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user