Narrow each helper's visibility to its actual reach instead of leaving
it exported "just in case":
- Drop the `export` keyword on file-local helpers / constants:
`getTrendSentiment`, `isPRWithin30Days`, `buildCoachMetricSourceType`,
`countPassedSchedules`, `getGithubConfig`, `isComparisonBaseline`,
`DASHBOARD_LAYOUT_VERSION`, `INTL_LOCALE_MAP`, `SIDE_EFFECT_NOTES_MAX`,
`MEDICATION_CATEGORIES`, `openApiBase`, `ALL_MOOD_GRANULARITIES`.
- Remove never-used dead code:
`CodexOAuthNotConfiguredError` (declared as a backwards-compat shim
no caller ever consumed), `editMessageReplyMarkup` (Telegram method
nothing on the bot dispatch tree calls), and the stale
`DISPLAY_TIMEZONE` re-export from `src/lib/format.ts`.
Knip drops from 48 -> 35 unused exports. Pure narrowing / deletion;
no behaviour change.
`recomputeMoodBucketsForEntry`, `recomputeMedicationComplianceForDay`,
and `recomputeMedicationComplianceForEvent` all declared an optional
`tx?` Prisma transaction client but no call site has ever passed one —
every caller (routes, jobs, worker, tests) invokes them outside a
`$transaction` block. The dead surface area kept dragging a
`PrismaLike` / `PrismaTxOrClient` alias plus a top-level `Prisma`
import along with it. With the param gone the helpers simply close
over the singleton `prisma` client; the alias and the
type-only import follow.
No call-site changes needed — the param was optional everywhere.
Both `/api/medications/intake?scope=today` and `/api/dashboard/summary`
carried their own copy of the v1.4.39 projection + idempotent
`createMany` + per-`(med, day)` compliance recompute. The duplicate
implementations had already drifted twice; fold them into one tested
location at `src/lib/medications/scheduling/project-today-intakes.ts`
so future changes (new IntakeSource literal, new schedule cadence,
audit metadata) only land once.
The helper stays read-shape-neutral — the two routes return different
projections of the resulting events, so the caller still owns its
typed re-read. Returns `{ projected, backfilled }` for the existing
`annotate` telemetry.
Net: ~200 lines collapsed into 145 (helper) + two short call sites.
The insights page kept `CorrelationResult` + `DataSummary` type imports
left over from an earlier refactor, and the slim summaries slice still
referenced `RollupGranularity`, `aggregateWmyBuckets`, and
`RollupBucketRow` after the v1.4.37.2 GROUP BY rewrite folded that work
into the narrow aggregate. Pure deletions — both helpers stay exported
for the test suite that still exercises them.
Mirror the bmi-status (v1.4.37) timeout-stub persist into the two
sibling endpoints. On a 20 s provider stall the route now writes a
sentinel auditLog row keyed to today's Berlin day so the next mount
short-circuits at the cache lookup instead of re-racing the same
provider call for the rest of the day.
This eliminates the ~14 s warm response on every iOS dashboard open
when the provider has stalled once that day. Response envelope
unchanged; iOS v0.5.4 contract preserved.
Tests cover both arms: the persist-on-timeout path (one create with
timeout:true, model:"timeout-stub", today's dateKey) and the
short-circuit-on-stub path (subsequent mount returns cached text,
provider never invoked).
The `sum_value IS NULL` UNION arm in `enqueueBootTimeRollupBackfill`
was added in v1.4.39 W-SUM to converge DAY rollup rows that pre-dated
the writer change. Production data has since converged, so the arm is
a permanent no-op seq scan over `measurement_rollups`. Per-day missing
coverage remains the sole discovery anchor.
Drops the legacy text-anchor unit test. The read-side `mean * count`
fallback in `/api/dashboard/summary` retains legacy-row readability
and stays covered by its dedicated route test.
Append a Postgres connection-pool sizing section to the self-hosting
scaling guide. Documents the v1.4.40 W-POOL default (20 slots per
container), the container_count × DATABASE_POOL_MAX × max_connections
envelope (with a 1-5 container ready-reckoner table), the env-var
override path, and PgBouncer as the escape hatch for deployments that
outgrow the per-process pool model. Cross-references src/lib/db.ts and
the empirical trace that motivated the bump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-v1.4.41 prompts lived in two homes — `src/lib/ai/prompts/` (per-
metric system prompts + safety contracts) and `src/lib/insights/prompt
*.ts` (user-prompt builder + comparison snapshot + the `compactSections`
helper). A new reader could not tell from filenames which file to
extend; both directories looked equally authoritative. Audit rec #3
flagged the split as the only undiscoverable seam in the AI layer.
Move both files into `src/lib/ai/prompts/`:
- `lib/insights/prompt.ts` → `lib/ai/prompts/insight-system-prompt.ts`
- `lib/insights/prompt-compact.ts` → `lib/ai/prompts/compact-sections.ts`
- co-located tests follow
Three importers updated: `app/api/insights/generate/route.ts`,
`lib/ai/coach/snapshot.ts`, the generate route test (the
`vi.mock('@/lib/insights/prompt')` path moved too). Prompts now have
ONE home and ONE mental model.
v1.4.41 W-DELETED-2 — assert that /api/export, /api/export/full-backup,
and /api/export/measurements all scope the measurement read to
deletedAt: null. Shape-level guard so a future refactor cannot silently
drop the filter and resurrect deleted rows in user-downloaded files.
Routes the registration-status, auth invalidation, notifications
preferences, and Settings about-section api-version reads through
the centralised queryKeys factory. Adds factory entries for the
remaining settings/admin surfaces a follow-up will migrate, and
extends the walker test to fail on any future bare-literal
queryKey in src/app/auth, src/app/notifications, and the
about-section. Also swaps the dashboard tile Suspense fallback
from null to a layout-stable card placeholder so a future RSC
hoist of any tile slot can't trigger CLS.
Captures SB-7 follow-up (check-user endpoint shipped), SB-8 (merge
semantics documented, no code change), SB-9 (already retired, no
streak surface exists), and the dashboard-widgets PUT 422 root cause:
the Zod validator is correct and additive-safe; the live 422s from
iOS v0.5.4 are an iOS-side payload bug (likely an unknown widget id
or an out-of-range order value). No server fix viable.
ls src/app/api/streak returns No such file or directory. No web
frontend code paths reference /api/streak/...; the only streak
references in the API tree are derived fields inside
/api/insights/targets (streakDays, streakHighlight).
Decision: nothing to deprecate. The route family either never existed
or was removed before the audit. iOS can safely treat /api/streak/*
as 404. Document filed so a future audit does not re-open the
question.
The dashboard layout resolver in src/lib/dashboard-layout.ts is
append-only on read: new widget ids added to DASHBOARD_WIDGET_IDS
automatically appear for every existing user on next GET, with
visible / tileVisible / order taken from DEFAULT_DASHBOARD_LAYOUT. No
database migration is ever required when introducing a new widget.
Documents the merge contract so a future widget-introduction does not
ship a one-shot SQL migration that would clobber every user's
customisation. PUT preservation rules captured: chartOverlayPrefs is
preserved-on-omit, comparisonBaseline is not. Investigation-only — no
code change.
Closes SB-7 follow-up. POST /api/auth/check-user accepts
{ identifier: string } (username or email, case-insensitive) and returns
{ branch, hasPasskey, hasPassword } where branch is one of:
- not_found no account
- passkey_only passkey present, no password
- email_fallback password present (passkey optional)
- exists neither credential (recovery path)
The booleans let the iOS client render a "Sign in with Passkey"
affordance alongside the password field without a second round-trip.
Five-case test pin covers every branch plus the 422 identifier-required
guard. The endpoint mirrors the envelope shape of /api/auth/passkey/
login-options and adds no new enumeration surface beyond that route.
v1.4.41 W-DELETED-2 — achievement progress now scopes the measurement
read to deletedAt: null so deleting a row immediately rolls back any
streak or count badge it had earned, matching the analytics + dashboard
sweeps from v1.4.40.
v1.4.41 W-DELETED-2 — the doctor-report-data aggregator (shared by
/api/doctor-report JSON + /api/doctor-report/pdf server render) and the
section-availability probe now filter deletedAt: null so deleted rows
never appear in the rendered PDF or trigger an empty section toggle.
Three structurally-distinct `interface AnalyticsData {…}` declarations
shared a name across `src/app/page.tsx` (dashboard), `src/app/insights/
page.tsx` (insights mother), and `src/components/onboarding/
getting-started-checklist.tsx` (checklist). The collision made the
shapes un-discoverable from any one site and forced every iOS-contract
addition to land in three places.
Hoist all three into `src/types/analytics.ts` as named exports:
`DashboardAnalyticsData`, `InsightsAnalyticsData`, `ChecklistAnalytics
Data`. Each call site re-imports its shape under the original alias to
keep downstream code readable; the slim `SubPageAnalyticsData` that was
already there stays put. We deliberately keep the four shapes distinct
(rather than collapsing to one with everything optional) so call sites
can't cross-read fields they shouldn't reach for — closes v1.4.39 org-
audit recommendation #2.
v1.4.41 W-DELETED-2 — full-backup JSON, per-type CSV, and the legacy
/api/export endpoint now scope reads to deletedAt: null so iOS undo
or pending-sync rows never reach the user's downloaded file or the
admin restore round-trip.
Resolves the single component → route-handler import in the codebase
(`components/admin/backups-section.tsx ← app/api/admin/backups/route.ts`)
flagged by the v1.4.39 org audit (recommendation #2). The DTOs now live
in `src/types/backups.ts`; the route handler keeps owning the HTTP
contract and re-imports the shared types from there.
Adds the v1.4.40 row + the four prior v1.4.39.x stacked rows to
.planning/round-audit-marathon-closure.md (v1.4.39.1/.2/.3/.4 were
documented inline in the corresponding memory files but the table
hadn't picked them up).
Commits the six v1.4.40 QA reviewer findings files (senior, security,
specialist, UX, product-lead, simplifier) so the audit trail stays
self-contained.
Adds round-v1440-perf-verify.md anchored on the live deploy commit
1114cf7e (squash-merge), Coolify deploy timestamp 2026-05-21T12:55:02Z,
docker workflow 26221332323. Includes the AP-2 .p8 install runbook for
SB-5 live effect.
Eleven touch-disjoint waves landed on develop plus a six-axis QA
reconcile round before tagging. See CHANGELOG.md for the user-facing
prose; this commit message documents the wave + report inventory for
release-management traceability.
Waves shipped (each with a phase report under .planning/):
- phase-W-POOL-v1440-report.md — p-limit(4) wrapper around the
analytics 15-way fan-out + pg.Pool max raised 10 -> 20. Closes the
v1.4.39 empirical-trace finding #1 (Prisma pool starvation).
- phase-W-DELETED-v1440-report.md — 11 reader-tier helpers + 3
integration contracts close audit Critical Finding #3 (soft-delete
invisibility) across dashboard, analytics, summaries, rollup tiers,
and the Coach snapshot path.
- phase-W-INSIGHTS-v1440-report.md — mood-rollup swap on the three
/api/insights/{features,targets,cards} routes plus the
compliance-rollup hook gap closure for bulk-projection mints; the
post-QA reconcile patch closes the six remaining measurement.findMany
sites in the cluster that still leaked tombstoned rows.
- phase-W-INFRA-v1440-report.md — rollup helper umbrella consolidation
under src/lib/rollups/* with zero orphan re-exports; lint, typecheck,
knip, and 93/93 rollup tests green post-restructure.
- phase-W-RSC-v1440-report.md — per-tile Suspense boundaries plus the
queryKey factory-bypass walker test (extended to comparison/ in the
reconcile pass).
- phase-W-PRIVACY-v1440-report.md — bilingual /privacy page with 17
paired-section tests pinning the 9 SB-3 disclosure requirements.
- phase-W-AASA-v1440-report.md — /.well-known/apple-app-site-association
endpoint pinned to S8WDX4W5KX.dev.healthlog.app with app-ID parity
assertions guarding against split rotation.
- phase-W-APNS-NOTIFY-v1440-report.md — MEDICATION_REMINDER
time-sensitive APNs payload (priority 10 + interruption-level)
scoped via a parameterised test that pins all 6 other event-types
do not bypass Focus. AP-2 .p8 install gates real delivery.
- phase-W-CONSENT-v1440-report.md — AI consent receipts CRUD with
append-only invariant pinned in test; revoke-then-regrant leaves
both rows; idempotent DELETE returns 200 + receipt: null.
- phase-W-WMY-WIRE-v1440-report.md — three previously-dead WMY readers
(week/month/year) wired through summaries-slice and
health-score-fast-path with a linear-composability parity test
pinning the cross-consumer routing contract.
- phase-W-GHOSTS-v1440-report.md — ~1 177 lines of dead code removed
across 9 atomic commits; startOfUtcDay + wallClockInTz consolidation
eliminates a sibling-drift risk; knip whitelist trimmed to three
files pending follow-up.
QA reconcile patches applied per the v1.4.40 QA round (six reports
under .planning/round-v1440-QA-*-findings.md):
- senior C1 — dashboard-suspense-boundaries regex aligned with the
post-W-INFRA Thread 2 userTimezone-local lift.
- senior H1 — soft-delete filter added to the six remaining
insights measurement.findMany sites.
- specialist H1 — Promise.allSettled around bulk-projection
compliance recompute on both /api/medications/intake and
/api/dashboard/summary.
- ux H1 — compare-toggle migrated to queryKeys.dashboardWidgets
factory; walker test extended to src/components/comparison/.
- security M1 — consent artefact 64 KB cap enforced via
Buffer.byteLength (UTF-8 bytes, not UTF-16 code units).
Quality gates green at tag: pnpm typecheck, pnpm lint, pnpm knip
--include files,dependencies,binaries,unlisted, pnpm test --run
(4726 passing / 1 skipped). Tests 4525 -> 4726 unit + 1 skipped over
the v1.4.39 -> v1.4.40 span.
User-facing prose: see CHANGELOG.md.
Eleven touch-disjoint waves landed on develop plus the post-QA reconcile
patches:
- W-POOL: p-limit(4) wrapper around the analytics 15-way fan-out and
pg.Pool max raised 10 -> 20 closes the v1.4.39 empirical-trace pool
starvation root cause. Wave-C chart-tile first-paint moves from +7.3 s
to +1.6 s on the empirical fixture.
- W-DELETED: 11 reader-tier helpers + 3 integration contracts close
audit Critical Finding #3 (soft-delete invisibility) across the
user-facing dashboard, analytics, and Coach paths.
- W-INSIGHTS: mood-rollup swap on the three target routes plus the
compliance-rollup hook gap closure; the post-QA reconcile patch
closes the six remaining measurement.findMany sites in the cluster
that still leaked tombstoned rows.
- W-INFRA: rollup umbrella consolidation under src/lib/rollups/ with
zero orphan re-exports; lint and knip CI gates green.
- W-RSC: per-tile Suspense boundaries plus the queryKey factory-bypass
walker test extended to comparison/.
- W-PRIVACY: bilingual /privacy page with 17 paired-section tests.
- W-AASA: /.well-known/apple-app-site-association endpoint pinned to
S8WDX4W5KX.dev.healthlog.app with app-ID parity assertions.
- W-APNS-NOTIFY: MEDICATION_REMINDER time-sensitive payload (priority 10
with interruption-level) scoped via parameterised test that pins all
6 other event-types do not bypass Focus.
- W-CONSENT: AI consent receipts CRUD with append-only invariant and
byte-bounded artefact cap.
- W-WMY-WIRE: three previously-dead WMY readers (week/month/year) wired
through summaries-slice and health-score-fast-path.
- W-GHOSTS: ~1 177 lines of dead code removed across 9 atomic commits.
Reconcile patches applied per the v1.4.40 QA round (.planning/round-v1440-QA-*-findings.md):
- senior C1 — dashboard-suspense-boundaries regex aligned with W-INFRA Thread 2
- senior H1 — soft-delete filter added to the six remaining insights findMany sites
- specialist H1 — Promise.allSettled around bulk-projection compliance recompute
- ux H1 — compare-toggle migrated to queryKeys.dashboardWidgets factory
- security M1 — consent artefact cap enforced via Buffer.byteLength
Wave reports under .planning/phase-W-*-v1440-report.md; full release
prose in CHANGELOG.md.
pnpm typecheck / pnpm lint / pnpm knip / pnpm test --run all green.
Tests 4525 -> 4726 unit + 1 skipped over the v1.4.39 -> v1.4.40 span.
`z.string().max()` counts JavaScript string units (UTF-16 code
units), not bytes. The audit-table guarantee for SB-10 consent
artefacts is byte-bounded — a UTF-8 artefact full of multi-byte code
points (CJK, emoji) would otherwise slip past the 64 KB row budget.
Replace the `.max()` with a `.refine()` that uses
`Buffer.byteLength(value, "utf8")`.
Closes security M1 from the v1.4.40 QA round.
The compare-toggle component still declared three bare-literal
`["user", "dashboardWidgets"]` queryKeys for read, onMutate
cancel/set, onError rollback and onSettled invalidation. Migrate every
call site to the central `queryKeys.dashboardWidgets()` factory so a
future rename or shape evolution only touches one file. Extend the
W-RSC factory-bypass walker to cover `src/components/comparison/`
so any future regression in this directory gets caught by CI.
Closes UX H1 from the v1.4.40 QA round.
The recompute helper swallows errors internally today, but the contract
is fragile — any future refactor that lets a throw escape would turn
the intake POST or dashboard summary GET into a 5xx for a strictly
best-effort hook. Wrap both call sites in `Promise.allSettled` so the
fail-soft contract is explicit at the call site, not buried in the
helper.
Closes specialist H1 from the v1.4.40 QA round.
The audit-H4 regex still pinned the pre-W-INFRA Thread 2 form
`useMemo(..., [user?.timezone])` while the production code lifts
`user?.timezone` to a `userTimezone` local one line above the
`useMemo` so the dep array stays stable. Update the test fixture to
match the new dependency form.
Closes senior C1 from the v1.4.40 QA round.
Installs knip 6.14.1 as a devDependency, ships a curated knip.json
covering every Next.js App Router special file plus the worker / script
/ test entry points, and adds a workflow that runs on every push to
main and every PR targeting main.
The gate trips on:
- unused files (a new orphan module ships zero callers)
- unused dependencies (a production dep with no consumer left behind)
- unused devDependencies (same, in dev land)
- unlisted binaries (a package.json script reaches for a CLI without
declaring it)
Unused-exports + unused-types stay informational this release because
the v1.4.40 W-GHOSTS sweep cleared 1 177 lines of dead code but the
historic exported-surface backlog still sits at ~ 487 exports + 52
types. Cleaning that incrementally avoids a single mega-PR risk.
Baseline carve-outs in knip.json:
ignore — three files W-GHOSTS missed in v1.4.40 (kept as known-zero
callers until a follow-up wave can verify they're safe to delete with
their dedicated test removed in the same commit):
- e2e/setup/test-helpers.ts (9-line barrel; future-multi-user hook)
- src/components/charts/compliance-line-chart.tsx (chart module only
referenced by a touch-action contract test)
- src/lib/logging/index.ts (21-line barrel re-export)
ignoreDependencies — runtime / build-time deps loaded outside the
import graph:
- @hookform/resolvers, react-hook-form: dynamic + framework conv.
- @prisma/client: re-exported via src/generated/ (which is ignored)
- next-themes: ThemeProvider mounts via dynamic import
- @vitejs/plugin-react: vitest.config consumer
- shadcn, tailwindcss, tw-animate-css: build pipeline
- testcontainers: integration-test bootstrap
- @types/* : ambient typings
ignoreBinaries — invoked via shell from integration helpers or
declared via :
- docker, docker-compose, pg_isready (integration setup)
- tsx (the openapi:generate + openapi:check scripts run via npx tsx)
- src/lib/medications/compliance-rollups.ts → src/lib/rollups/medication-compliance-rollups.ts
- co-located test follows the source file
10 importers updated covering the dashboard summary, every intake CRUD
route (single + bulk + import + Telegram webhook), the ingest endpoint,
the admin backups restore path, and the reminder worker job.
Closes the rollup umbrella restructure — every persistent-rollup
producer + reader now lives under src/lib/rollups/. Pure relocation
across all five batches: zero behaviour or exported-surface change.
- src/lib/mood/rollups.ts → src/lib/rollups/mood-rollups.ts
- co-located test follows the source file
17 importers updated from @/lib/mood/rollups to @/lib/rollups/mood-rollups,
covering the mood-entries CRUD routes, the moodlog webhook, the import
restore path, the insights comprehensive + targets routes, the analytics
+ features paths, and the reminder worker + moodlog sync job consumers.
Pure relocation.
- src/lib/measurements/rollup-read-wmy.ts → src/lib/rollups/measurement-read-wmy.ts
- src/lib/measurements/rollup-read-cumulative.ts → src/lib/rollups/measurement-read-cumulative.ts
- co-located tests follow the source files
- cumulative module's sibling import of apple-health-mapping promoted to
the @/lib alias since that mapping stays in src/lib/measurements/
Importers updated: 2 sites for WMY (health-score + summaries-slice),
zero external sites for cumulative (only co-located test reads it).
Pure relocation.
Creates the new src/lib/rollups/ aggregator directory and relocates
the main measurement rollup module:
- src/lib/measurements/rollups.ts → src/lib/rollups/measurement-rollups.ts
- src/lib/measurements/__tests__/rollups.test.ts →
src/lib/rollups/__tests__/measurement-rollups.test.ts
Every importer of @/lib/measurements/rollups is rewritten to
@/lib/rollups/measurement-rollups. The orphan sibling reference inside
src/lib/measurements/rollup-read.ts (still in its old home until the
next batch) is promoted to the @/lib alias so the chain keeps
typechecking through the in-flight move.
Pure relocation — no behaviour change, no exported-surface change. The
umbrella exists so the rollup contract is discoverable from a single
directory before the iOS sprint locks the boundary.
The useMemo introduced for the timezone-aware greeting in the W-RSC
per-tile Suspense pass listed [user?.timezone] as its dep array. React
Compiler's preserve-manual-memoization rule rejected the literal
optional-chain expression because the inferred dep is the wider 'user'
object — a less-specific source than the declared property access — so
the compiler refused to optimize the surrounding 1 400-line component
body.
Lift userTimezone into a local before the memo so the inferred dep and
the declared dep agree. No behaviour change; the memo still recomputes
only when the user's timezone string changes.
The three Measurement reads in the analytics route — glucose-by-context,
sleep-stage breakdown, and the fetchMeasurementSeriesChunked paged
helper that backs the per-type summarize loop and the BP-Zielbereich
windowing — were still pulling soft-deleted rows. W-DELETED skipped this
file assuming the W-POOL pool / p-limit pass covered it, but that pass
only addressed concurrency, not the deletedAt predicate.
Adds deletedAt: null to all three findMany clauses so the route shares
the same soft-delete contract as every other analytics read path. iOS
soft-delete unblocked.
The compliance-rollups module and the cadence scheduling module each
carried a private wallClockInTz definition. The cadence variant
returned the full {year,month,day,hour,minute,second,weekday} tuple;
the compliance variant returned only {year,month,day}. Promote one
canonical helper that returns every part — the compliance caller
destructures the three fields it needs — so the cross-tz ±3 h guard
stays in lock-step instead of drifting between sibling modules.
The mood-rollup and measurement-rollup writers carried byte-identical
copies of the helper that could drift independently. Promote one
canonical export under src/lib/tz/ so both writers — and any future
rollup table — import the same UTC-anchored truncation.
The runtime never reads the format namespace — every locale-aware
formatter routes through Intl.DateTimeFormat directly via
src/lib/format-locale.ts. The keys were only verified by their own
ordering smoke test, which is also removed.
The glucose UI only converts canonical mg/dL into mmol/L for display
via convertGlucose; the reverse helper had no caller outside its own
test. The Berlin-only day-key helper was already superseded by
userDayKey(date, tz) in v1.4.38 for every read path that consumed it;
its remaining presence as a tested module was dead weight.
Pins the cross-consumer routing contract the v1.4.40 wire-in relies
on. Both computeSummariesSlice (year-ago baseline) and
computeUserHealthScoreFastPath (weight long-window) now call
readBestGranularityRollups so the floor table is shared state. The
new tests prove:
- 90 d -> DAY (every coarser floor too high),
- 365 d -> MONTH (clears 181 d floor, short of 731 d YEAR floor),
- 1095 d -> YEAR (clears the 731 d YEAR floor).
Plus a parity assertion: a MONTH bucket consolidating 30 measurements
with mean 80 returns the same count-weighted mean as the two DAY
buckets (count 10 / mean 82 + count 20 / mean 79) that compose it.
Locks the linearly-composable contract count / mean carries across
granularities — any future floor adjustment forces both consumers to
re-pin in lock-step rather than silently diverging.
The bucket-fresh read paths probe coverage per-type via
coverage.get(type) or rely on isFullyCovered for the all-or-nothing
fast path; the typesMissingCoverage helper had no production caller.
The cumulative-batch reader was only exercised by its own test —
the analytics A2 path took the chunked per-type findMany route in
production. Strip both and their tests.
Wires readBestGranularityRollups into the WEIGHT-covered branch of
computeUserHealthScoreFastPath so the trailing-year weight baseline is
served from the WEEK / MONTH / YEAR rollup tier instead of falling
through to a live measurements walk. The 365-day window routes through
MONTH by default (~12 buckets) and falls back to WEEK -> DAY on per-
tier coverage miss. count / mean compose linearly across MONTH buckets
so the derived mean is mathematically equivalent to the per-row
average over the same window.
The read fires in parallel with the canonical DAY-bucket read for the
30-day weight series so wall-clock cost stays flat. Score shape is
unchanged; the weightLongWindow annotate carries the resolved
granularity + bucket count + mean so operators can verify the rollup
tier serves production traffic. The live-fallback branch deliberately
omits the long-window probe because the raw weight findMany already
does the work — guards against double-reading on cold mounts.
Neither function had any production caller. The chart components in
src/components/charts/* implement a local movingAverageByPoints rather
than reaching for the lib helper, and weeklyAverages was never imported
outside its own test file. detectAnomalies stays — it is reached
transitively through summarize() which the analytics, insights, and
measurements-series routes all consume.
Both routes were superseded by their /api/admin/monitoring/{umami,glitchtip}-test
counterparts which are the variants the admin UI actually fetches.
The duplicated routes had no callers anywhere in the source tree.