Skip to content

feat: prevention-first dashboard + calculated biomarkers engine#5

Open
Chocksy wants to merge 1 commit intozmeyer44:mainfrom
Chocksy:pr/prevention-dashboard
Open

feat: prevention-first dashboard + calculated biomarkers engine#5
Chocksy wants to merge 1 commit intozmeyer44:mainfrom
Chocksy:pr/prevention-dashboard

Conversation

@Chocksy
Copy link
Copy Markdown

@Chocksy Chocksy commented Apr 7, 2026

Summary

  • Prevention-first home page: Panel-based layout grouping biomarkers by clinical category (metabolic, cardiovascular, inflammation, thyroid, nutrients) with visual status indicators and progress tracking
  • Calculated biomarkers engine: HOMA-IR, Cholesterol/HDL ratio, Trig/HDL ratio, Non-HDL Cholesterol — computed from same-date input pairs
  • Prevention gap detection: Identifies never-tested biomarkers and recommends them based on clinical guidelines

Key Changes

  • apps/web/app/(dashboard)/(main)/home/page.tsx — Redesigned dashboard
  • apps/web/components/home/ — New panel components (BiomarkerPanelCard, EmptyMetricCard, PanelSectionHeader, WhatChanged)
  • apps/web/lib/prevention-panels.ts — 5 prevention panels with retest frequencies
  • packages/database/src/queries/calculated-metrics.ts — Formula engine with same-date pairing
  • services/ingestion-worker/src/steps/compute-derived.ts — Post-ingestion calculation step
  • apps/web/server/trpc/routers/testing.ts — Prevention gap detection + smart sorting

Screenshots

Screenshots of the new dashboard coming soon

Test plan

  • Verify home page shows panel-based layout with biomarker categories
  • Upload lab results with glucose + insulin → verify HOMA-IR calculated
  • Verify never-tested biomarkers appear in "Recommended tests" section
  • Verify retest recommendations sort correctly (flagged → gaps → routine)

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
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 7, 2026

@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-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 7, 2026

Greptile Summary

This 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 — @/hooks/use-dynamic-status, @/lib/panel-config, and the isTrendImproving function from @/lib/health-utils — causing a hard build failure that must be resolved before this can ship.

  • apps/web/app/(dashboard)/(main)/home/page.tsx and apps/web/components/home/feature-cards.tsx import non-existent modules; the app will not compile.
  • The client-side HOMA-IR fallback in home/page.tsx uses the most-recent glucose and insulin independently, skipping the same-date pairing that the server-side engine enforces, which can produce clinically incorrect values.

Confidence Score: 2/5

Not 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

Filename Overview
apps/web/app/(dashboard)/(main)/home/page.tsx Redesigned dashboard with panel layout; imports three non-existent modules (use-dynamic-status, panel-config, isTrendImproving) causing build failure, and a client-side HOMA-IR that skips same-date pairing
packages/database/src/queries/calculated-metrics.ts New formula engine for HOMA-IR, cholesterol ratios, and Non-HDL; correctly enforces same-date pairing; per-row insert loop could be batched
apps/web/server/trpc/routers/testing.ts Prevention gap detection, smart sorting, and never_tested items added to retest recommendations; logic is sound
services/ingestion-worker/src/steps/compute-derived.ts Thin orchestration layer that calls computeCalculatedMetrics post-ingestion; correctly scoped to ingested metric codes
apps/web/lib/prevention-panels.ts Clean definition of 5 prevention panels with frequencies; correctly used by testing router for gap detection
apps/web/components/home/biomarker-panel-card.tsx New card component for filled biomarker metrics; well-structured with status-aware styling
apps/web/components/home/feature-cards.tsx Changed to import useDynamicStatus from the non-existent @/hooks/use-dynamic-status; will not compile
packages/database/src/index.ts Exports computeCalculatedMetrics and CALCULATED_METRICS from the new calculated-metrics query module
apps/web/components/home/what-changed.tsx New component comparing two most-recent lab draw dates; straightforward and self-contained
apps/web/components/home/empty-metric-card.tsx Placeholder card for untested prevention panel metrics; minimal and correct
apps/web/components/home/panel-section-header.tsx Progress bar header showing in-range/warning/critical/untested counts per panel
apps/web/components/home/upcoming-retests.tsx Updated to support never_tested prevention gaps with separate visual grouping
apps/web/components/testing/retest-item.tsx Added never_tested urgency style and preventionPanel/preventionWhy fields

Sequence Diagram

sequenceDiagram
    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)
Loading

Reviews (1): Last reviewed commit: "feat: prevention-first dashboard + calcu..." | Re-trigger Greptile

Comment on lines +7 to +14
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";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 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.

Comment on lines +195 to +213
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]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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;
    }
  }
}

Comment on lines +247 to +265
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,
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant