From d22372e800f42db708665f2cedf42062b3131723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 26 Jun 2026 12:29:43 +0200 Subject: [PATCH] fix(insights): warm the period retrospective to the shared house voice The retrospective narrative was the one model surface that omitted the shared tone contract, so it read colder than the daily briefing beside it. Compose the tone fragment into both prompts and warm the hand-written tone line to name a genuine win when the period earns it, while keeping the descriptive-never-causal and no-alarm guards intact. The cross-surface coverage test now asserts the contract reaches this surface too. --- .../__tests__/shared-contracts-coverage.test.ts | 13 ++++++++++--- .../__tests__/period-narrative-generate.test.ts | 4 ++++ .../insights/narrative/period-narrative-generate.ts | 8 ++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts b/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts index e2c790a07..d9fc0f09a 100644 --- a/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts +++ b/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts @@ -67,11 +67,18 @@ const SURFACES: Record< forbiddenFiller, ], }, - // The narrative is descriptive-only, so it enforces every contract EXCEPT - // the motivating tone contract. + // v1.21.0 (coach C1 MEDIUM-1) — the retrospective narrative now composes the + // shared tone contract too, so it matches the warm house voice of the daily + // briefing beside it. Its own descriptive-never-causal guards stay intact. "period-narrative": { prompt: (l) => SYSTEM_PROMPTS_FOR_TEST[l], - contracts: [grounding, safetyGlp1, metricIdentifierBan, forbiddenFiller], + contracts: [ + grounding, + toneContract, + safetyGlp1, + metricIdentifierBan, + forbiddenFiller, + ], }, }; diff --git a/src/lib/insights/narrative/__tests__/period-narrative-generate.test.ts b/src/lib/insights/narrative/__tests__/period-narrative-generate.test.ts index 1ca917298..fa99fc7b3 100644 --- a/src/lib/insights/narrative/__tests__/period-narrative-generate.test.ts +++ b/src/lib/insights/narrative/__tests__/period-narrative-generate.test.ts @@ -25,6 +25,7 @@ import type { PeriodNarrativeContext, PeriodNarrativeResult, } from "@/lib/insights/narrative/period-narrative"; +import { toneContract } from "@/lib/ai/prompts/shared-contracts"; function readyContext( over: Partial = {}, @@ -170,6 +171,9 @@ describe("generatePeriodNarrative — descriptive generation", () => { }); expect(systemPrompt).toMatch(/never CAUSAL|DESCRIPTIVE/); expect(systemPrompt).toMatch(/no markdown/i); + // v1.21.0 (coach C1 MEDIUM-1) — the retrospective now carries the shared + // warm tone contract verbatim, matching the briefing's house voice. + expect(systemPrompt).toContain(toneContract.en); }); }); diff --git a/src/lib/insights/narrative/period-narrative-generate.ts b/src/lib/insights/narrative/period-narrative-generate.ts index b597a5511..114855493 100644 --- a/src/lib/insights/narrative/period-narrative-generate.ts +++ b/src/lib/insights/narrative/period-narrative-generate.ts @@ -104,11 +104,11 @@ Hard rules: - The supplied CONTEXT is the ONLY source of truth. Never state a number, trend, driver, or threshold that is not in it. - Be DESCRIPTIVE, never CAUSAL. Say "X moved with Y" or "X was associated with Y", never "X caused Y" or "because of X". - The listed drivers already survived statistical multiple-comparison control; restate them only as associations and keep their conservative meaning. -- No diagnosis, no medical advice, no alarm. Calm, factual, second person ("your"). +- No diagnosis, no medical advice, no alarm. Calm, factual, second person ("your"). When the period earned it, name one genuine win plainly; keep it warm and second-person, never a reflexive compliment. - 2 to 4 short sentences. Plain text only — no markdown, no headings, no bullet points, no emojis. - If the context is thin, say plainly that there is little to report this period rather than inventing detail. -${composeSharedContracts("en", ["grounding", "safetyGlp1", "metricIdentifierBan", "forbiddenFiller"])}`; +${composeSharedContracts("en", ["toneContract", "grounding", "safetyGlp1", "metricIdentifierBan", "forbiddenFiller"])}`; const SYSTEM_PROMPT_DE = `Du fasst den Gesundheits-Tracking-ZEITRAUM einer Person (eine Woche oder einen Monat) für diese Person zusammen. Prompt-Version: ${NARRATIVE_PROMPT_VERSION}. @@ -117,11 +117,11 @@ Feste Regeln: - Der bereitgestellte KONTEXT ist die EINZIGE Wahrheitsquelle. Nenne nie eine Zahl, einen Trend, einen Zusammenhang oder einen Schwellenwert, der nicht darin steht. - Sei BESCHREIBEND, nie URSÄCHLICH. Sage "X bewegte sich mit Y" oder "X war mit Y assoziiert", nie "X verursachte Y" oder "wegen X". - Die genannten Zusammenhänge haben bereits die statistische Mehrfachvergleichskorrektur überstanden; gib sie nur als Assoziationen wieder und bewahre ihre vorsichtige Bedeutung. -- Keine Diagnose, kein medizinischer Rat, keine Panik. Ruhig, sachlich, in der zweiten Person ("dein"). +- Keine Diagnose, kein medizinischer Rat, keine Panik. Ruhig, sachlich, in der zweiten Person ("dein"). Wenn der Zeitraum es hergibt, benenne einen echten Erfolg klar; bleib warm und in der zweiten Person, nie ein reflexhaftes Kompliment. - 2 bis 4 kurze Sätze. Nur Klartext — kein Markdown, keine Überschriften, keine Aufzählungen, keine Emojis. - Wenn der Kontext dünn ist, sage klar, dass es in diesem Zeitraum wenig zu berichten gibt, statt Details zu erfinden. -${composeSharedContracts("de", ["grounding", "safetyGlp1", "metricIdentifierBan", "forbiddenFiller"])}`; +${composeSharedContracts("de", ["toneContract", "grounding", "safetyGlp1", "metricIdentifierBan", "forbiddenFiller"])}`; /** * Test-only view of the composed system prompts (incl. the appended shared