feat: prevention-first dashboard + calculated biomarkers engine#5
feat: prevention-first dashboard + calculated biomarkers engine#5Chocksy wants to merge 1 commit intozmeyer44:mainfrom
Conversation
Redesigns the home page around preventive health with panel-based biomarker tracking and adds a calculated metrics engine. Prevention-first dashboard: - Panel-based layout grouping biomarkers by clinical category (metabolic, cardiovascular, inflammation, thyroid, nutrients) - BiomarkerPanelCard with visual status indicators - EmptyMetricCard for never-tested biomarkers - PanelSectionHeader with progress tracking - WhatChanged component highlighting recent results - Prevention gap detection in retest recommendations - Smart sorting: flagged → prevention gaps → routine Calculated biomarkers engine: - Formula definitions for HOMA-IR, Cholesterol/HDL ratio, Triglyceride/HDL ratio, Non-HDL Cholesterol - Same-date pairing: only computes when inputs tested on same date - Post-ingestion hook (compute-derived step in workflow) - Recalculation support via tRPC mutation Retest improvements: - never_tested urgency type for untested prevention metrics - Prevention panel context (which panel, why it matters) - Split view: retests vs recommended new tests
|
@Chocksy is attempting to deploy a commit to the Zach's Projects Team on Vercel. A member of the Team first needs to authorize it. |
Greptile SummaryThis PR introduces a prevention-first dashboard with panel-based biomarker grouping and a server-side calculated-metrics engine (HOMA-IR, cholesterol ratios). However, the homepage and feature-cards component import three modules that do not exist in the repository —
Confidence Score: 2/5Not safe to merge — missing modules cause a build failure on the primary dashboard path Two P0 findings (three missing modules break the build entirely for home/page.tsx and feature-cards.tsx) and one P1 data-correctness issue on client-side HOMA-IR lower the score to 2; the core calculated-metrics engine and testing router are well-implemented. apps/web/app/(dashboard)/(main)/home/page.tsx and apps/web/components/home/feature-cards.tsx need the three missing modules added before this PR can compile Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant IngestionWorker
participant computeCalculatedMetrics
participant DB
participant tRPCRouter
participant Dashboard
User->>IngestionWorker: Upload lab results
IngestionWorker->>DB: Insert raw observations
IngestionWorker->>computeCalculatedMetrics: computeDerived(ingestedMetricCodes)
computeCalculatedMetrics->>DB: Query input obs (same-date grouped)
computeCalculatedMetrics->>DB: Check existing calculated obs (dedupe)
computeCalculatedMetrics->>DB: Insert HOMA-IR / ratios as observations
computeCalculatedMetrics-->>IngestionWorker: { computed, skipped }
User->>Dashboard: Load home page
Dashboard->>tRPCRouter: observations.list (limit 500)
DB-->>Dashboard: Raw + calculated observations
Dashboard->>tRPCRouter: retest.getRecommendations
tRPCRouter->>DB: selectDistinctOn latest obs per metric
tRPCRouter->>DB: Query metricDefs + optimalRanges + userOverrides
tRPCRouter-->>Dashboard: Recommendations + prevention gaps
Dashboard->>Dashboard: panelData memo (PANELS from missing panel-config)
Dashboard->>Dashboard: Client-side HOMA-IR fallback (no same-date check)
Reviews (1): Last reviewed commit: "feat: prevention-first dashboard + calcu..." | Re-trigger Greptile |
| import { useDynamicStatus } from "@/hooks/use-dynamic-status"; | ||
| import { | ||
| formatRange, | ||
| isTrendImproving, | ||
| deriveStatus, | ||
| } from "@/lib/health-utils"; | ||
| import { PANELS } from "@/lib/panel-config"; | ||
| import { GreetingHeader } from "@/components/home/greeting-header"; |
There was a problem hiding this comment.
Missing modules break the build
home/page.tsx imports useDynamicStatus from "@/hooks/use-dynamic-status", PANELS from "@/lib/panel-config", and isTrendImproving from "@/lib/health-utils" — none of these exist in the repository. apps/web/hooks/use-dynamic-status.ts and apps/web/lib/panel-config.ts are not present anywhere in the codebase, and isTrendImproving is not exported from health-utils.ts (only formatRange is). apps/web/components/home/feature-cards.tsx also now imports useDynamicStatus, making that file broken too. The app will fail to compile until these three missing modules are added.
| const calculatedMetrics = useMemo(() => { | ||
| const map = new Map<string, { value: number; unit: string }>(); | ||
| const glucoseObs = byMetric.get("glucose"); | ||
| const insulinObs = byMetric.get("insulin"); | ||
|
|
||
| if (glucoseObs && insulinObs) { | ||
| const latestGlucose = glucoseObs.find((o) => o.valueNumeric != null); | ||
| const latestInsulin = insulinObs.find((o) => o.valueNumeric != null); | ||
| if (latestGlucose?.valueNumeric && latestInsulin?.valueNumeric) { | ||
| const homaIr = | ||
| (latestGlucose.valueNumeric * latestInsulin.valueNumeric) / 405; | ||
| map.set("homa_ir", { | ||
| value: Math.round(homaIr * 100) / 100, | ||
| unit: "", | ||
| }); | ||
| } | ||
| } | ||
| return map; | ||
| }, [byMetric]); |
There was a problem hiding this comment.
Client-side HOMA-IR ignores same-date requirement
The calculatedMetrics memo picks the most-recent glucose and the most-recent insulin independently, so they can be from completely different lab draws. The server-side computeCalculatedMetrics explicitly requires both inputs to share the same observedAt date (enforced by grouping observations by date key). A user who had glucose drawn in January and insulin drawn in March would see a client-computed HOMA-IR on the dashboard that the server would never store — misleading and potentially clinically significant. The fix is to align dates before computing:
if (glucoseObs && insulinObs) {
const glucoseDates = new Map(
glucoseObs
.filter((o) => o.valueNumeric != null)
.map((o) => [new Date(o.observedAt).toISOString().slice(0, 10), o]),
);
for (const insulinEntry of insulinObs) {
if (!insulinEntry.valueNumeric) continue;
const dateKey = new Date(insulinEntry.observedAt).toISOString().slice(0, 10);
const glucoseEntry = glucoseDates.get(dateKey);
if (glucoseEntry?.valueNumeric) {
const homaIr = (glucoseEntry.valueNumeric * insulinEntry.valueNumeric) / 405;
map.set("homa_ir", { value: Math.round(homaIr * 100) / 100, unit: "" });
break;
}
}
}| await db.insert(observations).values({ | ||
| userId: params.userId, | ||
| metricCode: formula.code, | ||
| category: formula.category, | ||
| valueNumeric: roundedValue, | ||
| valueText: String(roundedValue), | ||
| unit: formula.unit, | ||
| status: "confirmed", | ||
| confidenceScore: 1.0, | ||
| observedAt: obsDate, | ||
| importJobId: params.importJobId ?? null, | ||
| sourceArtifactId: params.sourceArtifactId ?? null, | ||
| metadataJson: { | ||
| source: "calculated", | ||
| formula: formula.code, | ||
| formulaText: formula.formulaText, | ||
| inputs: inputValues, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Sequential inserts inside a loop — prefer batch insert
Each computed metric fires an individual db.insert(...).values(...) inside the nested loop. For a user with many lab dates this can generate dozens of round-trips. Accumulating all rows and doing a single db.insert(observations).values(rows) at the end would be significantly more efficient and reduce lock contention.
Summary
Key Changes
apps/web/app/(dashboard)/(main)/home/page.tsx— Redesigned dashboardapps/web/components/home/— New panel components (BiomarkerPanelCard, EmptyMetricCard, PanelSectionHeader, WhatChanged)apps/web/lib/prevention-panels.ts— 5 prevention panels with retest frequenciespackages/database/src/queries/calculated-metrics.ts— Formula engine with same-date pairingservices/ingestion-worker/src/steps/compute-derived.ts— Post-ingestion calculation stepapps/web/server/trpc/routers/testing.ts— Prevention gap detection + smart sortingScreenshots
Screenshots of the new dashboard coming soon
Test plan