Enforce the reminder nag cap, compare the disclaimer version, drop the unread analytics columns

- The daily Coach sweep now charges each ignored day against
  surfaceCount and auto-dismisses at three — the cap the schema comment
  promised since v1.22 but nothing enforced.
- The welcome gate compares the stored disclaimer acknowledgment
  version against the current one, so revised disclaimer text
  re-prompts; /api/auth/me carries the version for the client.
- slope90 and anomalyCount leave the analytics wire: slope90 had no
  reader anywhere (computed on every request, in SQL twice), and the
  slim path could never compute anomalyCount (it always reported 0).
  anomalyCount stays as an in-process field on summarize() for the
  insights feature builder, which is its only real consumer.
This commit is contained in:
Marc-André Bombeck committed 2026-08-14 05:19:05 +02:00
1 parent 6b3a5a4182
commit 3b1314d1b0
29 files changed
+214 -104

No files matched your search

@@ -329,7 +329,6 @@ describe("GET /api/analytics", () => {
r2_7: 0.4,
slope30: -0.02,
r2_30: 0.3,
slope90: -0.01,
r2_90: 0.2,
},
] as never)
+9 -7
View File
@@ -233,19 +233,21 @@ async function buildAnalyticsResponse(user: AuthedUser, locale: Locale) {
// identical to the previous fan-out output for every field the
// dashboard actually consumes:
// - `count / latest / min / max / mean` from DAY buckets
// - `avg7 / avg30 / slope7 / slope30 / slope90` from the narrow query
// - `avg7 / avg30 / slope7 / slope30` from the narrow query
// - `avg30LastMonth` from the same narrow query (added in this
// release; previously only the live walk produced it)
// - `avg30LastYear` populated for any type whose WMY tier carries
// the year-ago window
// - `lastSeenByType` from the `DISTINCT ON (type)` latest read
//
// The two fields the slim path leaves at default values are
// `anomalyCount` (always 0) — the insights pipeline consumes it from
// its own `comprehensive-aggregator` narrow query, never from this
// route — and `avg30LastYear` on types with no WMY coverage, which
// matches the pre-fix behaviour for tenants whose year-ago window
// happened to fall outside the 425-day floor.
// `anomalyCount` and `slope90` left this wire in v1.37.19: the slim
// path could never compute the former (it always reported 0) and no
// client component read the latter — the insights pipeline computes
// both from its own `summarize()` / comprehensive-aggregator reads.
// The one field the slim path leaves at a default value is
// `avg30LastYear` on types with no WMY coverage, which matches the
// pre-fix behaviour for tenants whose year-ago window happened to
// fall outside the 425-day floor.
const slim = await computeSummariesSlice(user.id);
const results = slim.summaries;
const lastSeenByType = slim.lastSeenByType;
+4
View File
@@ -110,6 +110,10 @@ export const GET = apiHandler(async () => {
// when never acknowledged; the onboarding welcome gate reads this to
// decide whether to require the acknowledgment before "Get started".
disclaimerAcknowledgedAt: user.disclaimerAcknowledgedAt,
// The version that acknowledgment was given for. The welcome gate
// compares it against the current DISCLAIMER_VERSION so a revised
// disclaimer re-prompts instead of riding an old acknowledgment.
disclaimerAcknowledgedVersion: user.disclaimerAcknowledgedVersion,
// v1.18.6 — resumable module-tour progress. Null when the user has
// not started the tour; otherwise the resume point the launcher
// seeds its index from. Fail-soft parse: a corrupt blob degrades to
+1 -1
View File
@@ -253,7 +253,7 @@ export const GET = apiHandler(async () => {
bpPctInTarget,
weightSlope30: summaries.WEIGHT?.slope30?.slope ?? null,
pulseAvg30: summaries.PULSE?.avg30 ?? null,
pulseAnomalyCount: summaries.PULSE?.anomalyCount,
pulseAnomalyCount: summaries.PULSE?.anomalyCount ?? 0,
medications: medicationCompliance,
});
+1 -1
View File
@@ -535,7 +535,7 @@ export async function buildComprehensiveResponse(user: AuthedUser) {
bpPctInTarget,
weightSlope30: summaries.WEIGHT?.slope30?.slope ?? null,
pulseAvg30: summaries.PULSE?.avg30 ?? null,
pulseAnomalyCount: summaries.PULSE?.anomalyCount,
pulseAnomalyCount: summaries.PULSE?.anomalyCount ?? 0,
medications: medCompliance.map((m) => ({
name: m.name,
compliance7: m.compliance7,