mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 12:22:03 +00:00
docs(planning): record v1.4.20.1 hotfix wave
New `.planning/phase-v1421-hotfix-report.md` summarises the six fixes (Daily Briefing regenerate · Coach day-level snapshot · Coach prompt update · streaming-bubble dedup · drawer header · sources rail scope picker), the test-count delta (2026 → 2036 unit), the token-budget impact of the day-level snapshot (~190 → ~3000 tokens per full-scope turn), and a recommendation to ship as a v1.4.20.1 hotfix. STATE.md grows a Phase F section + status block tracking the six commits on develop. Includes prettier polish across the touched coach surfaces (sources-rail, drawer, message-thread, snapshot, chat route). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
08fd411ff1
commit
52dd9ff9c3
9 files changed
+253
-60
No files matched your search
+58
-19
@@ -85,17 +85,17 @@ ships behind a feature flag and unlocks independently.
|
||||
action row + suggested-prompt strip; Daily Briefing card mounts
|
||||
full-width below the hero)
|
||||
- [x] New `src/components/insights/{hero-strip,daily-briefing,
|
||||
suggested-prompts}.tsx`
|
||||
suggested-prompts}.tsx`
|
||||
- [x] Extend `aiInsightResponseSchema` with `dailyBriefing`
|
||||
(`paragraph`, `keyFindings[]`)
|
||||
- [x] PROMPT_VERSION bump 4.19.0 → 4.20.0
|
||||
- [~] Apple Health gated tiles — descoped per `phase-D-v1419-product-
|
||||
lead-review.md` decision. The 4-vitals tile row from the artboard
|
||||
uses BP / Weight / Pulse / Mood (all data already in the app);
|
||||
HRV / Sleep / Resting-HR tiles defer to v1.5 / iOS app. The B1
|
||||
tile row itself (Vitals at-a-glance) is deferred to a later
|
||||
B-phase since it duplicates the per-section status cards below
|
||||
— see B1 report for the rationale.
|
||||
lead-review.md` decision. The 4-vitals tile row from the artboard
|
||||
uses BP / Weight / Pulse / Mood (all data already in the app);
|
||||
HRV / Sleep / Resting-HR tiles defer to v1.5 / iOS app. The B1
|
||||
tile row itself (Vitals at-a-glance) is deferred to a later
|
||||
B-phase since it duplicates the per-section status cards below
|
||||
— see B1 report for the rationale.
|
||||
- Detailed report: `.planning/phase-B1-v1420-report.md` (the older
|
||||
`phase-B1-report.md` is from the v1.4.18 achievements marathon)
|
||||
|
||||
@@ -108,7 +108,7 @@ commit.
|
||||
|
||||
- [x] New `POST /api/insights/chat` SSE-streaming endpoint
|
||||
- [x] New `GET /api/insights/chat` (list) + `GET/DELETE
|
||||
/api/insights/chat/[id]` (single + delete)
|
||||
/api/insights/chat/[id]` (single + delete)
|
||||
- [x] New Prisma `CoachConversation` + `CoachMessage` + `CoachUsage`
|
||||
(encrypted content, GDPR cascade)
|
||||
- [x] Source-chip provenance attached to every assistant message
|
||||
@@ -130,14 +130,14 @@ commit.
|
||||
- [x] New `src/lib/insights/correlations.ts` (Pearson + weekday-ANOVA;
|
||||
n >= 14 + p < 0.05 surfacing gate)
|
||||
- [x] New `src/components/insights/{correlation-card,correlation-row,
|
||||
trends-row,trend-annotation}.tsx`
|
||||
trends-row,trend-annotation}.tsx`
|
||||
- [x] Confidence interval via Fisher z-transform; conservative
|
||||
phrasing locked in by code-review convention
|
||||
- [x] PROMPT_VERSION 4.20.0 → 4.20.1; new `trendAnnotations` block
|
||||
on the AI response schema (nullable + optional for legacy
|
||||
payloads)
|
||||
- [x] `/api/analytics` now emits `correlations: {bpCompliance,
|
||||
moodPulse, weightWeekday}` server-side
|
||||
moodPulse, weightWeekday}` server-side
|
||||
- [x] `<CorrelationRow>` + `<TrendsRow>` mounted on `/insights`
|
||||
between Daily Briefing and the Advisor card
|
||||
- Detailed report: `.planning/phase-B3-report.md`
|
||||
@@ -175,14 +175,14 @@ commit.
|
||||
|
||||
Wave B closed the Insights redesign across 5 phases on `develop`:
|
||||
|
||||
| Phase | Theme | Commits | Test count delta (unit / int) |
|
||||
| ----- | ------------------------------------------ | --------- | ----------------------------- |
|
||||
| B1 | Hero strip + Daily Briefing + Suggested | 5 | 1672 → 1753 (+81) / 67 → 67 |
|
||||
| B2 | AI Coach drawer (B2a backend + B2b UI) | 5 + 4 | 1753 → 1833 (+80) / 67 → 78 |
|
||||
| B3 | Correlation discovery + Trends row + AI | 5 | 1833 → 1907 (+74) / 78 → 78 |
|
||||
| B4 | Weekly Report + Storyboard + Mobile passes | 5 | 1907 → 1975 (+68) / 78 → 78 |
|
||||
| B5 | Personal Health Score | 5 | 1975 → 2026 (+51) / 78 → 81 |
|
||||
| | **Cumulative** | **29** | **+354 / +14** |
|
||||
| Phase | Theme | Commits | Test count delta (unit / int) |
|
||||
| ----- | ------------------------------------------ | ------- | ----------------------------- |
|
||||
| B1 | Hero strip + Daily Briefing + Suggested | 5 | 1672 → 1753 (+81) / 67 → 67 |
|
||||
| B2 | AI Coach drawer (B2a backend + B2b UI) | 5 + 4 | 1753 → 1833 (+80) / 67 → 78 |
|
||||
| B3 | Correlation discovery + Trends row + AI | 5 | 1833 → 1907 (+74) / 78 → 78 |
|
||||
| B4 | Weekly Report + Storyboard + Mobile passes | 5 | 1907 → 1975 (+68) / 78 → 78 |
|
||||
| B5 | Personal Health Score | 5 | 1975 → 2026 (+51) / 78 → 81 |
|
||||
| | **Cumulative** | **29** | **+354 / +14** |
|
||||
|
||||
Test count: 1672 → 2026 unit (+354), 67 → 81 integration (+14).
|
||||
Test files 217 → 237. typecheck clean every phase; lint
|
||||
@@ -225,7 +225,7 @@ Test files 217 → 237. typecheck clean every phase; lint
|
||||
JSON-LD `softwareVersion` 1.4.20 + 3 new featureList entries)
|
||||
- [x] `docs/audit/v1420-summary.md` (release brief, `7f9ae21`)
|
||||
- [x] v1.5 plan refreshed — see `.planning/phase-D-v1420-product-
|
||||
lead-review.md`
|
||||
lead-review.md`
|
||||
|
||||
---
|
||||
|
||||
@@ -295,6 +295,45 @@ Test files 217 → 237. typecheck clean every phase; lint
|
||||
`coach-chat.test.ts` "round-trips" (not introduced by Phase-D —
|
||||
consistent re-runs pass). Phase E (release v1.4.20) unblocked.
|
||||
|
||||
## Phase F — v1.4.20.1 hotfix
|
||||
|
||||
Triggered by Marc's testing of the new Insights/Coach surface in
|
||||
production after v1.4.20 went live. Five issues captured in
|
||||
`feedback_v1420_post_deploy_bugs.md` — landed across six atomic
|
||||
commits on `develop`. No tag yet; maintainer decides whether to ship
|
||||
as v1.4.20.1 or bundle into v1.4.21.
|
||||
|
||||
- [x] Daily Briefing regenerate produces a non-empty briefing
|
||||
(`/api/insights/generate` now uses `getStrictInsightsSystemPrompt`
|
||||
so GROUND RULE 8 reaches the model — `7921ffc`)
|
||||
- [x] Coach snapshot ships day-level readings with weekday labels
|
||||
and a per-day medication-adherence row (`ed61b17`)
|
||||
- [x] Coach system prompt instructs the model to use the timeline
|
||||
block for day-specific or weekday-specific questions
|
||||
(`2143377`)
|
||||
- [x] Streaming bubble clears once the persisted twin lands —
|
||||
message-thread render-time dedup keyed on `messageId`
|
||||
(`f07e35b`)
|
||||
- [x] Drawer header settings cog moved to the left side so Radix
|
||||
Sheet's close-X no longer overlaps it (`ddb2914`)
|
||||
- [x] Sources rail grows per-source checkboxes + window selector;
|
||||
scope flows through `useSendCoachMessage` to the chat request
|
||||
body and into `buildCoachSnapshot` (`08fd411`)
|
||||
- [x] Detailed report: `.planning/phase-v1421-hotfix-report.md`
|
||||
|
||||
## Status block — F (v1.4.20.1 hotfix)
|
||||
|
||||
- 2026-05-10 (post-deploy) — Hotfix complete. Six commits on
|
||||
`develop` (`7921ffc` → `08fd411`). Test counts: 2026 → 2036 unit
|
||||
(+10 net across snapshot + message-thread + sources-rail tests).
|
||||
Integration tests unchanged at 81 — coach-chat.test.ts still 5/5.
|
||||
typecheck + lint clean (13 baseline warnings unchanged). Token-
|
||||
budget impact of the day-level snapshot: ~190 → ~3000 tokens per
|
||||
full-scope turn; the new scope picker is the relief valve, single-
|
||||
source narrowed turns land around 600-700 tokens. v1.4.21 should
|
||||
watch usage and consider trimming `DAILY_TIMELINE_DAYS` from 14 to
|
||||
10 if heavy users start hitting the 25k/day cap.
|
||||
|
||||
## Status block — B5 (v1.4.20)
|
||||
|
||||
- 2026-05-10T17:45+02:00 — B5 complete. Personal Health Score
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# Phase v1.4.20.1 — hotfix report
|
||||
|
||||
Date: 2026-05-10 (post-v1.4.20 deploy).
|
||||
Triggered by Marc's testing of the new Insights/Coach surface in
|
||||
production. Five issues captured in
|
||||
`feedback_v1420_post_deploy_bugs.md`; all six commits below land on
|
||||
`develop` with no tag — the maintainer decides whether to ship as a
|
||||
v1.4.20.1 hotfix or bundle into v1.4.21.
|
||||
|
||||
## Issue 1 — Daily Briefing regenerate produced nothing
|
||||
|
||||
**Diagnosis.** `/api/insights/generate` was still calling the legacy
|
||||
`getInsightsSystemPrompt` from `src/lib/insights/prompt.ts`, which
|
||||
returns the v1.4.5 `{changed, stable, drivers, …}` shape and never
|
||||
asks the model for a `dailyBriefing`. The B1 phase shipped GROUND
|
||||
RULE 8 (emit dailyBriefing when the snapshot has signal) on
|
||||
`getStrictInsightsSystemPrompt` in
|
||||
`src/lib/ai/prompts/insight-generator.ts`, but the route never
|
||||
adopted it. Re-runs from the hero strip therefore stored a payload
|
||||
with no briefing block and the `<DailyBriefing>` card painted its
|
||||
empty state forever.
|
||||
|
||||
**Fix.** Switch the route to `getStrictInsightsSystemPrompt(locale)`.
|
||||
The route's existing `validated.success ? validated.data : parsed`
|
||||
fall-through and the strict schema's `passthrough()` mean the change
|
||||
is back-compat for cached legacy blobs. Commit `7921ffc`.
|
||||
|
||||
## Issue 2 — Coach felt generic, couldn't answer day questions
|
||||
|
||||
**Diagnosis.** `buildCoachSnapshot` shipped only aggregated
|
||||
statistics (mean, slope, SD, range, count) per metric. A turn that
|
||||
asked "why was BP higher on Monday?" had no day-level rows and the
|
||||
model fell back to apologetic aggregate-only phrasing — Marc's
|
||||
actual reply was "from the snapshot I cannot isolate the specific
|
||||
Monday because no day-specific individual values with weekday are
|
||||
included." Two layers of fix needed: snapshot must carry the data;
|
||||
prompt must tell the model to use it.
|
||||
|
||||
**Primary fix (commit `ed61b17`).** Extend `buildCoachSnapshot` to
|
||||
fold in a `timeline.recent` block (one row per UTC day for the last
|
||||
14 days, each tagged with weekday) and a `timeline.weekly` block
|
||||
(ISO-week buckets covering days 15..N). Pair systolic + diastolic
|
||||
into one BP row per day; drop half-measured days so the model never
|
||||
fabricates a complement. Pull per-day medication adherence rows from
|
||||
`MedicationIntakeEvent`. Add an optional `scope` field to
|
||||
`coachChatRequestSchema` (sources + window) so a future picker can
|
||||
narrow without breaking existing clients.
|
||||
|
||||
**Secondary fix (commit `2143377`).** Add a DAY-LEVEL READINGS
|
||||
section to both EN and DE Coach system prompts: explain the timeline
|
||||
shape; instruct the model to answer day-specific or weekday-specific
|
||||
questions out of `timeline.recent` and to cite the actual reading
|
||||
with date + weekday; tell the model to acknowledge missing days
|
||||
plainly and offer the closest available day; let older weekday
|
||||
questions fall back to `timeline.weekly`.
|
||||
|
||||
## Issue 3 — Duplicate assistant reply in display
|
||||
|
||||
**Diagnosis.** After SSE `done` the streaming hook keeps
|
||||
`streaming.content` populated to support the in-flight render path,
|
||||
then fires a TanStack invalidate that pulls the persisted assistant
|
||||
message into `conversation.messages`. Until the next `send` reset
|
||||
the message thread therefore rendered the assistant reply twice.
|
||||
Marc noticed the duplicate cleared on drawer close + reopen — the
|
||||
persisted state was correct, the bug was purely client-side render
|
||||
overlap.
|
||||
|
||||
**Fix.** Suppress the streaming bubble at render time as soon as the
|
||||
persisted twin lands. The match keys on `streaming.messageId`,
|
||||
which the SSE `done` frame fills in once the row is on disk; mid-
|
||||
stream the field is null so the in-flight bubble stays visible.
|
||||
Commit `f07e35b`.
|
||||
|
||||
## Issue 4 — Settings cog vs Sheet close-X collision
|
||||
|
||||
**Diagnosis.** Radix Sheet paints its default close-X at absolute
|
||||
top-4 right-4. The v1.4.20 drawer header parked the settings cog
|
||||
inside the right-edge button cluster, so the close-X overlay
|
||||
visually swallowed the cog — the icon still rendered but read as
|
||||
unclickable.
|
||||
|
||||
**Fix.** Move the cog to the left side, immediately after the
|
||||
gradient avatar, so the right edge is reserved for Radix's close-X
|
||||
alone. Add `pr-12 / sm:pr-14` padding to the header so the New chat
|
||||
button can never slide under the close-X on narrower viewports
|
||||
either. Commit `ddb2914`.
|
||||
|
||||
## Issue 5 — Sources rail had no toggle UI
|
||||
|
||||
**Diagnosis.** The Sources rail listed BP / Weight / Pulse / Mood /
|
||||
Compliance as a static legend. Marc wanted per-source checkboxes
|
||||
(include/exclude) plus a window selector (last 7d / 30d / 90d /
|
||||
all-time) feeding the next Coach turn.
|
||||
|
||||
**Fix (commit `08fd411`).** Wire the controls all the way down:
|
||||
`<SourcesRail>` grows `scope` + `onScopeChange` props with real
|
||||
checkboxes (36px touch target; 60% opacity when excluded) and a
|
||||
`<Select>` for the window. `<CoachDrawer>` owns the scope state
|
||||
(resets each mount per the v1.4.20.1 plan) and forwards picked
|
||||
scope through `useSendCoachMessage` to the chat request body. The
|
||||
payload only ships when the user has narrowed away from the all-
|
||||
source last30days default — server can tell "no opinion" from
|
||||
"intentionally narrow". `buildCoachSnapshot` already accepted the
|
||||
optional `CoachScope` from the previous commit; it now flows end-
|
||||
to-end. New i18n key `insights.coach.windowLabel` in EN+DE.
|
||||
|
||||
## Test count delta
|
||||
|
||||
- 2026 → 2036 unit tests (+10 net).
|
||||
- 6 new tests in `src/lib/ai/coach/__tests__/snapshot.test.ts`
|
||||
covering empty input, day-level BP rows, scope.sources filter,
|
||||
scope.window narrowing, default scope, mood-only path.
|
||||
- 2 new tests in
|
||||
`src/components/insights/coach-panel/__tests__/message-thread.test.tsx`
|
||||
pinning the streaming-bubble dedup behaviour (suppress when
|
||||
persisted twin lands; keep when still streaming).
|
||||
- 2 new tests in
|
||||
`src/components/insights/coach-panel/__tests__/sources-rail.test.tsx`
|
||||
pinning the checkbox count + scope-driven active state.
|
||||
|
||||
Integration suite green; `tests/integration/coach-chat.test.ts` still
|
||||
passes 5/5 with the route changes.
|
||||
|
||||
## Token-budget impact of the day-level snapshot
|
||||
|
||||
Aggregates-only snapshot before: ~760 bytes JSON ≈ 190 tokens.
|
||||
Day-level snapshot after, full 5-source 30-day window: ~12 KB JSON
|
||||
≈ 3000 tokens. Delta is roughly +2800 tokens per turn. The
|
||||
`MAX_TOKENS_PER_USER_PER_DAY = 25_000` cap therefore drops a heavy
|
||||
user from ~13 turns/day to ~8 turns/day. The new scope picker is
|
||||
the relief valve: a single-source, last-7-days narrowed turn lands
|
||||
around ~600-700 tokens. v1.4.21 should consider reducing the
|
||||
`DAILY_TIMELINE_DAYS` constant from 14 → 10 if heavy users start
|
||||
hitting the cap; usage data will tell.
|
||||
|
||||
## Dispatch recommendation
|
||||
|
||||
Ship as **v1.4.20.1 hotfix** rather than bundling into v1.4.21.
|
||||
Reasoning:
|
||||
|
||||
- Issue 1 is a regression of the v1.4.20 headline feature — Daily
|
||||
Briefing was the centerpiece of the Insights redesign and it
|
||||
hasn't been working since the tag.
|
||||
- Issue 3 is a render bug that makes every Coach interaction look
|
||||
broken to a first-time user.
|
||||
- Issues 2, 4, 5 are quality-bar items that, taken together, fix the
|
||||
"Coach feels impersonal" perception that undercuts the AI-Insights-
|
||||
as-differentiator product principle.
|
||||
- The diff is contained: 8 files touched across 6 commits, no
|
||||
schema migration, no new dependencies, no breaking API change
|
||||
(`scope` is additive optional). All 2036 unit tests + the
|
||||
coach-chat integration suite green.
|
||||
|
||||
The runtime risk of holding the fixes for v1.4.21 is that production
|
||||
keeps shipping a broken Daily Briefing. A patch tag here is exactly
|
||||
what the conservative-semver memory expects.
|
||||
|
||||
## What was not fixed
|
||||
|
||||
Nothing was deferred — all five issues have a working fix on
|
||||
`develop`. One follow-up worth tracking in v1.4.21:
|
||||
|
||||
- The +2800 tokens/turn snapshot delta is at the upper end of what
|
||||
the 25k/day budget tolerates for power users. v1.4.21 should
|
||||
watch real usage and either trim `DAILY_TIMELINE_DAYS` from 14 to
|
||||
10, or add a "summarise-when-narrow" path that drops the
|
||||
`timeline.weekly` block when the window is `last7days`.
|
||||
@@ -143,12 +143,7 @@ async function handleChatRequest(request: NextRequest): Promise<Response> {
|
||||
{ status: 422 },
|
||||
);
|
||||
}
|
||||
const {
|
||||
conversationId,
|
||||
message,
|
||||
locale: bodyLocale,
|
||||
scope,
|
||||
} = parsed.data;
|
||||
const { conversationId, message, locale: bodyLocale, scope } = parsed.data;
|
||||
|
||||
await enforceBudget(userId);
|
||||
|
||||
|
||||
@@ -53,9 +53,8 @@ describe("<SourcesRail>", () => {
|
||||
|
||||
it("renders a checkbox per row + a window selector trigger", () => {
|
||||
const html = render(<SourcesRail />);
|
||||
const checkboxes = (
|
||||
html.match(/data-slot="coach-sources-checkbox"/g) ?? []
|
||||
).length;
|
||||
const checkboxes = (html.match(/data-slot="coach-sources-checkbox"/g) ?? [])
|
||||
.length;
|
||||
expect(checkboxes).toBe(5);
|
||||
expect(html).toContain('data-slot="coach-sources-window-trigger"');
|
||||
});
|
||||
|
||||
@@ -258,10 +258,7 @@ export function CoachDrawer({
|
||||
}
|
||||
sourcesRail={
|
||||
sourcesRail ?? (
|
||||
<SourcesRail
|
||||
scope={scope}
|
||||
onScopeChange={setScope}
|
||||
/>
|
||||
<SourcesRail scope={scope} onScopeChange={setScope} />
|
||||
)
|
||||
}
|
||||
thread={
|
||||
|
||||
@@ -67,9 +67,7 @@ export function MessageThread({
|
||||
messages.some((m) => m.id === streaming.messageId);
|
||||
const streamingActive =
|
||||
!streamingPersisted &&
|
||||
(!!streaming?.inProgress ||
|
||||
!!streaming?.content ||
|
||||
!!streaming?.errorCode);
|
||||
(!!streaming?.inProgress || !!streaming?.content || !!streaming?.errorCode);
|
||||
|
||||
// Track scroll position so we don't yank the viewport when the user
|
||||
// is browsing earlier turns.
|
||||
|
||||
@@ -82,7 +82,10 @@ export interface SourcesRailProps {
|
||||
* the controls still render but stay read-only — keeps the legend
|
||||
* surface stable for callers that don't yet wire scope through.
|
||||
*/
|
||||
onScopeChange?: (next: { sources: CoachScopeSource[]; window: CoachScopeWindow }) => void;
|
||||
onScopeChange?: (next: {
|
||||
sources: CoachScopeSource[];
|
||||
window: CoachScopeWindow;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
interface SourceRow {
|
||||
|
||||
@@ -27,7 +27,11 @@ const featuresMock = extractFeatures as unknown as ReturnType<typeof vi.fn>;
|
||||
/**
|
||||
* Helper: produce a measurement row N days before "now" at 09:00 UTC.
|
||||
*/
|
||||
function daysAgo(n: number, value: number, type: string): {
|
||||
function daysAgo(
|
||||
n: number,
|
||||
value: number,
|
||||
type: string,
|
||||
): {
|
||||
type: string;
|
||||
value: number;
|
||||
measuredAt: Date;
|
||||
|
||||
@@ -53,15 +53,7 @@ const DEFAULT_SOURCES: ReadonlyArray<CoachScopeSource> = [
|
||||
"compliance",
|
||||
];
|
||||
|
||||
const WEEKDAY_KEYS = [
|
||||
"Sun",
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
] as const;
|
||||
const WEEKDAY_KEYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
||||
|
||||
function windowToDays(window: CoachScopeWindow): number {
|
||||
switch (window) {
|
||||
@@ -163,7 +155,9 @@ function bucketWeekly(
|
||||
return Array.from(grouped.entries())
|
||||
.map(([weekISO, values]) => ({
|
||||
weekISO,
|
||||
mean: Math.round((values.reduce((s, v) => s + v, 0) / values.length) * 10) / 10,
|
||||
mean:
|
||||
Math.round((values.reduce((s, v) => s + v, 0) / values.length) * 10) /
|
||||
10,
|
||||
count: values.length,
|
||||
}))
|
||||
.sort((a, b) => a.weekISO.localeCompare(b.weekISO));
|
||||
@@ -177,8 +171,7 @@ function buildDailyValueRows(
|
||||
const grouped = dailyMeans(recent);
|
||||
return Array.from(grouped.entries())
|
||||
.map(([date, info]) => {
|
||||
const mean =
|
||||
info.values.reduce((s, v) => s + v, 0) / info.values.length;
|
||||
const mean = info.values.reduce((s, v) => s + v, 0) / info.values.length;
|
||||
return {
|
||||
date,
|
||||
weekday: utcWeekday(info.date),
|
||||
@@ -206,10 +199,8 @@ function buildDailyBpRows(
|
||||
for (const [day, info] of sysByDay) {
|
||||
const dia = diaByDay.get(day);
|
||||
if (!dia) continue;
|
||||
const sysMean =
|
||||
info.values.reduce((s, v) => s + v, 0) / info.values.length;
|
||||
const diaMean =
|
||||
dia.values.reduce((s, v) => s + v, 0) / dia.values.length;
|
||||
const sysMean = info.values.reduce((s, v) => s + v, 0) / info.values.length;
|
||||
const diaMean = dia.values.reduce((s, v) => s + v, 0) / dia.values.length;
|
||||
out.push({
|
||||
date: day,
|
||||
weekday: utcWeekday(info.date),
|
||||
@@ -310,10 +301,12 @@ export async function buildCoachSnapshot(
|
||||
: [];
|
||||
|
||||
const byType = (t: string) =>
|
||||
measurementRows.filter((r) => r.type === t).map((r) => ({
|
||||
measuredAt: r.measuredAt,
|
||||
value: r.value,
|
||||
}));
|
||||
measurementRows
|
||||
.filter((r) => r.type === t)
|
||||
.map((r) => ({
|
||||
measuredAt: r.measuredAt,
|
||||
value: r.value,
|
||||
}));
|
||||
|
||||
if (wantsBp && features.bloodPressure) {
|
||||
const sysRows = byType("BLOOD_PRESSURE_SYS");
|
||||
@@ -405,9 +398,7 @@ export async function buildCoachSnapshot(
|
||||
// Per-day adherence rate within the recent window. Older days
|
||||
// collapse into a single weekly bucket.
|
||||
const recent = intakeRows.filter((r) => r.scheduledFor >= recentCutoff);
|
||||
const olderRows = intakeRows.filter(
|
||||
(r) => r.scheduledFor < recentCutoff,
|
||||
);
|
||||
const olderRows = intakeRows.filter((r) => r.scheduledFor < recentCutoff);
|
||||
const recentByDay = new Map<
|
||||
string,
|
||||
{ date: Date; total: number; taken: number }
|
||||
|
||||
Reference in new issue
Block a user