fix(targets): no adult weight and BMI bands on a child's record

Now that the BMI chart draws inside a managed record, a child's normal
BMI sat in the red under-17 band, and the weight page painted the adult
WHO band (about 26 to 35 kg for 118 cm), so a healthy 21 kg read as
underweight. The release's own weight-band test pinned that band.

No band here knows a child's age yet. For a record under 18, or a
managed record with no date of birth, the targets read names no WHO
weight range, no BMI range and no category; the record's own weight
target still shows, and the values and the chart stay. The BMI chart
and the weight band draw only what the server names. The weight-band
test now uses an adult height and gains the child case.

The targets builder and the vitals builder grow by two lines each for
the flag; their size baselines are raised to match.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 22:53:31 +01:00
1 parent 9683daf4f2
commit ce01d455be
8 files changed
+117 -19

No files matched your search

@@ -274,3 +274,50 @@ describe("buildVitalTargets — user weight target", () => {
expect(weight?.source).toBe("Custom");
});
});
describe("buildVitalTargets — a child's record", () => {
// 118 cm and 21 kg: a BMI of about 15, normal for a six-year-old and red
// under the adult cut-offs.
const child = {
recentMeasurements: [],
restingPulseProxy: [],
latestByType: { WEIGHT: 21 },
average30ByType: { WEIGHT: 21 },
heightCm: 118,
age: 6,
gender: null,
timezone: TZ,
now: NOW,
weightTargetOverride: null,
adultBands: false,
};
const card = (
targets: ReturnType<typeof buildVitalTargets>["targets"],
type: string,
) => targets.find((t) => t.type === type);
it("keeps the values and names no adult band or category", () => {
const { targets } = buildVitalTargets(child);
expect(card(targets, "WEIGHT")).toMatchObject({
current: 21,
range: null,
classification: null,
});
expect(card(targets, "BMI")).toMatchObject({
current: 15.1,
range: null,
classification: null,
});
});
it("keeps the record's own weight target", () => {
const { targets } = buildVitalTargets({
...child,
weightTargetOverride: { min: 19, max: 24 },
});
expect(card(targets, "WEIGHT")).toMatchObject({
range: { min: 19, max: 24 },
source: "Custom",
});
});
});
+2
View File
@@ -116,6 +116,7 @@ export async function buildTargetsResponse(user: AuthedUser) {
select: {
heightCm: true,
dateOfBirth: true,
managedProfileAt: true,
gender: true,
glucoseUnit: true,
hasDiabetes: true,
@@ -274,6 +275,7 @@ export async function buildTargetsResponse(user: AuthedUser) {
average30ByType,
heightCm,
age,
adultBands: age != null ? age >= 18 : dbUser?.managedProfileAt == null,
gender,
timezone,
now,
+13 -4
View File
@@ -74,6 +74,13 @@ interface VitalTargetsInput {
* names a category, not a goal. Absent or `null` keeps the age band.
*/
bpTargetOverride?: BloodPressureTargetRange | null;
/**
* Whether the adult WHO weight and BMI cut-offs apply. No band here knows a
* child's age yet, so for a record under 18, or a managed one with no date
* of birth, the weight card keeps only the record's own target, the BMI
* card names no band, and neither names a category. Absent means they do.
*/
adultBands?: boolean;
}
export interface VitalTargetsResult {
@@ -170,6 +177,7 @@ export function buildVitalTargets({
now,
weightTargetOverride,
bpTargetOverride = null,
adultBands = true,
}: VitalTargetsInput): VitalTargetsResult {
const targets: TargetItem[] = [];
const consistencyClock = { timezone, now };
@@ -181,10 +189,11 @@ export function buildVitalTargets({
// v1.34 — a user-set target outranks the WHO BMI band. The BMI
// classification below stays WHO-derived either way: it names which BMI
// category a reading falls in, which is a reference fact and not a goal.
const whoHeightCm = adultBands ? heightCm : null;
const weightRange =
weightTargetOverride ?? (heightCm ? getWeightRange(heightCm) : null);
weightTargetOverride ?? (whoHeightCm ? getWeightRange(whoHeightCm) : null);
let weightClassification: TargetItem["classification"] = null;
if (latestByType.WEIGHT != null && heightCm) {
if (latestByType.WEIGHT != null && whoHeightCm) {
const heightM = heightCm / 100;
const classification = classifyBMI(
latestByType.WEIGHT / (heightM * heightM),
@@ -389,14 +398,14 @@ export function buildVitalTargets({
? Math.round((averageWeight / heightSquared) * 10) / 10
: null;
let bmiClassification: TargetItem["classification"] = null;
if (currentBmi != null) {
if (currentBmi != null && adultBands) {
const classification = classifyBMI(currentBmi);
bmiClassification = {
category: classification.category,
color: classification.color,
};
}
const bmiRange = { min: 18.5, max: 24.9 };
const bmiRange = adultBands ? { min: 18.5, max: 24.9 } : null;
targets.push({
type: "BMI",
label: "BMI",