mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Say what the AI surfaces actually compute
Three places where the copy promised more than the handler delivers. The hero's personal-baseline line rendered unconditionally while both its siblings in the same meta row gate on their own data. It claims the subtitle above it was drawn from the reader's own last 90 days, but with no briefing the subtitle is fixed fallback copy computed from nothing — so an account with no briefing at all was told its generic sentence rested on a personal baseline. Gate it on the briefing, like the rest of the row. The pulse sub-page promised a personalised Karvonen target band. Karvonen is not computed anywhere: the band comes from CDC/NCHS population percentiles by age and sex, and falls back to a fixed 60-100 for a reader with no age on file. The copy now says that in all six locales, and the fallback's own source label says it is a general adult range rather than a personal one, because that arm is the same band for everyone. Karvonen itself stays unimplemented; whether to build it is a product decision, not a copy fix. The Coach's `allTime` window returns 365 days, identical to `lastYear`, and it is the default. "year so far" was wrong too — a rolling 365 days is not year-to-date. Both labels now say what the reads do. The two options remain functionally identical and one of them should probably go, but removing a default is a product decision.
This commit is contained in:
1 parent
c1811bc85b
commit
6f2f43bb49
10 files changed
+68
-30
No files matched your search
@@ -241,12 +241,35 @@ describe("<HeroStrip>", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("renders the personal-baseline meta line", () => {
|
||||
const html = render(<HeroStrip briefing={null} now={morningLocal} />);
|
||||
it("renders the personal-baseline meta line under a real briefing", () => {
|
||||
const html = render(
|
||||
<HeroStrip briefing={sampleBriefing} now={morningLocal} />,
|
||||
);
|
||||
expect(html).toMatch(/data-slot="insights-hero-strip-baseline"/);
|
||||
expect(html).toContain("Based on your last 90 days");
|
||||
});
|
||||
|
||||
/**
|
||||
* Inverted from the previous assertion, which rendered with
|
||||
* `briefing={null}` and still expected the baseline line.
|
||||
*
|
||||
* That pinned the defect. The line claims the subtitle above it was drawn
|
||||
* from the reader's own last 90 days, but with no briefing the subtitle is
|
||||
* `heroFallbackSubtitle` — fixed copy computed from nothing. Both siblings in
|
||||
* the same meta row (the freshness caption, the no-provider hint) already
|
||||
* gate on their own data; this one did not, so an account with no briefing at
|
||||
* all was told its generic sentence rested on a personal baseline.
|
||||
*/
|
||||
it("withholds the personal-baseline meta line when there is no briefing", () => {
|
||||
const html = render(<HeroStrip briefing={null} now={morningLocal} />);
|
||||
expect(html).not.toMatch(/data-slot="insights-hero-strip-baseline"/);
|
||||
expect(html).not.toContain("Based on your last 90 days");
|
||||
// The fallback subtitle still renders — only the provenance claim goes.
|
||||
expect(html).toContain(
|
||||
"A daily read of your trends, drawn straight from the numbers you've logged.",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders the freshness caption when updatedAt is supplied", () => {
|
||||
// v1.22 — the hero freshness line now uses `formatUpdatedLabel` for parity
|
||||
// with the briefing + per-metric cards: a same-day timestamp reads
|
||||
|
||||
@@ -199,14 +199,24 @@ export function HeroStrip({
|
||||
<ProseBlocks text={subtitle} />
|
||||
</div>
|
||||
<div className="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
|
||||
<span data-slot="insights-hero-strip-baseline">
|
||||
{t("insights.heroPersonalBaseline")}
|
||||
</span>
|
||||
{/* The baseline line describes where the SUBTITLE came from, so it
|
||||
may only show when the subtitle IS a briefing. Without one the
|
||||
subtitle falls back to generic copy derived from nothing, and
|
||||
this line went on claiming a personal baseline underneath it —
|
||||
the one part of the row that was ungated while both its
|
||||
siblings already checked for their own data first. */}
|
||||
{briefing && (
|
||||
<span data-slot="insights-hero-strip-baseline">
|
||||
{t("insights.heroPersonalBaseline")}
|
||||
</span>
|
||||
)}
|
||||
{generatedLine && (
|
||||
<>
|
||||
<span aria-hidden="true" className="opacity-50">
|
||||
·
|
||||
</span>
|
||||
{briefing && (
|
||||
<span aria-hidden="true" className="opacity-50">
|
||||
·
|
||||
</span>
|
||||
)}
|
||||
<span data-slot="insights-hero-strip-generated">
|
||||
{generatedLine}
|
||||
</span>
|
||||
|
||||
@@ -28,8 +28,8 @@ import { apiGet } from "@/lib/api/api-fetch";
|
||||
* nothing and a cold sub-page warms the cache its siblings reuse. We
|
||||
* never recompute the ranges here: the route is the single source of
|
||||
* truth for the age-based ESH blood-pressure band, the WHO weight / BMI
|
||||
* band, the Karvonen pulse band, the AASM sleep band, the ADA / DDG
|
||||
* glucose bands, and the medication / mood targets.
|
||||
* band, the CDC/NCHS percentile pulse band, the AASM sleep band, the
|
||||
* ADA / DDG glucose bands, and the medication / mood targets.
|
||||
*
|
||||
* The panel is a compact reference panel carrying the full target
|
||||
* context:
|
||||
|
||||
@@ -68,14 +68,19 @@ export function getPersonalizedPulseTarget(
|
||||
age: number | null,
|
||||
gender: PulseGender,
|
||||
): PersonalizedPulseTarget {
|
||||
// AHA fallback for adults when profile context is missing.
|
||||
// No age to personalise against (missing profile, or under 20 where the
|
||||
// percentile table does not apply), so this arm returns a fixed band that is
|
||||
// the same for everyone. The source label says so in as many words: the
|
||||
// surrounding UI describes the pulse band as age- and sex-based, and a reader
|
||||
// who falls into this arm needs to know theirs is not.
|
||||
if (age == null || age < 20) {
|
||||
return {
|
||||
greenMin: 60,
|
||||
greenMax: 100,
|
||||
orangeMin: 55,
|
||||
orangeMax: 105,
|
||||
source: "AHA (adults, resting pulse 60-100 bpm)",
|
||||
source:
|
||||
"AHA general adult range (resting pulse 60-100 bpm) — not personalised",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user