diff --git a/src/components/insights/__tests__/recommendation-card-confidence-integration.test.tsx b/src/components/insights/__tests__/recommendation-card-confidence-integration.test.tsx new file mode 100644 index 000000000..1c1da0e76 --- /dev/null +++ b/src/components/insights/__tests__/recommendation-card-confidence-integration.test.tsx @@ -0,0 +1,178 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +vi.mock("@/components/charts/health-chart", () => ({ + HealthChart: () =>
, +})); + +vi.mock("@/components/charts/mood-chart", () => ({ + MoodChart: () => , +})); + +vi.mock("@tanstack/react-query", () => ({ + useQuery: () => ({ data: null, isLoading: false }), + useQueryClient: () => ({ invalidateQueries: vi.fn() }), +})); + +import { I18nProvider } from "@/lib/i18n/context"; +import { InsightAdvisorCard } from "../insight-advisor-card"; +import type { InsightResult } from "@/lib/ai/types"; +import { generateInsight } from "@/lib/ai/generate-insight"; +import { MockAIProvider } from "@/lib/ai/mock-client"; + +/** + * v1.4.16 phase B5d — full-flow integration. + * + * MockAIProvider returns a payload where the model claims a high + * `confidence: 99`. The wrapper MUST overwrite that with the + * deterministic `computeConfidence()` value, which (with the default + * resolver: n=9, recencyDays=0, ratio=null) yields: + * + * nScore = 10 + 10*log10(9) ≈ 19.54 + * recency = 30 (recencyDays=0) + * signal = 15 (null) + * total ≈ 64.54 → 65 + * + * After generation, we mount InsightAdvisorCard with the parsed payload + * and assert the rendered ConfidenceMeter band corresponds to the + * server-computed value (medium/yellow), NOT the model's claimed 99 + * (which would have rendered the green high band). + */ + +const strictPayloadWithModelConfidence = { + summary: "BP runs slightly above baseline.", + recommendations: [ + { + id: "rec-1", + text: "Discuss home BP log with your physician.", + severity: "important", + // Model claims very high confidence — wrapper overrides. + confidence: 99, + metricSource: { + type: "bloodPressure", + timeRange: "last7days", + summary: "avg 138/86 across 9 readings", + n: 9, + }, + rationale: { + dataWindow: "last7days", + comparedTo: "your 90-day median (122/78)", + deviation: "+16/+8 mmHg above baseline over 9 of 9 readings", + }, + }, + ], + citations: [ + { + type: "bloodPressure", + timeRange: "last7days", + summary: "avg 138/86 across 9 readings", + }, + ], + warnings: [], +}; + +describe("end-to-end — generateInsight() confidence override flows into the card", () => { + it("renders the deterministic confidence (NOT the model-supplied 99) on the card", async () => { + const provider = new MockAIProvider({ + responses: JSON.stringify(strictPayloadWithModelConfidence), + }); + + const outcome = await generateInsight(provider, { + systemPrompt: "system", + userPrompt: "user", + }); + + const rec = outcome.parsed.recommendations[0]; + // Server-computed value at n=9, recencyDays=0, ratio=null = 65 + expect(rec.confidence).toBe(65); + expect(rec.confidence).not.toBe(99); + + const insight: InsightResult = { + insightType: "general", + summary: outcome.parsed.summary, + classification: "grenzwertig", + classificationLabel: "Borderline", + findings: [], + correlations: [], + recommendations: outcome.parsed.recommendations.map((r) => ({ + text: r.text, + referenceId: r.referenceId, + rationale: r.rationale, + metricSource: r.metricSource, + severity: r.severity, + id: r.id, + confidence: r.confidence, + })), + dataQuality: { coverage: "good", gaps: [], confidence: "hoch" }, + disclaimer: "Not a substitute for medical advice.", + }; + + const html = renderToStaticMarkup( +