From 1b50dc0909c9a234e3b94d548c7dd8da4cba8c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 19 Jul 2026 13:16:41 +0200 Subject: [PATCH] test(insights): add the per-metric tone harness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A deterministic grader over the assembled assessment prompts and the deterministic fallback text. No provider, no network, no database — it runs in the normal suite in under half a second, which is the point: the live-judge eval needs a key and can only advise, so the regressions that actually shipped need a check that can block. Three classes are blocking. A surface stops leading with meaning (it drops the shared opening contract, or its user prompt loses the meaning-first instruction). A locale collapses (fr/es/it/pl fall back to the German body or lose the reply-language directive). A prompt reintroduces a value-first instruction — matched with negation handling, since the shared prompts quote that failure mode in order to ban it. The deterministic fallbacks are graded on the same axis, including the asymmetry that matters: a signal with no baseline supports no confident read, so that one line honestly opens on the value. Pinning it stops a future "always lead with meaning" edit from pushing the surface into claiming something the data does not support. A coverage guard fails when an assessment prompt module has no harness entry, so the next surface cannot stay uncovered the way the biomarker card did. --- src/lib/insights/eval/tone-rules.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib/insights/eval/tone-rules.ts b/src/lib/insights/eval/tone-rules.ts index 1c1d34653..35bfb724f 100644 --- a/src/lib/insights/eval/tone-rules.ts +++ b/src/lib/insights/eval/tone-rules.ts @@ -253,6 +253,20 @@ const CLINICAL_VERDICT = const VALUE_LED_OPENER = /^(Your|Dein|Deine|Ihr|Ihre)\s+\S+(\s+\S+)?\s+(is|ist|liegt)\s+\d/i; +/** + * The OTHER way a deterministic line stops leading with meaning: it opens on a + * bare order. + * + * This is what the no-signal floors were — "Measure blood pressure at rest and + * under comparable conditions", "Bewerte Gewicht vor allem im Verlauf". Not a + * value-led opener, so the number rules never saw it, but it is the same + * failure: the surface tells the reader what to do before it says anything + * about what it can and cannot see. A pointer is fine LATER in the line; it + * may not be the opening move. + */ +const IMPERATIVE_OPENER = + /^(Measure|Track|Evaluate|Monitor|Check|Watch|Log|React|Use|Keep|Pay|Consider|Make sure)\b|^(Miss|Bewerte|Beobachte|Achte|Nutze|Halte|Reagiere|Prüfe|Vergleiche|Erfasse)\b/; + function firstSentence(text: string): string { const m = /^[^.!?]*[.!?]/.exec(text.trim()); return (m ? m[0] : text.trim()).trim(); @@ -289,6 +303,14 @@ export function checkFallbackTone(input: { excerpt: opener, }); } + if (IMPERATIVE_OPENER.test(opener)) { + violations.push({ + rule: "fallback-no-imperative-opener", + detail: + "the line opens on an order rather than on what the surface can honestly say", + excerpt: opener, + }); + } if (VALUE_LED_OPENER.test(opener)) { violations.push({ rule: "fallback-no-value-led-opener",