Commit Graph
12 Commits
Author SHA1 Message Date
Marc-André Bombeck 8cdb53cb0e perf(withings): write a measure sync in sets and refold only what changed
A manual full sync re-reads the whole history, and nearly all of it is
already stored. Each reading cost a findFirst plus an update, and every
re-read day was marked dirty, so the run refolded the DAY tier and queued
three coarse recomputes for every day of the account: about thirty
seconds of database work behind 1.4 s of Withings calls.

The measures leg now reads the stored rows of the window once, skips the
unchanged ones, updates the changed or tombstoned ones and inserts the
new ones in batches. Only inserted or changed readings count as touched.
recomputeBucketsForKeys refolds exactly the touched DAY buckets in one
aggregate and one transaction, and queues one coarse job per run of
adjacent buckets instead of three per day.
2026-10-20 20:13:13 +02:00
Marc-André Bombeck fea1d1e0b5 chore: strip planning tags from comments and enable the rule
Output of `node scripts/strip-planning-tags.mjs --write`: 3,157 comment
lines in 1,308 files lose the work-item identifier at their head. Release
versions and issue references stay; nothing outside comment text
changes, which the script checks per file against a comment-free
printout. healthlog/no-planning-tags is enabled at error level with this
commit, so the tree it lands on is clean.
2026-10-20 09:08:40 +02:00
Marc-André Bombeck 118494b91d test(rollups): stop the summary parity tests going red after midnight UTC
The two summary tests seeded readings counting back from the current
instant, a few more minutes earlier each, and compared the composed mean
with a plain AVG. Between midnight and about 01:15 UTC the earliest
minutes slid into the day before, a day gained a reading in another hour,
and for pulse, whose day is a mean of hourly means, the composed mean
left the plain AVG. The fixtures now count back from noon UTC yesterday,
and the expected mean is the one each type documents: AVG, or the mean
of hourly-mean days.
2026-10-11 12:54:37 +02:00
BernhardBuckel 497da7ca8b fix(pulse): make a day of pulse the mean of its hours' means
A watch samples pulse about every 5 to 10 minutes at rest and about every
5 seconds in a workout. The daily value was the plain mean over a day's
readings, so a workout hour of several hundred readings outweighed the other
23 hours: a day with one hard hour and 23 resting hours (60 bpm) read about
128 bpm, not 64.

A day of pulse is now the mean of its per-hour means, in the rollup DAY
bucket, the live per-day read the chart serves, and the series route's day
grain. Windows over several days weigh each day once instead of by its
reading count, so the bias does not return one level up. Reading counts,
min/max, and the regression sums still run over every reading. The chart
tooltip and the data table name the statistic, in all seven locales.

The statistic is limited to pulse; HRV, SpO2 and glucose keep the plain mean
(see src/lib/measurements/day-statistic.ts). Stored DAY rows are rewritten by
the next rollup recompute (admin recompute route), not on read.

Fixes #1107
2026-10-04 10:41:08 +02:00
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00
Marc-André Bombeck f903b320af test(rollups): compare rounded rollup means to live SQL within hundredths, not bit-for-bit
The rollup tier rounds its bucket mean to two decimals and live SQL rounds the
full-day AVG to two decimals; rounding through different intermediate paths can
land one cent apart on a half-cent boundary, so the 5-decimal toBeCloseTo was a
data-dependent flake. Assert to within a few hundredths; min/max stay exact.
2026-06-15 02:11:04 +02:00
Marc-André Bombeck 1114cf7e63 chore(release): v1.4.40 — comprehensive Critical+High architecture closure plus iOS PB30 enablement
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.
2026-05-21 12:48:02 +02:00
Marc-André Bombeck 9c13cba3f7 chore(release): v1.4.38.5
Analytics fast-path restored on long-running accounts. The boot-time
rollup-backfill discovery query was matching only ZERO-rollup users;
any account that ever folded silently stranded the moment a brand-
new measurement type landed. Discovery now widens to per-type
missing coverage so the boot-backfill queue picks these accounts up
on the next deploy and folds them back into the fast-path.
2026-05-18 06:07:14 +02:00
Marc-André Bombeck 61208da6ec test(rollups): force synchronous recompute before reading the aggregate
The v1.4.37.1 read-path hotfix changed `ensureUserRollupsFresh` from
an awaited populator to a fire-and-forget. On a cold testcontainer
the integration test inserts measurements and immediately reads
through `buildComprehensiveAggregate`, which now no longer waits for
the rollup-write path to land — the coverage probe sees no buckets
and the `dailyByType` branch returns an empty object.

Calls `recomputeUserRollups(user.id)` explicitly in the test setup
so the rollup-driven branch is exercised deterministically. Matches
the production warm-cache contract (the test was always asserting
the rollup-fed shape, just no longer racing against it).
2026-05-17 23:55:44 +02:00
Marc-André Bombeck 0e2a99f8e7 v1.4.36 — perf, charts, AI payload trim, UX punch
Heavy-aggregate skip on the rollup-fresh + fully-covered path,
daily chart fetches opt into rollup buckets via source=rollup,
Insights extractFeatures swap drops the prompt payload from
double-digit MB to the low hundreds of kB on power-user accounts,
page-level isLoading gate replaced with per-section early-skeleton
paint, trends-row chart slot pinned for CLS, IntakeHistoryListV2
restored on the medication detail page, About card folded into the
Admin Console with an auto-checking update badge, cumulative metric
tiles read the day's running sum, IP-whois fallback surfaces city
+ country when the ASN lookup misses. Migration 0068 adds an
additive users.insights_exclude_metrics column. Unit suite 4354
passing; typecheck + lint clean.
2026-05-17 13:22:51 +02:00
Marc-André Bombeck 5a3ca330ac v1.4.35.1 — auto-converge rollups on worker boot
Removes the operator step that was implicit in the v1.4.35 foundation
release. Self-hosters no longer need to run scripts/backfill-rollups
after upgrade; the worker boot discovers any account with measurements
but no rollup coverage and enqueues a one-shot full fold via pg-boss.

New rollup-full-backfill queue + handler folds one user per job at
serial concurrency. enqueueBootTimeRollupBackfill runs once at
startReminderWorker boot, idempotent across reboots via the
discovery query (matches only zero-rollup accounts) and a
singleton-key per user so fast restarts coalesce.

Best-effort: discovery errors surface through the helper's return
value and are logged via workerLog; the worker boot never fails
because the backfill missed.

Tests: 4285 unit + 230 integration. tsc + lint clean.
2026-05-17 11:29:00 +02:00
Marc-André Bombeck 8dc9edb65e v1.4.35 — persistent measurement rollups + partial read-swap
Foundation release for the persistent measurement_rollups cache tier.
Adds the additive table + enum, populates it from every measurement
write hook, lands the Apple-Health-import end-of-job backfill, and
ships the parity-checked read-swap on two reader surfaces.

The comprehensive insights aggregator and the slim analytics
summaries slice now source count / min / max / mean per type from the
DAY rollup buckets via aggregateBuckets, plus the comprehensive
dailyByType correlation feed straight from the bucket means. Slope /
R² / standard deviation / anomaly counts stay on live SQL — they do
not compose across DAY buckets. A defensive parity check against the
live aggregate's COUNT(*) falls back to live SQL when divergence is
detected.

Includes the new scripts/backfill-rollups.ts CLI for the one-shot
fold of historical measurements into all four granularities.

Tests: 4280 unit + 228 integration. tsc + lint clean. Migration
0067_v1434_measurement_rollups is idempotent under re-run.
2026-05-17 09:55:33 +02:00