mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
feat(insights): show the wellness assessment and add score explainers
The score-anatomy detail page now renders the per-score AI assessment that already rides GET /api/insights/derived (shipped to iOS in v1.13.2) — the web type dropped the field, so the card was silently discarded. The assessment mounts below the composition card via the same InsightStatusCard the metric sub-pages use, so it reads identically for readiness, sleep, recovery, strain and stress. Each score detail page also gains an educational intro caption beneath the heading, mirroring the metric sub-pages: new explainer bodies for the five scores across all six locales (sleepScore kept distinct from the sleep metric's own body), wired through SubPageShell's explainerMetric prop. The explainer-key guard resolves the score page's closed lookup record statically so the single dynamic prop stays verifiable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b07d77cbfa
commit
e9171c1e5f
10 files changed
+156
-27
No files matched your search
@@ -1,8 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Sparkles } from "lucide-react";
|
||||
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { useDerivedMetric } from "./use-derived-metric";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { InsightStatusCard } from "@/components/insights/insight-status-card";
|
||||
import type {
|
||||
ReadinessValue,
|
||||
SleepScoreValue,
|
||||
@@ -193,21 +196,43 @@ export function CompositeScoreAnatomy({
|
||||
}
|
||||
}
|
||||
|
||||
// v1.15.12 B3 — the per-score AI assessment ("why is this score what it is")
|
||||
// already rides the single `/api/insights/derived` route as `data.assessment`
|
||||
// (shipped to iOS in v1.13.2); the web simply discarded it. Render it BELOW
|
||||
// the anatomy card, reusing the same `<InsightStatusCard>` the metric
|
||||
// sub-pages mount so it reads identically. The route always populates a
|
||||
// non-empty assessment when the score `status === "ok"` (the deterministic
|
||||
// template at minimum, warmer AI prose when a provider is configured), so a
|
||||
// present `assessment` implies a real text — `hasProvider` is held true and
|
||||
// the card always renders its prose rather than the no-provider fallback.
|
||||
const assessment = data.assessment;
|
||||
|
||||
return (
|
||||
<ScoreAnatomyView
|
||||
title={title}
|
||||
score={score}
|
||||
hue={METRIC_HUE[metric]}
|
||||
caption={caption}
|
||||
contributors={contributors}
|
||||
coverage={data.coverage}
|
||||
confidence={data.confidence}
|
||||
provenance={data.provenance}
|
||||
method={method}
|
||||
standard={standard}
|
||||
insufficient={insufficient}
|
||||
className={className}
|
||||
/>
|
||||
<div className="space-y-3">
|
||||
<ScoreAnatomyView
|
||||
title={title}
|
||||
score={score}
|
||||
hue={METRIC_HUE[metric]}
|
||||
caption={caption}
|
||||
contributors={contributors}
|
||||
coverage={data.coverage}
|
||||
confidence={data.confidence}
|
||||
provenance={data.provenance}
|
||||
method={method}
|
||||
standard={standard}
|
||||
insufficient={insufficient}
|
||||
className={className}
|
||||
/>
|
||||
{assessment ? (
|
||||
<InsightStatusCard
|
||||
title={t("insights.assessmentTitle")}
|
||||
icon={<Sparkles className="h-5 w-5" />}
|
||||
text={assessment.text}
|
||||
hasProvider
|
||||
updatedAt={assessment.updatedAt}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
DerivedConfidence,
|
||||
DerivedProvenance,
|
||||
} from "@/lib/insights/derived/types";
|
||||
import type { DerivedAssessment } from "@/lib/insights/derived/derived-assessment";
|
||||
|
||||
/**
|
||||
* v1.10.0 — the single TanStack Query hook for the generic derived-metric
|
||||
@@ -44,6 +45,17 @@ export interface DerivedMetricResponse<T> {
|
||||
confidence: DerivedConfidence | null;
|
||||
provenance: DerivedProvenance;
|
||||
reason: string | null;
|
||||
/**
|
||||
* v1.13.2 — the per-score "why is this score what it is" assessment
|
||||
* ({ text, source, updatedAt }). Populated only for the assessable scores
|
||||
* (READINESS, SLEEP_SCORE, RECOVERY_SCORE, STRAIN_SCORE, STRESS_SCORE) when
|
||||
* `status === "ok"`, null otherwise. The single-metric route serves the
|
||||
* AI-warm prose (falling back to the deterministic template); the batch route
|
||||
* serves the cheap deterministic text only. Optional on the wire shape so
|
||||
* grid reads that drop it stay typed. v1.15.12 B3 surfaces it on the web
|
||||
* score-anatomy detail page (it already shipped to iOS in v1.13.2).
|
||||
*/
|
||||
assessment?: DerivedAssessment | null;
|
||||
}
|
||||
|
||||
export interface UseDerivedMetricOptions {
|
||||
|
||||
Reference in new issue
Block a user