mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
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:
1 parent
3f8c8e750c
commit
b92d5cec82
8 files changed
+561
-16
No files matched your search
@@ -1406,6 +1406,36 @@ const derivedMetricResponse = z
|
||||
"Flat `Derived<T>` envelope for one derived wellness metric. Pure compute over the rollup tier (no LLM, no narrative). iOS decodes one stable shape and combines values across metrics; coverage/confidence/provenance let it render the same honesty chips.",
|
||||
});
|
||||
|
||||
// v1.10.0 — batched derived-metric query. The `metrics` CSV carries one
|
||||
// or more `metric` / `metric:type` tokens; the route fans out server-side
|
||||
// under a bounded limiter with the profile loaded once, collapsing the
|
||||
// dashboard's cold-mount fan-out of N single-metric requests into one.
|
||||
const derivedBatchQuery = z
|
||||
.object({
|
||||
metrics: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(1024)
|
||||
.describe(
|
||||
"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.",
|
||||
),
|
||||
})
|
||||
.meta({ id: "DerivedBatchQuery" });
|
||||
|
||||
const derivedBatchResponse = z
|
||||
.object({
|
||||
metrics: z
|
||||
.record(z.string(), derivedMetricResponse)
|
||||
.describe(
|
||||
"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.",
|
||||
),
|
||||
})
|
||||
.meta({
|
||||
id: "DerivedBatchResponse",
|
||||
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.",
|
||||
});
|
||||
|
||||
// v1.10.0 — FDR-controlled correlation discovery result. One discovered,
|
||||
// statistically-defensible behaviour → next-day-outcome pair.
|
||||
const discoveredCorrelation = z
|
||||
@@ -3190,6 +3220,31 @@ export const openApiPaths: NonNullable<ZodOpenApiObject["paths"]> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"/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.",
|
||||
requestParams: {
|
||||
query: derivedBatchQuery,
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "The map of derived-metric values, keyed by token.",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: dataEnvelope(
|
||||
derivedBatchResponse,
|
||||
"DerivedBatchResponseEnvelope",
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...stdResponses,
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/insights/correlations": {
|
||||
get: {
|
||||
tags: ["Insights"],
|
||||
|
||||
Reference in new issue
Block a user