diff --git a/scripts/README.md b/scripts/README.md index 18da03099..a2640cd17 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,6 +1,6 @@ # `scripts/` -One-shot operator and build scripts. These are not part of the running application — they are run by hand during a deploy, a backfill, or a release, or wired into CI. Recurring work belongs on pg-boss (`src/lib/jobs/`), not here. +One-shot operator and build scripts. They are not part of the running application: they are run by hand during a deploy, a backfill, or a release, or wired into CI. Recurring work belongs on pg-boss (`src/lib/jobs/`), not here. ## How to run them @@ -18,23 +18,27 @@ typecheck. ## What lives here -- **`generate-openapi.ts`** — emits `docs/api/openapi.yaml` from the registered Zod schemas (`pnpm openapi:generate`). -- **`check-openapi.ts`** — compares a freshly generated spec against the committed YAML; CI fails on drift (`pnpm openapi:check`). -- **`check-env.ts`** — pre-deploy env-var sanity check against `env-manifest.json` (`pnpm check-env`); wired into CI. -- **`assert-deploy.ts`** — post-deploy `/api/version` assertion. -- **`rotate-encryption-key.ts`** — AES-256-GCM key rotation; see `docs/ops/encryption-key-rotation.md`. -- **`restore-backup.ts`** — restore an off-host AES-GCM backup from an S3-compatible bucket. -- **`repair-apple-health-distance.ts`** — repair walking-distance rows the `export.zip` import stored 1000x too small; dry-run by default, see `docs/ops/apple-health-distance-repair.md`. -- **`repair-intake-anomalies.ts`** — repair historic medication-ledger defects (duplicate dose-slot rows, implausible `taken_at`); dry-run by default, see `docs/ops/intake-repair.md`. -- **`backfill-rollups.ts`**, **`drain-per-sample-cumulative.ts`**, **`backfill-mood-note-column.ts`** — historical data backfills (note: the boot-time `rollup-full-backfill` queue handles new accounts automatically; the CLI is the manual fallback). -- **`seed-demo.ts`** + **`seed-demo-v15.sql`** — populate a demo tenant. -- **`bench/`** — `seed-large-account.ts` (a synthetic ten-year account of about 1.26 million measurement rows) and `run.ts` (latency and SQL statements per request against it); see `docs/contributing/benchmarks.md`. Results land in `bench/results/`. -- **`test-notifications.ts`** — exercise the notification dispatcher + reminder check. -- **`fetch-geolite2.sh`** — download the MaxMind GeoLite2 MMDBs (needs a licence key) before `docker build`. -- **`build-pdf-fonts.py`** — rebuild the fonts the doctor-report PDF embeds (`src/lib/doctor-report-pdf/fonts/`) from the upstream Noto sources; needs `fonttools`. The source URLs are in its header. -- **`install-hooks.sh`** — install the local git hooks. -- **`generate-sw-version.mjs`**, **`print-bundle-report.mjs`** — service-worker version stamp and bundle-size report. -- **`i18n/`** — locale tooling; **`__tests__/`** — tests for the scripts above. +- **`generate-openapi.ts`**: emits `docs/api/openapi.yaml` from the registered Zod schemas (`pnpm openapi:generate`). +- **`check-openapi.ts`**: compares a freshly generated spec against the committed YAML; CI fails on drift (`pnpm openapi:check`). +- **`check-env.ts`**: pre-deploy env-var sanity check against `env-manifest.json` (`pnpm check-env`); wired into CI. +- **`assert-deploy.ts`**: post-deploy `/api/version` assertion. +- **`rotate-encryption-key.ts`**: AES-256-GCM key rotation; see `docs/ops/encryption-key-rotation.md`. +- **`restore-backup.ts`**: restore an off-host AES-GCM backup from an S3-compatible bucket. +- **`repair-apple-health-distance.ts`**: repair walking-distance rows the `export.zip` import stored 1000x too small; dry-run by default, see `docs/ops/apple-health-distance-repair.md`. +- **`repair-intake-anomalies.ts`**: repair historic medication-ledger defects (duplicate dose-slot rows, implausible `taken_at`); dry-run by default, see `docs/ops/intake-repair.md`. +- **`backfill-rollups.ts`**, **`drain-per-sample-cumulative.ts`**, **`backfill-mood-note-column.ts`**: historical data backfills (the boot-time `rollup-full-backfill` queue handles new accounts automatically; the CLI is the manual fallback). +- **`seed-demo.ts`** + **`seed-demo-v15.sql`**: populate a demo tenant. +- **`bench/`**: `seed-large-account.ts` (a synthetic ten-year account of about 1.26 million measurement rows) and `run.ts` (latency and SQL statements per request against it); see `docs/contributing/benchmarks.md`. Results land in `bench/results/`. +- **`test-notifications.ts`**: exercise the notification dispatcher + reminder check. +- **`fetch-geolite2.sh`**: download the MaxMind GeoLite2 MMDBs (needs a licence key) before `docker build`. +- **`build-pdf-fonts.py`**: rebuild the fonts the doctor-report PDF embeds (`src/lib/doctor-report-pdf/fonts/`) from the upstream Noto sources; needs `fonttools`. The source URLs are in its header. +- **`install-hooks.sh`**: install the local git hooks. +- **`generate-sw-version.mjs`**, **`check-bundle-budget.mjs`**: service-worker version stamp and bundle-size report (`pnpm bundle-report`). +- **`repair-google-health-sleep.ts`**: re-syncs every Google Health connection so nights stored twice before v1.28.18 collapse to one set; dry-run by default, `--run` to execute. +- **`i18n-key-tool.mjs`**, **`split-description.mjs`**: edit one key across every `messages/*.json` (show, set, drop) and move an over-long card description's tail into its own key; **`measure-description-lengths.mjs`** measures the Settings and Admin description strings against the 120-character guideline. +- **`audit-column-readers.ts`**: lists write-only schema columns and open `@pending-consumer` markers; a review tool, not a gate. +- **`migrate-demo-edge01.sh`**: copies the demo account from one instance to a separate demo instance; see its README. +- **`__tests__/`**: tests for the scripts above. ## Conventions diff --git a/scripts/bench/results/v1.44.0-baseline.json b/scripts/bench/results/v1.44.0-baseline.json deleted file mode 100644 index d6c5cc5dd..000000000 --- a/scripts/bench/results/v1.44.0-baseline.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "meta": { - "label": "v1.44.0-baseline", - "commit": "d640eea32", - "date": "2026-10-22T03:54:05.746Z", - "node": "v22.23.2", - "postgres": "16.15", - "cpus": 14, - "loadAverage": [ - 9.4, - 20.1, - 20 - ], - "measurements": 1266549, - "iterations": 20, - "warmup": 3, - "heapMb": 792, - "idleStatementsPerSecond": 0, - "batch5k": { - "totalMs": 1722, - "totalStatements": 424 - } - }, - "results": [ - { - "name": "/api/auth/me", - "mode": "cold", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 12.1, - "p95Ms": 17.4, - "maxMs": 18.4, - "statementsMedian": 19, - "statementsMax": 19, - "failures": 0 - }, - { - "name": "/api/auth/me", - "mode": "warm", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 16.6, - "p95Ms": 22.2, - "maxMs": 24.4, - "statementsMedian": 19, - "statementsMax": 19, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "cold", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 205.6, - "p95Ms": 233.2, - "maxMs": 275.3, - "statementsMedian": 31, - "statementsMax": 32, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "warm", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 12.8, - "p95Ms": 14.3, - "maxMs": 27.5, - "statementsMedian": 7, - "statementsMax": 7, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "cold", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 2157.8, - "p95Ms": 2252.7, - "maxMs": 2292.9, - "statementsMedian": 158, - "statementsMax": 161, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "warm", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 15.6, - "p95Ms": 23.5, - "maxMs": 25.6, - "statementsMedian": 13, - "statementsMax": 16, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 50.8, - "p95Ms": 58.7, - "maxMs": 58.9, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 52.3, - "p95Ms": 59.5, - "maxMs": 59.9, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 289, - "p95Ms": 311.3, - "maxMs": 363.7, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 274.3, - "p95Ms": 355.8, - "maxMs": 366.8, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 1502.6, - "p95Ms": 2117.6, - "maxMs": 2122.9, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 1490.3, - "p95Ms": 1505.5, - "maxMs": 1555.2, - "statementsMedian": 8, - "statementsMax": 11, - "failures": 0 - }, - { - "name": "day view", - "mode": "cold", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 61.7, - "p95Ms": 77.5, - "maxMs": 86.1, - "statementsMedian": 49, - "statementsMax": 50, - "failures": 0 - }, - { - "name": "day view", - "mode": "warm", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 49, - "p95Ms": 55.6, - "maxMs": 62.5, - "statementsMedian": 48, - "statementsMax": 48, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "cold", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 124.1, - "p95Ms": 139.5, - "maxMs": 141.5, - "statementsMedian": 68, - "statementsMax": 68, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "warm", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 124.7, - "p95Ms": 157.8, - "maxMs": 165.3, - "statementsMedian": 68, - "statementsMax": 68, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "cold", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 95.4, - "p95Ms": 105, - "maxMs": 108.8, - "statementsMedian": 45, - "statementsMax": 45, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "warm", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 13, - "p95Ms": 15.4, - "maxMs": 15.9, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "batch ingest 5k (per 500)", - "mode": "write", - "request": "POST /api/measurements/batch ×10", - "n": 10, - "p50Ms": 163, - "p95Ms": 220.7, - "maxMs": 220.7, - "statementsMedian": 42, - "statementsMax": 45, - "failures": 0 - } - ] -} diff --git a/scripts/bench/results/v1.44.0-baseline.md b/scripts/bench/results/v1.44.0-baseline.md deleted file mode 100644 index 09ecb185e..000000000 --- a/scripts/bench/results/v1.44.0-baseline.md +++ /dev/null @@ -1,25 +0,0 @@ -# Benchmark v1.44.0-baseline - -Commit d640eea32, 2026-10-22T03:54:05.746Z. 1266549 measurement rows on the bench account, 20 measured requests per row after 3 warm-up requests, one at a time. Server: production build, web process, heap ceiling 792 MB. Postgres 16.15, 14 cores, load average 9.4,20.1,20 at the end. Idle background: 0 statements/s. - -| Request | Mode | n | p50 ms | p95 ms | max ms | statements (median) | statements (max) | -| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| /api/auth/me | cold | 20 | 12.1 | 17.4 | 18.4 | 19 | 19 | -| /api/auth/me | warm | 20 | 16.6 | 22.2 | 24.4 | 19 | 19 | -| dashboard summary | cold | 20 | 205.6 | 233.2 | 275.3 | 31 | 32 | -| dashboard summary | warm | 20 | 12.8 | 14.3 | 27.5 | 7 | 7 | -| dashboard snapshot | cold | 20 | 2157.8 | 2252.7 | 2292.9 | 158 | 161 | -| dashboard snapshot | warm | 20 | 15.6 | 23.5 | 25.6 | 13 | 16 | -| series pulse 30 d | cold | 20 | 50.8 | 58.7 | 58.9 | 8 | 9 | -| series pulse 30 d | warm | 20 | 52.3 | 59.5 | 59.9 | 8 | 8 | -| series pulse 365 d | cold | 20 | 289 | 311.3 | 363.7 | 8 | 9 | -| series pulse 365 d | warm | 20 | 274.3 | 355.8 | 366.8 | 8 | 8 | -| series pulse 3650 d | cold | 20 | 1502.6 | 2117.6 | 2122.9 | 8 | 9 | -| series pulse 3650 d | warm | 20 | 1490.3 | 1505.5 | 1555.2 | 8 | 11 | -| day view | cold | 20 | 61.7 | 77.5 | 86.1 | 49 | 50 | -| day view | warm | 20 | 49 | 55.6 | 62.5 | 48 | 48 | -| insights health status | cold | 20 | 124.1 | 139.5 | 141.5 | 68 | 68 | -| insights health status | warm | 20 | 124.7 | 157.8 | 165.3 | 68 | 68 | -| insights correlations | cold | 20 | 95.4 | 105 | 108.8 | 45 | 45 | -| insights correlations | warm | 20 | 13 | 15.4 | 15.9 | 8 | 8 | -| batch ingest 5k (per 500) | write | 10 | 163 | 220.7 | 220.7 | 42 | 45 | diff --git a/scripts/bench/results/v1.45.0-before-boot.json b/scripts/bench/results/v1.45.0-before-boot.json deleted file mode 100644 index 65e2ae420..000000000 --- a/scripts/bench/results/v1.45.0-before-boot.json +++ /dev/null @@ -1,1351 +0,0 @@ -{ - "meta": { - "label": "v1.45.0-before-boot", - "commit": "0f6d668df", - "date": "2026-10-22T04:36:55.826Z", - "node": "v22.23.2", - "cpus": 14, - "loadAverage": [ - 9.3, - 9.4, - 11.2 - ], - "heapMb": 792, - "idleSeconds": 300, - "healthyAtMs": 1590, - "readyAtMs": 5432, - "stallMaxMs": 0 - }, - "bootLines": [], - "stalls": [], - "heapLines": [ - { - "atMs": 5752, - "heap_used_mb": 184, - "heap_total_mb": 219, - "heap_limit_mb": 840, - "heap_used_ratio": 0.219, - "new_space_mb": 1, - "old_space_mb": 105, - "code_space_mb": 4, - "large_object_space_mb": 68, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 6, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 267, - "external_mb": 11, - "array_buffers_mb": 0, - "active_requests": 0, - "active_jobs": 0, - "in_flight_dropped": 0, - "uptime_s": 6 - }, - { - "atMs": 307706, - "heap_used_mb": 164, - "heap_total_mb": 170, - "heap_limit_mb": 840, - "heap_used_ratio": 0.196, - "new_space_mb": 0, - "old_space_mb": 99, - "code_space_mb": 5, - "large_object_space_mb": 58, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 3, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 623, - "external_mb": 11, - "array_buffers_mb": 0, - "active_requests": 0, - "active_jobs": 1, - "in_flight_dropped": 0, - "uptime_s": 308 - } - ], - "markCompacts": [ - { - "atMs": 307, - "beforeMb": 21.4, - "afterMb": 17.4 - }, - { - "atMs": 560, - "beforeMb": 49.9, - "afterMb": 47.8 - }, - { - "atMs": 685, - "beforeMb": 79.8, - "afterMb": 79.1 - }, - { - "atMs": 1324, - "beforeMb": 159.8, - "afterMb": 154.8 - }, - { - "atMs": 9449, - "beforeMb": 189.6, - "afterMb": 172.9 - }, - { - "atMs": 10070, - "beforeMb": 174.4, - "afterMb": 170.7 - }, - { - "atMs": 69903, - "beforeMb": 179.9, - "afterMb": 171.2 - }, - { - "atMs": 151031, - "beforeMb": 191.1, - "afterMb": 174.3 - }, - { - "atMs": 153639, - "beforeMb": 193.8, - "afterMb": 171.6 - }, - { - "atMs": 158553, - "beforeMb": 204.6, - "afterMb": 161.5 - }, - { - "atMs": 163529, - "beforeMb": 191.2, - "afterMb": 161.4 - }, - { - "atMs": 167487, - "beforeMb": 185.6, - "afterMb": 161.5 - }, - { - "atMs": 170381, - "beforeMb": 187.3, - "afterMb": 161.7 - }, - { - "atMs": 173875, - "beforeMb": 192.7, - "afterMb": 162.2 - }, - { - "atMs": 177580, - "beforeMb": 185.9, - "afterMb": 162.2 - }, - { - "atMs": 183462, - "beforeMb": 189.4, - "afterMb": 162.3 - }, - { - "atMs": 188340, - "beforeMb": 181.6, - "afterMb": 163.6 - }, - { - "atMs": 193243, - "beforeMb": 184.7, - "afterMb": 163.7 - }, - { - "atMs": 197708, - "beforeMb": 186.5, - "afterMb": 164.6 - }, - { - "atMs": 201596, - "beforeMb": 188.1, - "afterMb": 164.7 - }, - { - "atMs": 205007, - "beforeMb": 182.3, - "afterMb": 163.6 - }, - { - "atMs": 208279, - "beforeMb": 190.3, - "afterMb": 164.3 - }, - { - "atMs": 212210, - "beforeMb": 184.4, - "afterMb": 164.6 - }, - { - "atMs": 217239, - "beforeMb": 197.7, - "afterMb": 163.6 - }, - { - "atMs": 222139, - "beforeMb": 190.6, - "afterMb": 165 - }, - { - "atMs": 226947, - "beforeMb": 189.7, - "afterMb": 164.5 - }, - { - "atMs": 232566, - "beforeMb": 194.2, - "afterMb": 164.4 - }, - { - "atMs": 235870, - "beforeMb": 189.2, - "afterMb": 164.9 - }, - { - "atMs": 239409, - "beforeMb": 182.9, - "afterMb": 163.4 - }, - { - "atMs": 243098, - "beforeMb": 184.4, - "afterMb": 163.7 - }, - { - "atMs": 248670, - "beforeMb": 193.1, - "afterMb": 164.9 - }, - { - "atMs": 252268, - "beforeMb": 184.2, - "afterMb": 164.2 - }, - { - "atMs": 257945, - "beforeMb": 190.5, - "afterMb": 163.7 - }, - { - "atMs": 261312, - "beforeMb": 190.4, - "afterMb": 166.8 - }, - { - "atMs": 265221, - "beforeMb": 188.2, - "afterMb": 163.7 - }, - { - "atMs": 269662, - "beforeMb": 188.6, - "afterMb": 164.2 - }, - { - "atMs": 273624, - "beforeMb": 187.7, - "afterMb": 164.2 - }, - { - "atMs": 277464, - "beforeMb": 186.3, - "afterMb": 163.8 - }, - { - "atMs": 281950, - "beforeMb": 196.7, - "afterMb": 163.5 - }, - { - "atMs": 285110, - "beforeMb": 186.2, - "afterMb": 164.6 - }, - { - "atMs": 290497, - "beforeMb": 193.9, - "afterMb": 164.2 - }, - { - "atMs": 295540, - "beforeMb": 190.6, - "afterMb": 164.4 - }, - { - "atMs": 299084, - "beforeMb": 183.5, - "afterMb": 164.4 - }, - { - "atMs": 305318, - "beforeMb": 191.1, - "afterMb": 164.4 - }, - { - "atMs": 305585, - "beforeMb": 175.3, - "afterMb": 163.9 - }, - { - "atMs": 305613, - "beforeMb": 163.9, - "afterMb": 163.9 - }, - { - "atMs": 308911, - "beforeMb": 174, - "afterMb": 163.7 - } - ], - "tasks": [ - { - "startMs": 5, - "durationMs": 279, - "byModule": [ - [ - "(native)", - 122.3 - ], - [ - "npm:next", - 57.1 - ], - [ - "node:internal/modules/cjs/loader", - 41.7 - ], - [ - "node:internal/bootstrap/realm", - 24.7 - ], - [ - "node:fs", - 5.3 - ], - [ - "node:internal/deps/undici/undici", - 5.3 - ], - [ - "node:internal/modules/package_json_reader", - 4.5 - ], - [ - "node:path", - 4 - ], - [ - "node:internal/streams/destroy", - 1.5 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 1.5 - ], - [ - "node:zlib", - 1.5 - ], - [ - "node:worker_threads", - 1.5 - ], - [ - "node:internal/net", - 1.5 - ], - [ - "node:events", - 1.5 - ], - [ - "node:internal/dns/callback_resolver", - 1.3 - ] - ], - "byFunction": [ - [ - "readFileUtf8 (native)", - 63.6 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 29.6 - ], - [ - "compileForInternalLoader node:internal/bootstrap/realm", - 24.7 - ], - [ - "startServer npm:next", - 22.8 - ], - [ - "internalModuleStat (native)", - 19.1 - ], - [ - "dlopen (native)", - 16.1 - ], - [ - "(garbage collector) (native)", - 10.5 - ], - [ - "compileFunction (native)", - 8.5 - ], - [ - "(anonymous) npm:next", - 8.2 - ], - [ - "(anonymous) node:internal/modules/cjs/loader", - 7.9 - ], - [ - "stat node:internal/modules/cjs/loader", - 4.2 - ], - [ - "realpathSync node:fs", - 4.1 - ], - [ - "__require node:internal/deps/undici/undici", - 4.1 - ], - [ - "normalizeString node:path", - 4 - ], - [ - "__nccwpck_require__ npm:next", - 3.9 - ] - ], - "byEntry": [] - }, - { - "startMs": 339, - "durationMs": 324, - "byModule": [ - [ - "(native)", - 87.6 - ], - [ - "node:internal/modules/cjs/loader", - 54.2 - ], - [ - "npm:@prisma/client", - 38 - ], - [ - "wasm://wasm/00d1cb12", - 36 - ], - [ - "node:internal/modules/esm/utils", - 26.7 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 24.9 - ], - [ - "npm:next", - 22.1 - ], - [ - "npm:zod", - 6 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 3.1 - ], - [ - "npm:@prisma/client-runtime-utils", - 3 - ], - [ - "node:fs", - 2.9 - ], - [ - "node:internal/bootstrap/realm", - 2.8 - ], - [ - "npm:pg-protocol", - 1.8 - ], - [ - "node:path", - 1.5 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 1.5 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 47.1 - ], - [ - "(garbage collector) (native)", - 40.1 - ], - [ - "readFileUtf8 (native)", - 27 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 26.7 - ], - [ - "#u npm:@prisma/client", - 23.8 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 23.3 - ], - [ - "(anonymous) npm:next", - 6 - ], - [ - "(anonymous) npm:@prisma/client", - 6 - ], - [ - "(program) (native)", - 5.2 - ], - [ - "internalModuleStat (native)", - 4.5 - ], - [ - "wasm-function[1090] wasm://wasm/00d1cb12", - 4.5 - ], - [ - "wasm-function[2550] wasm://wasm/00d1cb12", - 3.8 - ], - [ - "readPackageJSON (native)", - 3.1 - ], - [ - "defineProp turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 3.1 - ], - [ - "(anonymous) node:internal/modules/cjs/loader", - 3 - ] - ], - "byEntry": [ - [ - "src/instrumentation.ts", - 80.6 - ], - [ - "src/lib/db-session.ts", - 60.7 - ], - [ - "src/lib/notifications/types.ts", - 13.2 - ], - [ - "src/lib/boot/readiness-summary.ts", - 13.2 - ], - [ - "src/lib/ai/local-host-allowlist.ts", - 13.2 - ], - [ - "src/generated/prisma/internal/class.ts", - 7.3 - ], - [ - "src/lib/logging/context.ts", - 6 - ], - [ - "src/lib/crypto.ts", - 6 - ], - [ - "src/lib/boot/key-check.ts", - 6 - ], - [ - "src/lib/validations/measurement.ts", - 3 - ], - [ - "src/lib/validations/entry-instant.ts", - 1.5 - ], - [ - "src/lib/medications/scheduling/hhmm.ts", - 1.5 - ] - ] - }, - { - "startMs": 698, - "durationMs": 753, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 178.2 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 177.4 - ], - [ - "(native)", - 83.8 - ], - [ - "npm:zod", - 39.5 - ], - [ - "npm:next", - 34.6 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 33.9 - ], - [ - "npm:mmdb-lib", - 26.1 - ], - [ - "node:fs", - 23.5 - ], - [ - "npm:yaml", - 22.4 - ], - [ - "src/lib/activity/goal-bounds.ts", - 18.5 - ], - [ - "node:internal/modules/esm/utils", - 14.7 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 9.3 - ], - [ - "src/lib/daily/today-overview.ts", - 4.6 - ], - [ - "npm:undici", - 4.5 - ], - [ - "npm:semver", - 4.2 - ] - ], - "byFunction": [ - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 177.4 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 172.5 - ], - [ - "readFileUtf8 (native)", - 37.3 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 32.4 - ], - [ - "(garbage collector) (native)", - 29.3 - ], - [ - "readFileSync node:fs", - 22 - ], - [ - "(anonymous) npm:mmdb-lib", - 20.5 - ], - [ - "(anonymous) src/lib/activity/goal-bounds.ts", - 18.5 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 14.7 - ], - [ - "u npm:zod", - 10.3 - ], - [ - "a npm:next", - 6.3 - ], - [ - "(anonymous) npm:zod", - 5.8 - ], - [ - "module.exports.Object.defineProperty.value npm:mmdb-lib", - 5.7 - ], - [ - "(anonymous) npm:next", - 5.4 - ], - [ - "w src/lib/daily/today-overview.ts", - 4.6 - ] - ], - "byEntry": [ - [ - "src/instrumentation.ts", - 410 - ], - [ - "src/lib/jobs/reminder/register-maintenance.ts", - 279.5 - ], - [ - "src/lib/sharing/acting-session.ts", - 138.5 - ], - [ - "src/lib/jobs/offhost-backup.ts", - 137.1 - ], - [ - "src/lib/integrations/oauth-refresh.ts", - 136.3 - ], - [ - "src/lib/jobs/morning-digest-refresh-shared.ts", - 136.3 - ], - [ - "src/lib/export/environment-backup.ts", - 134.1 - ], - [ - "src/lib/export/full-backup-payload.ts", - 134.1 - ], - [ - "src/app/api/health/route.ts", - 127.5 - ], - [ - "src/lib/insights/snapshot-hash.ts", - 124.9 - ], - [ - "src/lib/export/streamed-field.ts", - 106.8 - ], - [ - "src/lib/jobs/report-worker-error.ts", - 93 - ], - [ - "src/lib/labs/biomarker-resolve.ts", - 93 - ], - [ - "src/lib/jobs/reminder/google-health-sync.ts", - 89.6 - ], - [ - "src/lib/api-errors.ts", - 77.5 - ] - ] - } - ], - "bootSelf": [ - [ - "(native)", - 524.6 - ], - [ - "node:internal/modules/cjs/loader", - 274 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 202.3 - ], - [ - "npm:next", - 136.8 - ], - [ - "wasm://wasm/00d1cb12", - 108.1 - ], - [ - "npm:@prisma/client", - 76.1 - ], - [ - "npm:pg", - 56.5 - ], - [ - "npm:zod", - 45.5 - ], - [ - "node:internal/modules/esm/utils", - 41.4 - ], - [ - "npm:luxon", - 38.5 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 35.3 - ], - [ - "node:fs", - 31.7 - ], - [ - "node:internal/bootstrap/realm", - 28.9 - ], - [ - "npm:pg-protocol", - 27.7 - ], - [ - "npm:mmdb-lib", - 26.1 - ], - [ - "npm:yaml", - 22.4 - ], - [ - "npm:pg-boss", - 19.3 - ], - [ - "src/lib/activity/goal-bounds.ts", - 18.5 - ], - [ - "node:internal/process/task_queues", - 17.7 - ], - [ - "node:internal/streams/readable", - 15.5 - ], - [ - "node:internal/async_hooks", - 15.1 - ], - [ - "npm:cron-parser", - 14.6 - ], - [ - "node:internal/timers", - 12.8 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 12.4 - ], - [ - "node:buffer", - 11.6 - ], - [ - "node:internal/buffer", - 10 - ], - [ - "node:net", - 9.8 - ], - [ - "npm:pg-pool", - 9.5 - ], - [ - "node:internal/event_target", - 7.5 - ], - [ - "node:timers", - 7.3 - ], - [ - "node:path", - 7 - ], - [ - "node:internal/deps/undici/undici", - 6.8 - ], - [ - "npm:%40prisma", - 5.8 - ], - [ - "node:internal/streams/writable", - 5.7 - ], - [ - "node:internal/stream_base_commons", - 5.2 - ], - [ - "src/lib/daily/today-overview.ts", - 4.6 - ], - [ - "node:internal/modules/package_json_reader", - 4.5 - ], - [ - "npm:undici", - 4.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 4.5 - ], - [ - "node:events", - 4.4 - ] - ], - "heap": { - "probes": { - "parsed locale catalogs": { - "count": 32, - "retainedMb": 14.1 - } - }, - "totalMb": 172.3, - "nodeCount": 1412114, - "byType": [ - [ - "string", - 79.1 - ], - [ - "array", - 27.6 - ], - [ - "code", - 18.6 - ], - [ - "object", - 16.7 - ], - [ - "hidden", - 9.3 - ], - [ - "native", - 8 - ], - [ - "object shape", - 5.8 - ], - [ - "closure", - 5 - ], - [ - "concatenated string", - 1.3 - ], - [ - "number", - 0.4 - ], - [ - "regexp", - 0.2 - ], - [ - "sliced string", - 0.2 - ], - [ - "synthetic", - 0 - ], - [ - "symbol", - 0 - ], - [ - "bigint", - 0 - ] - ], - "byConstructor": [ - [ - "array:(object elements)", - 20.6, - 24726 - ], - [ - "object:Object", - 11.9, - 300844 - ], - [ - "native:system / JSArrayBufferData", - 7.5, - 169 - ], - [ - "array:(object properties)", - 4.4, - 6540 - ], - [ - "array:", - 2.7, - 9578 - ], - [ - "object:system / Context", - 1.6, - 28887 - ], - [ - "closure:", - 1.5, - 27081 - ], - [ - "object:Date1", - 1, - 10632 - ], - [ - "closure:native_bind", - 0.9, - 20134 - ], - [ - "object:Array", - 0.6, - 19550 - ], - [ - "object:a", - 0.4, - 3880 - ], - [ - "native:Node / histogram", - 0.4, - 1 - ], - [ - "closure:get", - 0.3, - 4717 - ], - [ - "object:Set", - 0.2, - 5102 - ], - [ - "closure:set", - 0.2, - 3414 - ], - [ - "object:d", - 0.2, - 3874 - ], - [ - "object:Module", - 0.1, - 1907 - ], - [ - "object:Promise", - 0.1, - 1590 - ], - [ - "closure:n", - 0.1, - 1400 - ], - [ - "closure:i", - 0.1, - 1390 - ], - [ - "object:n", - 0.1, - 1900 - ], - [ - "object:ZodOptional", - 0.1, - 236 - ], - [ - "object:_TstNode", - 0.1, - 939 - ], - [ - "native:Node / parser_buffer", - 0.1, - 1 - ], - [ - "object:TCP", - 0, - 7 - ], - [ - "closure:callbackTrampoline", - 0, - 1 - ], - [ - "closure:promiseInitHook", - 0, - 1 - ], - [ - "closure:promiseBeforeHook", - 0, - 1 - ], - [ - "closure:promiseAfterHook", - 0, - 1 - ], - [ - "object:global", - 0, - 4 - ] - ], - "largestStrings": [ - [ - 9.7, - "module.exports=[425781,(e,a,i)=>{a.exports=JSON.parse('{\"common\":{\"listCapped\":\"Hier stehen die letz" - ], - [ - 9.7, - "module.exports=[157893,(e,a,i)=>{a.exports=JSON.parse('{\"common\":{\"listCapped\":\"Hier stehen die letz" - ], - [ - 5.6, - "module.exports=[17856,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:(" - ], - [ - 5.6, - "module.exports=[234494,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:" - ], - [ - 4.4, - "const wasm = \"AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f3" - ], - [ - 4.4, - "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f38AYAR/f39/" - ], - [ - 3.7, - "module.exports=[799092,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),Objec" - ], - [ - 1.9, - "lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAKcrACARAACS" - ], - [ - 1.3, - "(()=>{var e,t,r={\"./dist/build/webpack/alias/react-dom-server.js\"(e,t,r){\"use strict\";var n;function" - ], - [ - 1.2, - "module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BEGIN ([A-Z\\" - ], - [ - 1.1, - "module.exports=[834886,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0});var r" - ], - [ - 1.1, - "// v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. See .plan" - ], - [ - 1.1, - "module.exports=[971743,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 1, - "module.exports=[835717,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 0.7, - "module.exports=[594108,806176,674750,98746,t=>{\"use strict\";var i;let r;function n(t){return t}funct" - ], - [ - 0.7, - "module.exports=[144056,t=>{\"use strict\";let i,n;function r(t){return t}function a(t){return t}functi" - ], - [ - 0.5, - "'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); // these aren't reall" - ], - [ - 0.5, - "import assert from 'node:assert'; import { indexKeysRaw, indexIncludeRaw, indexPredicateRaw, display" - ], - [ - 0.5, - "module.exports=[21025,(e,t,r)=>{t.exports=e.x(\"next/dist/build/adapter/setup-node-env.external.js\",(" - ], - [ - 0.4, - "(()=>{var e,t,r={\"./dist/compiled/@edge-runtime/cookies/index.js\"(e){\"use strict\";var t=Object.defin" - ], - [ - 0.3, - "(()=>{var e={975:(e,t,i)=>{const r=i(485);const{isObject:s,isArray:n}=i(648);const a=\"before\";const " - ], - [ - 0.3, - "(()=>{var a={32:(a,e,i)=>{\"use strict\"; /*! * accepts * Copyright(c) 2014 Jonathan Ong * Copyrigh" - ], - [ - 0.2, - "/* eslint-disable no-restricted-syntax -- everything above the v42 entry keeps its original SQL verb" - ], - [ - 0.2, - "\"use strict\";var pu=Object.create;var xr=Object.defineProperty;var du=Object.getOwnPropertyDescripto" - ], - [ - 0.2, - "\"use strict\"; Object.defineProperty(exports, \"__esModule\", { value: true }); 0 && (module.export" - ] - ], - "topRetainers": [ - { - "mb": 33.9, - "name": "object:yr", - "path": "[1] > [10] > 15 > extension > global > prisma > [1] > _engine" - }, - { - "mb": 10, - "name": "object:Object", - "path": "tX > onClose > onClose > context > system / Context > previous > system / Context > e > Context > __proto__ > c" - }, - { - "mb": 9.8, - "name": "code:.next/server/chu", - "path": "system / Context > timerListMap > Object > [120000] > TimersList > _idleNext > Timeout > _onTimeout > > shared > script" - }, - { - "mb": 9.7, - "name": "code:.next/server/chu", - "path": "Module > _cache > Object > .next/server/chunks/_09yxiz_._.js > Module > exports > Array > [1] > > shared > script" - }, - { - "mb": 7.4, - "name": "object:system / Context", - "path": "Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "Module > _cache > Object > .next/server/chunks/_0hfea7u._.js > Module > exports > Array > [1] > > shared > script" - }, - { - "mb": 4.4, - "name": "object:Generator", - "path": "[6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4]" - }, - { - "mb": 4.4, - "name": "string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/", - "path": "[1] > [21] > 59867" - }, - { - "mb": 3.7, - "name": "code:.next/server/chu", - "path": "[1] > [10] > 15 > extension > global > __healthlog_pgboss__ > PgBoss > _events > Object > error > > shared > script" - }, - { - "mb": 3.6, - "name": "object:Client", - "path": "Object > close > > context > system / Context > self > Connection > _events > Object > parseComplete > bound_this" - } - ], - "sourceStringsMb": 44.2 - } -} diff --git a/scripts/bench/results/v1.45.0-before.json b/scripts/bench/results/v1.45.0-before.json deleted file mode 100644 index 37365e6e9..000000000 --- a/scripts/bench/results/v1.45.0-before.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "meta": { - "label": "v1.45.0-before", - "commit": "1915f9152", - "date": "2026-10-22T05:51:47.626Z", - "node": "v22.23.2", - "postgres": "16.15", - "cpus": 14, - "loadAverage": [ - 29.9, - 21.9, - 18.9 - ], - "measurements": 1265561, - "iterations": 20, - "warmup": 3, - "heapMb": 792, - "idleStatementsPerSecond": 0, - "batch5k": { - "totalMs": 1730, - "totalStatements": 534 - } - }, - "results": [ - { - "name": "/api/auth/me", - "mode": "cold", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 9.2, - "p95Ms": 11.6, - "maxMs": 13.3, - "statementsMedian": 19, - "statementsMax": 19, - "failures": 0 - }, - { - "name": "/api/auth/me", - "mode": "warm", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 8.6, - "p95Ms": 10.5, - "maxMs": 13, - "statementsMedian": 19, - "statementsMax": 19, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "cold", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 170.2, - "p95Ms": 220.5, - "maxMs": 228.3, - "statementsMedian": 31, - "statementsMax": 34, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "warm", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 6, - "p95Ms": 6.7, - "maxMs": 6.8, - "statementsMedian": 7, - "statementsMax": 7, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "cold", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 1481.8, - "p95Ms": 1526.2, - "maxMs": 1530, - "statementsMedian": 158, - "statementsMax": 161, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "warm", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 7, - "p95Ms": 7.9, - "maxMs": 10.2, - "statementsMedian": 13, - "statementsMax": 13, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 24.4, - "p95Ms": 30.8, - "maxMs": 31.1, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 20.5, - "p95Ms": 29.4, - "maxMs": 30.2, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 184.1, - "p95Ms": 192.6, - "maxMs": 201.1, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 178.1, - "p95Ms": 186.5, - "maxMs": 187.6, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 1627, - "p95Ms": 1664.5, - "maxMs": 1675.7, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 2090.9, - "p95Ms": 2435.4, - "maxMs": 2529.7, - "statementsMedian": 8, - "statementsMax": 9, - "failures": 0 - }, - { - "name": "day view", - "mode": "cold", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 76.7, - "p95Ms": 93.6, - "maxMs": 116.2, - "statementsMedian": 49, - "statementsMax": 49, - "failures": 0 - }, - { - "name": "day view", - "mode": "warm", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 62.8, - "p95Ms": 72, - "maxMs": 73.5, - "statementsMedian": 48, - "statementsMax": 48, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "cold", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 244.7, - "p95Ms": 401.2, - "maxMs": 404.1, - "statementsMedian": 68, - "statementsMax": 69, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "warm", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 238.9, - "p95Ms": 399.1, - "maxMs": 476.6, - "statementsMedian": 68, - "statementsMax": 68, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "cold", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 105.6, - "p95Ms": 326.9, - "maxMs": 349.7, - "statementsMedian": 49, - "statementsMax": 52, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "warm", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 11.3, - "p95Ms": 16.6, - "maxMs": 20.3, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "batch ingest 5k (per 500)", - "mode": "write", - "request": "POST /api/measurements/batch ×10", - "n": 10, - "p50Ms": 166.1, - "p95Ms": 220.3, - "maxMs": 220.3, - "statementsMedian": 53, - "statementsMax": 56, - "failures": 0 - } - ] -} diff --git a/scripts/bench/results/v1.45.0-before.md b/scripts/bench/results/v1.45.0-before.md deleted file mode 100644 index 40bc7e19b..000000000 --- a/scripts/bench/results/v1.45.0-before.md +++ /dev/null @@ -1,25 +0,0 @@ -# Benchmark v1.45.0-before - -Commit 1915f9152 (the v1.44 runtime this branch starts from), 2026-10-22T05:51:47.626Z. 1265561 measurement rows on the bench account, 20 measured requests per row after 3 warm-up requests, one at a time. Server: production build, web process, heap ceiling 792 MB. Postgres 16.15, 14 cores, load average 29.9,21.9,18.9 at the end. Idle background: 0 statements/s. - -| Request | Mode | n | p50 ms | p95 ms | max ms | statements (median) | statements (max) | -| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| /api/auth/me | cold | 20 | 9.2 | 11.6 | 13.3 | 19 | 19 | -| /api/auth/me | warm | 20 | 8.6 | 10.5 | 13 | 19 | 19 | -| dashboard summary | cold | 20 | 170.2 | 220.5 | 228.3 | 31 | 34 | -| dashboard summary | warm | 20 | 6 | 6.7 | 6.8 | 7 | 7 | -| dashboard snapshot | cold | 20 | 1481.8 | 1526.2 | 1530 | 158 | 161 | -| dashboard snapshot | warm | 20 | 7 | 7.9 | 10.2 | 13 | 13 | -| series pulse 30 d | cold | 20 | 24.4 | 30.8 | 31.1 | 8 | 8 | -| series pulse 30 d | warm | 20 | 20.5 | 29.4 | 30.2 | 8 | 9 | -| series pulse 365 d | cold | 20 | 184.1 | 192.6 | 201.1 | 8 | 8 | -| series pulse 365 d | warm | 20 | 178.1 | 186.5 | 187.6 | 8 | 8 | -| series pulse 3650 d | cold | 20 | 1627 | 1664.5 | 1675.7 | 8 | 9 | -| series pulse 3650 d | warm | 20 | 2090.9 | 2435.4 | 2529.7 | 8 | 9 | -| day view | cold | 20 | 76.7 | 93.6 | 116.2 | 49 | 49 | -| day view | warm | 20 | 62.8 | 72 | 73.5 | 48 | 48 | -| insights health status | cold | 20 | 244.7 | 401.2 | 404.1 | 68 | 69 | -| insights health status | warm | 20 | 238.9 | 399.1 | 476.6 | 68 | 68 | -| insights correlations | cold | 20 | 105.6 | 326.9 | 349.7 | 49 | 52 | -| insights correlations | warm | 20 | 11.3 | 16.6 | 20.3 | 8 | 8 | -| batch ingest 5k (per 500) | write | 10 | 166.1 | 220.3 | 220.3 | 53 | 56 | diff --git a/scripts/bench/results/v1.45.0-boot-after.json b/scripts/bench/results/v1.45.0-boot-after.json deleted file mode 100644 index cb435b3cb..000000000 --- a/scripts/bench/results/v1.45.0-boot-after.json +++ /dev/null @@ -1,1648 +0,0 @@ -{ - "meta": { - "label": "v1.45.0-boot-after", - "commit": "a859adb59", - "date": "2026-10-22T04:49:50.419Z", - "node": "v22.23.2", - "cpus": 14, - "loadAverage": [ - 8.6, - 9.4, - 10.7 - ], - "heapMb": 792, - "idleSeconds": 300, - "healthyAtMs": 850, - "readyAtMs": 3038, - "stallMaxMs": 0 - }, - "bootLines": [ - "3038 ms: boot.steps {\"boot_queue_count\":122,\"boot_steps\":\"readiness_summary=2,key_check=187,time_zone_check=1,pulse_method_sync=25,monitors=3,worker_import=63,boss_start=266,load_worker_lifecycle=47,load_integration_sync=119,register_integration_sync=100,load_status=52,register_status=70,load_rollup=12,register_rollup=27,load_reminders=11,register_reminders=37,load_maintenance=50,register_maintena" - ], - "stalls": [], - "heapLines": [ - { - "atMs": 5524, - "heap_used_mb": 166, - "heap_total_mb": 192, - "heap_limit_mb": 840, - "heap_used_ratio": 0.197, - "new_space_mb": 12, - "old_space_mb": 91, - "code_space_mb": 3, - "large_object_space_mb": 53, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 6, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 430, - "external_mb": 12, - "array_buffers_mb": 1, - "active_requests": 0, - "active_jobs": 0, - "in_flight_dropped": 0, - "uptime_s": 6 - }, - { - "atMs": 305529, - "heap_used_mb": 153, - "heap_total_mb": 163, - "heap_limit_mb": 840, - "heap_used_ratio": 0.182, - "new_space_mb": 1, - "old_space_mb": 90, - "code_space_mb": 4, - "large_object_space_mb": 53, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 5, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 599, - "external_mb": 11, - "array_buffers_mb": 0, - "active_requests": 0, - "active_jobs": 0, - "in_flight_dropped": 0, - "uptime_s": 306 - } - ], - "markCompacts": [ - { - "atMs": 189, - "beforeMb": 20.2, - "afterMb": 17.1 - }, - { - "atMs": 374, - "beforeMb": 55.3, - "afterMb": 52.6 - }, - { - "atMs": 458, - "beforeMb": 83.2, - "afterMb": 81.1 - }, - { - "atMs": 1036, - "beforeMb": 148.4, - "afterMb": 132.7 - }, - { - "atMs": 16410, - "beforeMb": 159.6, - "afterMb": 153.2 - }, - { - "atMs": 17036, - "beforeMb": 153.4, - "afterMb": 151 - }, - { - "atMs": 61902, - "beforeMb": 167.5, - "afterMb": 157.5 - }, - { - "atMs": 245952, - "beforeMb": 183.4, - "afterMb": 152.6 - }, - { - "atMs": 303192, - "beforeMb": 165.6, - "afterMb": 151.8 - }, - { - "atMs": 303207, - "beforeMb": 151.8, - "afterMb": 151.8 - } - ], - "tasks": [ - { - "startMs": 6, - "durationMs": 175, - "byModule": [ - [ - "npm:next", - 47.2 - ], - [ - "node:internal/modules/cjs/loader", - 34.8 - ], - [ - "(native)", - 34.5 - ], - [ - "node:internal/bootstrap/realm", - 25.7 - ], - [ - "node:internal/deps/undici/undici", - 8.8 - ], - [ - "node:path", - 6.3 - ], - [ - "node:internal/modules/package_json_reader", - 3 - ], - [ - "node:internal/net", - 3 - ], - [ - "node:internal/modules/esm/resolve", - 2.5 - ], - [ - "node:fs", - 1.5 - ], - [ - "node:buffer", - 1.5 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 1.5 - ], - [ - "node:internal/util", - 1.5 - ], - [ - "node:events", - 1.5 - ], - [ - "node:internal/modules/helpers", - 1.1 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 32.2 - ], - [ - "compileForInternalLoader node:internal/bootstrap/realm", - 25.7 - ], - [ - "startServer npm:next", - 10.5 - ], - [ - "internalModuleStat (native)", - 9.5 - ], - [ - "_export npm:next", - 7.5 - ], - [ - "__require node:internal/deps/undici/undici", - 7.3 - ], - [ - "(garbage collector) (native)", - 5.8 - ], - [ - "readFileUtf8 (native)", - 5.8 - ], - [ - "resolve node:path", - 4.5 - ], - [ - "__nccwpck_require__ npm:next", - 4.5 - ], - [ - "(anonymous) npm:next", - 4.2 - ], - [ - "compileFunction (native)", - 3 - ], - [ - "ZodType npm:next", - 3 - ], - [ - "dlopen (native)", - 3 - ], - [ - "(anonymous) node:internal/modules/cjs/loader", - 2.6 - ] - ], - "byEntry": [] - }, - { - "startMs": 203, - "durationMs": 241, - "byModule": [ - [ - "(native)", - 52.7 - ], - [ - "node:internal/modules/cjs/loader", - 40.1 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 31.7 - ], - [ - "wasm://wasm/00d1cb12", - 28.7 - ], - [ - "npm:@prisma/client", - 28 - ], - [ - "npm:next", - 16.3 - ], - [ - "node:internal/modules/esm/utils", - 12 - ], - [ - "node:fs", - 4.5 - ], - [ - "npm:zod", - 3 - ], - [ - "node:internal/modules/helpers", - 3 - ], - [ - "npm:pg", - 3 - ], - [ - "node:internal/fs/utils", - 1.5 - ], - [ - "src/lib/validations/entry-instant.ts", - 1.5 - ], - [ - "npm:undici", - 1.5 - ], - [ - "node:internal/bootstrap/realm", - 1.5 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 37.1 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 31.7 - ], - [ - "(garbage collector) (native)", - 30.6 - ], - [ - "#u npm:@prisma/client", - 22.4 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 12 - ], - [ - "wasm-function[1454] wasm://wasm/00d1cb12", - 6.1 - ], - [ - "internalModuleStat (native)", - 5.8 - ], - [ - "readFileUtf8 (native)", - 4.5 - ], - [ - "js-to-wasm (native)", - 4.5 - ], - [ - "wasm-function[2550] wasm://wasm/00d1cb12", - 4.5 - ], - [ - "(anonymous) npm:next", - 4.3 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "toRealPath node:internal/modules/helpers", - 3 - ], - [ - "(anonymous) npm:pg", - 3 - ], - [ - "Module (native)", - 3 - ] - ], - "byEntry": [ - [ - "src/instrumentation.ts", - 80.8 - ], - [ - "src/lib/db-session.ts", - 49.5 - ], - [ - "src/lib/boot/startup-state.ts", - 25.6 - ], - [ - "src/lib/boot/boot-steps.ts", - 25.6 - ], - [ - "src/lib/logging/background.ts", - 25.6 - ], - [ - "src/lib/process-type.ts", - 25.5 - ], - [ - "src/lib/logging/context.ts", - 13.6 - ], - [ - "src/lib/validations/measurement.ts", - 12.1 - ], - [ - "src/lib/logging/redact.ts", - 10.5 - ], - [ - "src/lib/validations/entry-instant.ts", - 4.6 - ], - [ - "src/generated/prisma/internal/class.ts", - 3 - ], - [ - "src/lib/medications/scheduling/hhmm.ts", - 1.5 - ], - [ - "src/lib/env.ts", - 1.5 - ] - ] - }, - { - "startMs": 501, - "durationMs": 178, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 53.9 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 37.5 - ], - [ - "npm:next", - 18 - ], - [ - "npm:zod", - 12 - ], - [ - "node:internal/modules/esm/utils", - 10.5 - ], - [ - "(native)", - 8.7 - ], - [ - "node:fs", - 6 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 4.5 - ], - [ - "npm:undici", - 4.5 - ], - [ - "node:internal/url", - 1.8 - ], - [ - "node:internal/modules/esm/resolve", - 1.5 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 1.5 - ], - [ - "node:internal/modules/esm/module_job", - 1.5 - ], - [ - "node:internal/deps/undici/undici", - 1.5 - ], - [ - "node:internal/promise_hooks", - 1.5 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 52.4 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 37.5 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 10.5 - ], - [ - "(anonymous) npm:zod", - 6.1 - ], - [ - "(anonymous) npm:next", - 6 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 4.5 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "(garbage collector) (native)", - 3 - ], - [ - "realpathSync node:fs", - 2.9 - ], - [ - "Z npm:zod", - 2.9 - ], - [ - "finalizeResolution node:internal/modules/esm/resolve", - 1.5 - ], - [ - "pathToFileURL node:internal/url", - 1.5 - ], - [ - "a npm:next", - 1.5 - ], - [ - "./dist/esm/server/app-render/manifests-singleton.js npm:next", - 1.5 - ], - [ - "a.d npm:next", - 1.5 - ] - ], - "byEntry": [ - [ - "src/app/api/health/route.ts", - 61.4 - ], - [ - "src/lib/api-errors.ts", - 27 - ], - [ - "src/lib/jobs/job-failures.ts", - 14.9 - ], - [ - "src/lib/jobs/offhost-backup.ts", - 14.9 - ], - [ - "src/lib/managed-profiles/handover-access.ts", - 14.9 - ], - [ - "src/lib/sharing/acting-session.ts", - 14.9 - ], - [ - "src/lib/db-session.ts", - 13.4 - ], - [ - "src/lib/export/environment-backup.ts", - 13.4 - ], - [ - "src/lib/export/full-backup-payload.ts", - 13.4 - ], - [ - "src/lib/logging/background.ts", - 10.5 - ], - [ - "src/lib/auth/secure-cookie.ts", - 10.5 - ], - [ - "src/lib/notifications/types.ts", - 10.5 - ], - [ - "src/lib/logging/redact.ts", - 10.5 - ], - [ - "src/lib/validations/measurement.ts", - 9 - ], - [ - "src/instrumentation.ts", - 8.7 - ] - ] - }, - { - "startMs": 833, - "durationMs": 184, - "byModule": [ - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 38.9 - ], - [ - "node:internal/modules/cjs/loader", - 28.6 - ], - [ - "(native)", - 22.8 - ], - [ - "src/lib/i18n/server-catalogs.ts", - 15.1 - ], - [ - "npm:yaml", - 15 - ], - [ - "node:fs", - 12.1 - ], - [ - "npm:zod", - 9 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 5.9 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 4.5 - ], - [ - "npm:semver", - 3 - ], - [ - "npm:next", - 2.3 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/runtime-base.ts", - 1.5 - ], - [ - "node:internal/bootstrap/realm", - 1.5 - ], - [ - "src/lib/labs/unit-conversion/registry.ts", - 1.5 - ], - [ - "src/lib/validations/user-prefs.ts", - 1.5 - ] - ], - "byFunction": [ - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 38.9 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 28.6 - ], - [ - "(garbage collector) (native)", - 18.5 - ], - [ - "(anonymous) src/lib/i18n/server-catalogs.ts", - 15.1 - ], - [ - "readFileSync node:fs", - 12.1 - ], - [ - "z npm:zod", - 4.5 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 4.5 - ], - [ - "readFileUtf8 (native)", - 4.3 - ], - [ - "(anonymous) npm:zod", - 3 - ], - [ - "next npm:yaml", - 3 - ], - [ - "loadChunkAsync turbopack:///[turbopack]/nodejs/runtime/runtime-base.ts", - 1.5 - ], - [ - "compileForInternalLoader node:internal/bootstrap/realm", - 1.5 - ], - [ - "esmExport turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 1.5 - ], - [ - "(anonymous) src/lib/labs/unit-conversion/registry.ts", - 1.5 - ], - [ - "(anonymous) src/lib/validations/user-prefs.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 129.1 - ], - [ - "src/lib/jobs/reminder/google-health-sync.ts", - 88.5 - ], - [ - "src/lib/whoop/sync.ts", - 79.5 - ], - [ - "src/lib/whoop/sync-body.ts", - 63 - ], - [ - "src/lib/insights/bucket-series.ts", - 60 - ], - [ - "src/lib/whoop/oauth-descriptor.ts", - 60 - ], - [ - "src/lib/whoop/sync-core.ts", - 60 - ], - [ - "src/instrumentation.ts", - 43.4 - ], - [ - "src/lib/import/unzip-export-xml.ts", - 31.4 - ], - [ - "src/lib/jobs/integration-status-null-probe.ts", - 31.4 - ], - [ - "src/lib/ai/coach/memory/propose-plan.ts", - 30.1 - ], - [ - "src/lib/ai/coach/refusal.ts", - 27.1 - ], - [ - "src/lib/ai/coach/outbound-guard.ts", - 25.6 - ], - [ - "src/lib/i18n/server-catalogs.ts", - 24.1 - ], - [ - "src/lib/insights/snapshot-hash.ts", - 23.9 - ] - ] - }, - { - "startMs": 1099, - "durationMs": 54, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 23.9 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 10.6 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 5.8 - ], - [ - "src/lib/daily/today-overview.ts", - 4.5 - ], - [ - "src/lib/jobs/reminder-worker.ts", - 3 - ], - [ - "node:fs", - 3 - ], - [ - "npm:pg-boss", - 1.5 - ], - [ - "npm:zod", - 1.5 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 23.9 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 10.6 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 5.8 - ], - [ - "w src/lib/daily/today-overview.ts", - 4.5 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "work npm:pg-boss", - 1.5 - ], - [ - "(anonymous) src/lib/jobs/reminder-worker.ts", - 1.5 - ], - [ - "d9 npm:zod", - 1.5 - ], - [ - "v src/lib/jobs/reminder-worker.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 52.3 - ], - [ - "src/lib/jobs/reminder/insights-handlers.ts", - 22.3 - ], - [ - "src/lib/ai/prompts/arrival-reaction.ts", - 12 - ], - [ - "src/lib/daily/score-steady.ts", - 10.5 - ], - [ - "src/lib/dashboard/bands.ts", - 6 - ], - [ - "src/lib/jobs/data-arrival.ts", - 4.5 - ], - [ - "src/lib/dashboard/snapshot-read.ts", - 4.5 - ], - [ - "src/lib/daily/today-overview.ts", - 4.5 - ], - [ - "src/lib/daily/priority-item-key.ts", - 4.5 - ], - [ - "src/lib/ai/prompts/status-batch.ts", - 3 - ], - [ - "src/lib/jobs/reminder/registrar-shared.ts", - 1.5 - ], - [ - "src/lib/jobs/reminder/register-integration-sync.ts", - 1.5 - ], - [ - "src/lib/ai/prompts/base-system.ts", - 1.5 - ], - [ - "src/lib/insights/derived/derived-assessment-ai.ts", - 1.5 - ], - [ - "src/lib/measurements/list/sleep-nights.ts", - 1.5 - ] - ] - }, - { - "startMs": 1309, - "durationMs": 51, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 28 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 6 - ], - [ - "node:fs", - 4.5 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 3 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/runtime-base.ts", - 1.5 - ], - [ - "src/lib/jobs/failure-condition.ts", - 1.5 - ], - [ - "src/lib/labs/parse-reference-range.ts", - 1.5 - ], - [ - "npm:zod", - 1.5 - ], - [ - "node:internal/linkedlist", - 1.5 - ], - [ - "(native)", - 0.9 - ], - [ - "node:buffer", - 0.6 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 28 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 6 - ], - [ - "readFileSync node:fs", - 4.5 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 3 - ], - [ - "loadChunkAsync turbopack:///[turbopack]/nodejs/runtime/runtime-base.ts", - 1.5 - ], - [ - "(anonymous) src/lib/jobs/failure-condition.ts", - 1.5 - ], - [ - "r src/lib/labs/parse-reference-range.ts", - 1.5 - ], - [ - "u npm:zod", - 1.5 - ], - [ - "isEmpty node:internal/linkedlist", - 1.5 - ], - [ - "(garbage collector) (native)", - 0.9 - ], - [ - "getEncodingOps node:buffer", - 0.6 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 48.2 - ], - [ - "src/lib/jobs/reminder/ops-handlers.ts", - 14.1 - ], - [ - "src/lib/medications/ingredients/label-extract.ts", - 3 - ], - [ - "src/lib/export/restore-backup.ts", - 2.1 - ], - [ - "src/lib/jobs/failure-condition.ts", - 1.5 - ], - [ - "src/lib/documents/ai-runs/summary-run.ts", - 1.5 - ], - [ - "src/lib/documents/auto-stage-labs.ts", - 1.5 - ], - [ - "src/lib/documents/ai-runs/store.ts", - 1.5 - ], - [ - "src/lib/labs/parse-reference-range.ts", - 1.5 - ], - [ - "src/lib/labs/ocr-upload.ts", - 1.5 - ], - [ - "src/lib/jobs/document-content-index-backfill.ts", - 1.5 - ], - [ - "src/lib/labs/ocr-extract.ts", - 1.5 - ], - [ - "src/lib/dawarich/sync.ts", - 1.5 - ], - [ - "src/lib/validations/dawarich.ts", - 1.5 - ], - [ - "src/lib/jobs/auth-ledger-cleanup.ts", - 1.5 - ] - ] - } - ], - "bootSelf": [ - [ - "(native)", - 230.8 - ], - [ - "node:internal/modules/cjs/loader", - 223.6 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 124.7 - ], - [ - "npm:next", - 98.8 - ], - [ - "wasm://wasm/00d1cb12", - 62.6 - ], - [ - "npm:@prisma/client", - 56.2 - ], - [ - "node:fs", - 34.6 - ], - [ - "node:internal/bootstrap/realm", - 28.7 - ], - [ - "npm:pg", - 28.3 - ], - [ - "npm:zod", - 27 - ], - [ - "npm:pg-boss", - 24.6 - ], - [ - "node:internal/modules/esm/utils", - 22.5 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 20.6 - ], - [ - "npm:luxon", - 17 - ], - [ - "src/lib/i18n/server-catalogs.ts", - 15.1 - ], - [ - "npm:yaml", - 15 - ], - [ - "npm:pg-pool", - 13 - ], - [ - "node:internal/deps/undici/undici", - 10.4 - ], - [ - "node:path", - 7.8 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 7.5 - ], - [ - "node:internal/async_hooks", - 7.5 - ], - [ - "node:internal/process/task_queues", - 7.3 - ], - [ - "node:internal/modules/package_json_reader", - 6 - ], - [ - "npm:undici", - 6 - ], - [ - "npm:pg-protocol", - 6 - ], - [ - "npm:cron-parser", - 6 - ], - [ - "node:internal/stream_base_commons", - 5.7 - ], - [ - "node:events", - 5.5 - ], - [ - "node:internal/timers", - 5.5 - ], - [ - "node:internal/streams/writable", - 5.3 - ], - [ - "node:internal/async_local_storage/async_hooks", - 5.3 - ], - [ - "node:buffer", - 5.2 - ], - [ - "node:internal/util", - 4.5 - ], - [ - "npm:%40prisma", - 4.5 - ], - [ - "node:internal/crypto/random", - 4.5 - ], - [ - "src/lib/daily/today-overview.ts", - 4.5 - ], - [ - "node:internal/modules/helpers", - 4.2 - ], - [ - "node:internal/modules/esm/resolve", - 4 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 3 - ], - [ - "node:internal/net", - 3 - ] - ], - "heap": { - "probes": { - "parsed locale catalogs": { - "count": 8, - "retainedMb": 7 - } - }, - "totalMb": 160.1, - "nodeCount": 1275329, - "byType": [ - [ - "string", - 68.7 - ], - [ - "array", - 26.4 - ], - [ - "code", - 21.1 - ], - [ - "object", - 14.1 - ], - [ - "hidden", - 8.9 - ], - [ - "native", - 8 - ], - [ - "object shape", - 5.9 - ], - [ - "closure", - 5.2 - ], - [ - "concatenated string", - 1.3 - ], - [ - "regexp", - 0.2 - ], - [ - "sliced string", - 0.2 - ], - [ - "number", - 0.1 - ], - [ - "synthetic", - 0 - ], - [ - "symbol", - 0 - ], - [ - "bigint", - 0 - ] - ], - "byConstructor": [ - [ - "array:(object elements)", - 20.4, - 23797 - ], - [ - "object:Object", - 10.2, - 274231 - ], - [ - "native:system / JSArrayBufferData", - 7.6, - 171 - ], - [ - "array:(object properties)", - 3.6, - 6512 - ], - [ - "array:", - 2.4, - 10502 - ], - [ - "closure:", - 1.6, - 29042 - ], - [ - "object:system / Context", - 1.6, - 28873 - ], - [ - "closure:native_bind", - 1, - 20782 - ], - [ - "object:Array", - 0.6, - 19679 - ], - [ - "object:a", - 0.4, - 4438 - ], - [ - "native:Node / histogram", - 0.4, - 1 - ], - [ - "closure:get", - 0.3, - 4736 - ], - [ - "object:d", - 0.3, - 4425 - ], - [ - "object:Set", - 0.2, - 5673 - ], - [ - "closure:set", - 0.2, - 3432 - ], - [ - "object:Module", - 0.2, - 1958 - ], - [ - "closure:i", - 0.1, - 1581 - ], - [ - "closure:n", - 0.1, - 1572 - ], - [ - "object:Promise", - 0.1, - 1277 - ], - [ - "object:n", - 0.1, - 1881 - ], - [ - "object:ZodOptional", - 0.1, - 236 - ], - [ - "object:_TstNode", - 0.1, - 939 - ], - [ - "native:Node / parser_buffer", - 0.1, - 1 - ], - [ - "object:TCP", - 0, - 8 - ], - [ - "closure:callbackTrampoline", - 0, - 1 - ], - [ - "closure:promiseInitHook", - 0, - 1 - ], - [ - "closure:promiseBeforeHook", - 0, - 1 - ], - [ - "closure:promiseAfterHook", - 0, - 1 - ], - [ - "object:Signal", - 0, - 4 - ], - [ - "object:HTTPParser", - 0, - 2 - ] - ], - "largestStrings": [ - [ - 5.6, - "module.exports=[234494,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:" - ], - [ - 5.6, - "module.exports=[17856,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:(" - ], - [ - 5.6, - "module.exports=[17856,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:(" - ], - [ - 4.4, - "const wasm = \"AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f3" - ], - [ - 4.4, - "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f38AYAR/f39/" - ], - [ - 1.9, - "lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAKcrACARAACS" - ], - [ - 1.3, - "(()=>{var e,t,r={\"./dist/build/webpack/alias/react-dom-server.js\"(e,t,r){\"use strict\";var n;function" - ], - [ - 1.2, - "module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BEGIN ([A-Z\\" - ], - [ - 1.2, - "module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BEGIN ([A-Z\\" - ], - [ - 1.1, - "module.exports=[799092,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),Objec" - ], - [ - 1.1, - "// v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. See .plan" - ], - [ - 1.1, - "module.exports=[971743,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 1, - "module.exports=[835717,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 0.8, - "module.exports=[767189,e=>e.a(async(t,a)=>{try{var n=e.i(64105),r=e.i(563616),i=t([n]);[n]=i.then?(a" - ], - [ - 0.7, - "module.exports=[594108,806176,674750,98746,t=>{\"use strict\";var i;let r;function n(t){return t}funct" - ], - [ - 0.7, - "module.exports=[144056,t=>{\"use strict\";let i,n;function r(t){return t}function a(t){return t}functi" - ], - [ - 0.6, - "module.exports=[992657,e=>e.a(async(t,a)=>{try{var r=e.i(851258),n=t([r]);async function i(e,t,a,n){" - ], - [ - 0.6, - "module.exports=[388046,e=>{\"use strict\";class t{value;next;constructor(e){this.value=e}}class n{#e;#" - ], - [ - 0.6, - "module.exports=[388046,e=>{\"use strict\";class t{value;next;constructor(e){this.value=e}}class n{#e;#" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ] - ], - "topRetainers": [ - { - "mb": 33.9, - "name": "object:t", - "path": "[1] > [10] > 60 > extension > global > prisma > [1]" - }, - { - "mb": 7.9, - "name": "object:global", - "path": "[1] > [10] > 60 > extension" - }, - { - "mb": 7.4, - "name": "object:system / Context", - "path": "Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context" - }, - { - "mb": 5.6, - "name": "object:Module", - "path": "[1] > [10] > 97 > process > nextTick > patchedNextTick > context > system / Context > require > require > cache > .next/server/chunks/_1mg8fms._.js" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "require > cache > Object > .next/server/chunks/_1kwfnjg._.js > Module > exports > Array > [1] > > shared > script" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script" - }, - { - "mb": 4.4, - "name": "object:Generator", - "path": "[6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4]" - }, - { - "mb": 4.4, - "name": "string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/", - "path": "[1] > [21] > 62203" - }, - { - "mb": 3.3, - "name": "object:Object", - "path": "system / Context > require > require > cache > Object > .next/server/chunks/[turbopack]_runtime.js > Module > exports > > context > moduleCache" - } - ], - "sourceStringsMb": 39.8 - } -} diff --git a/scripts/bench/results/v1.45.0-boot-after.md b/scripts/bench/results/v1.45.0-boot-after.md deleted file mode 100644 index 4ec08b032..000000000 --- a/scripts/bench/results/v1.45.0-boot-after.md +++ /dev/null @@ -1,298 +0,0 @@ -# Boot profile v1.45.0-boot-after - -Commit a859adb59, 2026-10-22T04:49:50.419Z. Production build, every role, heap ceiling 792 MB, 300 s idle. 14 cores, load average 8.6,9.4,10.7 at the end. - -- first answer from /api/health: 850 ms; background role ready: 3038 ms -- event-loop stalls logged: 0, max 0 ms - -## Stalls - - -## Heap samples - -| at s | used MB | old | large obj | code | external | array buffers | rss | -| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| 6 | 166 | 91 | 53 | 3 | 12 | 1 | 430 | -| 306 | 153 | 90 | 53 | 4 | 11 | 0 | 599 | - -## Full collections (mark-compact) - -10 mark-compacts during the run. Used heap before the collection, and what survived it (the live heap): - -| at s | before MB | after MB | -| ---: | ---: | ---: | -| 0 | 20.2 | 17.1 | -| 0 | 55.3 | 52.6 | -| 0 | 83.2 | 81.1 | -| 1 | 148.4 | 132.7 | -| 16 | 159.6 | 153.2 | -| 17 | 153.4 | 151 | -| 62 | 167.5 | 157.5 | -| 246 | 183.4 | 152.6 | -| 303 | 165.6 | 151.8 | -| 303 | 151.8 | 151.8 | - -## Main-thread busy stretches over 50 ms without an idle sample, first two minutes - -### at 6 ms, 175 ms - -| self time by module | ms | -| --- | ---: | -| npm:next | 47.2 | -| node:internal/modules/cjs/loader | 34.8 | -| (native) | 34.5 | -| node:internal/bootstrap/realm | 25.7 | -| node:internal/deps/undici/undici | 8.8 | -| node:path | 6.3 | -| node:internal/modules/package_json_reader | 3 | -| node:internal/net | 3 | -| node:internal/modules/esm/resolve | 2.5 | -| node:fs | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | - -### at 203 ms, 241 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 52.7 | -| node:internal/modules/cjs/loader | 40.1 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 31.7 | -| wasm://wasm/00d1cb12 | 28.7 | -| npm:@prisma/client | 28 | -| npm:next | 16.3 | -| node:internal/modules/esm/utils | 12 | -| node:fs | 4.5 | -| npm:zod | 3 | -| node:internal/modules/helpers | 3 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/instrumentation.ts | 80.8 | -| src/lib/db-session.ts | 49.5 | -| src/lib/boot/startup-state.ts | 25.6 | -| src/lib/boot/boot-steps.ts | 25.6 | -| src/lib/logging/background.ts | 25.6 | -| src/lib/process-type.ts | 25.5 | -| src/lib/logging/context.ts | 13.6 | -| src/lib/validations/measurement.ts | 12.1 | -| src/lib/logging/redact.ts | 10.5 | -| src/lib/validations/entry-instant.ts | 4.6 | - -### at 501 ms, 178 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 53.9 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 37.5 | -| npm:next | 18 | -| npm:zod | 12 | -| node:internal/modules/esm/utils | 10.5 | -| (native) | 8.7 | -| node:fs | 6 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 4.5 | -| npm:undici | 4.5 | -| node:internal/url | 1.8 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/app/api/health/route.ts | 61.4 | -| src/lib/api-errors.ts | 27 | -| src/lib/jobs/job-failures.ts | 14.9 | -| src/lib/jobs/offhost-backup.ts | 14.9 | -| src/lib/managed-profiles/handover-access.ts | 14.9 | -| src/lib/sharing/acting-session.ts | 14.9 | -| src/lib/db-session.ts | 13.4 | -| src/lib/export/environment-backup.ts | 13.4 | -| src/lib/export/full-backup-payload.ts | 13.4 | -| src/lib/logging/background.ts | 10.5 | - -### at 833 ms, 184 ms - -| self time by module | ms | -| --- | ---: | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 38.9 | -| node:internal/modules/cjs/loader | 28.6 | -| (native) | 22.8 | -| src/lib/i18n/server-catalogs.ts | 15.1 | -| npm:yaml | 15 | -| node:fs | 12.1 | -| npm:zod | 9 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 5.9 | -| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 4.5 | -| npm:semver | 3 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 129.1 | -| src/lib/jobs/reminder/google-health-sync.ts | 88.5 | -| src/lib/whoop/sync.ts | 79.5 | -| src/lib/whoop/sync-body.ts | 63 | -| src/lib/insights/bucket-series.ts | 60 | -| src/lib/whoop/oauth-descriptor.ts | 60 | -| src/lib/whoop/sync-core.ts | 60 | -| src/instrumentation.ts | 43.4 | -| src/lib/import/unzip-export-xml.ts | 31.4 | -| src/lib/jobs/integration-status-null-probe.ts | 31.4 | - -### at 1099 ms, 54 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 23.9 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 10.6 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 5.8 | -| src/lib/daily/today-overview.ts | 4.5 | -| src/lib/jobs/reminder-worker.ts | 3 | -| node:fs | 3 | -| npm:pg-boss | 1.5 | -| npm:zod | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 52.3 | -| src/lib/jobs/reminder/insights-handlers.ts | 22.3 | -| src/lib/ai/prompts/arrival-reaction.ts | 12 | -| src/lib/daily/score-steady.ts | 10.5 | -| src/lib/dashboard/bands.ts | 6 | -| src/lib/jobs/data-arrival.ts | 4.5 | -| src/lib/dashboard/snapshot-read.ts | 4.5 | -| src/lib/daily/today-overview.ts | 4.5 | -| src/lib/daily/priority-item-key.ts | 4.5 | -| src/lib/ai/prompts/status-batch.ts | 3 | - -### at 1309 ms, 51 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 28 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 6 | -| node:fs | 4.5 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 3 | -| turbopack:///[turbopack]/nodejs/runtime/runtime-base.ts | 1.5 | -| src/lib/jobs/failure-condition.ts | 1.5 | -| src/lib/labs/parse-reference-range.ts | 1.5 | -| npm:zod | 1.5 | -| node:internal/linkedlist | 1.5 | -| (native) | 0.9 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 48.2 | -| src/lib/jobs/reminder/ops-handlers.ts | 14.1 | -| src/lib/medications/ingredients/label-extract.ts | 3 | -| src/lib/export/restore-backup.ts | 2.1 | -| src/lib/jobs/failure-condition.ts | 1.5 | -| src/lib/documents/ai-runs/summary-run.ts | 1.5 | -| src/lib/documents/auto-stage-labs.ts | 1.5 | -| src/lib/documents/ai-runs/store.ts | 1.5 | -| src/lib/labs/parse-reference-range.ts | 1.5 | -| src/lib/labs/ocr-upload.ts | 1.5 | - -## Self time by module until the worker is ready - -| module | ms | -| --- | ---: | -| (native) | 230.8 | -| node:internal/modules/cjs/loader | 223.6 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 124.7 | -| npm:next | 98.8 | -| wasm://wasm/00d1cb12 | 62.6 | -| npm:@prisma/client | 56.2 | -| node:fs | 34.6 | -| node:internal/bootstrap/realm | 28.7 | -| npm:pg | 28.3 | -| npm:zod | 27 | -| npm:pg-boss | 24.6 | -| node:internal/modules/esm/utils | 22.5 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 20.6 | -| npm:luxon | 17 | -| src/lib/i18n/server-catalogs.ts | 15.1 | -| npm:yaml | 15 | -| npm:pg-pool | 13 | -| node:internal/deps/undici/undici | 10.4 | -| node:path | 7.8 | -| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 7.5 | -| node:internal/async_hooks | 7.5 | -| node:internal/process/task_queues | 7.3 | -| node:internal/modules/package_json_reader | 6 | -| npm:undici | 6 | -| npm:pg-protocol | 6 | - -## Heap snapshot after 300 s idle (live heap after a full GC) - -160.1 MB in 1275329 nodes; module source strings over 50 KB: 39.8 MB. - -- parsed locale catalogs: 8 objects, 7 MB retained - -| node type | MB | -| --- | ---: | -| string | 68.7 | -| array | 26.4 | -| code | 21.1 | -| object | 14.1 | -| hidden | 8.9 | -| native | 8 | -| object shape | 5.9 | -| closure | 5.2 | -| concatenated string | 1.3 | -| regexp | 0.2 | -| sliced string | 0.2 | -| number | 0.1 | -| synthetic | 0 | -| symbol | 0 | -| bigint | 0 | - -| constructor | MB | count | -| --- | ---: | ---: | -| array:(object elements) | 20.4 | 23797 | -| object:Object | 10.2 | 274231 | -| native:system / JSArrayBufferData | 7.6 | 171 | -| array:(object properties) | 3.6 | 6512 | -| array: | 2.4 | 10502 | -| closure: | 1.6 | 29042 | -| object:system / Context | 1.6 | 28873 | -| closure:native_bind | 1 | 20782 | -| object:Array | 0.6 | 19679 | -| object:a | 0.4 | 4438 | -| native:Node / histogram | 0.4 | 1 | -| closure:get | 0.3 | 4736 | -| object:d | 0.3 | 4425 | -| object:Set | 0.2 | 5673 | -| closure:set | 0.2 | 3432 | -| object:Module | 0.2 | 1958 | -| closure:i | 0.1 | 1581 | -| closure:n | 0.1 | 1572 | -| object:Promise | 0.1 | 1277 | -| object:n | 0.1 | 1881 | - -| largest retained subtrees | MB | retaining path | -| --- | ---: | --- | -| object:t | 33.9 | [1] > [10] > 60 > extension > global > prisma > [1] | -| object:global | 7.9 | [1] > [10] > 60 > extension | -| object:system / Context | 7.4 | Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context | -| object:Module | 5.6 | [1] > [10] > 97 > process > nextTick > patchedNextTick > context > system / Context > require > require > cache > .n | -| code:.next/server/chu | 5.6 | require > cache > Object > .next/server/chunks/_1kwfnjg._.js > Module > exports > Array > [1] > > shared > script | -| code:.next/server/chu | 5.6 | global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script | -| object:Generator | 4.4 | [6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4 | -| string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/ | 4.4 | [1] > [21] > 62203 | -| object:Object | 3.3 | system / Context > require > require > cache > Object > .next/server/chunks/[turbopack]_runtime.js > Module > export | - -| largest strings | MB | -| --- | ---: | -| module.exports=[234494,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=> | 5.6 | -| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 | -| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 | -| const wasm = "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGAB | 4.4 | -| AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f3 | 4.4 | -| lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAK | 1.9 | -| (()=>{var e,t,r={"./dist/build/webpack/alias/react-dom-server.js"(e,t,r){"use strict";var | 1.3 | -| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 | -| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 | -| module.exports=[799092,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value: | 1.1 | -| // v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. | 1.1 | -| module.exports=[971743,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1.1 | -| module.exports=[835717,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1 | -| module.exports=[767189,e=>e.a(async(t,a)=>{try{var n=e.i(64105),r=e.i(563616),i=t([n]);[n] | 0.8 | -| module.exports=[594108,806176,674750,98746,t=>{"use strict";var i;let r;function n(t){retu | 0.7 | diff --git a/scripts/bench/results/v1.45.0-boot-final.json b/scripts/bench/results/v1.45.0-boot-final.json deleted file mode 100644 index e2f378cce..000000000 --- a/scripts/bench/results/v1.45.0-boot-final.json +++ /dev/null @@ -1,5073 +0,0 @@ -{ - "meta": { - "label": "v1.45.0-boot-final", - "commit": "a57fa8335", - "date": "2026-10-22T06:58:52.591Z", - "node": "v22.23.2", - "cpus": 14, - "loadAverage": [ - 5.6, - 6.4, - 11.1 - ], - "heapMb": 792, - "idleSeconds": 300, - "healthyAtMs": 1325, - "readyAtMs": 3217, - "stallMaxMs": 0 - }, - "bootLines": [ - "3217 ms: boot.steps {\"boot_queue_count\":122,\"boot_steps\":\"readiness_summary=3,key_check=172,time_zone_check=3,pulse_method_sync=18,monitors=6,worker_import=85,boss_start=186,load_worker_lifecycle=55,load_integration_sync=123,register_integration_sync=97,load_status=65,register_status=39,load_rollup=20,register_rollup=17,load_reminders=16,register_reminders=24,load_maintenance=58,register_maintenan" - ], - "stalls": [], - "heapLines": [ - { - "atMs": 5845, - "heap_used_mb": 225, - "heap_total_mb": 261, - "heap_limit_mb": 840, - "heap_used_ratio": 0.268, - "new_space_mb": 3, - "old_space_mb": 158, - "code_space_mb": 4, - "large_object_space_mb": 54, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 6, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 500, - "external_mb": 11, - "array_buffers_mb": 0, - "active_requests": 0, - "active_jobs": 1, - "in_flight_dropped": 0, - "uptime_s": 6 - }, - { - "atMs": 305875, - "heap_used_mb": 158, - "heap_total_mb": 179, - "heap_limit_mb": 840, - "heap_used_ratio": 0.188, - "new_space_mb": 1, - "old_space_mb": 96, - "code_space_mb": 5, - "large_object_space_mb": 53, - "new_large_object_space_mb": 0, - "code_large_object_space_mb": 0, - "trusted_space_mb": 3, - "read_only_space_mb": 0, - "other_spaces_mb": 0, - "rss_mb": 788, - "external_mb": 11, - "array_buffers_mb": 0, - "active_requests": 0, - "active_jobs": 1, - "in_flight_dropped": 0, - "uptime_s": 306 - } - ], - "markCompacts": [ - { - "atMs": 423, - "beforeMb": 21.5, - "afterMb": 17.5 - }, - { - "atMs": 712, - "beforeMb": 52.4, - "afterMb": 49.1 - }, - { - "atMs": 814, - "beforeMb": 85.1, - "afterMb": 81.6 - }, - { - "atMs": 1326, - "beforeMb": 133, - "afterMb": 127.6 - }, - { - "atMs": 2476, - "beforeMb": 199.1, - "afterMb": 148.4 - }, - { - "atMs": 5880, - "beforeMb": 230.9, - "afterMb": 147.8 - }, - { - "atMs": 6847, - "beforeMb": 200.4, - "afterMb": 146.6 - }, - { - "atMs": 8304, - "beforeMb": 203.8, - "afterMb": 146.5 - }, - { - "atMs": 12026, - "beforeMb": 226.7, - "afterMb": 143.2 - }, - { - "atMs": 12407, - "beforeMb": 181.7, - "afterMb": 150.2 - }, - { - "atMs": 15846, - "beforeMb": 231.8, - "afterMb": 142.3 - }, - { - "atMs": 16211, - "beforeMb": 181.3, - "afterMb": 142.3 - }, - { - "atMs": 19993, - "beforeMb": 223.6, - "afterMb": 149.8 - }, - { - "atMs": 20368, - "beforeMb": 191.4, - "afterMb": 154.8 - }, - { - "atMs": 23910, - "beforeMb": 236.5, - "afterMb": 142.5 - }, - { - "atMs": 24274, - "beforeMb": 181.4, - "afterMb": 142.5 - }, - { - "atMs": 30373, - "beforeMb": 222.8, - "afterMb": 149.5 - }, - { - "atMs": 31085, - "beforeMb": 197, - "afterMb": 149.2 - }, - { - "atMs": 33099, - "beforeMb": 210.8, - "afterMb": 142.7 - }, - { - "atMs": 36527, - "beforeMb": 226.3, - "afterMb": 143.8 - }, - { - "atMs": 37227, - "beforeMb": 189.6, - "afterMb": 143.2 - }, - { - "atMs": 40859, - "beforeMb": 223.8, - "afterMb": 144.9 - }, - { - "atMs": 44753, - "beforeMb": 220.7, - "afterMb": 143.5 - }, - { - "atMs": 45178, - "beforeMb": 183.6, - "afterMb": 150.9 - }, - { - "atMs": 49163, - "beforeMb": 242, - "afterMb": 151.4 - }, - { - "atMs": 52759, - "beforeMb": 232, - "afterMb": 144.7 - }, - { - "atMs": 53218, - "beforeMb": 185.2, - "afterMb": 151.3 - }, - { - "atMs": 56981, - "beforeMb": 234.1, - "afterMb": 145.1 - }, - { - "atMs": 60028, - "beforeMb": 225.8, - "afterMb": 147.5 - }, - { - "atMs": 63456, - "beforeMb": 234.1, - "afterMb": 150.7 - }, - { - "atMs": 66640, - "beforeMb": 231.3, - "afterMb": 146.1 - }, - { - "atMs": 68093, - "beforeMb": 195, - "afterMb": 146.1 - }, - { - "atMs": 71676, - "beforeMb": 232.2, - "afterMb": 145.8 - }, - { - "atMs": 75503, - "beforeMb": 237.7, - "afterMb": 159.5 - }, - { - "atMs": 78075, - "beforeMb": 248.5, - "afterMb": 146.6 - }, - { - "atMs": 84540, - "beforeMb": 224.4, - "afterMb": 147.7 - }, - { - "atMs": 88825, - "beforeMb": 200, - "afterMb": 147.4 - }, - { - "atMs": 93316, - "beforeMb": 200.9, - "afterMb": 147.3 - }, - { - "atMs": 99358, - "beforeMb": 219.8, - "afterMb": 146.9 - }, - { - "atMs": 104015, - "beforeMb": 220, - "afterMb": 147.8 - }, - { - "atMs": 109364, - "beforeMb": 221.8, - "afterMb": 148.5 - }, - { - "atMs": 118760, - "beforeMb": 225.8, - "afterMb": 147.2 - }, - { - "atMs": 124489, - "beforeMb": 225.4, - "afterMb": 151.2 - }, - { - "atMs": 131221, - "beforeMb": 231.1, - "afterMb": 150.8 - }, - { - "atMs": 135350, - "beforeMb": 215.3, - "afterMb": 150.9 - }, - { - "atMs": 139885, - "beforeMb": 206.8, - "afterMb": 149.6 - }, - { - "atMs": 147072, - "beforeMb": 223.8, - "afterMb": 150.1 - }, - { - "atMs": 150370, - "beforeMb": 168.9, - "afterMb": 149.9 - }, - { - "atMs": 161654, - "beforeMb": 203, - "afterMb": 151.7 - }, - { - "atMs": 166582, - "beforeMb": 176.7, - "afterMb": 151 - }, - { - "atMs": 170653, - "beforeMb": 173.8, - "afterMb": 150.7 - }, - { - "atMs": 173523, - "beforeMb": 184.1, - "afterMb": 150 - }, - { - "atMs": 181372, - "beforeMb": 224.9, - "afterMb": 150.8 - }, - { - "atMs": 187910, - "beforeMb": 223.3, - "afterMb": 149.9 - }, - { - "atMs": 199166, - "beforeMb": 210.8, - "afterMb": 150.6 - }, - { - "atMs": 203054, - "beforeMb": 202.6, - "afterMb": 151 - }, - { - "atMs": 209332, - "beforeMb": 230.1, - "afterMb": 150.4 - }, - { - "atMs": 214456, - "beforeMb": 219, - "afterMb": 151 - }, - { - "atMs": 218065, - "beforeMb": 193.1, - "afterMb": 150.9 - }, - { - "atMs": 221700, - "beforeMb": 195.9, - "afterMb": 150.9 - }, - { - "atMs": 225906, - "beforeMb": 210.4, - "afterMb": 150.7 - }, - { - "atMs": 227693, - "beforeMb": 176.5, - "afterMb": 150.9 - }, - { - "atMs": 237405, - "beforeMb": 204.1, - "afterMb": 151.2 - }, - { - "atMs": 241706, - "beforeMb": 197.9, - "afterMb": 151.5 - }, - { - "atMs": 245651, - "beforeMb": 210.7, - "afterMb": 152.3 - }, - { - "atMs": 249623, - "beforeMb": 197.7, - "afterMb": 151.4 - }, - { - "atMs": 251604, - "beforeMb": 185.1, - "afterMb": 151.7 - }, - { - "atMs": 253956, - "beforeMb": 188.5, - "afterMb": 152.1 - }, - { - "atMs": 257864, - "beforeMb": 205.5, - "afterMb": 151.9 - }, - { - "atMs": 262619, - "beforeMb": 224.7, - "afterMb": 151.3 - }, - { - "atMs": 272867, - "beforeMb": 224.8, - "afterMb": 152.2 - }, - { - "atMs": 280992, - "beforeMb": 182.2, - "afterMb": 151.9 - }, - { - "atMs": 284638, - "beforeMb": 169.3, - "afterMb": 152 - }, - { - "atMs": 291033, - "beforeMb": 176.2, - "afterMb": 152.2 - }, - { - "atMs": 293457, - "beforeMb": 174.5, - "afterMb": 153.3 - }, - { - "atMs": 297611, - "beforeMb": 196.2, - "afterMb": 151.9 - }, - { - "atMs": 300538, - "beforeMb": 184.5, - "afterMb": 152.5 - }, - { - "atMs": 302088, - "beforeMb": 177.9, - "afterMb": 152.5 - }, - { - "atMs": 303258, - "beforeMb": 176, - "afterMb": 151.2 - }, - { - "atMs": 303277, - "beforeMb": 151.2, - "afterMb": 151.2 - }, - { - "atMs": 306059, - "beforeMb": 165.5, - "afterMb": 152 - }, - { - "atMs": 306722, - "beforeMb": 162.4, - "afterMb": 152.2 - }, - { - "atMs": 308561, - "beforeMb": 172.2, - "afterMb": 151.9 - }, - { - "atMs": 309371, - "beforeMb": 168.8, - "afterMb": 151.8 - }, - { - "atMs": 311582, - "beforeMb": 170.5, - "afterMb": 152.9 - }, - { - "atMs": 312931, - "beforeMb": 170.8, - "afterMb": 152.5 - }, - { - "atMs": 314178, - "beforeMb": 171.1, - "afterMb": 153.5 - }, - { - "atMs": 316739, - "beforeMb": 178.9, - "afterMb": 152.9 - }, - { - "atMs": 318728, - "beforeMb": 179.7, - "afterMb": 151.8 - }, - { - "atMs": 321159, - "beforeMb": 173.5, - "afterMb": 152.3 - }, - { - "atMs": 325180, - "beforeMb": 178.5, - "afterMb": 151.2 - }, - { - "atMs": 326984, - "beforeMb": 170.6, - "afterMb": 152 - }, - { - "atMs": 329437, - "beforeMb": 177.2, - "afterMb": 152.1 - }, - { - "atMs": 331442, - "beforeMb": 170.2, - "afterMb": 153 - }, - { - "atMs": 332715, - "beforeMb": 180.7, - "afterMb": 153 - }, - { - "atMs": 334002, - "beforeMb": 180.2, - "afterMb": 153 - }, - { - "atMs": 335414, - "beforeMb": 173.5, - "afterMb": 153.1 - }, - { - "atMs": 337941, - "beforeMb": 169.7, - "afterMb": 151.8 - } - ], - "tasks": [ - { - "startMs": 5, - "durationMs": 396, - "byModule": [ - [ - "(native)", - 230.2 - ], - [ - "node:internal/modules/cjs/loader", - 57.8 - ], - [ - "npm:next", - 52.3 - ], - [ - "node:internal/bootstrap/realm", - 29.3 - ], - [ - "node:internal/deps/undici/undici", - 8.9 - ], - [ - "node:internal/modules/esm/loader", - 4.5 - ], - [ - "node:fs", - 3 - ], - [ - "node:internal/net", - 2.5 - ], - [ - "node:internal/bootstrap/switches/is_main_thread", - 2 - ], - [ - "node:os", - 1.8 - ], - [ - "node:events", - 1.5 - ], - [ - "node:internal/url", - 1.4 - ], - [ - "node:path", - 0.8 - ] - ], - "byFunction": [ - [ - "readFileUtf8 (native)", - 120 - ], - [ - "internalModuleStat (native)", - 65.3 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 43.3 - ], - [ - "compileForInternalLoader node:internal/bootstrap/realm", - 29.3 - ], - [ - "readPackageJSON (native)", - 17 - ], - [ - "dlopen (native)", - 15.5 - ], - [ - "__nccwpck_require__ npm:next", - 15.4 - ], - [ - "(anonymous) node:internal/modules/cjs/loader", - 13 - ], - [ - "startServer npm:next", - 11 - ], - [ - "(anonymous) npm:next", - 10.7 - ], - [ - "__require node:internal/deps/undici/undici", - 6 - ], - [ - "compileFunction (native)", - 5 - ], - [ - "(garbage collector) (native)", - 4.5 - ], - [ - "_interop_require_wildcard npm:next", - 3.4 - ], - [ - "realpathSync node:fs", - 3 - ] - ], - "byEntry": [] - }, - { - "startMs": 472, - "durationMs": 336, - "byModule": [ - [ - "(native)", - 121.3 - ], - [ - "wasm://wasm/00d1cb12", - 43 - ], - [ - "node:internal/modules/cjs/loader", - 40.5 - ], - [ - "npm:next", - 31.7 - ], - [ - "npm:@prisma/client", - 23.8 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 23.4 - ], - [ - "node:internal/modules/esm/utils", - 17.3 - ], - [ - "npm:zod", - 5.9 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 3 - ], - [ - "node:vm", - 1.7 - ], - [ - "turbopack:///[turbopack]/shared-node/base-externals-utils.ts", - 1.5 - ], - [ - "npm:undici", - 1.5 - ], - [ - "src/instrumentation.ts", - 1.5 - ], - [ - "node:internal/modules/package_json_reader", - 1.5 - ], - [ - "npm:@prisma/client-runtime-utils", - 1.5 - ] - ], - "byFunction": [ - [ - "readFileUtf8 (native)", - 57.9 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 33.1 - ], - [ - "(garbage collector) (native)", - 25.4 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 23.4 - ], - [ - "internalModuleStat (native)", - 22.4 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 17.3 - ], - [ - "#u npm:@prisma/client", - 16.5 - ], - [ - "(anonymous) npm:next", - 12.2 - ], - [ - "(anonymous) node:internal/modules/cjs/loader", - 5.8 - ], - [ - "wasm-function[1046] wasm://wasm/00d1cb12", - 4.5 - ], - [ - "js-to-wasm (native)", - 4.4 - ], - [ - "loadManifest npm:next", - 3 - ], - [ - "wasm-function[1286] wasm://wasm/00d1cb12", - 3 - ], - [ - "wasm-function[41] wasm://wasm/00d1cb12", - 3 - ], - [ - "wasm-function[915] wasm://wasm/00d1cb12", - 3 - ] - ], - "byEntry": [ - [ - "src/instrumentation.ts", - 95.8 - ], - [ - "src/lib/db-session.ts", - 47.3 - ], - [ - "src/lib/process-type.ts", - 27.1 - ], - [ - "src/lib/boot/startup-state.ts", - 25.5 - ], - [ - "src/lib/boot/boot-steps.ts", - 25.5 - ], - [ - "src/lib/logging/background.ts", - 25.5 - ], - [ - "src/lib/logging/redact.ts", - 13 - ], - [ - "src/lib/logging/context.ts", - 11 - ], - [ - "src/lib/validations/measurement.ts", - 10 - ], - [ - "src/generated/prisma/internal/class.ts", - 4.7 - ], - [ - "src/lib/validations/entry-instant.ts", - 4.5 - ], - [ - "src/lib/env.ts", - 2.8 - ], - [ - "src/lib/logging/config.ts", - 1.5 - ], - [ - "src/lib/crypto.ts", - 1.5 - ], - [ - "src/lib/crypto/canary.ts", - 1.5 - ] - ] - }, - { - "startMs": 811, - "durationMs": 225, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 52.3 - ], - [ - "(native)", - 48.2 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 37 - ], - [ - "npm:next", - 22.2 - ], - [ - "npm:zod", - 12.5 - ], - [ - "node:internal/modules/esm/utils", - 8.7 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 5.6 - ], - [ - "node:fs", - 4.5 - ], - [ - "node:internal/async_hooks", - 3 - ], - [ - "node:internal/modules/esm/translators", - 2.6 - ], - [ - "node:internal/deps/cjs-module-lexer/lexer", - 2 - ], - [ - "node:internal/modules/package_json_reader", - 1.7 - ], - [ - "node:internal/bootstrap/realm", - 1.5 - ], - [ - "node:internal/deps/undici/undici", - 1.5 - ], - [ - "npm:rrule-temporal", - 1.5 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 52.3 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 37 - ], - [ - "readFileUtf8 (native)", - 27.5 - ], - [ - "compileSourceTextModule node:internal/modules/esm/utils", - 8.7 - ], - [ - "read (native)", - 7.1 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 5.6 - ], - [ - "(garbage collector) (native)", - 4.2 - ], - [ - "(anonymous) npm:zod", - 3.9 - ], - [ - "(anonymous) npm:next", - 3.8 - ], - [ - "a npm:next", - 3.2 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "eJ npm:zod", - 3 - ], - [ - "u npm:zod", - 2.6 - ], - [ - "open (native)", - 2.2 - ], - [ - "compileFunctionForCJSLoader (native)", - 1.8 - ] - ], - "byEntry": [ - [ - "src/app/api/health/route.ts", - 62 - ], - [ - "src/lib/api-errors.ts", - 25.9 - ], - [ - "src/lib/auth/secure-cookie.ts", - 14.9 - ], - [ - "src/lib/logging/background.ts", - 13.4 - ], - [ - "src/lib/sharing/acting-session.ts", - 12.8 - ], - [ - "src/lib/validations/measurement.ts", - 11.9 - ], - [ - "src/lib/db-session.ts", - 11.3 - ], - [ - "src/lib/export/environment-backup.ts", - 11.3 - ], - [ - "src/lib/export/full-backup-payload.ts", - 11.3 - ], - [ - "src/lib/jobs/job-failures.ts", - 11.3 - ], - [ - "src/lib/jobs/offhost-backup.ts", - 11.3 - ], - [ - "src/lib/managed-profiles/handover-access.ts", - 11.3 - ], - [ - "src/instrumentation.ts", - 10.3 - ], - [ - "src/lib/notifications/types.ts", - 10.1 - ], - [ - "src/lib/logging/redact.ts", - 10.1 - ] - ] - }, - { - "startMs": 1063, - "durationMs": 246, - "byModule": [ - [ - "(native)", - 64.7 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 47.2 - ], - [ - "node:internal/modules/cjs/loader", - 31.6 - ], - [ - "npm:luxon", - 18 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 15.3 - ], - [ - "npm:yaml", - 15 - ], - [ - "npm:zod", - 10.2 - ], - [ - "src/lib/i18n/server-catalogs.ts", - 8.2 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 4.5 - ], - [ - "npm:cron-parser", - 4 - ], - [ - "npm:pg", - 3.9 - ], - [ - "node:fs", - 3 - ], - [ - "node:internal/process/task_queues", - 3 - ], - [ - "npm:semver", - 2.9 - ], - [ - "src/lib/ai/safety/outbound-screen.ts", - 1.5 - ] - ], - "byFunction": [ - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 47.2 - ], - [ - "wrapSafe node:internal/modules/cjs/loader", - 31.6 - ], - [ - "(garbage collector) (native)", - 30.3 - ], - [ - "readFileUtf8 (native)", - 24.8 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 15.3 - ], - [ - "(anonymous) src/lib/i18n/server-catalogs.ts", - 8.2 - ], - [ - "writev (native)", - 5 - ], - [ - "isValidZone npm:luxon", - 4.5 - ], - [ - "(anonymous) npm:zod", - 3 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "nextTick node:internal/process/task_queues", - 3 - ], - [ - "parse npm:yaml", - 2.7 - ], - [ - "partsOffset npm:luxon", - 2 - ], - [ - "bind npm:pg", - 1.5 - ], - [ - "#parseField npm:cron-parser", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 137.6 - ], - [ - "src/lib/jobs/reminder/google-health-sync.ts", - 78 - ], - [ - "src/lib/whoop/sync.ts", - 65.9 - ], - [ - "src/instrumentation.ts", - 53.8 - ], - [ - "src/lib/insights/bucket-series.ts", - 47.1 - ], - [ - "src/lib/whoop/oauth-descriptor.ts", - 47.1 - ], - [ - "src/lib/whoop/sync-core.ts", - 47.1 - ], - [ - "src/lib/whoop/sync-body.ts", - 47.1 - ], - [ - "src/lib/import/unzip-export-xml.ts", - 35.3 - ], - [ - "src/lib/jobs/integration-status-null-probe.ts", - 35.3 - ], - [ - "src/lib/insights/snapshot-hash.ts", - 25.2 - ], - [ - "src/lib/ai/prompts/safety-contracts.ts", - 22.1 - ], - [ - "src/lib/ai/prompts/native-prompts.ts", - 22.1 - ], - [ - "src/lib/ai/medical-references.ts", - 22.1 - ], - [ - "src/lib/notifications/delivery-identity.ts", - 15.8 - ] - ] - }, - { - "startMs": 1363, - "durationMs": 68, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 29.2 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 6.7 - ], - [ - "(native)", - 5.9 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 4.5 - ], - [ - "src/lib/daily/today-overview.ts", - 4.1 - ], - [ - "node:fs", - 3 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 3 - ], - [ - "node:internal/streams/writable", - 1.5 - ], - [ - "src/lib/jobs/reminder/insights-handlers.ts", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "npm:zod", - 1.5 - ], - [ - "src/lib/jobs/coach-nudge-ai.ts", - 1.5 - ], - [ - "npm:pg-pool", - 1.5 - ], - [ - "node:internal/bootstrap/node", - 1.5 - ], - [ - "node:internal/timers", - 1.3 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 29.2 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 6.7 - ], - [ - "readFileUtf8 (native)", - 5.9 - ], - [ - "defineProp turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 4.5 - ], - [ - "w src/lib/daily/today-overview.ts", - 4.1 - ], - [ - "readFileSync node:fs", - 3 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 3 - ], - [ - "onwrite node:internal/streams/writable", - 1.5 - ], - [ - "(anonymous) src/lib/jobs/reminder/insights-handlers.ts", - 1.5 - ], - [ - "(anonymous) npm:next", - 1.5 - ], - [ - "max npm:zod", - 1.5 - ], - [ - "module.exports.Object.freeze.yearly.id src/lib/jobs/coach-nudge-ai.ts", - 1.5 - ], - [ - "_acquireClient npm:pg-pool", - 1.5 - ], - [ - "get node:internal/bootstrap/node", - 1.5 - ], - [ - "Timeout node:internal/timers", - 1.3 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 62.5 - ], - [ - "src/lib/jobs/reminder/insights-handlers.ts", - 24.4 - ], - [ - "src/lib/ai/prompts/arrival-reaction.ts", - 12.8 - ], - [ - "src/lib/daily/score-steady.ts", - 11.3 - ], - [ - "src/lib/dashboard/bands.ts", - 7.1 - ], - [ - "src/lib/dashboard/snapshot-read.ts", - 5.8 - ], - [ - "src/lib/jobs/data-arrival.ts", - 4.5 - ], - [ - "src/lib/daily/today-overview.ts", - 4.1 - ], - [ - "src/lib/daily/priority-item-key.ts", - 4.1 - ], - [ - "src/lib/ai/prompts/status-batch.ts", - 3 - ], - [ - "src/lib/dashboard/score-narrative.ts", - 2.7 - ], - [ - "src/lib/jobs/ai-job-candidates.ts", - 2.7 - ], - [ - "src/lib/ai/prompts/general-status.ts", - 1.5 - ], - [ - "src/lib/insights/derived/derived-assessment-ai.ts", - 1.5 - ], - [ - "src/lib/insights/no-key-fallbacks.ts", - 1.5 - ] - ] - }, - { - "startMs": 1545, - "durationMs": 57, - "byModule": [ - [ - "node:internal/modules/cjs/loader", - 25.3 - ], - [ - "(native)", - 12.9 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 7.2 - ], - [ - "node:fs", - 4.2 - ], - [ - "node:buffer", - 1.5 - ], - [ - "npm:zod", - 1.5 - ], - [ - "src/lib/jobs/pr-detection.ts", - 1.5 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 1.5 - ], - [ - "src/lib/jobs/measurement-maintenance.ts", - 1.3 - ] - ], - "byFunction": [ - [ - "wrapSafe node:internal/modules/cjs/loader", - 25.3 - ], - [ - "readFileUtf8 (native)", - 11.4 - ], - [ - "instantiateModule turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 7.1 - ], - [ - "readFileSync node:fs", - 4.2 - ], - [ - "getEncodingOps node:buffer", - 1.5 - ], - [ - "dH npm:zod", - 1.5 - ], - [ - "(anonymous) src/lib/jobs/pr-detection.ts", - 1.5 - ], - [ - "(garbage collector) (native)", - 1.5 - ], - [ - "asyncModule turbopack:///[turbopack]/shared/runtime/async-module.ts", - 1.5 - ], - [ - "(anonymous) src/lib/jobs/measurement-maintenance.ts", - 1.3 - ], - [ - "getOrInstantiateModuleFromParent turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 0 - ] - ], - "byEntry": [ - [ - "src/lib/jobs/reminder-worker.ts", - 55.3 - ], - [ - "src/lib/jobs/reminder/ops-handlers.ts", - 13 - ], - [ - "src/lib/export/restore-backup.ts", - 3 - ], - [ - "src/lib/medications/ingredients/label-extract.ts", - 3 - ], - [ - "src/lib/environment/service.ts", - 1.5 - ], - [ - "src/lib/export/backup-blob.ts", - 1.5 - ], - [ - "src/lib/jobs/pr-detection.ts", - 1.5 - ], - [ - "src/lib/jobs/document-content-index-backfill.ts", - 1.5 - ], - [ - "src/lib/labs/ocr-extract.ts", - 1.5 - ], - [ - "src/lib/jobs/reminder/backup-handlers.ts", - 1.5 - ], - [ - "src/lib/export/tenant-boundary.ts", - 1.5 - ], - [ - "src/lib/gamification/achievements-result.ts", - 1.5 - ], - [ - "src/lib/jobs/measurement-maintenance.ts", - 1.3 - ], - [ - "src/lib/documents/auto-stage-labs.ts", - 1.2 - ] - ] - }, - { - "startMs": 1820, - "durationMs": 114, - "byModule": [ - [ - "src/lib/tz/zone-offset.ts", - 36 - ], - [ - "npm:@prisma/client", - 18.2 - ], - [ - "src/lib/validations/source-priority.ts", - 18.1 - ], - [ - "(native)", - 13.1 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 7.2 - ], - [ - "node:buffer", - 4.1 - ], - [ - "src/lib/analytics/source-priority.ts", - 3.7 - ], - [ - "npm:zod", - 2.3 - ], - [ - "npm:pg", - 2 - ], - [ - "src/lib/tz/format.ts", - 1.9 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "src/lib/tz/intl-cache.ts", - 1.5 - ], - [ - "node:internal/process/task_queues", - 1.3 - ], - [ - "npm:next", - 1.2 - ], - [ - "npm:%40prisma", - 1.1 - ] - ], - "byFunction": [ - [ - "l src/lib/tz/zone-offset.ts", - 30.4 - ], - [ - "I src/lib/validations/source-priority.ts", - 16.5 - ], - [ - "(garbage collector) (native)", - 7.4 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 7.2 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 5.7 - ], - [ - "runMicrotasks (native)", - 4.3 - ], - [ - "slice node:buffer", - 4.1 - ], - [ - "Zs npm:@prisma/client", - 4 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 3.7 - ], - [ - "Tp npm:@prisma/client", - 3 - ], - [ - "(anonymous) npm:@prisma/client", - 2.8 - ], - [ - "ni npm:@prisma/client", - 2.6 - ], - [ - "Fp npm:@prisma/client", - 1.5 - ], - [ - "Ae npm:@prisma/client", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 73.3 - ], - [ - "src/lib/tz/format.ts", - 40.7 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 40.7 - ], - [ - "src/lib/tz/zone-offset.ts", - 38.8 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 38.1 - ], - [ - "src/lib/analytics/source-priority.ts", - 28.1 - ], - [ - "src/lib/validations/source-priority.ts", - 20.4 - ], - [ - "src/lib/tz/intl-cache.ts", - 1.5 - ] - ] - }, - { - "startMs": 2407, - "durationMs": 50, - "byModule": [ - [ - "(native)", - 18.9 - ], - [ - "src/lib/validations/source-priority.ts", - 14.1 - ], - [ - "src/lib/analytics/source-priority.ts", - 3.7 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "npm:next", - 1.8 - ], - [ - "npm:%40prisma", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "npm:pg", - 1.5 - ], - [ - "npm:@prisma/client", - 1.3 - ], - [ - "node:internal/async_hooks", - 1.3 - ] - ], - "byFunction": [ - [ - "(garbage collector) (native)", - 14.8 - ], - [ - "I src/lib/validations/source-priority.ts", - 11.1 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 3.7 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 3 - ], - [ - "(program) (native)", - 3 - ], - [ - "Date1 npm:next", - 1.8 - ], - [ - "D npm:%40prisma", - 1.5 - ], - [ - "d src/lib/tz/format.ts", - 1.5 - ], - [ - "_propagate node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "_send npm:pg", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.3 - ], - [ - "promiseInitHook node:internal/async_hooks", - 1.3 - ], - [ - "utf8Slice (native)", - 1.2 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 26.6 - ], - [ - "src/lib/analytics/source-priority.ts", - 19.3 - ], - [ - "src/lib/validations/source-priority.ts", - 14.1 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 2.8 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ] - ] - }, - { - "startMs": 18192, - "durationMs": 59, - "byModule": [ - [ - "(native)", - 15.5 - ], - [ - "npm:@prisma/client", - 13.9 - ], - [ - "src/lib/validations/source-priority.ts", - 11.8 - ], - [ - "npm:pg", - 3.6 - ], - [ - "node:events", - 3 - ], - [ - "node:buffer", - 2.5 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 1.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "npm:pg-types", - 1.4 - ], - [ - "npm:next", - 1.4 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.2 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 10.3 - ], - [ - "(garbage collector) (native)", - 5.8 - ], - [ - "utf8Slice (native)", - 4.1 - ], - [ - "(program) (native)", - 3.1 - ], - [ - "emit node:events", - 3 - ], - [ - "Fp npm:@prisma/client", - 3 - ], - [ - "(anonymous) npm:@prisma/client", - 2.9 - ], - [ - "Zs npm:@prisma/client", - 2.6 - ], - [ - "slice node:buffer", - 2.5 - ], - [ - "runMicrotasks (native)", - 2.5 - ], - [ - "Ae npm:@prisma/client", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "m src/lib/measurements/consolidation-base.ts", - 1.5 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "promiseInitHook node:internal/async_hooks", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 18.9 - ], - [ - "src/lib/analytics/source-priority.ts", - 12.9 - ], - [ - "src/lib/validations/source-priority.ts", - 11.8 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 4.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 2.6 - ], - [ - "src/lib/tz/format.ts", - 2.6 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 2.6 - ] - ] - }, - { - "startMs": 19914, - "durationMs": 65, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 18.1 - ], - [ - "(native)", - 12.3 - ], - [ - "npm:@prisma/client", - 11.6 - ], - [ - "npm:next", - 5.9 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "npm:pg", - 2.6 - ], - [ - "npm:pg-protocol", - 2.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "node:buffer", - 1.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.3 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 14.1 - ], - [ - "(garbage collector) (native)", - 8.6 - ], - [ - "Date1 npm:next", - 5.9 - ], - [ - "(program) (native)", - 3.7 - ], - [ - "(anonymous) npm:@prisma/client", - 3 - ], - [ - "Fp npm:@prisma/client", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "c src/lib/tz/format.ts", - 3 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 2.9 - ], - [ - "_parseRowAsArray npm:pg", - 2.6 - ], - [ - "request npm:@prisma/client", - 2.5 - ], - [ - "lookupPublicResource node:internal/async_hooks", - 1.5 - ], - [ - "parseDataRowMessage npm:pg-protocol", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.5 - ], - [ - "Ae npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 29.8 - ], - [ - "src/lib/analytics/source-priority.ts", - 22.6 - ], - [ - "src/lib/validations/source-priority.ts", - 18.1 - ], - [ - "src/lib/tz/format.ts", - 5.8 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 5.8 - ], - [ - "src/lib/tz/zone-offset.ts", - 2.8 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 2.8 - ] - ] - }, - { - "startMs": 20296, - "durationMs": 55, - "byModule": [ - [ - "(native)", - 22.4 - ], - [ - "npm:@prisma/client", - 14.8 - ], - [ - "node:buffer", - 3 - ], - [ - "npm:zod", - 3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 3 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "node:internal/stream_base_commons", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "npm:pg-protocol", - 1.3 - ], - [ - "npm:pg", - 1.1 - ] - ], - "byFunction": [ - [ - "(garbage collector) (native)", - 15.3 - ], - [ - "Fp npm:@prisma/client", - 6.1 - ], - [ - "(program) (native)", - 4.1 - ], - [ - "slice node:buffer", - 3 - ], - [ - "te npm:zod", - 3 - ], - [ - "Tp npm:@prisma/client", - 3 - ], - [ - "m src/lib/measurements/consolidation-base.ts", - 3 - ], - [ - "runMicrotasks (native)", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.5 - ], - [ - "Ae npm:@prisma/client", - 1.5 - ], - [ - "(anonymous) src/lib/tz/format.ts", - 1.5 - ], - [ - "onStreamRead node:internal/stream_base_commons", - 1.5 - ], - [ - "utf8Slice (native)", - 1.5 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 10.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 6 - ], - [ - "src/lib/validations/source-priority.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 3 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 1.5 - ] - ] - }, - { - "startMs": 30289, - "durationMs": 69, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 18.3 - ], - [ - "(native)", - 12.9 - ], - [ - "npm:@prisma/client", - 8.8 - ], - [ - "node:buffer", - 6.8 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 4.5 - ], - [ - "src/lib/tz/format.ts", - 4 - ], - [ - "npm:pg-protocol", - 3 - ], - [ - "npm:zod", - 3 - ], - [ - "node:timers", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "node:events", - 1.2 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 12.4 - ], - [ - "(garbage collector) (native)", - 11.4 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 5.9 - ], - [ - "slice node:buffer", - 5.6 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 4.5 - ], - [ - "parseDataRowMessage npm:pg-protocol", - 3 - ], - [ - "h src/lib/tz/format.ts", - 3 - ], - [ - "te npm:zod", - 3 - ], - [ - "setTimeout node:timers", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "getOrSetAsyncId node:internal/async_hooks", - 1.5 - ], - [ - "utf8Slice (native)", - 1.5 - ], - [ - "$a npm:@prisma/client", - 1.5 - ], - [ - "Fp npm:@prisma/client", - 1.5 - ], - [ - "Tp npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 33 - ], - [ - "src/lib/analytics/source-priority.ts", - 24.3 - ], - [ - "src/lib/validations/source-priority.ts", - 21.3 - ], - [ - "src/lib/tz/format.ts", - 5.6 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 5.6 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 2.6 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ] - ] - }, - { - "startMs": 31004, - "durationMs": 68, - "byModule": [ - [ - "(native)", - 17.8 - ], - [ - "src/lib/validations/source-priority.ts", - 14.9 - ], - [ - "npm:@prisma/client", - 7.6 - ], - [ - "npm:next", - 4.5 - ], - [ - "node:internal/async_hooks", - 4.5 - ], - [ - "npm:pg-protocol", - 4.1 - ], - [ - "npm:pg", - 3 - ], - [ - "src/lib/tz/zone-offset.ts", - 3 - ], - [ - "node:buffer", - 2.7 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.4 - ], - [ - "src/lib/tz/format.ts", - 1.3 - ], - [ - "npm:zod", - 1.3 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 13.4 - ], - [ - "(garbage collector) (native)", - 10.7 - ], - [ - "Date1 npm:next", - 4.5 - ], - [ - "(program) (native)", - 4.5 - ], - [ - "_parseRowAsArray npm:pg", - 3 - ], - [ - "(anonymous) npm:@prisma/client", - 3 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 3 - ], - [ - "parseDataRowMessage npm:pg-protocol", - 2.6 - ], - [ - "slice node:buffer", - 1.5 - ], - [ - "handlePacket npm:pg-protocol", - 1.5 - ], - [ - "promiseBeforeHook node:internal/async_hooks", - 1.5 - ], - [ - "trackPromise node:internal/async_hooks", - 1.5 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 26.4 - ], - [ - "src/lib/analytics/source-priority.ts", - 19 - ], - [ - "src/lib/validations/source-priority.ts", - 16.2 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 6.1 - ], - [ - "src/lib/tz/format.ts", - 5.8 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 5.8 - ], - [ - "src/lib/tz/zone-offset.ts", - 4.5 - ] - ] - }, - { - "startMs": 35350, - "durationMs": 65, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 17 - ], - [ - "npm:@prisma/client", - 11.8 - ], - [ - "(native)", - 8.6 - ], - [ - "node:buffer", - 6.5 - ], - [ - "src/lib/tz/format.ts", - 4.5 - ], - [ - "npm:pg-protocol", - 3 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "npm:%40prisma", - 1.6 - ], - [ - "npm:pg", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "node:events", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "node:internal/async_context_frame", - 1.3 - ], - [ - "npm:pg-types", - 1.2 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 12.6 - ], - [ - "slice node:buffer", - 6.5 - ], - [ - "d src/lib/tz/format.ts", - 4.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 4.4 - ], - [ - "(garbage collector) (native)", - 4.3 - ], - [ - "Zs npm:@prisma/client", - 4.2 - ], - [ - "parseDataRowMessage npm:pg-protocol", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "utf8Slice (native)", - 2.8 - ], - [ - "mapQueryEngineResult npm:@prisma/client", - 1.7 - ], - [ - "D npm:%40prisma", - 1.6 - ], - [ - "handleDataRow npm:pg", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "ki npm:@prisma/client", - 1.5 - ], - [ - "Ae npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 26 - ], - [ - "src/lib/analytics/source-priority.ts", - 20 - ], - [ - "src/lib/validations/source-priority.ts", - 17 - ], - [ - "src/lib/tz/format.ts", - 4.5 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 4.5 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 3 - ] - ] - }, - { - "startMs": 36452, - "durationMs": 57, - "byModule": [ - [ - "(native)", - 20 - ], - [ - "src/lib/validations/source-priority.ts", - 16 - ], - [ - "npm:@prisma/client", - 7.3 - ], - [ - "node:internal/per_context/primordials", - 2.8 - ], - [ - "npm:%40prisma", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "node:internal/streams/readable", - 1.5 - ], - [ - "npm:pg-protocol", - 1.2 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.2 - ], - [ - "src/lib/tz/format.ts", - 1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 14.5 - ], - [ - "(garbage collector) (native)", - 13.1 - ], - [ - "(anonymous) npm:@prisma/client", - 3 - ], - [ - "utf8Slice (native)", - 3 - ], - [ - "(program) (native)", - 2.5 - ], - [ - "D npm:%40prisma", - 1.5 - ], - [ - "Fp npm:@prisma/client", - 1.5 - ], - [ - "ni npm:@prisma/client", - 1.5 - ], - [ - "Date1 npm:next", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "runMicrotasks (native)", - 1.5 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "promiseInitHook node:internal/async_hooks", - 1.5 - ], - [ - "next node:internal/per_context/primordials", - 1.5 - ], - [ - "Readable.push node:internal/streams/readable", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 21.2 - ], - [ - "src/lib/validations/source-priority.ts", - 16 - ], - [ - "src/lib/analytics/source-priority.ts", - 16 - ], - [ - "src/lib/tz/format.ts", - 2.5 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 2.5 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 2.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ] - ] - }, - { - "startMs": 38744, - "durationMs": 56, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 13.3 - ], - [ - "(native)", - 9.9 - ], - [ - "npm:@prisma/client", - 9.2 - ], - [ - "node:buffer", - 4.7 - ], - [ - "npm:pg-protocol", - 4.3 - ], - [ - "node:events", - 1.9 - ], - [ - "npm:pg", - 1.6 - ], - [ - "npm:%40prisma", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.4 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.4 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 8.9 - ], - [ - "(garbage collector) (native)", - 8.4 - ], - [ - "(anonymous) npm:@prisma/client", - 4.8 - ], - [ - "slice node:buffer", - 4.7 - ], - [ - "parseDataRowMessage npm:pg-protocol", - 3.1 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 2.9 - ], - [ - "emit node:events", - 1.9 - ], - [ - "cs npm:@prisma/client", - 1.7 - ], - [ - "_parseRowAsArray npm:pg", - 1.6 - ], - [ - "Zs npm:@prisma/client", - 1.6 - ], - [ - "writev (native)", - 1.5 - ], - [ - "D npm:%40prisma", - 1.5 - ], - [ - "Date1 npm:next", - 1.5 - ], - [ - "d src/lib/tz/format.ts", - 1.5 - ], - [ - "(anonymous) src/lib/validations/source-priority.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 21.8 - ], - [ - "src/lib/analytics/source-priority.ts", - 16 - ], - [ - "src/lib/validations/source-priority.ts", - 13.3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 4.3 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 3 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.4 - ] - ] - }, - { - "startMs": 42458, - "durationMs": 50, - "byModule": [ - [ - "npm:@prisma/client", - 13.5 - ], - [ - "src/lib/validations/source-priority.ts", - 10.1 - ], - [ - "(native)", - 7.6 - ], - [ - "npm:pg-protocol", - 3.2 - ], - [ - "node:events", - 3 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "npm:pg", - 2.8 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "npm:%40prisma", - 1.4 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 7 - ], - [ - "Fp npm:@prisma/client", - 4.5 - ], - [ - "(garbage collector) (native)", - 4.5 - ], - [ - "utf8Slice (native)", - 3.1 - ], - [ - "emit node:events", - 3 - ], - [ - "Tp npm:@prisma/client", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "#a npm:@prisma/client", - 1.8 - ], - [ - "(anonymous) npm:pg-protocol", - 1.6 - ], - [ - "handlePacket npm:pg-protocol", - 1.6 - ], - [ - "_parseRowAsArray npm:pg", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "promiseBeforeHook node:internal/async_hooks", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 17.3 - ], - [ - "src/lib/analytics/source-priority.ts", - 12.7 - ], - [ - "src/lib/validations/source-priority.ts", - 10.1 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 1.5 - ], - [ - "src/lib/jobs/job-lock.ts", - 1.3 - ], - [ - "src/lib/jobs/long-pass.ts", - 1.3 - ], - [ - "src/lib/jobs/run-job.ts", - 1.3 - ], - [ - "src/lib/jobs/job-observer.ts", - 1.3 - ], - [ - "src/lib/observability/in-flight.ts", - 1.3 - ] - ] - }, - { - "startMs": 45451, - "durationMs": 63, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 15.8 - ], - [ - "npm:@prisma/client", - 10.5 - ], - [ - "(native)", - 8.4 - ], - [ - "src/lib/tz/format.ts", - 5.3 - ], - [ - "node:internal/async_local_storage/async_hooks", - 4.4 - ], - [ - "node:buffer", - 3 - ], - [ - "npm:pg", - 3 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "npm:zod", - 3 - ], - [ - "npm:%40prisma", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "node:internal/stream_base_commons", - 1.4 - ], - [ - "npm:pg-protocol", - 1.4 - ], - [ - "node:internal/async_hooks", - 1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 13.1 - ], - [ - "(garbage collector) (native)", - 4.6 - ], - [ - "Zs npm:@prisma/client", - 4.5 - ], - [ - "_propagate node:internal/async_local_storage/async_hooks", - 4.4 - ], - [ - "slice node:buffer", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "te npm:zod", - 3 - ], - [ - "h src/lib/tz/format.ts", - 2.8 - ], - [ - "utf8Slice (native)", - 2.7 - ], - [ - "d src/lib/tz/format.ts", - 2.5 - ], - [ - "D npm:%40prisma", - 1.5 - ], - [ - "_handleDataRow npm:pg", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "Tp npm:@prisma/client", - 1.5 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 31.7 - ], - [ - "src/lib/analytics/source-priority.ts", - 21.8 - ], - [ - "src/lib/validations/source-priority.ts", - 18.8 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 5.3 - ], - [ - "src/lib/tz/format.ts", - 5.3 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 5.3 - ] - ] - }, - { - "startMs": 56854, - "durationMs": 66, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 15 - ], - [ - "npm:@prisma/client", - 13.7 - ], - [ - "(native)", - 6.6 - ], - [ - "node:buffer", - 6.6 - ], - [ - "npm:next", - 6 - ], - [ - "npm:pg", - 4.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 4.1 - ], - [ - "src/lib/tz/format.ts", - 3.1 - ], - [ - "node:internal/process/task_queues", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ], - [ - "npm:zod", - 1.5 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 13.5 - ], - [ - "slice node:buffer", - 6.6 - ], - [ - "Date1 npm:next", - 6 - ], - [ - "(anonymous) npm:@prisma/client", - 4.6 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 4.1 - ], - [ - "(program) (native)", - 3.8 - ], - [ - "_handleDataRow npm:pg", - 3 - ], - [ - "ni npm:@prisma/client", - 3 - ], - [ - "Zs npm:@prisma/client", - 3 - ], - [ - "d src/lib/tz/format.ts", - 1.6 - ], - [ - "nextTick node:internal/process/task_queues", - 1.5 - ], - [ - "utf8Slice (native)", - 1.5 - ], - [ - "_parseRowAsArray npm:pg", - 1.5 - ], - [ - "Dp npm:@prisma/client", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 25.3 - ], - [ - "src/lib/analytics/source-priority.ts", - 18.1 - ], - [ - "src/lib/validations/source-priority.ts", - 16.5 - ], - [ - "src/lib/tz/format.ts", - 3.1 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 3.1 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 3.1 - ] - ] - }, - { - "startMs": 56923, - "durationMs": 52, - "byModule": [ - [ - "(native)", - 20 - ], - [ - "npm:@prisma/client", - 10.1 - ], - [ - "node:buffer", - 3.7 - ], - [ - "npm:pg", - 3 - ], - [ - "src/lib/tz/format.ts", - 2.6 - ], - [ - "npm:pg-protocol", - 2.6 - ], - [ - "src/lib/analytics/source-priority.ts", - 2.6 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "npm:pg-types", - 1.5 - ], - [ - "npm:next", - 1.4 - ], - [ - "src/lib/validations/source-priority.ts", - 1.1 - ] - ], - "byFunction": [ - [ - "(garbage collector) (native)", - 20 - ], - [ - "slice node:buffer", - 3.7 - ], - [ - "Zs npm:@prisma/client", - 3 - ], - [ - "(anonymous) npm:@prisma/client", - 2.9 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 2.6 - ], - [ - "_handleDataRow npm:pg", - 1.5 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "h src/lib/tz/format.ts", - 1.5 - ], - [ - "(anonymous) npm:pg-protocol", - 1.5 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "_parseRowAsArray npm:pg", - 1.5 - ], - [ - "noParse npm:pg-types", - 1.5 - ], - [ - "Fp npm:@prisma/client", - 1.5 - ], - [ - "Tp npm:@prisma/client", - 1.4 - ], - [ - "Date1 npm:next", - 1.4 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 9.3 - ], - [ - "src/lib/analytics/source-priority.ts", - 5.3 - ], - [ - "src/lib/tz/format.ts", - 4.1 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 4.1 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 2.6 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "src/lib/validations/source-priority.ts", - 1.1 - ] - ] - }, - { - "startMs": 59508, - "durationMs": 56, - "byModule": [ - [ - "npm:@prisma/client", - 15.7 - ], - [ - "src/lib/validations/source-priority.ts", - 9.7 - ], - [ - "node:buffer", - 7 - ], - [ - "(native)", - 6.1 - ], - [ - "npm:pg-protocol", - 4.5 - ], - [ - "npm:next", - 4.1 - ], - [ - "src/lib/tz/format.ts", - 3 - ], - [ - "src/lib/analytics/source-priority.ts", - 3 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 1.5 - ] - ], - "byFunction": [ - [ - "Fp npm:@prisma/client", - 6.4 - ], - [ - "I src/lib/validations/source-priority.ts", - 6 - ], - [ - "slice node:buffer", - 5.5 - ], - [ - "(anonymous) npm:@prisma/client", - 4.6 - ], - [ - "handlePacket npm:pg-protocol", - 4.5 - ], - [ - "Date1 npm:next", - 4.1 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 3.8 - ], - [ - "utf8Slice (native)", - 3.1 - ], - [ - "(garbage collector) (native)", - 3 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 3 - ], - [ - "Zs npm:@prisma/client", - 1.6 - ], - [ - "request npm:@prisma/client", - 1.5 - ], - [ - "getEncodingOps node:buffer", - 1.5 - ], - [ - "Np npm:@prisma/client", - 1.5 - ], - [ - "d src/lib/tz/format.ts", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 21.7 - ], - [ - "src/lib/analytics/source-priority.ts", - 15.8 - ], - [ - "src/lib/validations/source-priority.ts", - 9.7 - ], - [ - "src/lib/tz/format.ts", - 5.9 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 5.9 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 5.9 - ], - [ - "src/lib/tz/zone-offset.ts", - 2.9 - ] - ] - }, - { - "startMs": 61315, - "durationMs": 62, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 20.1 - ], - [ - "npm:@prisma/client", - 14.1 - ], - [ - "(native)", - 8.2 - ], - [ - "npm:pg", - 4.5 - ], - [ - "node:buffer", - 3.1 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 3 - ], - [ - "npm:pg-protocol", - 1.5 - ], - [ - "npm:next", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.4 - ], - [ - "npm:zod", - 1.3 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 15.6 - ], - [ - "ni npm:@prisma/client", - 5.7 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 4.5 - ], - [ - "(program) (native)", - 3.7 - ], - [ - "utf8Slice (native)", - 3 - ], - [ - "Ae npm:@prisma/client", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "m src/lib/measurements/consolidation-base.ts", - 3 - ], - [ - "Zs npm:@prisma/client", - 2.7 - ], - [ - "getEncodingOps node:buffer", - 1.5 - ], - [ - "slice node:buffer", - 1.5 - ], - [ - "_parseRowAsArray npm:pg", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "(garbage collector) (native)", - 1.5 - ], - [ - "_handleDataRow npm:pg", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 30.3 - ], - [ - "src/lib/analytics/source-priority.ts", - 22.8 - ], - [ - "src/lib/validations/source-priority.ts", - 21.4 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 4.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 1.5 - ] - ] - }, - { - "startMs": 72986, - "durationMs": 57, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 19.1 - ], - [ - "npm:@prisma/client", - 10.2 - ], - [ - "(native)", - 6.6 - ], - [ - "npm:pg-protocol", - 5.5 - ], - [ - "npm:next", - 3 - ], - [ - "src/lib/analytics/source-priority.ts", - 3 - ], - [ - "npm:pg", - 2.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "node:buffer", - 1.1 - ], - [ - "node:events", - 1.1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 14.5 - ], - [ - "handlePacket npm:pg-protocol", - 4.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 4.5 - ], - [ - "Zs npm:@prisma/client", - 4.4 - ], - [ - "(garbage collector) (native)", - 4.1 - ], - [ - "Date1 npm:next", - 3 - ], - [ - "Tp npm:@prisma/client", - 3 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 3 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "_propagate node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "addFields npm:pg", - 1.5 - ], - [ - "utf8Slice (native)", - 1.5 - ], - [ - "(anonymous) npm:@prisma/client", - 1.5 - ], - [ - "(anonymous) src/lib/tz/format.ts", - 1.5 - ], - [ - "Fp npm:@prisma/client", - 1.2 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 29.6 - ], - [ - "src/lib/analytics/source-priority.ts", - 23.5 - ], - [ - "src/lib/validations/source-priority.ts", - 19.1 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 4.6 - ], - [ - "src/lib/tz/format.ts", - 4.5 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 4.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 3 - ] - ] - }, - { - "startMs": 73523, - "durationMs": 51, - "byModule": [ - [ - "src/lib/validations/source-priority.ts", - 12 - ], - [ - "npm:next", - 8.9 - ], - [ - "npm:@prisma/client", - 6 - ], - [ - "(native)", - 4.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 4.2 - ], - [ - "node:buffer", - 4 - ], - [ - "npm:pg", - 2.1 - ], - [ - "npm:pg-protocol", - 1.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "node:internal/async_hooks", - 1.5 - ], - [ - "npm:zod", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.1 - ], - [ - "node:internal/event_target", - 1 - ] - ], - "byFunction": [ - [ - "I src/lib/validations/source-priority.ts", - 9 - ], - [ - "Date1 npm:next", - 7.5 - ], - [ - "(anonymous) src/lib/analytics/source-priority.ts", - 4.2 - ], - [ - "slice node:buffer", - 4 - ], - [ - "(anonymous) npm:@prisma/client", - 3 - ], - [ - "(garbage collector) (native)", - 3 - ], - [ - "_parseRowAsArray npm:pg", - 2.1 - ], - [ - "handlePacket npm:pg-protocol", - 1.5 - ], - [ - "Fp npm:@prisma/client", - 1.5 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 1.5 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "getOrSetAsyncId node:internal/async_hooks", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "te npm:zod", - 1.5 - ], - [ - "ni npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 27.8 - ], - [ - "src/lib/analytics/source-priority.ts", - 17.7 - ], - [ - "src/lib/validations/source-priority.ts", - 13.5 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 8.6 - ], - [ - "src/lib/tz/format.ts", - 7.1 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 7.1 - ], - [ - "src/lib/tz/zone-offset.ts", - 6 - ] - ] - }, - { - "startMs": 75393, - "durationMs": 100, - "byModule": [ - [ - "(native)", - 32.6 - ], - [ - "npm:@prisma/client", - 17.2 - ], - [ - "src/lib/validations/source-priority.ts", - 14.6 - ], - [ - "npm:next", - 7.5 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 6.1 - ], - [ - "src/lib/tz/zone-offset.ts", - 5.6 - ], - [ - "npm:pg-protocol", - 4.1 - ], - [ - "npm:pg", - 2.5 - ], - [ - "src/lib/tz/format.ts", - 2.3 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "npm:zod", - 1.5 - ], - [ - "node:buffer", - 1.5 - ], - [ - "npm:%40prisma", - 1.5 - ], - [ - "src/lib/analytics/source-priority.ts", - 1.5 - ] - ], - "byFunction": [ - [ - "(garbage collector) (native)", - 28 - ], - [ - "I src/lib/validations/source-priority.ts", - 10.4 - ], - [ - "Date1 npm:next", - 7.5 - ], - [ - "(anonymous) npm:@prisma/client", - 5.7 - ], - [ - "Fp npm:@prisma/client", - 4.5 - ], - [ - "Zs npm:@prisma/client", - 4.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 4.3 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 4.1 - ], - [ - "handlePacket npm:pg-protocol", - 4.1 - ], - [ - "_ src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "(anonymous) src/lib/personal-records/pr-detection-worker.ts", - 3 - ], - [ - "_parseRowAsArray npm:pg", - 2.5 - ], - [ - "runMicrotasks (native)", - 2 - ], - [ - "_propagate node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "runInChildSpan npm:@prisma/client", - 1.5 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 34.6 - ], - [ - "src/lib/analytics/source-priority.ts", - 24.3 - ], - [ - "src/lib/validations/source-priority.ts", - 16.1 - ], - [ - "src/lib/tz/format.ts", - 10.9 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 10.9 - ], - [ - "src/lib/tz/zone-offset.ts", - 8.6 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 5.7 - ] - ] - }, - { - "startMs": 78085, - "durationMs": 54, - "byModule": [ - [ - "src/lib/tz/zone-offset.ts", - 26.6 - ], - [ - "src/lib/validations/source-priority.ts", - 13.5 - ], - [ - "npm:@prisma/client", - 4.2 - ], - [ - "(native)", - 4.1 - ], - [ - "node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "src/lib/tz/format.ts", - 1.5 - ], - [ - "src/lib/tz/intl-cache.ts", - 1.5 - ], - [ - "npm:pg", - 1.1 - ] - ], - "byFunction": [ - [ - "l src/lib/tz/zone-offset.ts", - 25.1 - ], - [ - "I src/lib/validations/source-priority.ts", - 10.6 - ], - [ - "(garbage collector) (native)", - 4.1 - ], - [ - "ni npm:@prisma/client", - 1.5 - ], - [ - "e.s.T src/lib/validations/source-priority.ts", - 1.5 - ], - [ - "_propagate node:internal/async_local_storage/async_hooks", - 1.5 - ], - [ - "(anonymous) src/lib/tz/zone-offset.ts", - 1.5 - ], - [ - "c src/lib/tz/format.ts", - 1.5 - ], - [ - "r src/lib/tz/intl-cache.ts", - 1.5 - ], - [ - "Zs npm:@prisma/client", - 1.5 - ], - [ - "(anonymous) src/lib/validations/source-priority.ts", - 1.4 - ], - [ - "Tp npm:@prisma/client", - 1.2 - ], - [ - "_handleDataRow npm:pg", - 1.1 - ] - ], - "byEntry": [ - [ - "src/lib/personal-records/pr-detection-worker.ts", - 44.7 - ], - [ - "src/lib/tz/format.ts", - 29.6 - ], - [ - "src/lib/measurements/consolidation-tz.ts", - 29.6 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 29.6 - ], - [ - "src/lib/tz/zone-offset.ts", - 28.1 - ], - [ - "src/lib/analytics/source-priority.ts", - 15 - ], - [ - "src/lib/validations/source-priority.ts", - 13.5 - ], - [ - "src/lib/tz/intl-cache.ts", - 1.5 - ] - ] - } - ], - "bootSelf": [ - [ - "(native)", - 853 - ], - [ - "npm:@prisma/client", - 295.9 - ], - [ - "node:internal/modules/cjs/loader", - 248.7 - ], - [ - "src/lib/validations/source-priority.ts", - 210.3 - ], - [ - "npm:next", - 188.4 - ], - [ - "turbopack:///[turbopack]/nodejs/runtime/build-base.ts", - 121.5 - ], - [ - "src/lib/tz/zone-offset.ts", - 83.2 - ], - [ - "wasm://wasm/00d1cb12", - 81.4 - ], - [ - "npm:pg", - 78.5 - ], - [ - "node:buffer", - 59.1 - ], - [ - "src/lib/tz/format.ts", - 55 - ], - [ - "npm:zod", - 50.7 - ], - [ - "src/lib/personal-records/pr-detection-worker.ts", - 48 - ], - [ - "npm:pg-protocol", - 37 - ], - [ - "node:internal/bootstrap/realm", - 32 - ], - [ - "src/lib/analytics/source-priority.ts", - 31.7 - ], - [ - "turbopack:///[turbopack]/shared/runtime/async-module.ts", - 27.7 - ], - [ - "node:internal/modules/esm/utils", - 26 - ], - [ - "npm:luxon", - 22.1 - ], - [ - "node:internal/async_hooks", - 21.7 - ], - [ - "node:internal/async_local_storage/async_hooks", - 21.2 - ], - [ - "node:fs", - 18.8 - ], - [ - "npm:pg-boss", - 18.3 - ], - [ - "npm:pg-pool", - 18.1 - ], - [ - "npm:yaml", - 15 - ], - [ - "src/lib/measurements/consolidation-base.ts", - 13.8 - ], - [ - "npm:%40prisma", - 13.2 - ], - [ - "node:internal/timers", - 13.1 - ], - [ - "node:internal/process/task_queues", - 12.6 - ], - [ - "turbopack:///[turbopack]/shared/runtime/runtime-utils.ts", - 12 - ], - [ - "node:internal/deps/undici/undici", - 11.9 - ], - [ - "node:events", - 11.8 - ], - [ - "npm:cron-parser", - 10 - ], - [ - "node:internal/streams/writable", - 9 - ], - [ - "node:net", - 8.5 - ], - [ - "src/lib/i18n/server-catalogs.ts", - 8.2 - ], - [ - "node:internal/stream_base_commons", - 7.2 - ], - [ - "src/lib/tz/intl-cache.ts", - 5.6 - ], - [ - "node:internal/url", - 5 - ], - [ - "node:internal/modules/package_json_reader", - 4.6 - ] - ], - "heap": { - "probes": { - "parsed locale catalogs": { - "count": 2, - "retainedMb": 1.6 - } - }, - "totalMb": 159.5, - "nodeCount": 1302659, - "byType": [ - [ - "string", - 67.5 - ], - [ - "array", - 26.6 - ], - [ - "code", - 19.2 - ], - [ - "object", - 15.9 - ], - [ - "hidden", - 9.2 - ], - [ - "native", - 8 - ], - [ - "object shape", - 5.9 - ], - [ - "closure", - 5.2 - ], - [ - "concatenated string", - 1.3 - ], - [ - "number", - 0.5 - ], - [ - "sliced string", - 0.2 - ], - [ - "regexp", - 0.2 - ], - [ - "synthetic", - 0 - ], - [ - "symbol", - 0 - ], - [ - "bigint", - 0 - ] - ], - "byConstructor": [ - [ - "array:(object elements)", - 20.8, - 25651 - ], - [ - "object:Object", - 11.4, - 293893 - ], - [ - "native:system / JSArrayBufferData", - 7.5, - 170 - ], - [ - "array:(object properties)", - 3.3, - 6517 - ], - [ - "array:", - 2.5, - 10169 - ], - [ - "closure:", - 1.6, - 29121 - ], - [ - "object:system / Context", - 1.6, - 28886 - ], - [ - "closure:native_bind", - 1, - 20804 - ], - [ - "object:Array", - 0.6, - 20938 - ], - [ - "object:Date1", - 0.5, - 5332 - ], - [ - "object:a", - 0.4, - 4438 - ], - [ - "native:Node / histogram", - 0.4, - 1 - ], - [ - "object:d", - 0.3, - 4425 - ], - [ - "closure:get", - 0.3, - 4732 - ], - [ - "object:Set", - 0.2, - 5670 - ], - [ - "object:Module", - 0.2, - 1963 - ], - [ - "closure:set", - 0.2, - 3428 - ], - [ - "closure:n", - 0.1, - 1572 - ], - [ - "closure:i", - 0.1, - 1578 - ], - [ - "object:Promise", - 0.1, - 1341 - ], - [ - "object:n", - 0.1, - 1881 - ], - [ - "object:ZodOptional", - 0.1, - 236 - ], - [ - "object:_TstNode", - 0.1, - 939 - ], - [ - "native:Node / parser_buffer", - 0.1, - 1 - ], - [ - "object:TCP", - 0, - 9 - ], - [ - "closure:callbackTrampoline", - 0, - 1 - ], - [ - "closure:promiseInitHook", - 0, - 1 - ], - [ - "closure:promiseBeforeHook", - 0, - 1 - ], - [ - "closure:promiseAfterHook", - 0, - 1 - ], - [ - "object:global", - 0, - 4 - ] - ], - "largestStrings": [ - [ - 5.6, - "module.exports=[234494,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:" - ], - [ - 5.6, - "module.exports=[17856,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:(" - ], - [ - 5.6, - "module.exports=[17856,A=>{\"use strict\";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i,bgBlack:(" - ], - [ - 4.4, - "const wasm = \"AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f3" - ], - [ - 4.4, - "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f38AYAR/f39/" - ], - [ - 1.9, - "lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAKcrACARAACS" - ], - [ - 1.3, - "(()=>{var e,t,r={\"./dist/build/webpack/alias/react-dom-server.js\"(e,t,r){\"use strict\";var n;function" - ], - [ - 1.2, - "module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BEGIN ([A-Z\\" - ], - [ - 1.2, - "module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BEGIN ([A-Z\\" - ], - [ - 1.1, - "module.exports=[799092,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),Objec" - ], - [ - 1.1, - "// v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. See .plan" - ], - [ - 1.1, - "module.exports=[971743,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 1, - "module.exports=[835717,(A,e,t)=>{\"use strict\";let r=A.r(57328),{addAbortListener:s}=A.r(687769),{Rea" - ], - [ - 0.8, - "module.exports=[767189,e=>e.a(async(t,a)=>{try{var n=e.i(64105),r=e.i(563616),i=t([n]);[n]=i.then?(a" - ], - [ - 0.7, - "module.exports=[594108,806176,674750,98746,t=>{\"use strict\";var i;let r;function n(t){return t}funct" - ], - [ - 0.7, - "module.exports=[144056,t=>{\"use strict\";let i,n;function r(t){return t}function a(t){return t}functi" - ], - [ - 0.6, - "module.exports=[992657,e=>e.a(async(t,a)=>{try{var r=e.i(851258),n=t([r]);async function i(e,t,a,n){" - ], - [ - 0.5, - "module.exports=[388046,e=>{\"use strict\";class t{value;next;constructor(e){this.value=e}}class n{#e;#" - ], - [ - 0.5, - "module.exports=[388046,e=>{\"use strict\";class t{value;next;constructor(e){this.value=e}}class n{#e;#" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ], - [ - 0.5, - "module.exports=[120630,(e,t,n)=>{\"use strict\";Object.defineProperty(n,\"__esModule\",{value:!0}),n.def" - ] - ], - "topRetainers": [ - { - "mb": 34.6, - "name": "object:yr", - "path": "[1] > [10] > 22 > extension > global > prisma > [1] > _engine" - }, - { - "mb": 7.4, - "name": "object:system / Context", - "path": "Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context" - }, - { - "mb": 5.9, - "name": "object:Promise", - "path": "Object > executor > e > #r > gr > transactions > Map > table > > 6 > closing" - }, - { - "mb": 5.6, - "name": "object:Module", - "path": "[1] > [10] > 99 > process > getBuiltinModule > getBuiltinModule > context > system / Context > $Module > Module > _cache > .next/server/chunks/_1mg8fms._.js" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "Module > _cache > Object > .next/server/chunks/_1kwfnjg._.js > Module > exports > Array > [1] > > shared > script" - }, - { - "mb": 5.6, - "name": "code:.next/server/chu", - "path": "global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script" - }, - { - "mb": 4.4, - "name": "object:Generator", - "path": "[6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4]" - }, - { - "mb": 4.4, - "name": "string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/", - "path": "[1] > [21] > 60025" - }, - { - "mb": 3.4, - "name": "object:Object", - "path": "system / Context > $Module > Module > _cache > Object > .next/server/chunks/[turbopack]_runtime.js > Module > exports > > context > moduleCache" - }, - { - "mb": 2.6, - "name": "object:global", - "path": "[1] > [10] > 22 > extension" - } - ], - "sourceStringsMb": 39.8 - } -} diff --git a/scripts/bench/results/v1.45.0-boot-final.md b/scripts/bench/results/v1.45.0-boot-final.md deleted file mode 100644 index f1bdaed56..000000000 --- a/scripts/bench/results/v1.45.0-boot-final.md +++ /dev/null @@ -1,804 +0,0 @@ -# Boot profile v1.45.0-boot-final - -Commit a57fa8335, 2026-10-22T06:58:52.591Z. Production build, every role, heap ceiling 792 MB, 300 s idle. 14 cores, load average 5.6,6.4,11.1 at the end. - -- first answer from /api/health: 1325 ms; background role ready: 3217 ms -- event-loop stalls logged: 0, max 0 ms - -## Stalls - - -## Heap samples - -| at s | used MB | old | large obj | code | external | array buffers | rss | -| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| 6 | 225 | 158 | 54 | 4 | 11 | 0 | 500 | -| 306 | 158 | 96 | 53 | 5 | 11 | 0 | 788 | - -## Full collections (mark-compact) - -98 mark-compacts during the run. Used heap before the collection, and what survived it (the live heap): - -| at s | before MB | after MB | -| ---: | ---: | ---: | -| 0 | 21.5 | 17.5 | -| 8 | 203.8 | 146.5 | -| 24 | 236.5 | 142.5 | -| 41 | 223.8 | 144.9 | -| 60 | 225.8 | 147.5 | -| 85 | 224.4 | 147.7 | -| 124 | 225.4 | 151.2 | -| 167 | 176.7 | 151 | -| 209 | 230.1 | 150.4 | -| 242 | 197.9 | 151.5 | -| 273 | 224.8 | 152.2 | -| 302 | 177.9 | 152.5 | -| 312 | 170.5 | 152.9 | -| 327 | 170.6 | 152 | -| 338 | 169.7 | 151.8 | - -## Main-thread busy stretches over 50 ms without an idle sample, first two minutes - -### at 5 ms, 396 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 230.2 | -| node:internal/modules/cjs/loader | 57.8 | -| npm:next | 52.3 | -| node:internal/bootstrap/realm | 29.3 | -| node:internal/deps/undici/undici | 8.9 | -| node:internal/modules/esm/loader | 4.5 | -| node:fs | 3 | -| node:internal/net | 2.5 | -| node:internal/bootstrap/switches/is_main_thread | 2 | -| node:os | 1.8 | - -| inclusive, application modules | ms | -| --- | ---: | - -### at 472 ms, 336 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 121.3 | -| wasm://wasm/00d1cb12 | 43 | -| node:internal/modules/cjs/loader | 40.5 | -| npm:next | 31.7 | -| npm:@prisma/client | 23.8 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 23.4 | -| node:internal/modules/esm/utils | 17.3 | -| npm:zod | 5.9 | -| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 3 | -| node:vm | 1.7 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/instrumentation.ts | 95.8 | -| src/lib/db-session.ts | 47.3 | -| src/lib/process-type.ts | 27.1 | -| src/lib/boot/startup-state.ts | 25.5 | -| src/lib/boot/boot-steps.ts | 25.5 | -| src/lib/logging/background.ts | 25.5 | -| src/lib/logging/redact.ts | 13 | -| src/lib/logging/context.ts | 11 | -| src/lib/validations/measurement.ts | 10 | -| src/generated/prisma/internal/class.ts | 4.7 | - -### at 811 ms, 225 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 52.3 | -| (native) | 48.2 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 37 | -| npm:next | 22.2 | -| npm:zod | 12.5 | -| node:internal/modules/esm/utils | 8.7 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 5.6 | -| node:fs | 4.5 | -| node:internal/async_hooks | 3 | -| node:internal/modules/esm/translators | 2.6 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/app/api/health/route.ts | 62 | -| src/lib/api-errors.ts | 25.9 | -| src/lib/auth/secure-cookie.ts | 14.9 | -| src/lib/logging/background.ts | 13.4 | -| src/lib/sharing/acting-session.ts | 12.8 | -| src/lib/validations/measurement.ts | 11.9 | -| src/lib/db-session.ts | 11.3 | -| src/lib/export/environment-backup.ts | 11.3 | -| src/lib/export/full-backup-payload.ts | 11.3 | -| src/lib/jobs/job-failures.ts | 11.3 | - -### at 1063 ms, 246 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 64.7 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 47.2 | -| node:internal/modules/cjs/loader | 31.6 | -| npm:luxon | 18 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 15.3 | -| npm:yaml | 15 | -| npm:zod | 10.2 | -| src/lib/i18n/server-catalogs.ts | 8.2 | -| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 4.5 | -| npm:cron-parser | 4 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 137.6 | -| src/lib/jobs/reminder/google-health-sync.ts | 78 | -| src/lib/whoop/sync.ts | 65.9 | -| src/instrumentation.ts | 53.8 | -| src/lib/insights/bucket-series.ts | 47.1 | -| src/lib/whoop/oauth-descriptor.ts | 47.1 | -| src/lib/whoop/sync-core.ts | 47.1 | -| src/lib/whoop/sync-body.ts | 47.1 | -| src/lib/import/unzip-export-xml.ts | 35.3 | -| src/lib/jobs/integration-status-null-probe.ts | 35.3 | - -### at 1363 ms, 68 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 29.2 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 6.7 | -| (native) | 5.9 | -| turbopack:///[turbopack]/shared/runtime/runtime-utils.ts | 4.5 | -| src/lib/daily/today-overview.ts | 4.1 | -| node:fs | 3 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 3 | -| node:internal/streams/writable | 1.5 | -| src/lib/jobs/reminder/insights-handlers.ts | 1.5 | -| npm:next | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 62.5 | -| src/lib/jobs/reminder/insights-handlers.ts | 24.4 | -| src/lib/ai/prompts/arrival-reaction.ts | 12.8 | -| src/lib/daily/score-steady.ts | 11.3 | -| src/lib/dashboard/bands.ts | 7.1 | -| src/lib/dashboard/snapshot-read.ts | 5.8 | -| src/lib/jobs/data-arrival.ts | 4.5 | -| src/lib/daily/today-overview.ts | 4.1 | -| src/lib/daily/priority-item-key.ts | 4.1 | -| src/lib/ai/prompts/status-batch.ts | 3 | - -### at 1545 ms, 57 ms - -| self time by module | ms | -| --- | ---: | -| node:internal/modules/cjs/loader | 25.3 | -| (native) | 12.9 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 7.2 | -| node:fs | 4.2 | -| node:buffer | 1.5 | -| npm:zod | 1.5 | -| src/lib/jobs/pr-detection.ts | 1.5 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 1.5 | -| src/lib/jobs/measurement-maintenance.ts | 1.3 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/jobs/reminder-worker.ts | 55.3 | -| src/lib/jobs/reminder/ops-handlers.ts | 13 | -| src/lib/export/restore-backup.ts | 3 | -| src/lib/medications/ingredients/label-extract.ts | 3 | -| src/lib/environment/service.ts | 1.5 | -| src/lib/export/backup-blob.ts | 1.5 | -| src/lib/jobs/pr-detection.ts | 1.5 | -| src/lib/jobs/document-content-index-backfill.ts | 1.5 | -| src/lib/labs/ocr-extract.ts | 1.5 | -| src/lib/jobs/reminder/backup-handlers.ts | 1.5 | - -### at 1820 ms, 114 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/tz/zone-offset.ts | 36 | -| npm:@prisma/client | 18.2 | -| src/lib/validations/source-priority.ts | 18.1 | -| (native) | 13.1 | -| src/lib/personal-records/pr-detection-worker.ts | 7.2 | -| node:buffer | 4.1 | -| src/lib/analytics/source-priority.ts | 3.7 | -| npm:zod | 2.3 | -| npm:pg | 2 | -| src/lib/tz/format.ts | 1.9 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 73.3 | -| src/lib/tz/format.ts | 40.7 | -| src/lib/measurements/consolidation-tz.ts | 40.7 | -| src/lib/tz/zone-offset.ts | 38.8 | -| src/lib/measurements/consolidation-base.ts | 38.1 | -| src/lib/analytics/source-priority.ts | 28.1 | -| src/lib/validations/source-priority.ts | 20.4 | -| src/lib/tz/intl-cache.ts | 1.5 | - -### at 2407 ms, 50 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 18.9 | -| src/lib/validations/source-priority.ts | 14.1 | -| src/lib/analytics/source-priority.ts | 3.7 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| npm:next | 1.8 | -| npm:%40prisma | 1.5 | -| src/lib/tz/format.ts | 1.5 | -| node:internal/async_local_storage/async_hooks | 1.5 | -| npm:pg | 1.5 | -| npm:@prisma/client | 1.3 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 26.6 | -| src/lib/analytics/source-priority.ts | 19.3 | -| src/lib/validations/source-priority.ts | 14.1 | -| src/lib/tz/format.ts | 3 | -| src/lib/measurements/consolidation-tz.ts | 3 | -| src/lib/measurements/consolidation-base.ts | 2.8 | -| src/lib/tz/zone-offset.ts | 1.5 | - -### at 18192 ms, 59 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 15.5 | -| npm:@prisma/client | 13.9 | -| src/lib/validations/source-priority.ts | 11.8 | -| npm:pg | 3.6 | -| node:events | 3 | -| node:buffer | 2.5 | -| src/lib/measurements/consolidation-base.ts | 1.5 | -| src/lib/personal-records/pr-detection-worker.ts | 1.5 | -| node:internal/async_hooks | 1.5 | -| npm:pg-types | 1.4 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 18.9 | -| src/lib/analytics/source-priority.ts | 12.9 | -| src/lib/validations/source-priority.ts | 11.8 | -| src/lib/measurements/consolidation-base.ts | 4.5 | -| src/lib/tz/zone-offset.ts | 2.6 | -| src/lib/tz/format.ts | 2.6 | -| src/lib/measurements/consolidation-tz.ts | 2.6 | - -### at 19914 ms, 65 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 18.1 | -| (native) | 12.3 | -| npm:@prisma/client | 11.6 | -| npm:next | 5.9 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| src/lib/tz/format.ts | 3 | -| npm:pg | 2.6 | -| npm:pg-protocol | 2.5 | -| node:internal/async_hooks | 1.5 | -| src/lib/analytics/source-priority.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 29.8 | -| src/lib/analytics/source-priority.ts | 22.6 | -| src/lib/validations/source-priority.ts | 18.1 | -| src/lib/tz/format.ts | 5.8 | -| src/lib/measurements/consolidation-tz.ts | 5.8 | -| src/lib/tz/zone-offset.ts | 2.8 | -| src/lib/measurements/consolidation-base.ts | 2.8 | - -### at 20296 ms, 55 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 22.4 | -| npm:@prisma/client | 14.8 | -| node:buffer | 3 | -| npm:zod | 3 | -| src/lib/measurements/consolidation-base.ts | 3 | -| src/lib/tz/format.ts | 1.5 | -| node:internal/stream_base_commons | 1.5 | -| src/lib/analytics/source-priority.ts | 1.5 | -| src/lib/personal-records/pr-detection-worker.ts | 1.5 | -| npm:pg-protocol | 1.3 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 10.5 | -| src/lib/analytics/source-priority.ts | 6 | -| src/lib/validations/source-priority.ts | 3 | -| src/lib/measurements/consolidation-base.ts | 3 | -| src/lib/tz/format.ts | 1.5 | -| src/lib/measurements/consolidation-tz.ts | 1.5 | - -### at 30289 ms, 69 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 18.3 | -| (native) | 12.9 | -| npm:@prisma/client | 8.8 | -| node:buffer | 6.8 | -| src/lib/personal-records/pr-detection-worker.ts | 4.5 | -| src/lib/tz/format.ts | 4 | -| npm:pg-protocol | 3 | -| npm:zod | 3 | -| node:timers | 1.5 | -| node:internal/async_hooks | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 33 | -| src/lib/analytics/source-priority.ts | 24.3 | -| src/lib/validations/source-priority.ts | 21.3 | -| src/lib/tz/format.ts | 5.6 | -| src/lib/measurements/consolidation-tz.ts | 5.6 | -| src/lib/measurements/consolidation-base.ts | 2.6 | -| src/lib/tz/zone-offset.ts | 1.5 | - -### at 31004 ms, 68 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 17.8 | -| src/lib/validations/source-priority.ts | 14.9 | -| npm:@prisma/client | 7.6 | -| npm:next | 4.5 | -| node:internal/async_hooks | 4.5 | -| npm:pg-protocol | 4.1 | -| npm:pg | 3 | -| src/lib/tz/zone-offset.ts | 3 | -| node:buffer | 2.7 | -| src/lib/analytics/source-priority.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 26.4 | -| src/lib/analytics/source-priority.ts | 19 | -| src/lib/validations/source-priority.ts | 16.2 | -| src/lib/measurements/consolidation-base.ts | 6.1 | -| src/lib/tz/format.ts | 5.8 | -| src/lib/measurements/consolidation-tz.ts | 5.8 | -| src/lib/tz/zone-offset.ts | 4.5 | - -### at 35350 ms, 65 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 17 | -| npm:@prisma/client | 11.8 | -| (native) | 8.6 | -| node:buffer | 6.5 | -| src/lib/tz/format.ts | 4.5 | -| npm:pg-protocol | 3 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| npm:%40prisma | 1.6 | -| npm:pg | 1.5 | -| src/lib/analytics/source-priority.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 26 | -| src/lib/analytics/source-priority.ts | 20 | -| src/lib/validations/source-priority.ts | 17 | -| src/lib/tz/format.ts | 4.5 | -| src/lib/measurements/consolidation-tz.ts | 4.5 | -| src/lib/measurements/consolidation-base.ts | 3 | - -### at 36452 ms, 57 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 20 | -| src/lib/validations/source-priority.ts | 16 | -| npm:@prisma/client | 7.3 | -| node:internal/per_context/primordials | 2.8 | -| npm:%40prisma | 1.5 | -| npm:next | 1.5 | -| src/lib/tz/zone-offset.ts | 1.5 | -| node:internal/async_hooks | 1.5 | -| node:internal/streams/readable | 1.5 | -| npm:pg-protocol | 1.2 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 21.2 | -| src/lib/validations/source-priority.ts | 16 | -| src/lib/analytics/source-priority.ts | 16 | -| src/lib/tz/format.ts | 2.5 | -| src/lib/measurements/consolidation-tz.ts | 2.5 | -| src/lib/measurements/consolidation-base.ts | 2.5 | -| src/lib/tz/zone-offset.ts | 1.5 | - -### at 38744 ms, 56 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 13.3 | -| (native) | 9.9 | -| npm:@prisma/client | 9.2 | -| node:buffer | 4.7 | -| npm:pg-protocol | 4.3 | -| node:events | 1.9 | -| npm:pg | 1.6 | -| npm:%40prisma | 1.5 | -| npm:next | 1.5 | -| src/lib/tz/format.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 21.8 | -| src/lib/analytics/source-priority.ts | 16 | -| src/lib/validations/source-priority.ts | 13.3 | -| src/lib/measurements/consolidation-base.ts | 4.3 | -| src/lib/tz/format.ts | 3 | -| src/lib/measurements/consolidation-tz.ts | 3 | -| src/lib/tz/zone-offset.ts | 1.4 | - -### at 42458 ms, 50 ms - -| self time by module | ms | -| --- | ---: | -| npm:@prisma/client | 13.5 | -| src/lib/validations/source-priority.ts | 10.1 | -| (native) | 7.6 | -| npm:pg-protocol | 3.2 | -| node:events | 3 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| src/lib/tz/format.ts | 3 | -| npm:pg | 2.8 | -| node:internal/async_hooks | 1.5 | -| npm:%40prisma | 1.4 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 17.3 | -| src/lib/analytics/source-priority.ts | 12.7 | -| src/lib/validations/source-priority.ts | 10.1 | -| src/lib/tz/format.ts | 3 | -| src/lib/measurements/consolidation-tz.ts | 3 | -| src/lib/measurements/consolidation-base.ts | 1.5 | -| src/lib/jobs/job-lock.ts | 1.3 | -| src/lib/jobs/long-pass.ts | 1.3 | -| src/lib/jobs/run-job.ts | 1.3 | -| src/lib/jobs/job-observer.ts | 1.3 | - -### at 45451 ms, 63 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 15.8 | -| npm:@prisma/client | 10.5 | -| (native) | 8.4 | -| src/lib/tz/format.ts | 5.3 | -| node:internal/async_local_storage/async_hooks | 4.4 | -| node:buffer | 3 | -| npm:pg | 3 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| npm:zod | 3 | -| npm:%40prisma | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 31.7 | -| src/lib/analytics/source-priority.ts | 21.8 | -| src/lib/validations/source-priority.ts | 18.8 | -| src/lib/measurements/consolidation-base.ts | 5.3 | -| src/lib/tz/format.ts | 5.3 | -| src/lib/measurements/consolidation-tz.ts | 5.3 | - -### at 56854 ms, 66 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 15 | -| npm:@prisma/client | 13.7 | -| (native) | 6.6 | -| node:buffer | 6.6 | -| npm:next | 6 | -| npm:pg | 4.5 | -| src/lib/personal-records/pr-detection-worker.ts | 4.1 | -| src/lib/tz/format.ts | 3.1 | -| node:internal/process/task_queues | 1.5 | -| node:internal/async_hooks | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 25.3 | -| src/lib/analytics/source-priority.ts | 18.1 | -| src/lib/validations/source-priority.ts | 16.5 | -| src/lib/tz/format.ts | 3.1 | -| src/lib/measurements/consolidation-tz.ts | 3.1 | -| src/lib/measurements/consolidation-base.ts | 3.1 | - -### at 56923 ms, 52 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 20 | -| npm:@prisma/client | 10.1 | -| node:buffer | 3.7 | -| npm:pg | 3 | -| src/lib/tz/format.ts | 2.6 | -| npm:pg-protocol | 2.6 | -| src/lib/analytics/source-priority.ts | 2.6 | -| src/lib/personal-records/pr-detection-worker.ts | 1.5 | -| src/lib/tz/zone-offset.ts | 1.5 | -| npm:pg-types | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 9.3 | -| src/lib/analytics/source-priority.ts | 5.3 | -| src/lib/tz/format.ts | 4.1 | -| src/lib/measurements/consolidation-tz.ts | 4.1 | -| src/lib/measurements/consolidation-base.ts | 2.6 | -| src/lib/tz/zone-offset.ts | 1.5 | -| src/lib/validations/source-priority.ts | 1.1 | - -### at 59508 ms, 56 ms - -| self time by module | ms | -| --- | ---: | -| npm:@prisma/client | 15.7 | -| src/lib/validations/source-priority.ts | 9.7 | -| node:buffer | 7 | -| (native) | 6.1 | -| npm:pg-protocol | 4.5 | -| npm:next | 4.1 | -| src/lib/tz/format.ts | 3 | -| src/lib/analytics/source-priority.ts | 3 | -| node:internal/async_hooks | 1.5 | -| src/lib/measurements/consolidation-base.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 21.7 | -| src/lib/analytics/source-priority.ts | 15.8 | -| src/lib/validations/source-priority.ts | 9.7 | -| src/lib/tz/format.ts | 5.9 | -| src/lib/measurements/consolidation-tz.ts | 5.9 | -| src/lib/measurements/consolidation-base.ts | 5.9 | -| src/lib/tz/zone-offset.ts | 2.9 | - -### at 61315 ms, 62 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 20.1 | -| npm:@prisma/client | 14.1 | -| (native) | 8.2 | -| npm:pg | 4.5 | -| node:buffer | 3.1 | -| src/lib/personal-records/pr-detection-worker.ts | 3 | -| src/lib/measurements/consolidation-base.ts | 3 | -| npm:pg-protocol | 1.5 | -| npm:next | 1.5 | -| src/lib/analytics/source-priority.ts | 1.4 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 30.3 | -| src/lib/analytics/source-priority.ts | 22.8 | -| src/lib/validations/source-priority.ts | 21.4 | -| src/lib/measurements/consolidation-base.ts | 4.5 | -| src/lib/tz/zone-offset.ts | 1.5 | -| src/lib/tz/format.ts | 1.5 | -| src/lib/measurements/consolidation-tz.ts | 1.5 | - -### at 72986 ms, 57 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 19.1 | -| npm:@prisma/client | 10.2 | -| (native) | 6.6 | -| npm:pg-protocol | 5.5 | -| npm:next | 3 | -| src/lib/analytics/source-priority.ts | 3 | -| npm:pg | 2.5 | -| src/lib/personal-records/pr-detection-worker.ts | 1.5 | -| node:internal/async_local_storage/async_hooks | 1.5 | -| src/lib/tz/format.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 29.6 | -| src/lib/analytics/source-priority.ts | 23.5 | -| src/lib/validations/source-priority.ts | 19.1 | -| src/lib/measurements/consolidation-base.ts | 4.6 | -| src/lib/tz/format.ts | 4.5 | -| src/lib/measurements/consolidation-tz.ts | 4.5 | -| src/lib/tz/zone-offset.ts | 3 | - -### at 73523 ms, 51 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/validations/source-priority.ts | 12 | -| npm:next | 8.9 | -| npm:@prisma/client | 6 | -| (native) | 4.5 | -| src/lib/analytics/source-priority.ts | 4.2 | -| node:buffer | 4 | -| npm:pg | 2.1 | -| npm:pg-protocol | 1.5 | -| src/lib/personal-records/pr-detection-worker.ts | 1.5 | -| src/lib/tz/zone-offset.ts | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 27.8 | -| src/lib/analytics/source-priority.ts | 17.7 | -| src/lib/validations/source-priority.ts | 13.5 | -| src/lib/measurements/consolidation-base.ts | 8.6 | -| src/lib/tz/format.ts | 7.1 | -| src/lib/measurements/consolidation-tz.ts | 7.1 | -| src/lib/tz/zone-offset.ts | 6 | - -### at 75393 ms, 100 ms - -| self time by module | ms | -| --- | ---: | -| (native) | 32.6 | -| npm:@prisma/client | 17.2 | -| src/lib/validations/source-priority.ts | 14.6 | -| npm:next | 7.5 | -| src/lib/personal-records/pr-detection-worker.ts | 6.1 | -| src/lib/tz/zone-offset.ts | 5.6 | -| npm:pg-protocol | 4.1 | -| npm:pg | 2.5 | -| src/lib/tz/format.ts | 2.3 | -| node:internal/async_local_storage/async_hooks | 1.5 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 34.6 | -| src/lib/analytics/source-priority.ts | 24.3 | -| src/lib/validations/source-priority.ts | 16.1 | -| src/lib/tz/format.ts | 10.9 | -| src/lib/measurements/consolidation-tz.ts | 10.9 | -| src/lib/tz/zone-offset.ts | 8.6 | -| src/lib/measurements/consolidation-base.ts | 5.7 | - -### at 78085 ms, 54 ms - -| self time by module | ms | -| --- | ---: | -| src/lib/tz/zone-offset.ts | 26.6 | -| src/lib/validations/source-priority.ts | 13.5 | -| npm:@prisma/client | 4.2 | -| (native) | 4.1 | -| node:internal/async_local_storage/async_hooks | 1.5 | -| src/lib/tz/format.ts | 1.5 | -| src/lib/tz/intl-cache.ts | 1.5 | -| npm:pg | 1.1 | - -| inclusive, application modules | ms | -| --- | ---: | -| src/lib/personal-records/pr-detection-worker.ts | 44.7 | -| src/lib/tz/format.ts | 29.6 | -| src/lib/measurements/consolidation-tz.ts | 29.6 | -| src/lib/measurements/consolidation-base.ts | 29.6 | -| src/lib/tz/zone-offset.ts | 28.1 | -| src/lib/analytics/source-priority.ts | 15 | -| src/lib/validations/source-priority.ts | 13.5 | -| src/lib/tz/intl-cache.ts | 1.5 | - -## Self time by module until the worker is ready - -| module | ms | -| --- | ---: | -| (native) | 853 | -| npm:@prisma/client | 295.9 | -| node:internal/modules/cjs/loader | 248.7 | -| src/lib/validations/source-priority.ts | 210.3 | -| npm:next | 188.4 | -| turbopack:///[turbopack]/nodejs/runtime/build-base.ts | 121.5 | -| src/lib/tz/zone-offset.ts | 83.2 | -| wasm://wasm/00d1cb12 | 81.4 | -| npm:pg | 78.5 | -| node:buffer | 59.1 | -| src/lib/tz/format.ts | 55 | -| npm:zod | 50.7 | -| src/lib/personal-records/pr-detection-worker.ts | 48 | -| npm:pg-protocol | 37 | -| node:internal/bootstrap/realm | 32 | -| src/lib/analytics/source-priority.ts | 31.7 | -| turbopack:///[turbopack]/shared/runtime/async-module.ts | 27.7 | -| node:internal/modules/esm/utils | 26 | -| npm:luxon | 22.1 | -| node:internal/async_hooks | 21.7 | -| node:internal/async_local_storage/async_hooks | 21.2 | -| node:fs | 18.8 | -| npm:pg-boss | 18.3 | -| npm:pg-pool | 18.1 | -| npm:yaml | 15 | - -## Heap snapshot after 300 s idle (live heap after a full GC) - -159.5 MB in 1302659 nodes; module source strings over 50 KB: 39.8 MB. - -- parsed locale catalogs: 2 objects, 1.6 MB retained - -| node type | MB | -| --- | ---: | -| string | 67.5 | -| array | 26.6 | -| code | 19.2 | -| object | 15.9 | -| hidden | 9.2 | -| native | 8 | -| object shape | 5.9 | -| closure | 5.2 | -| concatenated string | 1.3 | -| number | 0.5 | -| sliced string | 0.2 | -| regexp | 0.2 | -| synthetic | 0 | -| symbol | 0 | -| bigint | 0 | - -| constructor | MB | count | -| --- | ---: | ---: | -| array:(object elements) | 20.8 | 25651 | -| object:Object | 11.4 | 293893 | -| native:system / JSArrayBufferData | 7.5 | 170 | -| array:(object properties) | 3.3 | 6517 | -| array: | 2.5 | 10169 | -| closure: | 1.6 | 29121 | -| object:system / Context | 1.6 | 28886 | -| closure:native_bind | 1 | 20804 | -| object:Array | 0.6 | 20938 | -| object:Date1 | 0.5 | 5332 | -| object:a | 0.4 | 4438 | -| native:Node / histogram | 0.4 | 1 | -| object:d | 0.3 | 4425 | -| closure:get | 0.3 | 4732 | -| object:Set | 0.2 | 5670 | -| object:Module | 0.2 | 1963 | -| closure:set | 0.2 | 3428 | -| closure:n | 0.1 | 1572 | -| closure:i | 0.1 | 1578 | -| object:Promise | 0.1 | 1341 | - -| largest retained subtrees | MB | retaining path | -| --- | ---: | --- | -| object:yr | 34.6 | [1] > [10] > 22 > extension > global > prisma > [1] > _engine | -| object:system / Context | 7.4 | Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [25] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4] > context | -| object:Promise | 5.9 | Object > executor > e > #r > gr > transactions > Map > table > > 6 > closing | -| object:Module | 5.6 | [1] > [10] > 99 > process > getBuiltinModule > getBuiltinModule > context > system / Context > $Module > Module > _cache > | -| code:.next/server/chu | 5.6 | Module > _cache > Object > .next/server/chunks/_1kwfnjg._.js > Module > exports > Array > [1] > > shared > script | -| code:.next/server/chu | 5.6 | global > prisma > [1] > t > _engineConfig > Object > compilerWasm > Object > getRuntime > getRuntime > shared > script | -| object:Generator | 4.4 | [6] > principal_realm > Node / PrincipalRealm > base_object_list > Node / BaseObjectList > [26] > Node / ModuleWrap > native_to_javascript > ModuleWrap > 2 > [4 | -| string:AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/ | 4.4 | [1] > [21] > 60025 | -| object:Object | 3.4 | system / Context > $Module > Module > _cache > Object > .next/server/chunks/[turbopack]_runtime.js > Module > export | -| object:global | 2.6 | [1] > [10] > 22 > extension | - -| largest strings | MB | -| --- | ---: | -| module.exports=[234494,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=> | 5.6 | -| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 | -| module.exports=[17856,A=>{"use strict";var e,C,g,I,Q=Object.defineProperty,B={},s={$:()=>i | 5.6 | -| const wasm = "AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGAB | 4.4 | -| AGFzbQEAAAABtgRLYAN/f38Bf2ACf38Bf2ACf38AYAF/AGADf39/AGAEf39/fwBgBX9/f39/AGABfwF/YAZ/f39/f3 | 4.4 | -| lliADOAUngIEAACgKwAgBgAAoSsAIAcAAKErACAIAACiKwAgCQAAoysAIAoAAKQrACALAAClKwAgDAAApisAIA0AAK | 1.9 | -| (()=>{var e,t,r={"./dist/build/webpack/alias/react-dom-server.js"(e,t,r){"use strict";var | 1.3 | -| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 | -| module.exports=[842865,(e,t,r)=>{let i=e.r(522734);t.exports=function(e){return e?/-----BE | 1.2 | -| module.exports=[799092,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value: | 1.1 | -| // v1.4.34 web-freeze. Schema is locked additive-only until iOS launch // + v1.5.0 marker. | 1.1 | -| module.exports=[971743,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1.1 | -| module.exports=[835717,(A,e,t)=>{"use strict";let r=A.r(57328),{addAbortListener:s}=A.r(68 | 1 | -| module.exports=[767189,e=>e.a(async(t,a)=>{try{var n=e.i(64105),r=e.i(563616),i=t([n]);[n] | 0.8 | -| module.exports=[594108,806176,674750,98746,t=>{"use strict";var i;let r;function n(t){retu | 0.7 | diff --git a/scripts/bench/results/v1.45.0-fan-out.md b/scripts/bench/results/v1.45.0-fan-out.md deleted file mode 100644 index 22fa259a6..000000000 --- a/scripts/bench/results/v1.45.0-fan-out.md +++ /dev/null @@ -1,22 +0,0 @@ -# v1.45.0: request fan-out - -Same runs as `v1.45.0-read-path.md`. - -| request | statements before | statements after | p50 ms before / after | -| --- | ---: | ---: | ---: | -| /api/auth/me | 19 | 14 | 9 / 8 | -| day view, cold | 49 | 48 | 77 / 68 | - -- `/api/auth/me` read the AppSettings singleton six times (MFA policy, - Assistant switches, module availability, provider presence, AI usage - bounds, reasoning controls); one memoised read per request now serves all - six (`src/lib/settings/operator-row.ts`). On the small budget fixture it - went from 18 to 13 statements. The remaining reads are the session, the - grant, cycle profile, onboarding, consent, coach usage and five user reads - with different column sets. -- The day view's record sections run six at a time instead of about - twenty-five at once, so one day never holds more than a third of the pool. - The statement count is the same; what changes is the peak of connections. -- Keyset pagination for `/api/measurements` was not done: a cursor needs a - new request parameter and a response field, a contract change for the - native client. diff --git a/scripts/bench/results/v1.45.0-final.json b/scripts/bench/results/v1.45.0-final.json deleted file mode 100644 index 6b31e6c98..000000000 --- a/scripts/bench/results/v1.45.0-final.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "meta": { - "label": "v1.45.0-final", - "commit": "a57fa8335", - "date": "2026-10-22T06:52:09.539Z", - "node": "v22.23.2", - "postgres": "16.15", - "cpus": 14, - "loadAverage": [ - 6.8, - 8.9, - 14.5 - ], - "measurements": 1265561, - "iterations": 20, - "warmup": 3, - "heapMb": 792, - "idleStatementsPerSecond": 0, - "batch5k": { - "totalMs": 4448, - "totalStatements": 504 - } - }, - "results": [ - { - "name": "/api/auth/me", - "mode": "cold", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 8.3, - "p95Ms": 27.2, - "maxMs": 221, - "statementsMedian": 14, - "statementsMax": 14, - "failures": 0 - }, - { - "name": "/api/auth/me", - "mode": "warm", - "request": "GET /api/auth/me", - "n": 20, - "p50Ms": 7.9, - "p95Ms": 10.1, - "maxMs": 17.1, - "statementsMedian": 14, - "statementsMax": 17, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "cold", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 86.6, - "p95Ms": 100.8, - "maxMs": 126.8, - "statementsMedian": 30, - "statementsMax": 31, - "failures": 0 - }, - { - "name": "dashboard summary", - "mode": "warm", - "request": "GET /api/dashboard/summary", - "n": 20, - "p50Ms": 9.2, - "p95Ms": 17.1, - "maxMs": 18.4, - "statementsMedian": 6, - "statementsMax": 6, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "cold", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 545.3, - "p95Ms": 592.5, - "maxMs": 606.3, - "statementsMedian": 153, - "statementsMax": 156, - "failures": 0 - }, - { - "name": "dashboard snapshot", - "mode": "warm", - "request": "GET /api/dashboard/snapshot", - "n": 20, - "p50Ms": 8.1, - "p95Ms": 17.9, - "maxMs": 21.9, - "statementsMedian": 10, - "statementsMax": 10, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 18.7, - "p95Ms": 27.2, - "maxMs": 36.1, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 30 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=30", - "n": 20, - "p50Ms": 15.9, - "p95Ms": 21.8, - "maxMs": 26, - "statementsMedian": 8, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 40.7, - "p95Ms": 55.6, - "maxMs": 56.2, - "statementsMedian": 7, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "series pulse 365 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=365", - "n": 20, - "p50Ms": 38.3, - "p95Ms": 59.6, - "maxMs": 88.6, - "statementsMedian": 7, - "statementsMax": 7, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "cold", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 334.9, - "p95Ms": 348.5, - "maxMs": 351.2, - "statementsMedian": 7, - "statementsMax": 7, - "failures": 0 - }, - { - "name": "series pulse 3650 d", - "mode": "warm", - "request": "GET /api/measurements/series?kind=pulse&days=3650", - "n": 20, - "p50Ms": 328.1, - "p95Ms": 346, - "maxMs": 348.9, - "statementsMedian": 7, - "statementsMax": 8, - "failures": 0 - }, - { - "name": "day view", - "mode": "cold", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 68.4, - "p95Ms": 102.6, - "maxMs": 104.7, - "statementsMedian": 48, - "statementsMax": 49, - "failures": 0 - }, - { - "name": "day view", - "mode": "warm", - "request": "GET /api/day/2026-10-21", - "n": 20, - "p50Ms": 46.4, - "p95Ms": 66.1, - "maxMs": 72.6, - "statementsMedian": 47, - "statementsMax": 47, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "cold", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 156, - "p95Ms": 190, - "maxMs": 197.7, - "statementsMedian": 67, - "statementsMax": 67, - "failures": 0 - }, - { - "name": "insights health status", - "mode": "warm", - "request": "GET /api/insights/health-status", - "n": 20, - "p50Ms": 173.6, - "p95Ms": 1163.4, - "maxMs": 1419.4, - "statementsMedian": 67, - "statementsMax": 67, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "cold", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 106.1, - "p95Ms": 229.1, - "maxMs": 383.3, - "statementsMedian": 48, - "statementsMax": 49, - "failures": 0 - }, - { - "name": "insights correlations", - "mode": "warm", - "request": "GET /api/insights/correlations", - "n": 20, - "p50Ms": 18.7, - "p95Ms": 626.6, - "maxMs": 1136.5, - "statementsMedian": 7, - "statementsMax": 7, - "failures": 0 - }, - { - "name": "batch ingest 5k (per 500)", - "mode": "write", - "request": "POST /api/measurements/batch ×10", - "n": 10, - "p50Ms": 185.7, - "p95Ms": 1269.2, - "maxMs": 1269.2, - "statementsMedian": 50, - "statementsMax": 53, - "failures": 0 - } - ] -} diff --git a/scripts/bench/results/v1.45.0-final.md b/scripts/bench/results/v1.45.0-final.md deleted file mode 100644 index dc26d0366..000000000 --- a/scripts/bench/results/v1.45.0-final.md +++ /dev/null @@ -1,25 +0,0 @@ -# Benchmark v1.45.0-final - -Commit a57fa8335 with the working tree of the final commits, 2026-10-22T06:52:09.539Z. 1265561 measurement rows on the bench account, 20 measured requests per row after 3 warm-up requests, one at a time. Server: production build, web process, heap ceiling 792 MB. Postgres 16.15, 14 cores, load average 6.8,8.9,14.5 at the end. Idle background: 0 statements/s. - -| Request | Mode | n | p50 ms | p95 ms | max ms | statements (median) | statements (max) | -| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| /api/auth/me | cold | 20 | 8.3 | 27.2 | 221 | 14 | 14 | -| /api/auth/me | warm | 20 | 7.9 | 10.1 | 17.1 | 14 | 17 | -| dashboard summary | cold | 20 | 86.6 | 100.8 | 126.8 | 30 | 31 | -| dashboard summary | warm | 20 | 9.2 | 17.1 | 18.4 | 6 | 6 | -| dashboard snapshot | cold | 20 | 545.3 | 592.5 | 606.3 | 153 | 156 | -| dashboard snapshot | warm | 20 | 8.1 | 17.9 | 21.9 | 10 | 10 | -| series pulse 30 d | cold | 20 | 18.7 | 27.2 | 36.1 | 8 | 8 | -| series pulse 30 d | warm | 20 | 15.9 | 21.8 | 26 | 8 | 8 | -| series pulse 365 d | cold | 20 | 40.7 | 55.6 | 56.2 | 7 | 8 | -| series pulse 365 d | warm | 20 | 38.3 | 59.6 | 88.6 | 7 | 7 | -| series pulse 3650 d | cold | 20 | 334.9 | 348.5 | 351.2 | 7 | 7 | -| series pulse 3650 d | warm | 20 | 328.1 | 346 | 348.9 | 7 | 8 | -| day view | cold | 20 | 68.4 | 102.6 | 104.7 | 48 | 49 | -| day view | warm | 20 | 46.4 | 66.1 | 72.6 | 47 | 47 | -| insights health status | cold | 20 | 156 | 190 | 197.7 | 67 | 67 | -| insights health status | warm | 20 | 173.6 | 1163.4 | 1419.4 | 67 | 67 | -| insights correlations | cold | 20 | 106.1 | 229.1 | 383.3 | 48 | 49 | -| insights correlations | warm | 20 | 18.7 | 626.6 | 1136.5 | 7 | 7 | -| batch ingest 5k (per 500) | write | 10 | 185.7 | 1269.2 | 1269.2 | 50 | 53 | diff --git a/scripts/bench/results/v1.45.0-read-path.md b/scripts/bench/results/v1.45.0-read-path.md deleted file mode 100644 index 8894340d8..000000000 --- a/scripts/bench/results/v1.45.0-read-path.md +++ /dev/null @@ -1,45 +0,0 @@ -# v1.45.0: read path - -Request benchmark on the seeded account (1,265,561 rows), production build, -web process, heap ceiling 792 MB, 20 requests per row, one at a time. -`v1.45.0-before` is the v1.44 runtime (1915f9152), `v1.45.0-final` this -branch; both on the same machine within the hour, load average 7 to 30 -(other work on the host), so latency compares within a factor of noise and -statements exactly. Cold means every request follows a one-row manual sync. - -| request | before p50 / p95 ms | after p50 / p95 ms | statements before / after | -| --- | ---: | ---: | ---: | -| dashboard snapshot, cold | 1482 / 1526 | 545 / 593 | 158 / 153 | -| dashboard summary, cold | 170 / 221 | 87 / 101 | 31 / 30 | -| series pulse 3650 d, cold | 1627 / 1665 | 335 / 349 | 8 / 7 | -| series pulse 3650 d, warm | 2091 / 2435 | 328 / 346 | 8 / 7 | -| series pulse 365 d, cold | 184 / 193 | 41 / 56 | 8 / 7 | -| series pulse 30 d, cold | 24 / 31 | 19 / 27 | 8 / 8 | -| insights health status, cold | 245 / 401 | 156 / 190 | 68 / 67 | - -What moved them: - -- **JIT off for app sessions.** Postgres compiled every fold whose estimated - cost passed `jit_above_cost`; on the ten-year pulse pick that was 100 to - 140 ms of a 220 ms statement (`EXPLAIN ANALYZE`: 395 ms with JIT, 220 ms - without). -- **The canonical-source pick groups first.** `DISTINCT ON` now sorts the - distinct `(type, day, source)` triples instead of every raw reading (the - ten-year pulse pick sorted 260,000 rows and spilled to disk); 220 ms to - 175 ms on its own. Parity: `tests/integration/canonical-source-pick-parity.test.ts` - runs the old statement beside the new one over five reader shapes and two - ladders on a fixture with overlapping and off-ladder sources, tombstones, - empty days and readings around midnight; identical rows. -- **The dense series reads its rows once.** Buckets and strip figures come - from one statement over a materialised window instead of two statements - that each picked the canonical source and scanned the readings. Parity: - `tests/integration/dense-series-read-parity.test.ts`, the two old - statements as reference, pulse (two ranked sources), glucose and oxygen - saturation, day and hour grains, UTC, Europe/Berlin, Asia/Kolkata and - America/New_York; identical buckets and figures (relative 1e-9). -- **Closed blocks survive a sync.** The slice's pre-fold remainder (every - reading older than five years, the snapshot's largest statement at 357 ms) - and the day view's past history blocks are cached under a key that names - the instant their readings end before; a sync of newer readings no longer - drops them (`src/lib/cache/closed-block.ts`). The values are the same - statement's; only how long they are kept changed. diff --git a/scripts/bench/results/v1.45.0-workout-clusters.md b/scripts/bench/results/v1.45.0-workout-clusters.md deleted file mode 100644 index 18ab5a3b4..000000000 --- a/scripts/bench/results/v1.45.0-workout-clusters.md +++ /dev/null @@ -1,28 +0,0 @@ -# v1.45.0: workout duplicate window - -A correctness change, not a speed one; the request benchmark does not read -workouts, so there is no latency row for it. - -Before: two copies of one session were collapsed only when both starts fell -in the same fixed five-minute clock slot (`floor(start / 5 min)`). A pair 30 s -apart that straddled a slot boundary stayed two workouts in the list, the -batch write, the calendar, strain and the Coach. - -After: per user and sport, sorted by start, a workout joins the open cluster -while it starts less than five minutes after the cluster's first start. - -Proof (`src/lib/measurements/__tests__/pick-canonical-workout-rows.property.test.ts`, -against the previous picker kept as `pick-canonical-workout-rows.legacy.ts`): - -| property | inputs | -| --- | --- | -| same rows as the fixed slots wherever no pair within five minutes straddles a slot boundary, three ladders | 4,000 generated inputs, over 1,000 comparable | -| same strain impulses as before on those inputs | 2,000 generated inputs, over 500 comparable | -| two sources' copies collapse at any offset under five minutes (the old picker missed a share) | 3,000 random offsets | -| five minutes or more apart never collapse | 2,000 | -| a uniform shift of every start keeps the picked set | 1,500 | -| a row of workouts four minutes apart does not chain | fixed case | - -One existing case changed its expectation: three runs at 08:04, 08:08 and -08:12 used to stay three because they sat in three slots; 08:08 is now the -08:04 run from a lower-ranked source (four minutes apart), and 08:12 stays. diff --git a/scripts/bench/results/v1.45.0-write-path.md b/scripts/bench/results/v1.45.0-write-path.md deleted file mode 100644 index c4c78a0c2..000000000 --- a/scripts/bench/results/v1.45.0-write-path.md +++ /dev/null @@ -1,26 +0,0 @@ -# v1.45.0: write path - -Same runs as `v1.45.0-read-path.md`. - -| request | before p50 ms | after p50 ms | statements before / after | -| --- | ---: | ---: | ---: | -| batch ingest, 500 fresh rows per request | 166 | 186 | 53 / 50 | - -Fresh rows were already set-based (v1.44); the benchmark's ingest is all -fresh rows, so it shows no gain, as expected. The changes are for re-posts: - -- **Unchanged `stats:` re-posts** write nothing and start nothing. One read - over the batch's `stats:` ids replaces a reconciler transaction (about ten - round trips) per row, and an all-unchanged batch no longer refolds, enqueues - rollups or PR detection, or marks caches stale. The per-entry status stays - `updated` and the summary's `updated` count is unchanged. - `tests/integration/routes/measurements-batch-unchanged-stats.test.ts`: four - unchanged re-posts take at most 12 statements in total (before: about 40) - with zero rollup, invalidation and PR calls; a changed value, a changed - device or a tombstoned row still goes through the reconciler. -- **Apple Health re-import** skips the UPDATE of a row stored with exactly the - values it would write; one read per chunk instead of one UPDATE per row. - `tests/integration/apple-health-reimport-unchanged.test.ts`: a re-import of - 20 stored rows reports the same per-type counts and sends no UPDATE. -- **Narrower invalidation.** The batch route tells the cache how far back it - wrote (`touchedFrom`), so closed blocks of older readings stay. diff --git a/scripts/cleanup-apple-health-phantom-meds.ts b/scripts/cleanup-apple-health-phantom-meds.ts deleted file mode 100644 index cd9f76a45..000000000 --- a/scripts/cleanup-apple-health-phantom-meds.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Remove the phantom Apple-Health mirror medications minted by the unstable - * externalId bug (a fresh key per sync sweep => one duplicate row per sweep). - * - * SAFETY: - * - Dry-run by default. Deletes ONLY when called with `--execute`. - * - Targets ONLY rows that match ALL of: the given name, externalSource = - * APPLE_HEALTH, ZERO intake events, and ZERO schedules. A row with any - * intake history or any schedule is never touched. Real medications - * (different name, or with intake/schedule history) can never match. - * - Each delete replicates the DELETE /api/medications/{id} route exactly: - * revoke medication-scoped tokens, deleteMedicationCategory, cascade - * delete, audit row. Caches are busted per affected user at the end (and - * otherwise clear on TTL / the next write). - * - * STOP THE SOURCE FIRST: while the iOS Apple-Health medication mirror is still - * enabled on the device, every sync re-mints these rows and cleanup will undo - * itself. Turn the toggle off (or ship the iOS fix) before running --execute. - * - * Run (inside the app container): - * pnpm dlx tsx scripts/cleanup-apple-health-phantom-meds.ts # dry-run - * pnpm dlx tsx scripts/cleanup-apple-health-phantom-meds.ts --execute # delete - * pnpm dlx tsx scripts/cleanup-apple-health-phantom-meds.ts --name "Blutdruckmittel" --execute - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { auditLog } from "@/lib/auth/audit"; -import { invalidateUserMedications } from "@/lib/cache/invalidate"; -import { deleteMedicationCategory } from "@/lib/medication-category"; - -function argValue(flag: string): string | undefined { - const i = process.argv.indexOf(flag); - return i >= 0 ? process.argv[i + 1] : undefined; -} - -async function main(): Promise { - const name = argValue("--name") ?? "Blutdruckmittel"; - const execute = process.argv.includes("--execute"); - - // Candidate set: name + Apple-Health provenance only. Intake / schedule - // counts are checked per row below and are the hard safety gate. - const candidates = await prisma.medication.findMany({ - where: { name, externalSource: "APPLE_HEALTH" }, - select: { id: true, userId: true, externalId: true, createdAt: true }, - orderBy: { createdAt: "asc" }, - }); - - const deletable: { id: string; userId: string }[] = []; - const skipped: { id: string; reason: string }[] = []; - - for (const m of candidates) { - const [intakes, schedules] = await Promise.all([ - prisma.medicationIntakeEvent.count({ - where: { medicationId: m.id, deletedAt: null }, - }), - prisma.medicationSchedule.count({ where: { medicationId: m.id } }), - ]); - if (intakes > 0 || schedules > 0) { - skipped.push({ - id: m.id, - reason: `has ${intakes} intake(s) / ${schedules} schedule(s) — PRESERVED`, - }); - continue; - } - deletable.push({ id: m.id, userId: m.userId }); - } - - console.log( - `\n=== cleanup plan for name "${name}" (externalSource APPLE_HEALTH) ===`, - ); - console.log(`candidates found: ${candidates.length}`); - console.log(`safe to delete: ${deletable.length}`); - console.log(`preserved (intake/schedule present): ${skipped.length}`); - if (skipped.length > 0) { - console.log("\nPreserved rows:"); - for (const s of skipped) console.log(` ${s.id} — ${s.reason}`); - } - console.log("\nRows to delete:"); - for (const d of deletable) console.log(` ${d.id}`); - - if (!execute) { - console.log( - "\nDRY RUN — nothing was deleted. Re-run with --execute to remove the rows above.", - ); - return; - } - - console.log(`\n--execute: deleting ${deletable.length} row(s)...`); - const affectedUsers = new Set(); - let deleted = 0; - for (const { id, userId } of deletable) { - // Mirror the DELETE route: revoke medication-scoped tokens (none expected - // on a phantom row, harmless if absent), category cleanup, cascade delete, - // audit row. - await prisma.apiToken.updateMany({ - where: { - userId, - revoked: false, - permissions: { has: `medication:${id}:ingest` }, - }, - data: { revoked: true }, - }); - await deleteMedicationCategory(id); - await prisma.medication.delete({ where: { id } }); - await auditLog("medication.delete", { - userId, - details: { - medicationId: id, - name, - reason: "apple_health_phantom_cleanup", - }, - }); - affectedUsers.add(userId); - deleted += 1; - } - - for (const userId of affectedUsers) { - invalidateUserMedications(userId, { evict: true }); - } - - console.log( - `\nDone. Deleted ${deleted} row(s) across ${affectedUsers.size} user(s).`, - ); - console.log( - "Caches were busted for the affected users; a page refresh reflects the change.", - ); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error(err); - process.exit(1); - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/compare-correlation-findings.ts b/scripts/compare-correlation-findings.ts deleted file mode 100644 index 886e5e0f4..000000000 --- a/scripts/compare-correlation-findings.ts +++ /dev/null @@ -1,338 +0,0 @@ -/** - * Read-only before/after count of correlation findings per account, for the - * release-candidate dry run of the v1.42 statistics change. - * - * v1.42 changes what the discovery engine accepts: trend and season are - * removed from both series before correlating, p-values use the effective - * sample size, shrinkage and the phrasing tier read that effective size, and - * the weather channels are tested once over the averaged day-before-and-same- - * day exposure instead of a next-day lag. Every one of those can make an - * existing finding disappear. This script answers "how many, and where", - * on a copy of production, before the release ships. - * - * For each account it builds the matrix exactly as the correlations route - * does (same assembler, same 180-day window, same module map, same tiered - * read) and scans it twice: - * - * - BEFORE: the pre-v1.42 rules, restated here from the engine's own - * exported pieces (lag join at 1 day, Pearson with df = n − 2, BH-FDR, - * shrinkage and tier by n), over the raw daily weather values; - * - AFTER: the engine as it now ships. - * - * It prints counts only. No correlation, p-value, metric value or account - * identifier leaves the database: accounts are listed by a short hash of - * their id, channels by their stable key family. Nothing is written — the - * pattern store is not synced, no cache is touched. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/compare-correlation-findings.ts [--limit N] - */ -import "dotenv/config"; - -import { createHash } from "node:crypto"; - -import { prisma } from "@/lib/db"; -import { resolveModuleMap } from "@/lib/modules/gate"; -import { assembleDiscoveryMatrix } from "@/lib/insights/discovery-matrix"; -import { MIN_PAIRED_N, pearson } from "@/lib/insights/correlations"; -import { - benjaminiHochberg, - confidenceTier, - discoverCorrelations, - discoverEmergingCorrelations, - EARLY_FDR_Q, - EARLY_MIN_PAIRS, - EARLY_WINDOW_DAYS, - FDR_Q, - filterSeriesToWindow, - lagJoin, - metricFamily, - shrinkEstimate, - type DailySeriesPoint, - type NamedSeries, -} from "@/lib/insights/correlation-discovery"; -import { ENVIRONMENT_FIELDS } from "@/lib/environment/fields"; -import { - isSurfaceVisible, - correlationChannelSurfaceId, -} from "@/lib/modules/surface"; -import { wallClockInTz } from "@/lib/tz/wall-clock"; -import { DEFAULT_TIMEZONE } from "@/lib/tz/format"; - -const MS_PER_DAY = 86_400_000; -/** The correlations route's window. */ -const WINDOW_DAYS = 180; - -interface Finding { - behaviour: string; - outcome: string; -} - -interface LegacyFinding extends Finding { - tier: ReturnType; -} - -/** The pre-v1.42 discovery rules, restated from the engine's parts. */ -function legacyDiscover( - series: NamedSeries[], - opts: { minPairs: number; fdrQ: number }, -): { found: LegacyFinding[]; pairsTested: number } { - const behaviours = series.filter((s) => s.role === "behaviour"); - const outcomes = series.filter((s) => s.role === "outcome"); - const tested: Array = []; - for (const b of behaviours) { - for (const o of outcomes) { - if (metricFamily(b.key) === metricFamily(o.key)) continue; - const { xs, ys } = lagJoin(b.points, o.points, 1); - if (xs.length < opts.minPairs) continue; - const result = pearson({ xs, ys, minPairs: opts.minPairs }); - if (result.status !== "ok") continue; - tested.push({ - behaviour: b.key, - outcome: o.key, - n: result.n, - r: result.r, - pValue: result.pValue, - }); - } - } - const q = benjaminiHochberg(tested.map((t) => t.pValue)); - const found = tested - .filter((t, i) => t.pValue < 0.05 && q[i] <= opts.fdrQ) - .map((t) => ({ - behaviour: t.behaviour, - outcome: t.outcome, - tier: confidenceTier(shrinkEstimate(t.r, t.n), t.n), - })) - .filter((t) => t.tier !== null); - return { found, pairsTested: tested.length }; -} - -/** The pre-v1.42 emerging pass: same rules, trailing window, no faint tier. */ -function legacyEmerging( - series: NamedSeries[], - retrospective: Finding[], - recentFromDayKey: string, -): Finding[] { - const recent = legacyDiscover( - filterSeriesToWindow(series, recentFromDayKey), - { minPairs: EARLY_MIN_PAIRS, fdrQ: EARLY_FDR_Q }, - ); - const established = new Set(retrospective.map(key)); - return recent.found - .filter((f) => f.tier !== "faint") - .filter((f) => !established.has(key(f))); -} - -function key(f: Finding): string { - return `${f.behaviour}␟${f.outcome}`; -} - -function tzDayKey(at: Date, tz: string): string { - const { year, month, day } = wallClockInTz(at, tz); - return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`; -} - -/** - * The weather channels as the engine read them before v1.42: one raw daily - * value per field, no averaging. Read here directly because the shipped - * series now carries the averaged window. - */ -async function legacyEnvironmentSeries( - userId: string, - since: Date, -): Promise> { - // Same UTC lower bound the engine's own environment read uses. - const sinceKey = since.toISOString().slice(0, 10); - const rows = await prisma.environmentContext.findMany({ - where: { userId, date: { gte: sinceKey } }, - orderBy: { date: "asc" }, - take: 1000, - }); - const out = new Map(); - for (const field of ENVIRONMENT_FIELDS) { - const points: DailySeriesPoint[] = []; - for (const row of rows) { - const raw = row[field.column]; - if (raw == null || !Number.isFinite(raw)) continue; - const value = - field.column === "sunshineSec" || field.column === "daylightSec" - ? raw / 3600 - : raw; - points.push({ day: row.date, value }); - } - out.set(field.key, points); - } - return out; -} - -/** Channel family label for the aggregate table (no user data). */ -function familyLabel(channel: string): string { - if (channel.startsWith("ENV_")) return "ENVIRONMENT"; - if (channel.startsWith("CUSTOM_METRIC:")) return "CUSTOM_METRIC"; - if (channel.startsWith("SYMPTOM:")) return "SYMPTOM_EVENT"; - if (channel.startsWith("FACTOR:")) return "MOOD_FACTOR"; - return channel; -} - -interface Tally { - before: number; - after: number; -} - -function bump(map: Map, k: string, side: keyof Tally) { - const t = map.get(k) ?? { before: 0, after: 0 }; - t[side]++; - map.set(k, t); -} - -function parseLimit(argv: string[]): number | undefined { - const i = argv.indexOf("--limit"); - if (i === -1) return undefined; - const n = Number(argv[i + 1]); - return Number.isInteger(n) && n > 0 ? n : undefined; -} - -async function main(): Promise { - const limit = parseLimit(process.argv.slice(2)); - const users = await prisma.user.findMany({ - select: { id: true, timezone: true }, - orderBy: { createdAt: "asc" }, - ...(limit ? { take: limit } : {}), - }); - - const now = new Date(); - const since = new Date(now.getTime() - WINDOW_DAYS * MS_PER_DAY); - const byBehaviour = new Map(); - const byOutcome = new Map(); - const totals = { - accounts: 0, - scanned: 0, - retroBefore: 0, - retroAfter: 0, - recentBefore: 0, - recentAfter: 0, - kept: 0, - lost: 0, - gained: 0, - }; - - console.log( - "account tested(b/a) retro b→a recent b→a kept lost new env b→a", - ); - for (const user of users) { - totals.accounts++; - const tz = user.timezone ?? DEFAULT_TIMEZONE; - const modules = await resolveModuleMap(user.id); - const { series } = await assembleDiscoveryMatrix(user.id, { - tz, - since, - fetchMode: "tiered", - modules, - }); - const envVisible = isSurfaceVisible( - correlationChannelSurfaceId(ENVIRONMENT_FIELDS[0].key), - modules, - ); - const rawEnv = envVisible - ? await legacyEnvironmentSeries(user.id, since) - : new Map(); - const legacySeries: NamedSeries[] = series.map((s) => - s.key.startsWith("ENV_") - ? { key: s.key, role: s.role, points: rawEnv.get(s.key) ?? [] } - : { key: s.key, role: s.role, label: s.label, points: s.points }, - ); - - const recentFromDayKey = tzDayKey( - new Date(now.getTime() - EARLY_WINDOW_DAYS * MS_PER_DAY), - tz, - ); - - const legacy = legacyDiscover(legacySeries, { - minPairs: MIN_PAIRED_N, - fdrQ: FDR_Q, - }); - const before = legacy.found; - const beforeRecent = legacyEmerging(legacySeries, before, recentFromDayKey); - const after = discoverCorrelations(series, { locale: "en" }); - const afterRecent = discoverEmergingCorrelations(series, after, { - recentFromDayKey, - locale: "en", - }); - - if (legacy.pairsTested === 0 && after.pairsTested === 0) continue; - totals.scanned++; - - const beforeKeys = new Set(before.map(key)); - const afterKeys = new Set(after.discovered.map(key)); - const kept = [...afterKeys].filter((k) => beforeKeys.has(k)).length; - const lost = beforeKeys.size - kept; - const gained = afterKeys.size - kept; - const envBefore = before.filter((f) => f.behaviour.startsWith("ENV_")); - const envAfter = after.discovered.filter((f) => - f.behaviour.startsWith("ENV_"), - ); - - for (const f of before) { - bump(byBehaviour, familyLabel(f.behaviour), "before"); - bump(byOutcome, familyLabel(f.outcome), "before"); - } - for (const f of after.discovered) { - bump(byBehaviour, familyLabel(f.behaviour), "after"); - bump(byOutcome, familyLabel(f.outcome), "after"); - } - - totals.retroBefore += before.length; - totals.retroAfter += after.discovered.length; - totals.recentBefore += beforeRecent.length; - totals.recentAfter += afterRecent.emerging.length; - totals.kept += kept; - totals.lost += lost; - totals.gained += gained; - - const tag = createHash("sha256").update(user.id).digest("hex").slice(0, 8); - console.log( - [ - tag.padEnd(9), - `${legacy.pairsTested}/${after.pairsTested}`.padEnd(12), - `${before.length}→${after.discovered.length}`.padEnd(10), - `${beforeRecent.length}→${afterRecent.emerging.length}`.padEnd(11), - String(kept).padStart(4), - String(lost).padStart(4), - String(gained).padStart(3), - ` ${envBefore.length}→${envAfter.length}`, - ].join(" "), - ); - } - - console.log(""); - console.log( - `accounts ${totals.accounts}, with a testable matrix ${totals.scanned}`, - ); - console.log( - `retrospective findings ${totals.retroBefore} → ${totals.retroAfter} (kept ${totals.kept}, lost ${totals.lost}, new ${totals.gained})`, - ); - console.log( - `emerging findings ${totals.recentBefore} → ${totals.recentAfter}`, - ); - const table = (title: string, map: Map) => { - console.log(""); - console.log(`${title}: before → after`); - for (const [k, t] of [...map.entries()].sort((a, b) => - a[0].localeCompare(b[0]), - )) { - console.log(` ${k.padEnd(24)} ${t.before} → ${t.after}`); - } - }; - table("by behaviour family", byBehaviour); - table("by outcome family", byOutcome); -} - -main() - .catch((err: unknown) => { - console.error(err); - process.exitCode = 1; - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/convert-noncanonical-measurement-units.ts b/scripts/convert-noncanonical-measurement-units.ts deleted file mode 100644 index 18a75cbda..000000000 --- a/scripts/convert-noncanonical-measurement-units.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Convert measurement rows stored in a RECOGNISED non-canonical unit to - * canonical SI, in place. Companion to - * `diagnose-noncanonical-measurement-units.ts` — run the diagnose first. - * - * SAFETY: - * - Dry-run by default. Writes ONLY when called with `--execute`. - * - Touches ONLY rows whose stored `unit` differs from the type's canonical - * unit AND resolves through the shared alias resolver (lb/lbs/pound(s), - * in/inch(es), °F, mmol/L). A row whose unit is already canonical, or whose - * unit is unrecognised, is never touched. - * - Each row is rewritten to the canonical `{ value, unit }` the same - * resolver the CSV importer + MCP write path use produces, so the number is - * re-expressed, never re-labelled in place (which would corrupt it). - * - Affected users' measurement caches are busted at the end. - * - * This repairs the storage LABEL + value for the recognised-alias legacy rows - * (the MCP-leak class). It cannot repair a row whose unit was already canonical - * but whose value was a foreign unit — that class is not mechanically - * identifiable (see the diagnose script). - * - * Run (inside the app container): - * pnpm dlx tsx scripts/convert-noncanonical-measurement-units.ts # dry-run - * pnpm dlx tsx scripts/convert-noncanonical-measurement-units.ts --execute # write - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { getUnitForType } from "@/lib/validations/measurement"; -import { resolveToCanonicalUnit } from "@/lib/measurements/unit-aliases"; -import { invalidateUserMeasurements } from "@/lib/cache/invalidate"; - -async function main(): Promise { - const execute = process.argv.includes("--execute"); - - const groups = await prisma.measurement.groupBy({ - by: ["type", "unit"], - _count: { _all: true }, - }); - const convertibleGroups = groups.filter((g) => { - if (g.unit === null || g.unit === getUnitForType(g.type)) return false; - return resolveToCanonicalUnit(g.type, 1, g.unit) !== null; - }); - - if (convertibleGroups.length === 0) { - console.log( - "Nothing to convert — no recognised non-canonical units found.", - ); - return; - } - - const affectedUsers = new Set(); - let converted = 0; - - for (const g of convertibleGroups) { - const canonical = getUnitForType(g.type); - const rows = await prisma.measurement.findMany({ - where: { type: g.type, unit: g.unit }, - select: { id: true, userId: true, value: true, unit: true }, - }); - for (const row of rows) { - const resolved = resolveToCanonicalUnit( - g.type, - row.value, - row.unit as string, - ); - if (!resolved) continue; // defensive; group already filtered - converted += 1; - affectedUsers.add(row.userId); - console.log( - ` ${execute ? "convert" : "would convert"} ${g.type} ${row.id}: ` + - `${row.value} ${row.unit} -> ${resolved.value} ${resolved.unit}`, - ); - if (execute) { - await prisma.measurement.update({ - where: { id: row.id }, - data: { value: resolved.value, unit: canonical }, - }); - } - } - } - - if (execute) { - for (const userId of affectedUsers) { - invalidateUserMeasurements(userId, { evict: true }); - } - console.log( - `\nConverted ${converted} row(s) across ${affectedUsers.size} user(s). ` + - "Rollups recompute on the next read/backfill.", - ); - } else { - console.log( - `\nDry run: ${converted} row(s) across ${affectedUsers.size} user(s) ` + - "would be converted. Re-run with --execute to write.", - ); - } -} - -main() - .catch((err) => { - console.error(err); - process.exitCode = 1; - }) - .finally(() => prisma.$disconnect()); diff --git a/scripts/diagnose-apple-health-meds.ts b/scripts/diagnose-apple-health-meds.ts deleted file mode 100644 index ee1aa7269..000000000 --- a/scripts/diagnose-apple-health-meds.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Read-only diagnostic for the phantom Apple-Health medication mirror. - * - * Prints the shape of every medication matching a name (default - * "Blutdruckmittel"): its provenance (externalSource / externalId), dose, - * asNeeded flag, createdAt, and its intake-event + schedule counts. Nothing is - * written. Use it to confirm the root cause before running any cleanup: - * - * - externalSource = APPLE_HEALTH with many DISTINCT externalId values - * (address-shaped or otherwise mutually different) => the iOS mirror is - * minting a fresh key per sync, so idempotency never matches. - * - externalSource NULL => the rows are not mirror rows (a different path). - * - createdAt spread => one row per sync sweep vs a single burst. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/diagnose-apple-health-meds.ts - * pnpm dlx tsx scripts/diagnose-apple-health-meds.ts --name "Blutdruckmittel" - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; - -function argValue(flag: string): string | undefined { - const i = process.argv.indexOf(flag); - return i >= 0 ? process.argv[i + 1] : undefined; -} - -async function main(): Promise { - const name = argValue("--name") ?? "Blutdruckmittel"; - - const meds = await prisma.medication.findMany({ - where: { name }, - select: { - id: true, - userId: true, - name: true, - dose: true, - asNeeded: true, - externalSource: true, - externalId: true, - createdAt: true, - }, - orderBy: { createdAt: "asc" }, - }); - - if (meds.length === 0) { - console.log(`No medications found with name "${name}".`); - return; - } - - const rows = []; - for (const m of meds) { - const [intakes, schedules] = await Promise.all([ - prisma.medicationIntakeEvent.count({ - where: { medicationId: m.id, deletedAt: null }, - }), - prisma.medicationSchedule.count({ where: { medicationId: m.id } }), - ]); - rows.push({ - id: m.id, - user: m.userId.slice(0, 8), - source: m.externalSource ?? "NULL", - externalId: m.externalId ?? "NULL", - dose: m.dose, - asNeeded: m.asNeeded, - intakes, - schedules, - createdAt: m.createdAt.toISOString(), - }); - } - - console.log(`\n=== ${rows.length} medication(s) named "${name}" ===\n`); - console.table(rows); - - const distinctExternalIds = new Set( - meds.map((m) => m.externalId).filter((v): v is string => v !== null), - ); - const bySource = new Map(); - for (const m of meds) { - const key = m.externalSource ?? "NULL"; - bySource.set(key, (bySource.get(key) ?? 0) + 1); - } - const created = meds.map((m) => m.createdAt.getTime()); - const withIntake = rows.filter((r) => r.intakes > 0).length; - const withSchedule = rows.filter((r) => r.schedules > 0).length; - - console.log("\n=== summary ==="); - console.log(`total rows: ${rows.length}`); - console.log( - `externalSource breakdown: ${[...bySource.entries()] - .map(([k, v]) => `${k}=${v}`) - .join(", ")}`, - ); - console.log(`distinct externalId: ${distinctExternalIds.size}`); - console.log(`rows with >=1 intake: ${withIntake}`); - console.log(`rows with >=1 schedule: ${withSchedule}`); - console.log( - `createdAt range: ${new Date(Math.min(...created)).toISOString()} .. ${new Date(Math.max(...created)).toISOString()}`, - ); - console.log( - "\nSample externalId values (first 5):\n" + - meds - .slice(0, 5) - .map((m) => ` ${m.externalId ?? "NULL"}`) - .join("\n"), - ); - console.log( - "\nRead-only. No rows were modified. If the shape confirms the phantom set,\n" + - "run scripts/cleanup-apple-health-phantom-meds.ts (dry-run first).", - ); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error(err); - process.exit(1); - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/diagnose-cycle-state.ts b/scripts/diagnose-cycle-state.ts deleted file mode 100644 index 85856e998..000000000 --- a/scripts/diagnose-cycle-state.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Read-only diagnostic for cycle records the module has stopped reading. - * - * Two defects put records into states that look, from the app, like no periods - * were ever logged. Both are fixed going forward; neither repairs the rows it - * already made, and neither is visible from the surface it broke. - * - * The first is a closed torso. Logging a period start closes the cycle before - * it, and removing that start used to leave the closure standing. The previous - * cycle keeps an end date that a cycle which no longer exists gave it, and a - * record whose last cycle is closed has no open cycle for the engine to - * forecast from, so the prediction disappears and does not come back. The - * signature is exact and needs no audit trail: a live cycle carrying an end - * date with no live cycle after it. A cycle is closed by its successor, so - * closed with no successor means the successor is gone. - * - * The second is a period that only ever existed as flow. Cycle rows used to be - * written by the one-tap period boundary alone, so bleeding entered through the - * log sheet's chips, the bulk drain or an Apple Health import produced day-logs - * and no cycle at all. The engine reads cycle rows, so the record holds periods - * and reports none. - * - * The bleeding-day rule below is imported from the write path rather than - * restated, so this report and the app cannot drift on what counts as a period - * start. - * - * Nothing is written. The output is a per-record inventory plus the two flags, - * so the state can be read before anything is decided about it. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/diagnose-cycle-state.ts - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { bleedOpensCycle } from "@/lib/cycle/cycle-boundaries"; -import { dayDiff } from "@/lib/cycle/day-math"; - -/** Below this many observed cycles the app withholds its phase band. */ -const STILL_LEARNING_BELOW = 3; - -interface CycleRow { - id: string; - startDate: string; - endDate: string | null; - lengthDays: number | null; - periodEndDate: string | null; - deletedAt: Date | null; - isPredicted: boolean; -} - -interface DayRow { - date: string; - flow: string | null; - intermenstrualBleeding: boolean; -} - -function describeCycle(c: CycleRow): string { - const state = c.deletedAt - ? `deleted ${c.deletedAt.toISOString().slice(0, 10)}` - : c.endDate - ? `closed ${c.endDate}` - : "open"; - const length = c.lengthDays === null ? "-" : `${c.lengthDays}d`; - const period = c.periodEndDate ? ` periodEnd=${c.periodEndDate}` : ""; - const predicted = c.isPredicted ? " PREDICTED" : ""; - return `${c.startDate} ${state.padEnd(20)} length=${length.padEnd(5)}${period}${predicted}`; -} - -async function main(): Promise { - // Every record that has ever touched the module, from either side: a cycle - // row, or a day-log. A record with day-logs and no cycles is precisely one - // of the cases this exists to find, so it cannot be found by cycles alone. - const [cycleOwners, dayOwners] = await Promise.all([ - prisma.menstrualCycle.findMany({ - distinct: ["userId"], - select: { userId: true }, - }), - prisma.cycleDayLog.findMany({ - distinct: ["userId"], - select: { userId: true }, - }), - ]); - const userIds = Array.from( - new Set([ - ...cycleOwners.map((r) => r.userId), - ...dayOwners.map((r) => r.userId), - ]), - ).sort(); - - if (userIds.length === 0) { - console.log("No cycle records on this instance. Nothing to report."); - return; - } - - let torsoTotal = 0; - let flowOnlyTotal = 0; - let staleBoundaryTotal = 0; - - for (const userId of userIds) { - const [cycles, days] = await Promise.all([ - prisma.menstrualCycle.findMany({ - where: { userId }, - orderBy: { startDate: "asc" }, - select: { - id: true, - startDate: true, - endDate: true, - lengthDays: true, - periodEndDate: true, - deletedAt: true, - isPredicted: true, - }, - }) as Promise, - prisma.cycleDayLog.findMany({ - where: { userId, deletedAt: null }, - orderBy: { date: "asc" }, - select: { date: true, flow: true, intermenstrualBleeding: true }, - }) as Promise, - ]); - - const live = cycles.filter((c) => c.deletedAt === null); - const liveStarts = live.map((c) => c.startDate); - const startSet = new Set(liveStarts); - - console.log(`record ${userId}`); - console.log( - ` ${cycles.length} cycle row(s), ${live.length} live, ` + - `${days.length} day-log(s)`, - ); - for (const c of cycles) console.log(` ${describeCycle(c)}`); - - // A closed cycle with nothing after it: closed by a start that is gone. - const torsos = live.filter( - (c, i) => c.endDate !== null && i === live.length - 1, - ); - // A closed cycle whose boundary does not match the successor it has. - const stale = live.filter((c, i) => { - const next = live[i + 1]; - if (!next) return false; - const expectedLength = dayDiff(next.startDate, c.startDate); - return c.endDate === null || c.lengthDays !== expectedLength; - }); - - // Bleeding days the write path would treat as a period start today, on a - // date carrying no live cycle. Walked forward so each decision sees the - // starts that already exist plus the ones this pass would infer, exactly - // as a replay of the writes would. - const inferred: string[] = [...liveStarts].sort(); - const flowOnly: string[] = []; - for (const d of days) { - if (startSet.has(d.date)) continue; - const prior = inferred.filter((s) => s <= d.date).at(-1) ?? null; - if ( - bleedOpensCycle( - d.flow as never, - d.intermenstrualBleeding, - prior, - d.date, - ) - ) { - flowOnly.push(d.date); - inferred.push(d.date); - inferred.sort(); - } - } - - if (torsos.length > 0) { - torsoTotal += torsos.length; - console.log( - ` CLOSED TORSO: ${torsos - .map((c) => `${c.startDate} (ends ${c.endDate})`) - .join(", ")}`, - ); - console.log( - " The last cycle is closed and has no successor, so the engine has " + - "no open cycle to forecast from. Re-opening it means clearing its " + - "endDate and lengthDays.", - ); - } - if (stale.length > 0) { - staleBoundaryTotal += stale.length; - console.log( - ` STALE BOUNDARY: ${stale.map((c) => c.startDate).join(", ")}`, - ); - console.log( - " The stored end or length disagrees with the next cycle's start.", - ); - } - if (flowOnly.length > 0) { - flowOnlyTotal += flowOnly.length; - console.log(` PERIOD WITH NO CYCLE ROW: ${flowOnly.join(", ")}`); - console.log( - " Bleeding was logged on these days and no cycle opens on them. " + - "Re-saving each day through the log sheet opens it now.", - ); - } - if (live.length > 0 && live.length - 1 < STILL_LEARNING_BELOW) { - console.log( - ` Only ${live.length - 1} observed cycle length(s): under the ` + - `${STILL_LEARNING_BELOW}-cycle gate, so the phase band stays withheld.`, - ); - } - console.log(""); - } - - console.log( - `${userIds.length} record(s) inspected. ` + - `${torsoTotal} closed torso(s), ${staleBoundaryTotal} stale boundary(ies), ` + - `${flowOnlyTotal} logged period(s) with no cycle row.`, - ); - console.log("Nothing was changed."); -} - -main() - .catch((error) => { - console.error(error); - process.exitCode = 1; - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/diagnose-implausible-measurements.ts b/scripts/diagnose-implausible-measurements.ts deleted file mode 100644 index 0be518a26..000000000 --- a/scripts/diagnose-implausible-measurements.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Read-only diagnostic for stored measurement rows outside the app's own - * declared plausibility domain. - * - * `VALUE_RANGES` in `@/lib/validations/measurement` is the band the - * application says a metric can occupy. Every path a person can drive has - * always enforced it; the provider sync writers did not, so rows the - * application itself calls impossible reached the table — and every mean, - * median and personal band built from them inherited the impossibility. The - * writers enforce it now, which stops new rows landing. It says nothing about - * the rows already there. - * - * This reports them and repairs nothing. What to do with a stored impossible - * row is a judgement the data cannot make: a pulse of 55 million is plainly a - * decode slip and a candidate for deletion, while a weight of 0.9 kg might be - * a neonatal record on a shared scale, or a unit mix-up, or a real reading the - * band is simply too narrow for. Deleting somebody's health record on a - * heuristic is not a thing a script gets to decide. - * - * Output is aggregate: one line per (metric, source) bucket with the row - * count, the number of distinct records affected, the observed extremes, and - * the window they fall in. Individual readings are not printed — the bucket - * plus the metric's own declared bound is what a decision needs, and a log - * file full of somebody's vitals is not. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/diagnose-implausible-measurements.ts - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { VALUE_RANGES } from "@/lib/validations/measurement"; - -interface BucketRow { - type: string; - source: string; - row_count: bigint; - record_count: bigint; - min_value: number; - max_value: number; - first_at: Date; - last_at: Date; - tombstoned: bigint; -} - -function ymd(date: Date): string { - return date.toISOString().slice(0, 10); -} - -async function main(): Promise { - const types = Object.keys(VALUE_RANGES).sort(); - const mins = types.map((t) => VALUE_RANGES[t]!.min); - const maxes = types.map((t) => VALUE_RANGES[t]!.max); - - // The band table rides in as three parallel bound arrays rather than a - // spliced VALUES list, so the query carries no interpolated text at all. - // `value <> value` is the NaN probe: a double-precision column can hold one, - // and it compares false against every bound including itself. - const buckets = await prisma.$queryRaw` - WITH bands(type, min_value, max_value) AS ( - SELECT * FROM unnest( - ${types}::text[], - ${mins}::double precision[], - ${maxes}::double precision[] - ) - ) - SELECT - m."type"::text AS type, - m."source"::text AS source, - COUNT(*)::bigint AS row_count, - COUNT(DISTINCT m."user_id")::bigint AS record_count, - MIN(m."value") AS min_value, - MAX(m."value") AS max_value, - MIN(m."measured_at") AS first_at, - MAX(m."measured_at") AS last_at, - COUNT(*) FILTER (WHERE m."deleted_at" IS NOT NULL)::bigint AS tombstoned - FROM "measurements" m - JOIN bands b ON b."type" = m."type"::text - WHERE m."value" < b.min_value - OR m."value" > b.max_value - OR m."value" <> m."value" - GROUP BY m."type", m."source" - ORDER BY COUNT(*) DESC, m."type"::text ASC, m."source"::text ASC - `; - - if (buckets.length === 0) { - console.log( - `Every stored measurement sits inside its metric's declared range ` + - `(${types.length} metric bands checked). Nothing to review.`, - ); - return; - } - - const totalRows = buckets.reduce((sum, b) => sum + Number(b.row_count), 0); - const affectedTypes = new Set(buckets.map((b) => b.type)); - const affectedSources = new Set(buckets.map((b) => b.source)); - - console.log( - `${totalRows} stored row(s) sit outside the declared plausibility band, ` + - `across ${affectedTypes.size} metric(s) and ${affectedSources.size} source(s).\n` + - `Nothing below is modified. Each line is a (metric, source) bucket; the ` + - `band is the metric's own declared domain.\n`, - ); - - // Group by metric so the declared band is stated once per heading, and rank - // the metrics by how much of the problem each one is. The query's own - // ordering already ranks the sources inside a metric. - const byType = new Map(); - for (const bucket of buckets) { - const bucketsForType = byType.get(bucket.type); - if (bucketsForType) bucketsForType.push(bucket); - else byType.set(bucket.type, [bucket]); - } - const ranked = [...byType.entries()].sort( - (a, b) => - b[1].reduce((sum, x) => sum + Number(x.row_count), 0) - - a[1].reduce((sum, x) => sum + Number(x.row_count), 0), - ); - - for (const [type, rows] of ranked) { - const band = VALUE_RANGES[type]!; - console.log(`${type} — declared band ${band.min}…${band.max}`); - for (const bucket of rows) { - const live = Number(bucket.row_count) - Number(bucket.tombstoned); - console.log( - ` ${bucket.source.padEnd(14)} ` + - `${String(bucket.row_count).padStart(7)} row(s) ` + - `(${live} live, ${bucket.tombstoned} tombstoned) ` + - `across ${bucket.record_count} record(s) — ` + - `observed ${bucket.min_value}…${bucket.max_value}, ` + - `${ymd(bucket.first_at)} → ${ymd(bucket.last_at)}`, - ); - } - } - - console.log( - `\nThe sync writers refuse these values now, so the population above is ` + - `closed unless a writer without the gate is still in play. Removing a ` + - `row is a maintainer decision, per bucket.`, - ); -} - -main() - .catch((err) => { - console.error(err); - process.exitCode = 1; - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/diagnose-manual-sleep-hours.ts b/scripts/diagnose-manual-sleep-hours.ts deleted file mode 100644 index 38a4b51a9..000000000 --- a/scripts/diagnose-manual-sleep-hours.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Read-only diagnostic for hand-entered sleep rows stored in the wrong unit. - * - * `SLEEP_DURATION` is stored in minutes. The manual entry field asked for - * hours and sent the typed number through unconverted, so a night entered as - * 7.5 was filed as seven and a half MINUTES. Nothing objected: a single sleep - * STAGE really can be that short, so the value sits inside the column's - * plausibility band and looks like an ordinary row. - * - * That ambiguity is why this reports and does not repair. A stage segment of - * eight minutes and a night typed as eight hours are the same number in the - * same column, and only the person who entered it knows which it was. What CAN - * be narrowed is the population: a MANUAL, whole-night row (no sleep stage) is - * one somebody typed into the field, and a typed night shorter than the - * longest plausible hours entry is almost certainly hours that were read as - * minutes. - * - * The output lists the candidates with their date, stored value and the night - * they would have been if the number was hours, so the maintainer can decide - * per row. Nothing is written. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/diagnose-manual-sleep-hours.ts - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { MINUTES_PER_HOUR } from "@/lib/measurements/entry-units"; - -/** - * Longest night anybody types into an hours field. A stored value at or below - * this reads as hours; above it, the number can only ever have been minutes. - */ -const MAX_PLAUSIBLE_HOURS = 24; - -async function main(): Promise { - const suspects = await prisma.measurement.findMany({ - where: { - type: "SLEEP_DURATION", - source: "MANUAL", - deletedAt: null, - // Whole-night rows only — a staged row comes from a sync source, never - // from the entry field. - sleepStage: null, - value: { gt: 0, lte: MAX_PLAUSIBLE_HOURS }, - }, - orderBy: [{ userId: "asc" }, { measuredAt: "asc" }], - select: { - id: true, - userId: true, - value: true, - measuredAt: true, - createdAt: true, - }, - }); - - if (suspects.length === 0) { - console.log( - "No hand-entered sleep rows in the hours-shaped band. Nothing to review.", - ); - return; - } - - const byUser = new Map(); - for (const row of suspects) { - const bucket = byUser.get(row.userId); - if (bucket) bucket.push(row); - else byUser.set(row.userId, [row]); - } - - console.log( - `${suspects.length} hand-entered sleep row(s) across ${byUser.size} record(s) ` + - `store a value of ${MAX_PLAUSIBLE_HOURS} or less.\n` + - `Each is either a very short recorded sleep, or hours that were read as minutes.\n`, - ); - - for (const [userId, rows] of byUser) { - console.log(`record ${userId} — ${rows.length} row(s)`); - for (const row of rows) { - const asHours = row.value * MINUTES_PER_HOUR; - console.log( - ` ${row.measuredAt.toISOString().slice(0, 10)} id=${row.id} ` + - `stored=${row.value} min if it was hours=${asHours} min ` + - `(${(asHours / MINUTES_PER_HOUR).toFixed(2)} h) ` + - `entered=${row.createdAt.toISOString().slice(0, 10)}`, - ); - } - console.log(""); - } - - console.log( - "Nothing was changed. Correct a row from the record list, or delete and re-enter it.", - ); -} - -main() - .catch((error) => { - console.error(error); - process.exitCode = 1; - }) - .finally(() => { - void prisma.$disconnect(); - }); diff --git a/scripts/diagnose-noncanonical-measurement-units.ts b/scripts/diagnose-noncanonical-measurement-units.ts deleted file mode 100644 index 9c0667bda..000000000 --- a/scripts/diagnose-noncanonical-measurement-units.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Read-only diagnostic for measurement rows stored in a NON-canonical unit. - * - * Canonical storage is SI: every write path stamps `getUnitForType(type)`. The - * one historical leak was the MCP `log_measurement` tool, which persisted a - * caller-supplied `unit` verbatim (closed in v1.32.26). A row whose `unit` - * column differs from its type's canonical unit is such a legacy row, and it - * mislabels every display surface that trusts the column. - * - * This groups measurements by `(type, unit)` and prints every group whose - * `unit` is not the canonical unit for its type, flagging whether the stored - * unit is a RECOGNISED alias (mechanically convertible by the companion - * convert script) or UNKNOWN (needs a manual look). Nothing is written. - * - * NOT auto-fixable and deliberately out of scope: a row whose stored unit IS - * already canonical but whose VALUE was a foreign unit (e.g. a lb reading typed - * into the old kg-labelled entry form). Those are indistinguishable from real - * canonical readings; the reporter edits them via the record list. - * - * Run (inside the app container, which has DATABASE_URL): - * pnpm dlx tsx scripts/diagnose-noncanonical-measurement-units.ts - */ -import "dotenv/config"; - -import { prisma } from "@/lib/db"; -import { getUnitForType } from "@/lib/validations/measurement"; -import { resolveToCanonicalUnit } from "@/lib/measurements/unit-aliases"; - -async function main(): Promise { - const groups = await prisma.measurement.groupBy({ - by: ["type", "unit"], - _count: { _all: true }, - }); - - const mismatches = groups.filter((g) => { - if (g.unit === null) return false; - return g.unit !== getUnitForType(g.type); - }); - - if (mismatches.length === 0) { - console.log("No non-canonical measurement units found. Storage is clean."); - return; - } - - let convertible = 0; - let unknown = 0; - console.log( - `Found ${mismatches.length} non-canonical (type, unit) group(s):\n`, - ); - for (const g of mismatches.sort((a, b) => b._count._all - a._count._all)) { - const canonical = getUnitForType(g.type); - // Probe convertibility with a neutral value — only the recognition matters. - const recognised = resolveToCanonicalUnit(g.type, 1, g.unit as string); - const status = recognised ? "CONVERTIBLE" : "UNKNOWN — manual review"; - if (recognised) convertible += g._count._all; - else unknown += g._count._all; - console.log( - ` ${g.type}: stored="${g.unit}" canonical="${canonical}" ` + - `rows=${g._count._all} [${status}]`, - ); - } - console.log( - `\nTotals: ${convertible} row(s) convertible, ${unknown} row(s) unknown.`, - ); - console.log( - "Convert the recognised aliases with:\n" + - " pnpm dlx tsx scripts/convert-noncanonical-measurement-units.ts --execute", - ); -} - -main() - .catch((err) => { - console.error(err); - process.exitCode = 1; - }) - .finally(() => prisma.$disconnect()); diff --git a/scripts/i18n-key-tool.mjs b/scripts/i18n-key-tool.mjs index 4aa1ca599..26b4de22d 100644 --- a/scripts/i18n-key-tool.mjs +++ b/scripts/i18n-key-tool.mjs @@ -1,5 +1,5 @@ /** - * One-shot helper for a text sweep across the six locale bundles. + * One-shot helper for a text sweep across every locale bundle. * * node scripts/i18n-key-tool.mjs drop [...] * node scripts/i18n-key-tool.mjs set diff --git a/scripts/i18n/build-locale.py b/scripts/i18n/build-locale.py deleted file mode 100644 index e37905ca2..000000000 --- a/scripts/i18n/build-locale.py +++ /dev/null @@ -1,404 +0,0 @@ -#!/usr/bin/env python3 -""" -v1.4.25 W9e — build messages/{fr,es,it,pl}.json from messages/en.json. - -These are AI-initial translations. A MaintainershipBanner runs at the -top of /auth-shell for non-maintained locales, advertising the AI -provenance and linking to the GitHub translation-feedback template. - -We translate via a layered lookup: - 1. Full-path overrides (e.g. "auth.email" -> precise FR) - 2. Last-segment lookups (covers Save/Cancel/Delete/etc.) - 3. Whole-string lookups (covers common short phrases) - 4. Token-by-token translation for sentences - 5. Fallback: keep the English value verbatim (the banner says so) - -Placeholders ({foo}, {bar}, ) are preserved verbatim — the -translator never touches text inside curly braces or angle brackets. - -Run: python3 scripts/i18n/build-locale.py -""" -import json -import os -import re -import sys -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -MESSAGES = ROOT / "messages" -EN = json.loads((MESSAGES / "en.json").read_text(encoding="utf-8")) - -# --------------------------------------------------------------------------- -# Core UI vocabulary — high-confidence translations for the most-used -# short strings. Each entry is keyed on the lowercase English form (with -# trailing punctuation/ellipsis stripped) so a "Save" / "save" / "Save..." -# all hit the same row. -# --------------------------------------------------------------------------- -# Format: english_lower -> { fr, es, it, pl } -COMMON_VOCAB: dict[str, dict[str, str]] = { - # Buttons / actions - "save": {"fr": "Enregistrer", "es": "Guardar", "it": "Salva", "pl": "Zapisz"}, - "saved": {"fr": "Enregistré", "es": "Guardado", "it": "Salvato", "pl": "Zapisano"}, - "cancel": {"fr": "Annuler", "es": "Cancelar", "it": "Annulla", "pl": "Anuluj"}, - "delete": {"fr": "Supprimer", "es": "Eliminar", "it": "Elimina", "pl": "Usuń"}, - "edit": {"fr": "Modifier", "es": "Editar", "it": "Modifica", "pl": "Edytuj"}, - "create": {"fr": "Créer", "es": "Crear", "it": "Crea", "pl": "Utwórz"}, - "close": {"fr": "Fermer", "es": "Cerrar", "it": "Chiudi", "pl": "Zamknij"}, - "confirm": {"fr": "Confirmer", "es": "Confirmar", "it": "Conferma", "pl": "Potwierdź"}, - "back": {"fr": "Retour", "es": "Atrás", "it": "Indietro", "pl": "Wstecz"}, - "next": {"fr": "Suivant", "es": "Siguiente", "it": "Avanti", "pl": "Dalej"}, - "yes": {"fr": "Oui", "es": "Sí", "it": "Sì", "pl": "Tak"}, - "no": {"fr": "Non", "es": "No", "it": "No", "pl": "Nie"}, - "search": {"fr": "Rechercher", "es": "Buscar", "it": "Cerca", "pl": "Szukaj"}, - "filter": {"fr": "Filtrer", "es": "Filtrar", "it": "Filtra", "pl": "Filtruj"}, - "add": {"fr": "Ajouter", "es": "Añadir", "it": "Aggiungi", "pl": "Dodaj"}, - "import": {"fr": "Importer", "es": "Importar", "it": "Importa", "pl": "Importuj"}, - "export": {"fr": "Exporter", "es": "Exportar", "it": "Esporta", "pl": "Eksportuj"}, - "reset": {"fr": "Réinitialiser", "es": "Restablecer", "it": "Reimposta", "pl": "Resetuj"}, - "test": {"fr": "Tester", "es": "Probar", "it": "Verifica", "pl": "Testuj"}, - "copy": {"fr": "Copier", "es": "Copiar", "it": "Copia", "pl": "Kopiuj"}, - "retry": {"fr": "Réessayer", "es": "Reintentar", "it": "Riprova", "pl": "Spróbuj ponownie"}, - "replace": {"fr": "Remplacer", "es": "Reemplazar", "it": "Sostituisci", "pl": "Zastąp"}, - "loading": {"fr": "Chargement…", "es": "Cargando…", "it": "Caricamento…", "pl": "Ładowanie…"}, - "loading...": {"fr": "Chargement…", "es": "Cargando…", "it": "Caricamento…", "pl": "Ładowanie…"}, - "error": {"fr": "Erreur", "es": "Error", "it": "Errore", "pl": "Błąd"}, - "success": {"fr": "Succès", "es": "Éxito", "it": "Successo", "pl": "Sukces"}, - "unknown": {"fr": "Inconnu", "es": "Desconocido", "it": "Sconosciuto", "pl": "Nieznane"}, - "unknown error": {"fr": "Erreur inconnue", "es": "Error desconocido", "it": "Errore sconosciuto", "pl": "Nieznany błąd"}, - "network error": {"fr": "Erreur réseau", "es": "Error de red", "it": "Errore di rete", "pl": "Błąd sieci"}, - "no data available": {"fr": "Aucune donnée disponible", "es": "Sin datos disponibles", "it": "Nessun dato disponibile", "pl": "Brak danych"}, - "all": {"fr": "Tout", "es": "Todo", "it": "Tutto", "pl": "Wszystko"}, - "active": {"fr": "Actif", "es": "Activo", "it": "Attivo", "pl": "Aktywne"}, - "inactive": {"fr": "Inactif", "es": "Inactivo", "it": "Inattivo", "pl": "Nieaktywne"}, - "enabled": {"fr": "Activé", "es": "Activado", "it": "Abilitato", "pl": "Włączone"}, - "disabled": {"fr": "Désactivé", "es": "Desactivado", "it": "Disabilitato", "pl": "Wyłączone"}, - "optional": {"fr": "Facultatif", "es": "Opcional", "it": "Opzionale", "pl": "Opcjonalne"}, - "required": {"fr": "Requis", "es": "Obligatorio", "it": "Obbligatorio", "pl": "Wymagane"}, - "or": {"fr": "ou", "es": "o", "it": "o", "pl": "lub"}, - "not logged in": {"fr": "Non connecté", "es": "No conectado", "it": "Non connesso", "pl": "Niezalogowany"}, - "access denied": {"fr": "Accès refusé", "es": "Acceso denegado", "it": "Accesso negato", "pl": "Brak dostępu"}, - "copied!": {"fr": "Copié !", "es": "¡Copiado!", "it": "Copiato!", "pl": "Skopiowano!"}, - "show password": {"fr": "Afficher le mot de passe", "es": "Mostrar contraseña", "it": "Mostra password", "pl": "Pokaż hasło"}, - "hide password": {"fr": "Masquer le mot de passe", "es": "Ocultar contraseña", "it": "Nascondi password", "pl": "Ukryj hasło"}, - "more options": {"fr": "Plus d'options", "es": "Más opciones", "it": "Altre opzioni", "pl": "Więcej opcji"}, - "copy details": {"fr": "Copier les détails", "es": "Copiar detalles", "it": "Copia dettagli", "pl": "Kopiuj szczegóły"}, - "report issue": {"fr": "Signaler un problème", "es": "Reportar problema", "it": "Segnala un problema", "pl": "Zgłoś problem"}, - - # Auth - "log in": {"fr": "Se connecter", "es": "Iniciar sesión", "it": "Accedi", "pl": "Zaloguj się"}, - "login": {"fr": "Connexion", "es": "Iniciar sesión", "it": "Accedi", "pl": "Zaloguj"}, - "log out": {"fr": "Se déconnecter", "es": "Cerrar sesión", "it": "Disconnetti", "pl": "Wyloguj się"}, - "logout": {"fr": "Déconnexion", "es": "Cerrar sesión", "it": "Disconnetti", "pl": "Wyloguj"}, - "register": {"fr": "S'inscrire", "es": "Registrarse", "it": "Registrati", "pl": "Zarejestruj się"}, - "sign in": {"fr": "Se connecter", "es": "Iniciar sesión", "it": "Accedi", "pl": "Zaloguj się"}, - "sign up": {"fr": "S'inscrire", "es": "Registrarse", "it": "Registrati", "pl": "Zarejestruj się"}, - "email": {"fr": "E-mail", "es": "Correo electrónico", "it": "E-mail", "pl": "E-mail"}, - "password": {"fr": "Mot de passe", "es": "Contraseña", "it": "Password", "pl": "Hasło"}, - "username": {"fr": "Nom d'utilisateur", "es": "Nombre de usuario", "it": "Nome utente", "pl": "Nazwa użytkownika"}, - - # Health terms (Apple Health / Withings vocabulary) - "blood pressure": {"fr": "Tension artérielle", "es": "Presión arterial", "it": "Pressione arteriosa", "pl": "Ciśnienie krwi"}, - "pulse": {"fr": "Pouls", "es": "Pulso", "it": "Polso", "pl": "Tętno"}, - "weight": {"fr": "Poids", "es": "Peso", "it": "Peso", "pl": "Waga"}, - "mood": {"fr": "Humeur", "es": "Estado de ánimo", "it": "Umore", "pl": "Nastrój"}, - "sleep": {"fr": "Sommeil", "es": "Sueño", "it": "Sonno", "pl": "Sen"}, - "steps": {"fr": "Pas", "es": "Pasos", "it": "Passi", "pl": "Kroki"}, - "medications": {"fr": "Médicaments", "es": "Medicamentos", "it": "Farmaci", "pl": "Leki"}, - "medication": {"fr": "Médicament", "es": "Medicamento", "it": "Farmaco", "pl": "Lek"}, - "height": {"fr": "Taille", "es": "Altura", "it": "Altezza", "pl": "Wzrost"}, - "bmi": {"fr": "IMC", "es": "IMC", "it": "IMC", "pl": "BMI"}, - "heart rate": {"fr": "Fréquence cardiaque", "es": "Frecuencia cardíaca", "it": "Frequenza cardiaca", "pl": "Tętno"}, - "resting heart rate": {"fr": "Fréquence cardiaque au repos", "es": "Frecuencia cardíaca en reposo", "it": "Frequenza cardiaca a riposo", "pl": "Tętno spoczynkowe"}, - "body fat": {"fr": "Masse grasse", "es": "Grasa corporal", "it": "Massa grassa", "pl": "Tkanka tłuszczowa"}, - - # Nav - "dashboard": {"fr": "Tableau de bord", "es": "Panel", "it": "Cruscotto", "pl": "Pulpit"}, - "insights": {"fr": "Analyses", "es": "Análisis", "it": "Analisi", "pl": "Statystyki"}, - "settings": {"fr": "Paramètres", "es": "Ajustes", "it": "Impostazioni", "pl": "Ustawienia"}, - "profile": {"fr": "Profil", "es": "Perfil", "it": "Profilo", "pl": "Profil"}, - "trends": {"fr": "Tendances", "es": "Tendencias", "it": "Tendenze", "pl": "Trendy"}, - "history": {"fr": "Historique", "es": "Historial", "it": "Cronologia", "pl": "Historia"}, - "overview": {"fr": "Vue d'ensemble", "es": "Resumen", "it": "Panoramica", "pl": "Przegląd"}, - "language": {"fr": "Langue", "es": "Idioma", "it": "Lingua", "pl": "Język"}, - "timezone": {"fr": "Fuseau horaire", "es": "Zona horaria", "it": "Fuso orario", "pl": "Strefa czasowa"}, - - # Time periods - "today": {"fr": "Aujourd'hui", "es": "Hoy", "it": "Oggi", "pl": "Dzisiaj"}, - "yesterday": {"fr": "Hier", "es": "Ayer", "it": "Ieri", "pl": "Wczoraj"}, - "tomorrow": {"fr": "Demain", "es": "Mañana", "it": "Domani", "pl": "Jutro"}, - "this week": {"fr": "Cette semaine", "es": "Esta semana", "it": "Questa settimana", "pl": "Ten tydzień"}, - "this month": {"fr": "Ce mois-ci", "es": "Este mes", "it": "Questo mese", "pl": "Ten miesiąc"}, - "last 7 days": {"fr": "7 derniers jours", "es": "Últimos 7 días", "it": "Ultimi 7 giorni", "pl": "Ostatnie 7 dni"}, - "last 30 days": {"fr": "30 derniers jours", "es": "Últimos 30 días", "it": "Ultimi 30 giorni", "pl": "Ostatnie 30 dni"}, - "last 90 days": {"fr": "90 derniers jours", "es": "Últimos 90 días", "it": "Ultimi 90 giorni", "pl": "Ostatnie 90 dni"}, - "all time": {"fr": "Toute la période", "es": "Todo el tiempo", "it": "Tutto il tempo", "pl": "Cały okres"}, - - # Mood scale - "great": {"fr": "Excellent", "es": "Excelente", "it": "Ottimo", "pl": "Świetnie"}, - "good": {"fr": "Bien", "es": "Bien", "it": "Bene", "pl": "Dobrze"}, - "neutral": {"fr": "Neutre", "es": "Neutral", "it": "Neutro", "pl": "Neutralnie"}, - "bad": {"fr": "Mauvais", "es": "Mal", "it": "Male", "pl": "Źle"}, - "awful": {"fr": "Terrible", "es": "Terrible", "it": "Pessimo", "pl": "Okropnie"}, - - # Misc - "date": {"fr": "Date", "es": "Fecha", "it": "Data", "pl": "Data"}, - "time": {"fr": "Heure", "es": "Hora", "it": "Ora", "pl": "Czas"}, - "value": {"fr": "Valeur", "es": "Valor", "it": "Valore", "pl": "Wartość"}, - "type": {"fr": "Type", "es": "Tipo", "it": "Tipo", "pl": "Typ"}, - "note": {"fr": "Note", "es": "Nota", "it": "Nota", "pl": "Notatka"}, - "notes": {"fr": "Notes", "es": "Notas", "it": "Note", "pl": "Notatki"}, - "name": {"fr": "Nom", "es": "Nombre", "it": "Nome", "pl": "Nazwa"}, - "description": {"fr": "Description", "es": "Descripción", "it": "Descrizione", "pl": "Opis"}, - "actions": {"fr": "Actions", "es": "Acciones", "it": "Azioni", "pl": "Akcje"}, - "status": {"fr": "Statut", "es": "Estado", "it": "Stato", "pl": "Status"}, - "total": {"fr": "Total", "es": "Total", "it": "Totale", "pl": "Łącznie"}, - "average": {"fr": "Moyenne", "es": "Promedio", "it": "Media", "pl": "Średnia"}, - "min": {"fr": "Min", "es": "Mín", "it": "Min", "pl": "Min"}, - "max": {"fr": "Max", "es": "Máx", "it": "Max", "pl": "Maks"}, - "from": {"fr": "De", "es": "Desde", "it": "Da", "pl": "Od"}, - "to": {"fr": "À", "es": "Hasta", "it": "A", "pl": "Do"}, - "of": {"fr": "de", "es": "de", "it": "di", "pl": "z"}, - - # Charts - "chart": {"fr": "Graphique", "es": "Gráfico", "it": "Grafico", "pl": "Wykres"}, - "trend": {"fr": "Tendance", "es": "Tendencia", "it": "Tendenza", "pl": "Trend"}, - "compare": {"fr": "Comparer", "es": "Comparar", "it": "Confronta", "pl": "Porównaj"}, - "comparison": {"fr": "Comparaison", "es": "Comparación", "it": "Confronto", "pl": "Porównanie"}, - - # Doctor report - "patient": {"fr": "Patient", "es": "Paciente", "it": "Paziente", "pl": "Pacjent"}, - "gender": {"fr": "Sexe", "es": "Sexo", "it": "Sesso", "pl": "Płeć"}, - "male": {"fr": "Homme", "es": "Hombre", "it": "Uomo", "pl": "Mężczyzna"}, - "female": {"fr": "Femme", "es": "Mujer", "it": "Donna", "pl": "Kobieta"}, - "non-binary": {"fr": "Non-binaire", "es": "No binario", "it": "Non binario", "pl": "Niebinarna"}, - "date of birth": {"fr": "Date de naissance", "es": "Fecha de nacimiento", "it": "Data di nascita", "pl": "Data urodzenia"}, - - # GLP-1 - "glp-1 therapy": {"fr": "Traitement GLP-1", "es": "Terapia GLP-1", "it": "Terapia GLP-1", "pl": "Terapia GLP-1"}, - "current dose": {"fr": "Dose actuelle", "es": "Dosis actual", "it": "Dose attuale", "pl": "Aktualna dawka"}, - "compliance": {"fr": "Observance", "es": "Cumplimiento", "it": "Aderenza", "pl": "Przestrzeganie zaleceń"}, - "compliance rate": {"fr": "Taux d'observance", "es": "Tasa de cumplimiento", "it": "Tasso di aderenza", "pl": "Wskaźnik przestrzegania"}, - "taken": {"fr": "Pris", "es": "Tomada", "it": "Assunta", "pl": "Przyjęte"}, - "skipped": {"fr": "Sauté", "es": "Omitida", "it": "Saltata", "pl": "Pominięte"}, - "missed": {"fr": "Manqué", "es": "Perdida", "it": "Persa", "pl": "Pominięte"}, - "side effect": {"fr": "Effet secondaire", "es": "Efecto secundario", "it": "Effetto collaterale", "pl": "Skutek uboczny"}, - "dose": {"fr": "Dose", "es": "Dosis", "it": "Dose", "pl": "Dawka"}, - - # Brand/identity - "healthlog": {"fr": "HealthLog", "es": "HealthLog", "it": "HealthLog", "pl": "HealthLog"}, - - # Settings sections - "account": {"fr": "Compte", "es": "Cuenta", "it": "Account", "pl": "Konto"}, - "appearance": {"fr": "Apparence", "es": "Apariencia", "it": "Aspetto", "pl": "Wygląd"}, - "notifications": {"fr": "Notifications", "es": "Notificaciones", "it": "Notifiche", "pl": "Powiadomienia"}, - "integrations": {"fr": "Intégrations", "es": "Integraciones", "it": "Integrazioni", "pl": "Integracje"}, - "data": {"fr": "Données", "es": "Datos", "it": "Dati", "pl": "Dane"}, - "privacy": {"fr": "Confidentialité", "es": "Privacidad", "it": "Privacy", "pl": "Prywatność"}, - "security": {"fr": "Sécurité", "es": "Seguridad", "it": "Sicurezza", "pl": "Bezpieczeństwo"}, - "theme": {"fr": "Thème", "es": "Tema", "it": "Tema", "pl": "Motyw"}, - "light": {"fr": "Clair", "es": "Claro", "it": "Chiaro", "pl": "Jasny"}, - "dark": {"fr": "Sombre", "es": "Oscuro", "it": "Scuro", "pl": "Ciemny"}, - "system": {"fr": "Système", "es": "Sistema", "it": "Sistema", "pl": "Systemowy"}, - - # Onboarding - "welcome": {"fr": "Bienvenue", "es": "Bienvenido", "it": "Benvenuto", "pl": "Witamy"}, - "get started": {"fr": "Commencer", "es": "Empezar", "it": "Inizia", "pl": "Rozpocznij"}, - "continue": {"fr": "Continuer", "es": "Continuar", "it": "Continua", "pl": "Kontynuuj"}, - "skip": {"fr": "Ignorer", "es": "Omitir", "it": "Salta", "pl": "Pomiń"}, - "finish": {"fr": "Terminer", "es": "Finalizar", "it": "Termina", "pl": "Zakończ"}, - "done": {"fr": "Terminé", "es": "Hecho", "it": "Fatto", "pl": "Gotowe"}, - - # Errors - "page not found": {"fr": "Page introuvable", "es": "Página no encontrada", "it": "Pagina non trovata", "pl": "Strona nie znaleziona"}, - "something went wrong": {"fr": "Une erreur est survenue", "es": "Algo salió mal", "it": "Si è verificato un errore", "pl": "Coś poszło nie tak"}, -} - -# Full-path overrides for highly contextual strings -PATH_OVERRIDES: dict[str, dict[str, str]] = { - # doctorReport core - "doctorReport.title": { - "fr": "Rapport de santé", - "es": "Informe de salud", - "it": "Rapporto sanitario", - "pl": "Raport zdrowotny", - }, - "doctorReport.subtitle": { - "fr": "HealthLog — Rapport personnel de santé", - "es": "HealthLog — Informe personal de salud", - "it": "HealthLog — Rapporto personale di salute", - "pl": "HealthLog — Osobisty raport zdrowotny", - }, - "doctorReport.period": { - "fr": "Période du rapport", - "es": "Período del informe", - "it": "Periodo del rapporto", - "pl": "Okres raportu", - }, - "doctorReport.createdOn": { - "fr": "Créé le", - "es": "Creado el", - "it": "Creato il", - "pl": "Utworzono", - }, - "doctorReport.vitalsTitle": { - "fr": "Signes vitaux — Résumé", - "es": "Signos vitales — Resumen", - "it": "Parametri vitali — Riepilogo", - "pl": "Parametry życiowe — Podsumowanie", - }, - "doctorReport.footerDisclaimer1": { - "fr": "Ce rapport a été généré automatiquement à partir de données auto-déclarées et vise à soutenir", - "es": "Este informe se generó automáticamente a partir de datos autorregistrados y está destinado a apoyar", - "it": "Questo rapporto è stato generato automaticamente da dati auto-registrati ed è destinato a supportare", - "pl": "Ten raport został wygenerowany automatycznie z danych samodzielnie rejestrowanych i ma na celu wspierać", - }, - "doctorReport.footerDisclaimer2": { - "fr": "la conversation médecin-patient. Il ne remplace pas un diagnostic médical. Les corrélations n'impliquent pas la causalité.", - "es": "la conversación entre médico y paciente. No sustituye un diagnóstico médico. Las correlaciones no implican causalidad.", - "it": "il dialogo medico-paziente. Non sostituisce una diagnosi medica. Le correlazioni non implicano causalità.", - "pl": "rozmowę lekarza z pacjentem. Nie zastępuje diagnozy medycznej. Korelacje nie oznaczają związku przyczynowego.", - }, - "doctorReport.footerSource": { - "fr": "Source : HealthLog | Fuseau horaire : Europe/Berlin | Créé : {timestamp}", - "es": "Fuente: HealthLog | Zona horaria: Europe/Berlin | Creado: {timestamp}", - "it": "Fonte: HealthLog | Fuso orario: Europe/Berlin | Creato: {timestamp}", - "pl": "Źródło: HealthLog | Strefa czasowa: Europe/Berlin | Utworzono: {timestamp}", - }, - # Maintainership banner — referenced by the React component - "i18n.maintainershipBanner.notice": { - "fr": "Cette traduction est maintenue par IA et peut contenir des imprécisions.", - "es": "Esta traducción está mantenida por IA y puede contener imprecisiones.", - "it": "Questa traduzione è gestita tramite IA e potrebbe contenere imprecisioni.", - "pl": "To tłumaczenie jest utrzymywane przez AI i może zawierać nieścisłości.", - }, - "i18n.maintainershipBanner.cta": { - "fr": "Aidez-nous sur GitHub", - "es": "Ayúdanos en GitHub", - "it": "Aiutaci su GitHub", - "pl": "Pomóż na GitHubie", - }, - "i18n.maintainershipBanner.dismiss": { - "fr": "Ignorer", - "es": "Cerrar", - "it": "Chiudi", - "pl": "Zamknij", - }, -} - -# Last-segment fallback: many keys named after the EN word ("save", -# "cancel", "close", "delete"). When the value is just the capitalised -# / titlecased English version, we can translate by the EN value lookup. -PLACEHOLDER_RE = re.compile(r"(\{[^}]+\}|<[^>]+>|`[^`]+`)") - - -def split_protected(s: str) -> list[tuple[str, bool]]: - """Split a string into (chunk, is_protected) parts. Protected chunks - are placeholders/tags that must NEVER be translated.""" - parts: list[tuple[str, bool]] = [] - last = 0 - for m in PLACEHOLDER_RE.finditer(s): - if m.start() > last: - parts.append((s[last : m.start()], False)) - parts.append((m.group(0), True)) - last = m.end() - if last < len(s): - parts.append((s[last:], False)) - return parts - - -def translate_chunk(text: str, locale: str) -> str: - """Apply COMMON_VOCAB to a free-text chunk. Lowercase lookup; preserves - the original capitalisation of the FIRST translated word.""" - if not text.strip(): - return text - # Whole-string lookup first - key = text.strip().lower().rstrip(".!?:,;") - if key in COMMON_VOCAB and locale in COMMON_VOCAB[key]: - translated = COMMON_VOCAB[key][locale] - # Preserve leading whitespace + trailing punctuation - leading = text[: len(text) - len(text.lstrip())] - trailing_punct = "" - stripped = text.strip() - while stripped and stripped[-1] in ".!?:,;": - trailing_punct = stripped[-1] + trailing_punct - stripped = stripped[:-1] - return f"{leading}{translated}{trailing_punct}" - # No translation — return verbatim - return text - - -def translate_value(value: str, locale: str, full_path: str) -> str: - """Translate one leaf value to the target locale. - - Priority: - 1. PATH_OVERRIDES exact match - 2. Whole-value lookup in COMMON_VOCAB - 3. Verbatim English (banner advertises this) - """ - # Empty / whitespace - if not value.strip(): - return value - - # 1. Path override - if full_path in PATH_OVERRIDES and locale in PATH_OVERRIDES[full_path]: - return PATH_OVERRIDES[full_path][locale] - - # 2. Whole-value lookup with placeholder protection - parts = split_protected(value) - out: list[str] = [] - for chunk, protected in parts: - if protected: - out.append(chunk) - else: - out.append(translate_chunk(chunk, locale)) - return "".join(out) - - -def walk_and_translate(node, locale: str, prefix: str = "") -> object: - if isinstance(node, dict): - return {k: walk_and_translate(v, locale, f"{prefix}.{k}" if prefix else k) for k, v in node.items()} - if isinstance(node, str): - return translate_value(node, locale, prefix) - return node - - -def main() -> int: - if len(sys.argv) > 1: - targets = sys.argv[1:] - else: - targets = ["fr", "es", "it", "pl"] - - for locale in targets: - out = walk_and_translate(EN, locale) - out_path = MESSAGES / f"{locale}.json" - # Preserve EN's pretty-print style (2-space indent, sorted? — EN is not sorted) - # We mirror EN's literal indent + key order so diffs stay reviewable. - out_path.write_text( - json.dumps(out, ensure_ascii=False, indent=2) + "\n", - encoding="utf-8", - ) - # Verify key parity - new = json.loads(out_path.read_text(encoding="utf-8")) - en_keys, new_keys = set(), set() - - def flat(o, p, s): - if isinstance(o, dict): - for k, v in o.items(): - flat(v, f"{p}.{k}" if p else k, s) - elif isinstance(o, str): - s.add(p) - - flat(EN, "", en_keys) - flat(new, "", new_keys) - only_en = en_keys - new_keys - only_new = new_keys - en_keys - print(f" {locale}.json: {len(new_keys)} leaf keys; only_en={len(only_en)}; only_new={len(only_new)}") - if only_en or only_new: - print(" PARITY MISMATCH!", file=sys.stderr) - return 1 - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/seed-demo-apps01.sql b/scripts/seed-demo-apps01.sql deleted file mode 100644 index 5fac887ea..000000000 --- a/scripts/seed-demo-apps01.sql +++ /dev/null @@ -1,154 +0,0 @@ --- HealthLog demo seed for apps01 — v1.15 (cycle-inclusive, FEMALE demo). --- --- Creates / refreshes the `demo` account as a WOMAN with cycle tracking on, --- NOW()-anchored + idempotent (re-runnable: wipes the demo user's rows first). --- Showcases vitals + mood + the v1.15 cycle vertical (5 cycles, daily BBT with --- a biphasic shift, mucus/OPK progression, period flow, phase-clustered --- symptoms), plus phase-dependent RHR/HRV/sleep so the phase×vitals insights --- board surfaces real findings. role=USER (login is open on prod; an --- admin demo would be a risk). AI insights are generated separately afterwards. --- --- Run: ssh apps-01 'docker exec -i psql -U healthlog -d healthlog' < scripts/seed-demo-apps01.sql - -BEGIN; - --- ── User (FEMALE, USER role) ────────────────────────────────────── -INSERT INTO users (id, username, email, password_hash, role, height_cm, date_of_birth, gender, timezone, locale, onboarding_completed_at, created_at, updated_at) -SELECT 'usr_demo_cf31025295714ece8d91f5af13afd76d', - 'demo', 'demo@healthlog.app', - '$argon2id$v=19$m=19456,t=2,p=1$3KvrEIuI5UnwG53GPbS71w$mL8ZQotIXn9iLFIw3yHXbh6SitX1w7TXKI5ahWcKkF8', - 'USER', 168, TIMESTAMP '1992-03-15', 'FEMALE', 'Europe/Berlin', 'en', NOW(), NOW(), NOW() -WHERE NOT EXISTS (SELECT 1 FROM users WHERE username = 'demo'); - -UPDATE users SET - gender = 'FEMALE', role = 'USER', height_cm = 168, date_of_birth = TIMESTAMP '1992-03-15', - password_hash = '$argon2id$v=19$m=19456,t=2,p=1$3KvrEIuI5UnwG53GPbS71w$mL8ZQotIXn9iLFIw3yHXbh6SitX1w7TXKI5ahWcKkF8', - onboarding_completed_at = NOW(), timezone = 'Europe/Berlin', locale = 'en', updated_at = NOW() -WHERE username = 'demo'; - -CREATE TEMP TABLE _demo AS SELECT id AS user_id FROM users WHERE username = 'demo' LIMIT 1; - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM _demo) THEN - RAISE EXCEPTION 'seed-demo-apps01: demo user not resolved'; - END IF; -END $$; - --- ── Wipe prior demo rows (idempotent) ───────────────────────────── -DELETE FROM cycle_symptom_links WHERE day_log_id IN (SELECT id FROM cycle_day_logs WHERE user_id IN (SELECT user_id FROM _demo)); -DELETE FROM cycle_day_logs WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM cycle_predictions WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM menstrual_cycles WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM mood_entries WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM measurements WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM measurement_rollups WHERE user_id IN (SELECT user_id FROM _demo); -DELETE FROM mood_entry_rollups WHERE user_id IN (SELECT user_id FROM _demo); - --- ── Cycle profile (tracking ON) ─────────────────────────────────── -INSERT INTO cycle_profiles (id, user_id, goal, cycle_tracking_enabled, typical_cycle_length, typical_period_length, luteal_phase_length, prediction_enabled, raw_chart_mode, discreet_notifications, sensitive_category_encryption, created_at, updated_at) -SELECT 'cp_' || replace(gen_random_uuid()::text, '-', ''), user_id, 'GENERAL_HEALTH', true, 28, 5, 14, true, false, false, true, NOW(), NOW() -FROM _demo -WHERE NOT EXISTS (SELECT 1 FROM cycle_profiles cp JOIN _demo d ON cp.user_id = d.user_id); - -UPDATE cycle_profiles SET - cycle_tracking_enabled = true, goal = 'GENERAL_HEALTH', prediction_enabled = true, - typical_cycle_length = 28, typical_period_length = 5, luteal_phase_length = 14, updated_at = NOW() -WHERE user_id IN (SELECT user_id FROM _demo); - --- ── Measurements (90-day sliding window, phase-aware) ───────────── --- cycleDay(d) = ((128 - d) % 28) + 1 ; luteal = cycleDay > 13. -WITH demo AS (SELECT user_id FROM _demo), days AS (SELECT generate_series(0, 89) AS d) -INSERT INTO measurements (id, user_id, type, value, unit, source, glucose_context, measured_at, created_at, updated_at) -SELECT 'm_' || replace(gen_random_uuid()::text, '-', ''), (SELECT user_id FROM demo), v.type::measurement_type, v.value, v.unit, 'MANUAL'::measurement_source, - v.gc::glucose_context, - NOW() - (d || ' days')::interval - (random() * 5 || ' hours')::interval, - NOW() - (d || ' days')::interval, NOW() - (d || ' days')::interval -FROM days, -LATERAL ( - SELECT ((128 - d) % 28) + 1 AS cd -) c, -LATERAL (VALUES - ('WEIGHT', round((66.0 - (90 - d) * 0.011 + (random() - 0.5) * 0.5)::numeric, 1)::float8, 'kg', NULL), - ('BLOOD_PRESSURE_SYS', round((116 + (random() - 0.5) * 8)::numeric, 0)::float8, 'mmHg', NULL), - ('BLOOD_PRESSURE_DIA', round((74 + (random() - 0.5) * 6)::numeric, 0)::float8, 'mmHg', NULL), - ('PULSE', round((68 + (random() - 0.5) * 8)::numeric, 0)::float8, 'bpm', NULL), - ('RESTING_HEART_RATE', round((56 + (CASE WHEN c.cd > 13 THEN 3 ELSE 0 END) + (random() - 0.5) * 3)::numeric, 0)::float8, 'bpm', NULL), - ('HEART_RATE_VARIABILITY', round((68 - (CASE WHEN c.cd > 13 THEN 7 ELSE 0 END) + (random() - 0.5) * 6)::numeric, 0)::float8, 'ms', NULL), - ('SLEEP_DURATION', round((445 - (CASE WHEN c.cd > 13 THEN 18 ELSE 0 END) + (random() - 0.5) * 40)::numeric, 0)::float8, 'minutes', NULL), - ('ACTIVITY_STEPS', round((8200 + (random() - 0.5) * 3500)::numeric, 0)::float8, 'steps', NULL), - ('BODY_FAT', round((27.5 - (90 - d) * 0.006 + (random() - 0.5) * 0.4)::numeric, 1)::float8, '%', NULL), - ('BODY_TEMPERATURE', round(((CASE WHEN c.cd <= 13 THEN 36.36 ELSE 36.73 END) + (random() - 0.5) * 0.08)::numeric, 2)::float8, 'celsius', NULL), - ('BLOOD_GLUCOSE', round((92 + (random() - 0.5) * 18)::numeric, 0)::float8, 'mg/dL', 'FASTING') -) AS v(type, value, unit, gc); - --- ── Mood (90 days, luteal dip) ──────────────────────────────────── -WITH demo AS (SELECT user_id FROM _demo), days AS (SELECT generate_series(0, 89) AS d) -INSERT INTO mood_entries (id, user_id, date, mood, score, source, mood_logged_at, synced_at, tz, sync_version, created_at, updated_at) -SELECT 'mo_' || replace(gen_random_uuid()::text, '-', ''), (SELECT user_id FROM demo), - to_char(NOW() - (d || ' days')::interval, 'YYYY-MM-DD'), - CASE s.score WHEN 1 THEN 'LAUSIG' WHEN 2 THEN 'SCHLECHT' WHEN 3 THEN 'OKAY' WHEN 4 THEN 'GUT' ELSE 'SUPER_GUT' END, - s.score, 'MANUAL', - NOW() - (d || ' days')::interval - (random() * 6 || ' hours')::interval, - NOW() - (d || ' days')::interval, 'Europe/Berlin', 0, - NOW() - (d || ' days')::interval, NOW() - (d || ' days')::interval -FROM days, -LATERAL (SELECT ((128 - d) % 28) + 1 AS cd) c, -LATERAL ( - SELECT GREATEST(1, LEAST(5, - round((CASE WHEN c.cd > 23 THEN 2.6 WHEN c.cd > 13 THEN 3.3 ELSE 3.9 END) + (random() - 0.5) * 1.6)::int - )) AS score -) s; - --- ── Menstrual cycles (5, NOW-anchored; last open) ───────────────── -WITH offs AS (SELECT unnest(ARRAY[128, 100, 72, 44, 16]) AS off) -INSERT INTO menstrual_cycles (id, user_id, start_date, end_date, period_end_date, length_days, ovulation_date, ovulation_confirmed, is_predicted, tz, created_at, updated_at) -SELECT 'mc_' || replace(gen_random_uuid()::text, '-', ''), (SELECT user_id FROM _demo), - to_char(NOW() - (off || ' days')::interval, 'YYYY-MM-DD'), - CASE WHEN off = 16 THEN NULL ELSE to_char(NOW() - ((off - 27) || ' days')::interval, 'YYYY-MM-DD') END, - to_char(NOW() - ((off - 4) || ' days')::interval, 'YYYY-MM-DD'), - CASE WHEN off = 16 THEN NULL ELSE 28 END, - to_char(NOW() - ((off - 13) || ' days')::interval, 'YYYY-MM-DD'), - true, false, 'Europe/Berlin', NOW(), NOW() -FROM offs; - --- ── Cycle day-logs (128 days) + phase-clustered symptom links ───── -WITH demo AS (SELECT user_id FROM _demo), -gen AS ( - SELECT d, ((128 - d) % 28) + 1 AS cd, to_char(NOW() - (d || ' days')::interval, 'YYYY-MM-DD') AS dstr - FROM generate_series(0, 127) AS d -), -ins AS ( - INSERT INTO cycle_day_logs (id, user_id, date, flow, basal_body_temp_c, cervical_mucus, ovulation_test, source, tz, sync_version, created_at, updated_at) - SELECT 'cdl_' || replace(gen_random_uuid()::text, '-', ''), (SELECT user_id FROM demo), g.dstr, - (CASE g.cd WHEN 1 THEN 'MEDIUM' WHEN 2 THEN 'HEAVY' WHEN 3 THEN 'MEDIUM' WHEN 4 THEN 'LIGHT' WHEN 5 THEN 'SPOTTING' ELSE NULL END)::flow_level, - round(((CASE WHEN g.cd <= 13 THEN 36.36 ELSE 36.73 END) + (random() - 0.5) * 0.08)::numeric, 2)::float8, - (CASE WHEN g.cd BETWEEN 1 AND 5 THEN NULL WHEN g.cd BETWEEN 6 AND 9 THEN 'STICKY' WHEN g.cd BETWEEN 10 AND 12 THEN 'CREAMY' WHEN g.cd BETWEEN 13 AND 14 THEN 'EGG_WHITE' WHEN g.cd BETWEEN 15 AND 16 THEN 'WATERY' ELSE 'DRY' END)::cervical_mucus, - (CASE WHEN g.cd = 13 THEN 'POSITIVE_LH_SURGE' WHEN g.cd IN (11, 12, 14) THEN 'NEGATIVE' ELSE NULL END)::ovulation_test, - 'MANUAL'::measurement_source, 'Europe/Berlin', 0, - NOW() - (g.d || ' days')::interval, NOW() - (g.d || ' days')::interval - FROM gen g - RETURNING id, date -), -link_menstrual AS ( - INSERT INTO cycle_symptom_links (day_log_id, symptom_id, severity, created_at) - SELECT ins.id, s.id, (2 + floor(random() * 3))::int, NOW() - FROM ins JOIN gen g ON g.dstr = ins.date - CROSS JOIN LATERAL (SELECT id FROM cycle_symptoms WHERE user_id IS NULL AND key IN ('cramps', 'headache', 'fatigue')) s - WHERE g.cd BETWEEN 1 AND 3 -) -INSERT INTO cycle_symptom_links (day_log_id, symptom_id, severity, created_at) -SELECT ins.id, s.id, (1 + floor(random() * 3))::int, NOW() -FROM ins JOIN gen g ON g.dstr = ins.date -CROSS JOIN LATERAL (SELECT id FROM cycle_symptoms WHERE user_id IS NULL AND key IN ('mood_swings', 'bloating', 'breast_tenderness')) s -WHERE g.cd BETWEEN 24 AND 28; - -COMMIT; - --- ── Summary ─────────────────────────────────────────────────────── -SELECT 'demo user' AS what, id FROM users WHERE username = 'demo'; -SELECT 'measurements' AS what, count(*) FROM measurements WHERE user_id = (SELECT id FROM users WHERE username = 'demo'); -SELECT 'mood' AS what, count(*) FROM mood_entries WHERE user_id = (SELECT id FROM users WHERE username = 'demo'); -SELECT 'cycles' AS what, count(*) FROM menstrual_cycles WHERE user_id = (SELECT id FROM users WHERE username = 'demo'); -SELECT 'day_logs' AS what, count(*) FROM cycle_day_logs WHERE user_id = (SELECT id FROM users WHERE username = 'demo'); -SELECT 'symptom_links' AS what, count(*) FROM cycle_symptom_links l JOIN cycle_day_logs dl ON l.day_log_id = dl.id WHERE dl.user_id = (SELECT id FROM users WHERE username = 'demo'); diff --git a/scripts/seed-demo-extend-1y.sql b/scripts/seed-demo-extend-1y.sql deleted file mode 100644 index d611aea68..000000000 --- a/scripts/seed-demo-extend-1y.sql +++ /dev/null @@ -1,216 +0,0 @@ --- HealthLog demo 1-year history extender for apps01. --- --- WHAT THIS DOES --- Extends the demo account's data BACKWARD from the existing ~90-day seed --- (scripts/seed-demo-apps01.sql) to ~365 days of coverage, and adds ~365 days --- of realistic medication-intake history for the two pre-existing demo meds. --- It mirrors that seed EXACTLY: same column lists, enum values, NOW()-anchored --- offset math (cycleDay(d) = ((128 - d) % 28) + 1), cuid/uuid-id style, and the --- same phase-aware vitals / mood / cycle channels. --- --- The seed covers offsets 0..89 (measurements + mood), 0..127 (cycle day-logs), --- and 5 cycles at NOW-offsets {128,100,72,44,16}. This file ADDS ONLY the older --- tail so nothing is duplicated: --- - measurements / mood : offsets 90..364 (thinned to every 2nd day past ~180d) --- - cycle day-logs : offsets 128..364 --- - menstrual cycles : 7 more 28-day cycles at offsets 156..352 --- - medication intakes : ~365d of due slots for both demo meds --- --- SCOPE + SAFETY --- - Demo-user-scoped: EVERY statement filters on the hardcoded demo user id. --- - Idempotent: every INSERT guards on the live unique index via ON CONFLICT --- DO NOTHING (or a date-offset window that begins past the seed's coverage), --- so re-running adds nothing new. --- - NO DDL (no CREATE/ALTER/DROP/TRUNCATE), no changes to any other user, --- no table-wide DELETE/UPDATE. --- --- Run: ssh apps-01 'docker exec -i psql -U healthlog -d healthlog' < scripts/seed-demo-extend-1y.sql - -\set uid 'usr_demo_cf31025295714ece8d91f5af13afd76d' -\set medA 'cmq2lzjqh005x01nrys2crzg9' -- Mounjaro: weekly @ 08:00 (rolling 7d) -\set medB 'cmq2m0mzk006101nrdpijbij0' -- Ramipril: twice daily @ 08:00 + 18:00 - -BEGIN; - --- Refuse to run if the demo user does not exist. -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM users WHERE id = 'usr_demo_cf31025295714ece8d91f5af13afd76d') THEN - RAISE EXCEPTION 'seed-demo-extend-1y: demo user not found'; - END IF; -END $$; - --- ── Measurements: offsets 90..364, phase-aware (mirrors the seed channels) ───── --- Thin the deep tail: keep every day for 90..179, every 2nd day for 180..364, to --- keep row volume sane while still covering the full year. Unique index --- (user_id, type, measured_at, source, sleep_stage) NULLS NOT DISTINCT — but --- measured_at carries a random sub-day jitter, so collisions are effectively --- impossible on a fresh tail; ON CONFLICT DO NOTHING makes re-runs safe anyway. -WITH days AS ( - SELECT d FROM generate_series(90, 364) AS d - WHERE d < 180 OR (d % 2) = 0 -) -INSERT INTO measurements (id, user_id, type, value, unit, source, glucose_context, measured_at, created_at, updated_at) -SELECT 'm_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - v.type::measurement_type, v.value, v.unit, 'MANUAL'::measurement_source, - v.gc::glucose_context, - NOW() - (d || ' days')::interval - (random() * 5 || ' hours')::interval, - NOW() - (d || ' days')::interval, NOW() - (d || ' days')::interval -FROM days, -LATERAL (SELECT ((128 - d) % 28) + 1 AS cd) c, -LATERAL (VALUES - ('WEIGHT', round((66.0 - (90 - d) * 0.011 + (random() - 0.5) * 0.5)::numeric, 1)::float8, 'kg', NULL), - ('BLOOD_PRESSURE_SYS', round((116 + (random() - 0.5) * 8)::numeric, 0)::float8, 'mmHg', NULL), - ('BLOOD_PRESSURE_DIA', round((74 + (random() - 0.5) * 6)::numeric, 0)::float8, 'mmHg', NULL), - ('PULSE', round((68 + (random() - 0.5) * 8)::numeric, 0)::float8, 'bpm', NULL), - ('RESTING_HEART_RATE', round((56 + (CASE WHEN c.cd > 13 THEN 3 ELSE 0 END) + (random() - 0.5) * 3)::numeric, 0)::float8, 'bpm', NULL), - ('HEART_RATE_VARIABILITY', round((68 - (CASE WHEN c.cd > 13 THEN 7 ELSE 0 END) + (random() - 0.5) * 6)::numeric, 0)::float8, 'ms', NULL), - ('SLEEP_DURATION', round((445 - (CASE WHEN c.cd > 13 THEN 18 ELSE 0 END) + (random() - 0.5) * 40)::numeric, 0)::float8, 'minutes', NULL), - ('ACTIVITY_STEPS', round((8200 + (random() - 0.5) * 3500)::numeric, 0)::float8, 'steps', NULL), - ('BODY_FAT', round((27.5 - (90 - d) * 0.006 + (random() - 0.5) * 0.4)::numeric, 1)::float8, '%', NULL), - ('BODY_TEMPERATURE', round(((CASE WHEN c.cd <= 13 THEN 36.36 ELSE 36.73 END) + (random() - 0.5) * 0.08)::numeric, 2)::float8, 'celsius', NULL), - ('BLOOD_GLUCOSE', round((92 + (random() - 0.5) * 18)::numeric, 0)::float8, 'mg/dL', 'FASTING') -) AS v(type, value, unit, gc) -ON CONFLICT DO NOTHING; - --- ── Mood: offsets 90..364, luteal dip (mirrors the seed) ─────────────────────── --- Unique index (user_id, date, mood_logged_at). One entry per day, fresh tail. -WITH days AS (SELECT generate_series(90, 364) AS d) -INSERT INTO mood_entries (id, user_id, date, mood, score, source, mood_logged_at, synced_at, tz, sync_version, created_at, updated_at) -SELECT 'mo_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - to_char(NOW() - (d || ' days')::interval, 'YYYY-MM-DD'), - CASE s.score WHEN 1 THEN 'LAUSIG' WHEN 2 THEN 'SCHLECHT' WHEN 3 THEN 'OKAY' WHEN 4 THEN 'GUT' ELSE 'SUPER_GUT' END, - s.score, 'MANUAL', - NOW() - (d || ' days')::interval - (random() * 6 || ' hours')::interval, - NOW() - (d || ' days')::interval, 'Europe/Berlin', 0, - NOW() - (d || ' days')::interval, NOW() - (d || ' days')::interval -FROM days, -LATERAL (SELECT ((128 - d) % 28) + 1 AS cd) c, -LATERAL ( - SELECT GREATEST(1, LEAST(5, - round((CASE WHEN c.cd > 23 THEN 2.6 WHEN c.cd > 13 THEN 3.3 ELSE 3.9 END) + (random() - 0.5) * 1.6)::int - )) AS score -) s -ON CONFLICT DO NOTHING; - --- ── Menstrual cycles: 7 more 28-day cycles, offsets 156..352 ─────────────────── --- Continues the seed's cadence backward (seed had {128,100,72,44,16}; next is 156). --- Unique index (user_id, start_date) → ON CONFLICT DO NOTHING. All closed cycles. -WITH offs AS (SELECT unnest(ARRAY[156, 184, 212, 240, 268, 296, 324, 352]) AS off) -INSERT INTO menstrual_cycles (id, user_id, start_date, end_date, period_end_date, length_days, ovulation_date, ovulation_confirmed, is_predicted, tz, created_at, updated_at) -SELECT 'mc_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - to_char(NOW() - (off || ' days')::interval, 'YYYY-MM-DD'), - to_char(NOW() - ((off - 27) || ' days')::interval, 'YYYY-MM-DD'), - to_char(NOW() - ((off - 4) || ' days')::interval, 'YYYY-MM-DD'), - 28, - to_char(NOW() - ((off - 13) || ' days')::interval, 'YYYY-MM-DD'), - true, false, 'Europe/Berlin', NOW(), NOW() -FROM offs -ON CONFLICT (user_id, start_date) DO NOTHING; - --- ── Cycle day-logs: offsets 128..364 + phase-clustered symptom links ─────────── --- Mirrors the seed exactly (flow/BBT/mucus/OPK by cycleDay). Unique index --- (user_id, date) → ON CONFLICT DO NOTHING. Symptom links only attach to rows --- this run actually inserts (RETURNING), so re-runs add no duplicate links. -WITH gen AS ( - SELECT d, ((128 - d) % 28) + 1 AS cd, to_char(NOW() - (d || ' days')::interval, 'YYYY-MM-DD') AS dstr - FROM generate_series(128, 364) AS d -), -ins AS ( - INSERT INTO cycle_day_logs (id, user_id, date, flow, basal_body_temp_c, cervical_mucus, ovulation_test, source, tz, sync_version, created_at, updated_at) - SELECT 'cdl_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', g.dstr, - (CASE g.cd WHEN 1 THEN 'MEDIUM' WHEN 2 THEN 'HEAVY' WHEN 3 THEN 'MEDIUM' WHEN 4 THEN 'LIGHT' WHEN 5 THEN 'SPOTTING' ELSE NULL END)::flow_level, - round(((CASE WHEN g.cd <= 13 THEN 36.36 ELSE 36.73 END) + (random() - 0.5) * 0.08)::numeric, 2)::float8, - (CASE WHEN g.cd BETWEEN 1 AND 5 THEN NULL WHEN g.cd BETWEEN 6 AND 9 THEN 'STICKY' WHEN g.cd BETWEEN 10 AND 12 THEN 'CREAMY' WHEN g.cd BETWEEN 13 AND 14 THEN 'EGG_WHITE' WHEN g.cd BETWEEN 15 AND 16 THEN 'WATERY' ELSE 'DRY' END)::cervical_mucus, - (CASE WHEN g.cd = 13 THEN 'POSITIVE_LH_SURGE' WHEN g.cd IN (11, 12, 14) THEN 'NEGATIVE' ELSE NULL END)::ovulation_test, - 'MANUAL'::measurement_source, 'Europe/Berlin', 0, - NOW() - (g.d || ' days')::interval, NOW() - (g.d || ' days')::interval - FROM gen g - ON CONFLICT (user_id, date) DO NOTHING - RETURNING id, date -), -link_menstrual AS ( - INSERT INTO cycle_symptom_links (day_log_id, symptom_id, severity, created_at) - SELECT ins.id, s.id, (2 + floor(random() * 3))::int, NOW() - FROM ins JOIN gen g ON g.dstr = ins.date - CROSS JOIN LATERAL (SELECT id FROM cycle_symptoms WHERE user_id IS NULL AND key IN ('cramps', 'headache', 'fatigue')) s - WHERE g.cd BETWEEN 1 AND 3 -) -INSERT INTO cycle_symptom_links (day_log_id, symptom_id, severity, created_at) -SELECT ins.id, s.id, (1 + floor(random() * 3))::int, NOW() -FROM ins JOIN gen g ON g.dstr = ins.date -CROSS JOIN LATERAL (SELECT id FROM cycle_symptoms WHERE user_id IS NULL AND key IN ('mood_swings', 'bloating', 'breast_tenderness')) s -WHERE g.cd BETWEEN 24 AND 28; - --- ── Medication intakes: Med A (weekly @ 08:00 Berlin), ~365d ─────────────────── --- The existing demo rows store scheduled_for in UTC at 06:00 (= 08:00 Berlin DST). --- We mint slots at the Berlin local 08:00 and convert to the stored UTC wall-time --- with AT TIME ZONE so winter (07:00 UTC) vs summer (06:00 UTC) is honoured. --- Live partial unique index: (user_id, medication_id, scheduled_for, source) --- WHERE deleted_at IS NULL → ON CONFLICT on that predicate makes re-runs safe. --- Compliance ~85-90%: deterministic per-slot pseudo-random skip ~12% of weeks. --- Never future-dated: WHERE the slot timestamp <= NOW(). -WITH slots AS ( - SELECT - ( (date_trunc('day', NOW() AT TIME ZONE 'Europe/Berlin') - (w * 7 || ' days')::interval + interval '8 hours') - AT TIME ZONE 'Europe/Berlin' ) AT TIME ZONE 'UTC' AS sched_utc, - -- deterministic skip decision per week offset (no random(): re-run stable) - ( (hashtextextended('medA-week-' || w, 0) & 2147483647)::numeric / 2147483647.0 ) AS r - FROM generate_series(0, 52) AS w -- 53 weekly slots = w*7 days back ≈ 371 days -) -INSERT INTO medication_intake_events (id, user_id, medication_id, scheduled_for, taken_at, skipped, source, created_at, updated_at, sync_version) -SELECT 'cmq_demo_a_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - 'cmq2lzjqh005x01nrys2crzg9', - s.sched_utc, - CASE WHEN s.r < 0.88 THEN s.sched_utc + (interval '5 minutes' + (s.r * interval '90 minutes')) ELSE NULL END, - CASE WHEN s.r < 0.88 THEN false ELSE true END, - 'WEB'::intake_source, - s.sched_utc, s.sched_utc, 0 -FROM slots s -WHERE s.sched_utc <= NOW() - -- skip slots already covered by the seeded rows (06-06 06:00 + 06-13 06:00) - AND s.sched_utc < date_trunc('day', NOW()) - interval '1 day' -ON CONFLICT (user_id, medication_id, scheduled_for, source) WHERE deleted_at IS NULL DO NOTHING; - --- ── Medication intakes: Med B (twice daily @ 08:00 + 18:00 Berlin), ~365d ────── --- One row per due slot per day. Compliance ~85-90%: deterministic per-slot skip --- ~12% of individual slots. Same TZ + ON CONFLICT + no-future-date guards. -WITH slots AS ( - SELECT - ( (date_trunc('day', NOW() AT TIME ZONE 'Europe/Berlin') - (dd || ' days')::interval + (hh || ' hours')::interval) - AT TIME ZONE 'Europe/Berlin' ) AT TIME ZONE 'UTC' AS sched_utc, - ( (hashtextextended('medB-' || dd || '-' || hh, 0) & 2147483647)::numeric / 2147483647.0 ) AS r - FROM generate_series(0, 364) AS dd - CROSS JOIN (VALUES (8), (18)) AS h(hh) -) -INSERT INTO medication_intake_events (id, user_id, medication_id, scheduled_for, taken_at, skipped, source, created_at, updated_at, sync_version) -SELECT 'cmq_demo_b_' || replace(gen_random_uuid()::text, '-', ''), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - 'cmq2m0mzk006101nrdpijbij0', - s.sched_utc, - CASE WHEN s.r < 0.88 THEN s.sched_utc + (interval '5 minutes' + (s.r * interval '120 minutes')) ELSE NULL END, - CASE WHEN s.r < 0.88 THEN false ELSE true END, - 'WEB'::intake_source, - s.sched_utc, s.sched_utc, 0 -FROM slots s -WHERE s.sched_utc <= NOW() - -- skip slots already covered by the seeded rows (today's 06:00 + 16:00 UTC) - AND s.sched_utc < date_trunc('day', NOW()) -ON CONFLICT (user_id, medication_id, scheduled_for, source) WHERE deleted_at IS NULL DO NOTHING; - -COMMIT; - --- ── Verification (demo-user-scoped totals after apply) ───────────────────────── -\echo === post-apply totals (demo user) === -SELECT 'measurements' AS what, count(*), min(measured_at)::date AS oldest, max(measured_at)::date AS newest FROM measurements WHERE user_id = :'uid'; -SELECT 'mood' AS what, count(*), min(date) AS oldest, max(date) AS newest FROM mood_entries WHERE user_id = :'uid'; -SELECT 'cycles' AS what, count(*), min(start_date) AS oldest, max(start_date) AS newest FROM menstrual_cycles WHERE user_id = :'uid'; -SELECT 'day_logs' AS what, count(*), min(date) AS oldest, max(date) AS newest FROM cycle_day_logs WHERE user_id = :'uid'; -SELECT 'symptom_links' AS what, count(*) FROM cycle_symptom_links l JOIN cycle_day_logs dl ON l.day_log_id = dl.id WHERE dl.user_id = :'uid'; -SELECT 'intake_medA' AS what, count(*), count(*) FILTER (WHERE taken_at IS NOT NULL) AS taken, count(*) FILTER (WHERE skipped) AS skipped FROM medication_intake_events WHERE user_id = 'usr_demo_cf31025295714ece8d91f5af13afd76d' AND medication_id = 'cmq2lzjqh005x01nrys2crzg9'; -SELECT 'intake_medB' AS what, count(*), count(*) FILTER (WHERE taken_at IS NOT NULL) AS taken, count(*) FILTER (WHERE skipped) AS skipped FROM medication_intake_events WHERE user_id = 'usr_demo_cf31025295714ece8d91f5af13afd76d' AND medication_id = 'cmq2m0mzk006101nrdpijbij0'; diff --git a/scripts/seed-demo-meds-apps01.sql b/scripts/seed-demo-meds-apps01.sql deleted file mode 100644 index b2fb1fedf..000000000 --- a/scripts/seed-demo-meds-apps01.sql +++ /dev/null @@ -1,65 +0,0 @@ --- HealthLog demo medications for apps01 — canonical ids the 1-year extender --- (scripts/seed-demo-extend-1y.sql) references for its intake history. --- Idempotent. Creates the two demo meds + schedules so the extender's --- medication_intake_events FK resolves and the medication cards/compliance --- surfaces populate. Demo user id is the canonical seeded id. --- --- Run: ssh apps-01 'docker exec -i psql -U healthlog -d healthlog' < scripts/seed-demo-meds-apps01.sql - -BEGIN; - -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM users WHERE id = 'usr_demo_cf31025295714ece8d91f5af13afd76d') THEN - RAISE EXCEPTION 'seed-demo-meds: demo user not found'; - END IF; -END $$; - --- ── Med A — Mounjaro (GLP-1, weekly on Sundays @ 08:00) ──────────── -INSERT INTO medications - (id, user_id, name, dose, treatment_class, doses_per_unit, active, notifications_enabled, starts_on, created_at, updated_at) -VALUES - ('cmq2lzjqh005x01nrys2crzg9', 'usr_demo_cf31025295714ece8d91f5af13afd76d', - 'Mounjaro', '5 mg', 'GLP1', 4, true, true, - (NOW() - INTERVAL '380 days')::date, NOW() - INTERVAL '380 days', NOW()) -ON CONFLICT (id) DO UPDATE SET - name = EXCLUDED.name, dose = EXCLUDED.dose, treatment_class = EXCLUDED.treatment_class, - doses_per_unit = EXCLUDED.doses_per_unit, active = true, starts_on = EXCLUDED.starts_on, - updated_at = NOW(); - -INSERT INTO medication_schedules - (id, medication_id, window_start, window_end, label, times_of_day, days_of_week, rrule) -VALUES - ('sched_demo_medA_mounjaro', 'cmq2lzjqh005x01nrys2crzg9', - '08:00', '10:00', 'Wöchentlich', ARRAY['08:00'], '7', 'FREQ=WEEKLY;BYDAY=SU') -ON CONFLICT (id) DO UPDATE SET - window_start = EXCLUDED.window_start, window_end = EXCLUDED.window_end, - label = EXCLUDED.label, times_of_day = EXCLUDED.times_of_day, - days_of_week = EXCLUDED.days_of_week, rrule = EXCLUDED.rrule; - --- ── Med B — Ramipril (twice daily @ 08:00 + 18:00) ───────────────── -INSERT INTO medications - (id, user_id, name, dose, treatment_class, active, notifications_enabled, starts_on, created_at, updated_at) -VALUES - ('cmq2m0mzk006101nrdpijbij0', 'usr_demo_cf31025295714ece8d91f5af13afd76d', - 'Ramipril', '5 mg', 'GENERIC', true, true, - (NOW() - INTERVAL '380 days')::date, NOW() - INTERVAL '380 days', NOW()) -ON CONFLICT (id) DO UPDATE SET - name = EXCLUDED.name, dose = EXCLUDED.dose, treatment_class = EXCLUDED.treatment_class, - active = true, starts_on = EXCLUDED.starts_on, updated_at = NOW(); - -INSERT INTO medication_schedules - (id, medication_id, window_start, window_end, label, times_of_day, days_of_week, rrule) -VALUES - ('sched_demo_medB_ramipril', 'cmq2m0mzk006101nrdpijbij0', - '08:00', '18:00', 'Morgens & Abends', ARRAY['08:00','18:00'], NULL, 'FREQ=DAILY') -ON CONFLICT (id) DO UPDATE SET - window_start = EXCLUDED.window_start, window_end = EXCLUDED.window_end, - label = EXCLUDED.label, times_of_day = EXCLUDED.times_of_day, - days_of_week = EXCLUDED.days_of_week, rrule = EXCLUDED.rrule; - -COMMIT; - -\echo === demo meds === -SELECT id, name, dose, treatment_class, active FROM medications -WHERE user_id = 'usr_demo_cf31025295714ece8d91f5af13afd76d' ORDER BY name; diff --git a/scripts/seed-demo-scores-1y.sql b/scripts/seed-demo-scores-1y.sql deleted file mode 100644 index d608b39b5..000000000 --- a/scripts/seed-demo-scores-1y.sql +++ /dev/null @@ -1,153 +0,0 @@ --- HealthLog demo computed-wellness-score seeder for apps01 (1 year). --- --- WHAT THIS DOES --- Seeds the three PERSISTED nightly wellness scores for the demo account so --- all of the dashboard's "Deine Gesundheitswerte" rings render with a full --- year of history. These three are normally written by the nightly score --- crons (src/lib/jobs/{recovery,stress,strain}-score.ts) as COMPUTED --- Measurement rows; the demo never had them computed, so only the two --- compute-on-read composites (READINESS + SLEEP_SCORE — derived live from the --- demo's RHR/HRV/sleep/mood, NOT stored) were showing. --- --- channel type external_id unit value --- Recovery RECOVERY_SCORE recovery:YYYY-MM-DD score 0..100 (55-90) --- Strain STRAIN_SCORE strain:YYYY-MM-DD score 0..100 (20-75) --- Stress STRESS_SCORE stress:YYYY-MM-DD score 0..100 (25-65) --- --- READINESS and SLEEP_SCORE are NOT seeded here: neither exists in the --- measurement_type enum (verified live) — both are recomputed on read from --- the underlying signals the demo already carries, so they already render and --- a stored row would be ignored. Seeding them would be dead data. --- --- Row shape mirrors src/lib/insights/score-row.ts::upsertScoreRow exactly: --- source = COMPUTED, unit = 'score', measured_at = noon UTC on the scored day, --- external_id = ':YYYY-MM-DD', sleep_stage / device_type / --- external_source_version / deleted_at all NULL. --- --- Coverage: one row per (channel, UTC day) for the last 365 days (offsets --- 1..365 — offset 0 is "today", which the nightly cron scores as the PREVIOUS --- day, so the freshest persisted day is yesterday; this matches the job and --- keeps every row strictly measured_at <= NOW()). 365 days x 3 channels = --- 1,095 rows on a clean apply. --- --- DETERMINISM --- Values + ids are hash-derived from the day stamp (hashtextextended / --- md5 — NO random()), so a re-run is byte-stable. Idempotent re-runs are --- additionally guarded by ON CONFLICT DO NOTHING on the live unique index --- (user_id, type, source, external_id) — the exact key the cron upserts on. --- --- SCOPE + SAFETY --- - Demo-user-scoped: EVERY statement hardcodes the demo user id. --- - Idempotent: ON CONFLICT DO NOTHING; re-running adds nothing. --- - No DDL (no CREATE/ALTER/DROP/TRUNCATE), no other-user writes, no --- table-wide DELETE/UPDATE, no future-dated rows (measured_at <= NOW()). --- --- Run: ssh apps-01 'docker exec -i psql -U healthlog -d healthlog' < scripts/seed-demo-scores-1y.sql - -\set uid 'usr_demo_cf31025295714ece8d91f5af13afd76d' - -BEGIN; - --- Refuse to run if the demo user does not exist. -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM users WHERE id = 'usr_demo_cf31025295714ece8d91f5af13afd76d') THEN - RAISE EXCEPTION 'seed-demo-scores-1y: demo user not found'; - END IF; -END $$; - --- ── The three persisted score channels, one row per (channel, day) ───────────── --- offsets 1..365 (the scored day is the day that just ended; offset 0 = today is --- never persisted by the cron). day_key is the UTC calendar day; measured_at is --- noon UTC on it, mirroring scoreMeasuredAt(). Deterministic values: --- - week phase : a gentle 7-day sinusoid keyed to the day-of-year (weekly --- rhythm — lighter mid-week, heavier toward the weekend). --- - noise : per-(channel,day) hash in [0,1), no random(). --- - recovery : 55..90, dips after a "hard" (high-strain) day. --- - strain : 20..75, weekend / mid-week training spikes (~25% of days). --- - stress : 25..65, inversely coupled to recovery (recovery up → stress --- down) so the two rings tell a coherent story. -WITH days AS ( - SELECT - d, - (NOW() AT TIME ZONE 'UTC')::date - d AS day_key - FROM generate_series(1, 365) AS d -), -calc AS ( - SELECT - d, - day_key, - (day_key + 12)::timestamp AS measured_at, -- noon UTC on the scored day - -- weekly rhythm in [-1, 1] from ISO day-of-week (Mon=1..Sun=7) - sin(2 * pi() * (extract(isodow FROM day_key)::numeric - 1) / 7.0) AS week_phase, - -- deterministic per-day "is this a training day" flag (~25%) - ( (hashtextextended('score-train-' || day_key::text, 0) & 2147483647)::numeric / 2147483647.0 ) AS train_r, - -- independent deterministic noise streams per channel, all in [0,1) - ( (hashtextextended('score-rec-' || day_key::text, 0) & 2147483647)::numeric / 2147483647.0 ) AS rec_r, - ( (hashtextextended('score-str-' || day_key::text, 0) & 2147483647)::numeric / 2147483647.0 ) AS str_r, - ( (hashtextextended('score-stress-' || day_key::text, 0) & 2147483647)::numeric / 2147483647.0 ) AS stress_r - FROM days -), -vals AS ( - SELECT - d, day_key, measured_at, train_r, - -- STRAIN 20..75: base + weekly rhythm + a spike on training days. - LEAST(75, GREATEST(20, round( - 40 + week_phase * 8 - + (CASE WHEN train_r < 0.25 THEN 22 ELSE 0 END) - + (str_r - 0.5) * 14 - )))::int AS strain, - -- RECOVERY 55..90: base + weekly rhythm, suppressed the day after a hard day. - LEAST(90, GREATEST(55, round( - 74 + week_phase * 6 - - (CASE WHEN train_r < 0.25 THEN 7 ELSE 0 END) - + (rec_r - 0.5) * 16 - )))::int AS recovery - FROM calc -), -final AS ( - SELECT - v.d, v.day_key, v.measured_at, v.strain, v.recovery, - -- STRESS 25..65: inverse to recovery (high recovery → low stress) + noise. - LEAST(65, GREATEST(25, round( - 90 - v.recovery * 0.5 - + (c.stress_r - 0.5) * 14 - )))::int AS stress - FROM vals v JOIN calc c USING (d) -), -rows AS ( - SELECT day_key, measured_at, type, ext_prefix, value FROM final, - LATERAL (VALUES - ('RECOVERY_SCORE', 'recovery:', recovery), - ('STRAIN_SCORE', 'strain:', strain), - ('STRESS_SCORE', 'stress:', stress) - ) AS ch(type, ext_prefix, value) -) -INSERT INTO measurements (id, user_id, type, value, unit, source, measured_at, external_id, created_at, updated_at) -SELECT - -- deterministic id from the per-row external_id → byte-stable re-runs. - 'm_score_' || md5(ext_prefix || to_char(day_key, 'YYYY-MM-DD')), - 'usr_demo_cf31025295714ece8d91f5af13afd76d', - type::measurement_type, - value::float8, - 'score', - 'COMPUTED'::measurement_source, - measured_at, - ext_prefix || to_char(day_key, 'YYYY-MM-DD'), - measured_at, - measured_at -FROM rows -WHERE measured_at <= NOW() -ON CONFLICT (user_id, type, source, external_id) DO NOTHING; - -COMMIT; - --- ── Verification (demo-user-scoped, all five score channels) ─────────────────── -\echo === post-apply score coverage (demo user) === -SELECT type, count(*) AS rows, min(measured_at)::date AS oldest, max(measured_at)::date AS newest -FROM measurements -WHERE user_id = :'uid' - AND type IN ('RECOVERY_SCORE', 'STRAIN_SCORE', 'STRESS_SCORE') -GROUP BY type -ORDER BY type; -\echo (READINESS + SLEEP_SCORE are compute-on-read composites — not stored, render from underlying signals.) diff --git a/scripts/v1341-audit-ledger-check.mjs b/scripts/v1341-audit-ledger-check.mjs deleted file mode 100644 index 83e5724b5..000000000 --- a/scripts/v1341-audit-ledger-check.mjs +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env node - -import { readFileSync, statSync } from "node:fs"; -import { resolve } from "node:path"; - -const ledgerArg = process.argv[2]; -if (!ledgerArg) { - console.error("usage: node scripts/v1341-audit-ledger-check.mjs "); - process.exit(2); -} - -const ledgerPath = resolve(ledgerArg); -const MAX_BYTES = 256 * 1024; -const REQUIRED_FIELDS = [ - "Source", - "Status", - "Severity", - "Category", - "Evidence", - "Affected path", - "Attempted refutation", - "Impact", - "Requirement", - "Planned verification", - "Confidence", - "Unresolved", -]; -const ALLOWED_STATUS = new Set([ - "confirmed", - "suspected", - "blocked", - "cleared", - "refuted", -]); -const ALLOWED_SEVERITY = new Set(["critical", "high", "medium", "low", "info"]); -const ALLOWED_CONFIDENCE = new Set(["high", "medium", "low"]); -const REQUIRED_SOURCES = ["research:", "correctness:", "live:", "performance:"]; -const REQUIRED_CATEGORIES = [ - "day-boundary", - "silent-success", - "cache-freshness", - "persistence", - "outcome-honesty", -]; -const PRIVACY_PATTERNS = [ - { - name: "email address", - pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i, - }, - { - name: "authorization credential", - pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]+/i, - }, - { - name: "secret assignment", - pattern: - /\b(?:token|secret|password|api[_-]?key|client[_-]?secret)\s*[:=]\s*["']?[A-Za-z0-9._~+/=-]{8,}/i, - }, - { - name: "URL or endpoint with authority", - pattern: /\bhttps?:\/\/[^\s)>\]]+/i, - }, - { - name: "IPv4 address", - pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/, - }, - { - name: "UUID-like identifier", - pattern: - /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/i, - }, - { - name: "long hexadecimal identifier", - pattern: /\b[0-9a-f]{32,}\b/i, - }, - { - name: "local user filesystem path", - pattern: /(?:\/Users\/|\/home\/)[^\s`]+/, - }, - { - name: "explicit health value", - pattern: - /\b(?:glucose|blood pressure|heart rate|weight|bmi|hba1c)\s*(?:value\s*)?[:=]\s*-?\d+(?:\.\d+)?/i, - }, -]; - -const errors = []; -let source; -try { - if (statSync(ledgerPath).size > MAX_BYTES) { - errors.push(`ledger exceeds ${MAX_BYTES} bytes`); - } - source = readFileSync(ledgerPath, "utf8"); -} catch (error) { - console.error( - `v1341-audit-ledger-check: cannot read ledger (${error instanceof Error ? error.message : "unknown error"})`, - ); - process.exit(2); -} - -for (const check of PRIVACY_PATTERNS) { - if (check.pattern.test(source)) - errors.push(`privacy-dangerous ${check.name} detected`); -} - -const headingPattern = /^## (AUD-\d{3}) — (.+)$/gm; -const headings = [...source.matchAll(headingPattern)]; -if (headings.length === 0) errors.push("no finding headings found"); -if (headings.length > 200) errors.push("finding count exceeds 200"); - -const ids = new Set(); -const sourceRecords = new Set(); -const records = []; - -for (let index = 0; index < headings.length; index += 1) { - const match = headings[index]; - const id = match[1]; - const title = match[2].trim(); - const bodyStart = match.index + match[0].length; - const bodyEnd = headings[index + 1]?.index ?? source.length; - const body = source.slice(bodyStart, bodyEnd); - const fields = new Map(); - - if (ids.has(id)) errors.push(`${id}: duplicate finding ID`); - ids.add(id); - if (!title) errors.push(`${id}: missing title`); - - for (const line of body.split("\n")) { - const fieldMatch = line.match(/^- \*\*([^*]+):\*\* (.+)$/); - if (!fieldMatch) continue; - const [, name, value] = fieldMatch; - if (fields.has(name)) errors.push(`${id}: duplicate field ${name}`); - fields.set(name, value.trim()); - } - - for (const field of REQUIRED_FIELDS) { - if (!fields.get(field)) errors.push(`${id}: missing ${field}`); - } - - const status = fields.get("Status")?.toLowerCase(); - const severity = fields.get("Severity")?.toLowerCase(); - const confidence = fields.get("Confidence")?.toLowerCase(); - const unresolved = fields.get("Unresolved")?.toLowerCase(); - const sourceField = fields.get("Source"); - - if (status && !ALLOWED_STATUS.has(status)) { - errors.push(`${id}: invalid Status ${status}`); - } - if (severity && !ALLOWED_SEVERITY.has(severity)) { - errors.push(`${id}: invalid Severity ${severity}`); - } - if (confidence && !ALLOWED_CONFIDENCE.has(confidence)) { - errors.push(`${id}: invalid Confidence ${confidence}`); - } - if (unresolved && !["yes", "no"].includes(unresolved)) { - errors.push(`${id}: Unresolved must be yes or no`); - } - if (["cleared", "refuted"].includes(status) && unresolved !== "no") { - errors.push(`${id}: ${status} findings must set Unresolved to no`); - } - if (sourceField) { - if (sourceRecords.has(sourceField)) - errors.push(`${id}: duplicate Source record`); - sourceRecords.add(sourceField); - } - - records.push({ id, fields }); -} - -for (let index = 0; index < records.length; index += 1) { - const expected = `AUD-${String(index + 1).padStart(3, "0")}`; - if (records[index].id !== expected) { - errors.push(`${records[index].id}: expected sequential ID ${expected}`); - } -} - -for (const prefix of REQUIRED_SOURCES) { - if ( - !records.some((record) => record.fields.get("Source")?.includes(prefix)) - ) { - errors.push(`missing source family ${prefix}`); - } -} -for (const category of REQUIRED_CATEGORIES) { - if (!records.some((record) => record.fields.get("Category") === category)) { - errors.push(`missing required category ${category}`); - } -} - -if (errors.length > 0) { - console.error(`v1341-audit-ledger-check: FAIL (${errors.length} issue(s))`); - for (const error of errors) console.error(`- ${error}`); - process.exit(1); -} - -console.log( - `v1341-audit-ledger-check: PASS (${records.length} unique findings; privacy scan clean)`, -); diff --git a/scripts/v1341-performance-baseline.mjs b/scripts/v1341-performance-baseline.mjs deleted file mode 100644 index 700fbd85c..000000000 --- a/scripts/v1341-performance-baseline.mjs +++ /dev/null @@ -1,356 +0,0 @@ -#!/usr/bin/env node - -import { execFileSync } from "node:child_process"; -import { writeFileSync } from "node:fs"; -import { arch, cpus, platform, release } from "node:os"; -import { resolve } from "node:path"; -import { performance } from "node:perf_hooks"; - -const LIMITS = Object.freeze({ - deadlineMs: 30_000, - warmSamples: 11, - googlePages: 786, - googleRowsPerPage: 32, - reminderMedications: 2_000, - reminderSlotsPerMedication: 4, - reminderCleanupBacklog: 2_000, - healthDenseDays: 42, - healthSamplesPerDay: 1_440, - healthSparseDays: 379, - healthSparseSamplesPerDay: 8, -}); - -const args = process.argv.slice(2); -const fixtureMode = args.includes("--fixture"); -const outputIndex = args.indexOf("--output"); -const outputPath = - outputIndex >= 0 && args[outputIndex + 1] - ? resolve(args[outputIndex + 1]) - : null; - -function fail(message) { - console.error(`v1341-performance-baseline: ${message}`); - process.exit(2); -} - -if (!fixtureMode) { - fail( - "refusing to run without --fixture; live and production probes are unsupported", - ); -} -if (process.env.NODE_ENV === "production") { - fail("refusing to run with NODE_ENV=production"); -} -if (!outputPath) { - fail("--output is required"); -} -if (args.some((arg) => /^(--url|--host|--database|--token|--live)/.test(arg))) { - fail( - "network, database, credential, and live-target arguments are unsupported", - ); -} - -const startedAt = performance.now(); -const deadlineAt = startedAt + LIMITS.deadlineMs; - -function assertWithinDeadline() { - if (performance.now() > deadlineAt) { - throw new Error(`fixture deadline exceeded (${LIMITS.deadlineMs} ms)`); - } -} - -function seededNumber(index, salt) { - let value = (index + 1) ^ salt; - value = Math.imul(value ^ (value >>> 16), 0x45d9f3b); - value = Math.imul(value ^ (value >>> 16), 0x45d9f3b); - return (value ^ (value >>> 16)) >>> 0; -} - -function percentile(sorted, fraction) { - return sorted[ - Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1) - ]; -} - -function formatMs(value) { - return value.toFixed(3); -} - -function formatBytes(value) { - const sign = value < 0 ? "-" : ""; - return `${sign}${(Math.abs(value) / 1024 / 1024).toFixed(2)} MiB`; -} - -function measure(operation) { - assertWithinDeadline(); - const beforeHeap = process.memoryUsage().heapUsed; - const before = performance.now(); - const result = operation(); - const elapsedMs = performance.now() - before; - const heapDeltaBytes = process.memoryUsage().heapUsed - beforeHeap; - assertWithinDeadline(); - return { elapsedMs, heapDeltaBytes, result }; -} - -function benchmark(name, operation) { - const cold = measure(operation); - const warm = []; - for (let index = 0; index < LIMITS.warmSamples; index += 1) { - warm.push(measure(operation)); - } - const elapsed = warm.map((sample) => sample.elapsedMs).sort((a, b) => a - b); - const heaps = warm.map((sample) => sample.heapDeltaBytes); - const checksums = new Set([ - cold.result.checksum, - ...warm.map((sample) => sample.result.checksum), - ]); - if (checksums.size !== 1) { - throw new Error(`${name} fixture produced a non-deterministic checksum`); - } - return { - name, - coldMs: cold.elapsedMs, - coldHeapBytes: cold.heapDeltaBytes, - medianMs: percentile(elapsed, 0.5), - p95Ms: percentile(elapsed, 0.95), - maxMs: elapsed.at(-1), - maxWarmHeapBytes: Math.max(...heaps), - checksum: cold.result.checksum, - counters: cold.result.counters, - }; -} - -function googleHistoryFixture() { - const accumulated = []; - let checksum = 0; - let finalTokenPresent = false; - - for (let page = 0; page < LIMITS.googlePages; page += 1) { - const encodedPage = JSON.stringify({ - rows: Array.from({ length: LIMITS.googleRowsPerPage }, (_, row) => ({ - t: page * LIMITS.googleRowsPerPage + row, - v: seededNumber(page * LIMITS.googleRowsPerPage + row, 17) % 10_000, - })), - next: page + 1 < LIMITS.googlePages ? `fixture-page-${page + 1}` : null, - }); - const decodedPage = JSON.parse(encodedPage); - accumulated.push(...decodedPage.rows); - finalTokenPresent = decodedPage.next !== null; - } - - for (const row of accumulated) { - checksum = (checksum + row.t * 17 + row.v) >>> 0; - } - return { - checksum, - counters: { - pages: LIMITS.googlePages, - rows: accumulated.length, - concurrency: 1, - finalTokenPresent, - }, - }; -} - -function reminderCohortFixture() { - let checksum = 0; - let dueCandidates = 0; - let modeledQueries = 1; - - for (let cleanup = 0; cleanup < LIMITS.reminderCleanupBacklog; cleanup += 1) { - checksum = (checksum + seededNumber(cleanup, 31)) >>> 0; - } - - for ( - let medication = 0; - medication < LIMITS.reminderMedications; - medication += 1 - ) { - modeledQueries += 2; - for (let slot = 0; slot < LIMITS.reminderSlotsPerMedication; slot += 1) { - const phase = seededNumber( - medication * LIMITS.reminderSlotsPerMedication + slot, - 43, - ); - if (phase % 5 === 0) { - dueCandidates += 1; - modeledQueries += 2; - } - checksum = (checksum + (phase & 0xffff)) >>> 0; - } - } - return { - checksum, - counters: { - medications: LIMITS.reminderMedications, - slots: LIMITS.reminderMedications * LIMITS.reminderSlotsPerMedication, - cleanupBacklog: LIMITS.reminderCleanupBacklog, - dueCandidates, - modeledQueries, - concurrency: 1, - }, - }; -} - -function healthScoreFixture() { - const denseRows = Array.from( - { length: LIMITS.healthDenseDays * LIMITS.healthSamplesPerDay }, - (_, index) => ({ - day: Math.floor(index / LIMITS.healthSamplesPerDay), - source: index % 3, - value: seededNumber(index, 59) % 1_000, - }), - ); - const sparseRows = Array.from( - { length: LIMITS.healthSparseDays * LIMITS.healthSparseSamplesPerDay }, - (_, index) => ({ - day: Math.floor(index / LIMITS.healthSparseSamplesPerDay), - domain: index % 8, - value: seededNumber(index, 71) % 1_000, - }), - ); - - const dailyCanonical = new Map(); - for (const row of denseRows) { - const prior = dailyCanonical.get(row.day); - if (!prior || row.source < prior.source) dailyCanonical.set(row.day, row); - } - const domainTotals = new Array(8).fill(0); - const domainCounts = new Array(8).fill(0); - for (const row of sparseRows) { - domainTotals[row.domain] += row.value; - domainCounts[row.domain] += 1; - } - let checksum = 0; - for (const row of dailyCanonical.values()) - checksum = (checksum + row.value) >>> 0; - for (let domain = 0; domain < domainTotals.length; domain += 1) { - const normalized = Math.round(domainTotals[domain] / domainCounts[domain]); - checksum = (checksum * 31 + normalized) >>> 0; - } - return { - checksum, - counters: { - denseRows: denseRows.length, - sparseRows: sparseRows.length, - canonicalDays: dailyCanonical.size, - domains: domainTotals.length, - concurrency: 1, - parity: "stable-checksum", - }, - }; -} - -function commitSha() { - try { - return execFileSync("git", ["rev-parse", "--short", "HEAD"], { - encoding: "utf8", - stdio: ["ignore", "pipe", "ignore"], - }).trim(); - } catch { - return "unavailable"; - } -} - -function counterSummary(result) { - return Object.entries(result.counters) - .map(([key, value]) => `${key}=${value}`) - .join("; "); -} - -try { - const results = [ - benchmark("Google history pagination shape", googleHistoryFixture), - benchmark("Medication reminder cohort shape", reminderCohortFixture), - benchmark("Health Score read/canonicalization shape", healthScoreFixture), - ]; - const durationMs = performance.now() - startedAt; - const generatedAt = new Date().toISOString(); - const markdown = `# v1.34.1 Performance Baseline - -**Generated:** ${generatedAt} - -**Commit:** \`${commitSha()}\` - -**Exit status:** PASS - -**Mode:** deterministic local fixture only - -## Safety and interpretation - -- No network, database, provider, production, or staging endpoint was contacted. -- Fixtures contain generated counters only. The report emits no tokens, identifiers, payloads, health values, hostnames, usernames, or working-directory paths. -- Concurrency is fixed at one, warm sample count is ${LIMITS.warmSamples}, and the whole run has a ${LIMITS.deadlineMs / 1000}-second deadline. -- Timing is a local code-shape baseline, not a production latency or capacity claim. Database, provider, network, framework, cache, and rendering costs remain unmeasured. -- The Health Score fixture exercises dense/sparse row construction, source canonicalization, and a stable composite proxy. It does not replace production algorithm parity or database-backed performance tests. - -## Environment - -| Attribute | Value | -| --- | --- | -| Node | ${process.version} | -| Platform | ${platform()} ${release()} | -| Architecture | ${arch()} | -| Logical CPUs | ${cpus().length} | -| Process | single local Node process | -| Total harness duration | ${formatMs(durationMs)} ms | - -## Fixture scale - -| Path shape | Fixed scale | -| --- | --- | -| Google history | ${LIMITS.googlePages} serial pages × ${LIMITS.googleRowsPerPage} generated rows; terminal page has no remaining token | -| Reminder cohort | ${LIMITS.reminderMedications} medications × ${LIMITS.reminderSlotsPerMedication} slots plus ${LIMITS.reminderCleanupBacklog} cleanup records | -| Health Score | ${LIMITS.healthDenseDays} dense days × ${LIMITS.healthSamplesPerDay} samples plus ${LIMITS.healthSparseDays} sparse days × ${LIMITS.healthSparseSamplesPerDay} samples | - -## Measurements - -| Local path shape | Cold | Warm median | Warm p95 | Warm max | Cold heap delta | Max warm heap delta | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -${results - .map( - (result) => - `| ${result.name} | ${formatMs(result.coldMs)} ms | ${formatMs(result.medianMs)} ms | ${formatMs(result.p95Ms)} ms | ${formatMs(result.maxMs)} ms | ${formatBytes(result.coldHeapBytes)} | ${formatBytes(result.maxWarmHeapBytes)} |`, - ) - .join("\n")} - -### Determinism and bounded counters - -${results - .map( - (result) => - `- **${result.name}:** checksum \`${result.checksum.toString(16).padStart(8, "0")}\`; ${counterSummary(result)}.`, - ) - .join("\n")} - -## Route/path interpretation - -| Representative path | What this fixture measures | What it does not measure | -| --- | --- | --- | -| Google full-sync pagination | Serial JSON page materialization and accumulation at the observed 786-page shape | Provider latency, DB writes, token refresh, rollups, or a safe universal page ceiling | -| Medication reminder worker | Serial cohort/slot decisions, cleanup scan, and modeled query amplification | Prisma latency, locks, dispatch latency, or the live cohort size | -| Dashboard / analytics Health Score | Dense/sparse row processing, canonical selection, and stable composite proxy | SQL plans, cache behavior, exact production score parity, or user-visible route latency | - -## Hypotheses and unmeasured risks - -- **Hypothesis:** Serial provider latency and write statements dominate the Google full-sync wall time; this local CPU result neither confirms nor refutes that hypothesis. -- **Hypothesis:** Reminder query/dispatch amplification, not local loop cost, determines whether a high-water pass approaches its 15-minute cadence. -- **Hypothesis:** Cold database row materialization and canonicalization dominate dense-user Health Score work; production impact remains unconfirmed. -- **Unmeasured:** network and database tails, cache hit/miss ratios, cross-process invalidation, framework rendering, device/browser performance, and production cohort distributions. -- **Do not infer:** a provider defect, a safe page cap, a production regression, or permission to optimize from this report alone. - -## Reproduction - -\`node scripts/v1341-performance-baseline.mjs --fixture --output .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md\` -`; - - writeFileSync(outputPath, markdown, { encoding: "utf8", mode: 0o600 }); - console.log( - `v1341-performance-baseline: PASS (${results.length} fixtures, ${formatMs(durationMs)} ms)`, - ); -} catch (error) { - console.error( - `v1341-performance-baseline: FAIL (${error instanceof Error ? error.message : "unknown error"})`, - ); - process.exit(1); -} diff --git a/scripts/verify-v137-adversarial.ts b/scripts/verify-v137-adversarial.ts deleted file mode 100644 index fab06f39c..000000000 --- a/scripts/verify-v137-adversarial.ts +++ /dev/null @@ -1,305 +0,0 @@ -/** - * v1.37.0 integration verifier — the inventories, and whether any of them is empty. - * - * ## What this is for - * - * The release is proved by inventories: a migration allocation, a set of - * admitted mutating handlers, a notification delivery matrix, a set of security - * principals, five accessibility states and eight journeys. Every one of them is - * consumed by a test that iterates it, and every one of those tests passes - * trivially when the list it iterates is empty. That is the failure this script - * exists to catch, and it is not hypothetical — three separate checks in this - * repository have been found green because their matcher matched nothing. - * - * So the rule here is that a count is asserted before it is used, and asserted - * against a number written down rather than against "more than zero" alone. - * A list that lost half its entries is not caught by a non-empty check. - * - * ## What this is NOT - * - * It does not run the suites and it cannot tell whether the tests that consume - * these inventories assert anything worth asserting. It answers one question — - * "is there something in each of these, and is it the amount there is supposed - * to be" — and leaves the rest to the tests themselves. - * - * ## Usage - * - * pnpm dlx tsx scripts/verify-v137-adversarial.ts # everything - * pnpm dlx tsx scripts/verify-v137-adversarial.ts --check-inventories # counts only - * pnpm dlx tsx scripts/verify-v137-adversarial.ts --check-migrations # allocation only - * - * Exits non-zero on the first failing assertion, and prints every check it ran - * so a run that checked nothing is visible as a run that printed nothing. - */ -import { readdirSync, readFileSync } from "node:fs"; -import { join } from "node:path"; - -import { - ACCESSIBILITY_STATES, - SHARING_ACCESSIBILITY_STATES, - V137_E2E_JOURNEYS, -} from "../tests/fixtures/v137/e2e-journeys"; -import { NOTIFICATION_DELIVERY_MATRIX } from "../tests/fixtures/v137/notification-matrix"; -import { SECURITY_PRINCIPALS } from "../tests/fixtures/v137/security-principals"; -import { ADMITTED_MUTATING_HANDLERS } from "../tests/fixtures/v137/sharing-matrix"; - -const ROOT = process.cwd(); - -let failures = 0; -let checks = 0; - -function check(label: string, ok: boolean, detail: string): void { - checks += 1; - if (ok) { - console.log(` ok ${label} — ${detail}`); - return; - } - console.error(` FAIL ${label} — ${detail}`); - failures += 1; -} - -function expectCount(label: string, actual: number, expected: number): void { - check(label, actual === expected, `${actual} (expected ${expected})`); -} - -/* -------------------------------------------------------------------------- */ -/* Migration allocation */ -/* -------------------------------------------------------------------------- */ - -/** - * The release's allocation, corrected. The synthesis said 0296-0299 and called - * the range frozen; the release shipped seven. The list is here so a builder - * inventing an eighth number, or a merge quietly renumbering one of these, - * fails rather than being noticed at the release audit or not at all. - */ -const MIGRATION_ALLOCATION = [ - "0296_account_grant_scope_and_manage", - "0297_managed_profile", - "0298_notification_delivery_identity", - "0299_medication_intake_import_actor_attribution", - "0300_notification_event_created_at_index", - "0301_notification_egress_authorization", - "0302_session_record_epoch", -] as const; - -/** - * Triggers this release installs. Neither is modelled in the Prisma schema, so - * `migrate diff` has nothing to compare them against and cannot report one - * going missing — the drift check is structurally blind here. Their presence in - * the migration text is the cheapest honest signal; the behaviour is proved by - * the integration suites named beside each one. - */ -const MIGRATION_TRIGGERS: ReadonlyArray<{ - trigger: string; - migration: string; - provenBy: string; -}> = [ - { - trigger: "push_attempt_attribution_guard", - migration: "0298_notification_delivery_identity", - provenBy: "tests/integration/notification-attribution.test.ts", - }, - { - trigger: "sessions_record_epoch_bump", - migration: "0302_session_record_epoch", - provenBy: "tests/integration/record-session-epoch-trigger.test.ts", - }, -]; - -function checkMigrations(): void { - console.log("migration allocation"); - const dir = join(ROOT, "prisma/migrations"); - const present = readdirSync(dir, { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name); - - check( - "the migrations directory really loaded", - present.length > 100, - `${present.length} migrations on disk`, - ); - - const release = present.filter((name) => /^(029[6-9]|030[0-2])_/.test(name)); - expectCount( - "the release allocates exactly seven", - release.length, - MIGRATION_ALLOCATION.length, - ); - - for (const expected of MIGRATION_ALLOCATION) { - check( - `allocated ${expected.slice(0, 4)}`, - present.includes(expected), - expected, - ); - } - - // Every number used once. A collision is the thing renumbering exists to - // prevent, and two directories sharing a prefix apply in an order nobody - // chose. - const releaseNumbers = release.map((name) => name.slice(0, 4)); - const duplicated = releaseNumbers.filter( - (n, i) => releaseNumbers.indexOf(n) !== i, - ); - check( - "no number is allocated twice in the release range", - duplicated.length === 0, - duplicated.length === 0 ? "none" : duplicated.join(", "), - ); - - for (const { trigger, migration, provenBy } of MIGRATION_TRIGGERS) { - const sql = readFileSync(join(dir, migration, "migration.sql"), "utf8"); - check( - `${migration} installs ${trigger}`, - sql.includes(trigger), - `behaviour proved by ${provenBy}`, - ); - } -} - -/* -------------------------------------------------------------------------- */ -/* Inventories */ -/* -------------------------------------------------------------------------- */ - -function checkInventories(): void { - console.log("inventories"); - - // Handlers. Every admitted mutation is meant to run level, owner, actor, - // effect and encrypted-field cases; an empty list runs none of them and the - // matrix test reports the same green either way. - check( - "the admitted mutating handlers are non-empty", - ADMITTED_MUTATING_HANDLERS.length > 0, - `${ADMITTED_MUTATING_HANDLERS.length} handlers`, - ); - // Keyed on route AND action, because one route legitimately appears more - // than once: an `[id]` route carries an update and a delete, and they are - // different contracts with different admitted levels. Keying on the route - // alone reported eleven false collisions on the first run of this script. - const handlerKeys = new Set( - ADMITTED_MUTATING_HANDLERS.map((h) => `${h.action} ${h.route}`), - ); - expectCount( - "no route and action pair is admitted twice", - handlerKeys.size, - ADMITTED_MUTATING_HANDLERS.length, - ); - check( - "every handler names a module that exists", - ADMITTED_MUTATING_HANDLERS.every((h) => { - try { - return ( - readFileSync(join(ROOT, "src", h.handlerModule), "utf8").length > 0 - ); - } catch { - return false; - } - }), - "each handlerModule resolves under src/", - ); - - // The notification matrix is two records by two Guardians by four channels. - // Written as a product, so a dropped channel shows up here as sixteen minus - // four rather than as a suite that simply ran fewer cases. - expectCount( - "the notification delivery matrix is 2 records x 2 Guardians x 4 channels", - NOTIFICATION_DELIVERY_MATRIX.length, - 16, - ); - check( - "no delivery in the matrix is interactive", - NOTIFICATION_DELIVERY_MATRIX.every((row) => row.interactive === false), - "a Guardian's notification offers no action buttons", - ); - - // Security principals. Each names an actor, a record and a recipient; a - // principal missing one of the three would let a test assert against - // undefined and pass. - const principals = Object.entries(SECURITY_PRINCIPALS); - check( - "the security principals are non-empty", - principals.length > 0, - `${principals.length} principals`, - ); - for (const [name, principal] of principals) { - const { actorUserId, recordUserId, recipientUserId } = principal.identities; - check( - `${name} names all three identities`, - Boolean(actorUserId && recordUserId && recipientUserId), - `${actorUserId} / ${recordUserId} / ${recipientUserId}`, - ); - } - - // Accessibility states and journeys. Both are enumerations the browser suite - // is read back against, and both are exact rather than non-empty: a ninth - // journey nobody counted and a fifth state quietly absorbed into a fourth are - // the two failures the enumeration exists to make visible. - expectCount( - "exactly five accessibility states", - ACCESSIBILITY_STATES.length, - 5, - ); - expectCount( - "every state carries a case", - SHARING_ACCESSIBILITY_STATES.length, - 5, - ); - expectCount("exactly eight journeys", V137_E2E_JOURNEYS.length, 8); - expectCount( - "every journey id is distinct", - new Set(V137_E2E_JOURNEYS.map((j) => j.id)).size, - 8, - ); - - // Each journey id must appear in a test title somewhere under e2e/ or tests/ - // or src/__tests__/. `sharing-accessibility-states.test.tsx` pins WHICH file - // carries each one; this leg is the cheaper cross-check that none of the - // eight has become a claim with no test anywhere at all. - const searched = [ - ...readdirSync(join(ROOT, "e2e")).map((f) => join("e2e", f)), - ...readdirSync(join(ROOT, "tests/integration")).map((f) => - join("tests/integration", f), - ), - ...readdirSync(join(ROOT, "src/__tests__")).map((f) => - join("src/__tests__", f), - ), - ].filter((f) => f.endsWith(".ts") || f.endsWith(".tsx")); - check( - "the test tree really loaded", - searched.length > 50, - `${searched.length} files scanned`, - ); - const corpus = searched - .map((f) => readFileSync(join(ROOT, f), "utf8")) - .join("\n"); - for (const journey of V137_E2E_JOURNEYS) { - check( - `${journey.id} is marked in a test title`, - corpus.includes(`[${journey.id}]`), - journey.claim.slice(0, 60) + "…", - ); - } -} - -/* -------------------------------------------------------------------------- */ - -const argv = process.argv.slice(2); -const only = argv.filter((a) => a.startsWith("--")); -const runMigrations = only.length === 0 || only.includes("--check-migrations"); -const runInventories = - only.length === 0 || only.includes("--check-inventories"); - -if (runMigrations) checkMigrations(); -if (runInventories) checkInventories(); - -// A run that asserted nothing is a run that proves nothing, and it would exit 0. -if (checks === 0) { - console.error("no checks ran — refusing to report success"); - process.exit(1); -} - -console.log( - `\n${checks} check(s), ${failures} failure(s)` + - (failures === 0 ? " — inventories are non-empty and exact" : ""), -); -process.exit(failures === 0 ? 0 : 1);