feat(insights): batch the derived-metric reads behind one route

The Insights cold mount fired 14+ independent GET /api/insights/derived
requests — Readiness, Sleep and the vitals baselines are compute-on-visit,
and all of them share one Prisma pool. That is the pool-starvation class
that surfaces as the app hanging then recovering.

Add GET /api/insights/derived/batch?metrics=a,b,c[:type] that resolves the
requested metrics server-side under a bounded p-limit(4) with the profile
loaded once via the shared loadBaselineProfile, returning a map of the same
flat Derived<T> envelope keyed by the request token. Add the useDerivedBatch
hook + the insightsDerivedBatch query-key factory entry; the single-metric
route + hook stay for the per-score detail pages. The single route now also
adopts loadBaselineProfile instead of re-inlining the profile read. Honors
the existing 8s ceiling, retry:0, replace-with-fallback, and warms nothing
on visit. Register the new route in the OpenAPI contract.
This commit is contained in:
Marc-André Bombeck committed 2026-06-03 00:04:56 +02:00
1 parent 3f8c8e750c
commit b92d5cec82
8 files changed
+561 -16

No files matched your search

+69
View File
@@ -2484,6 +2484,40 @@ paths:
"401": *a1
"422": *a2
"429": *a3
/api/insights/derived/batch:
get:
tags:
- Insights
summary: Derived wellness metrics (batched compute-once)
description: v1.10.0 — resolve several derived wellness metrics in ONE request. The `metrics` CSV names the metrics (a
`metric:type` token sub-targets a VITALS_BASELINE vital); the server fans out under a bounded limiter with the
profile loaded once and returns a map keyed by the per-request token. Collapses the Insights cold-mount fan-out
of 14+ independent single-metric requests — the pool-starvation class that surfaces as a hang-then-recover. The
single-metric route stays for the per-score detail pages. Auth via cookie or Bearer.
parameters:
- in: query
name: metrics
schema:
type: string
minLength: 1
maxLength: 1024
description: Comma-separated derived-metric tokens. Each is a `<DERIVED_METRIC_ID>` or
`<DERIVED_METRIC_ID>:<MeasurementType>` (the colon sub-targets a VITALS_BASELINE vital). An unknown id
422s; at most 24 tokens; duplicates collapse.
required: true
description: Comma-separated derived-metric tokens. Each is a `<DERIVED_METRIC_ID>` or
`<DERIVED_METRIC_ID>:<MeasurementType>` (the colon sub-targets a VITALS_BASELINE vital). An unknown id 422s;
at most 24 tokens; duplicates collapse.
responses:
"200":
description: The map of derived-metric values, keyed by token.
content:
application/json:
schema:
$ref: "#/components/schemas/DerivedBatchResponseEnvelope"
"401": *a1
"422": *a2
"429": *a3
/api/insights/correlations:
get:
tags:
@@ -7720,6 +7754,41 @@ components:
- windowDays
- computedAt
additionalProperties: false
DerivedBatchResponseEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/DerivedBatchResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
DerivedBatchResponse:
type: object
properties:
metrics:
type: object
propertyNames:
type: string
additionalProperties:
$ref: "#/components/schemas/DerivedMetricResponse"
description: Map keyed by the per-request token (`<metric>` or `<metric>:<type>`). Each value is the same flat
`Derived<T>` envelope the single-metric route returns, so a client decodes one shape and reads back exactly
the tokens it asked for.
required:
- metrics
additionalProperties: false
description: Batched derived-metric values. One request resolves the whole dashboard grid (the wellness scores + the
derived re-frames + one baseline per vital) instead of N concurrent single-metric requests sharing the Prisma
pool. Pure compute over the rollup tier — no LLM, no narrative, no cache table.
CorrelationDiscoveryResponseEnvelope:
type: object
properties: