mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
feat(coach): add acute red-flag escalation to the shared safety contract
The shared contracts cover chronic deferral (dose, diagnosis, drug-level to a clinician) but had no acute branch. Add a closed-list crisis clause — chest pain, syncope, sudden severe symptoms, hypertensive-crisis readings, suicidal ideation — that points to prompt or emergency care without diagnosing, and keep every other turn calm and non-alarmist. It rides the single shared-contract source so it lands on the Coach, the per-metric status cards, the comprehensive briefing, and the period narrative; the coverage test asserts it on all four surfaces.
This commit is contained in:
1 parent
8c09970d4a
commit
34ed1a0c93
6 files changed
+47
-3
No files matched your search
@@ -900,6 +900,7 @@ export function getCoachSystemPrompt(
|
||||
"grounding",
|
||||
"toneContract",
|
||||
"safetyGlp1",
|
||||
"safetyAcute",
|
||||
"metricIdentifierBan",
|
||||
"forbiddenFiller",
|
||||
])}`
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
type ContractLocale,
|
||||
@@ -43,6 +44,7 @@ const SURFACES: Record<
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
],
|
||||
@@ -53,6 +55,7 @@ const SURFACES: Record<
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
],
|
||||
@@ -63,6 +66,7 @@ const SURFACES: Record<
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
],
|
||||
@@ -71,7 +75,13 @@ const SURFACES: Record<
|
||||
// the motivating tone contract.
|
||||
"period-narrative": {
|
||||
prompt: (l) => SYSTEM_PROMPTS_FOR_TEST[l],
|
||||
contracts: [grounding, safetyGlp1, metricIdentifierBan, forbiddenFiller],
|
||||
contracts: [
|
||||
grounding,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,4 +110,15 @@ describe("shared-contract cross-surface coverage", () => {
|
||||
expect(SYSTEM_PROMPTS_FOR_TEST[locale]).toContain(safetyGlp1[locale]);
|
||||
}
|
||||
});
|
||||
|
||||
it("the acute red-flag escalation contract reaches all four surfaces", () => {
|
||||
for (const locale of LOCALES) {
|
||||
expect(getStrictInsightsSystemPrompt(locale)).toContain(
|
||||
safetyAcute[locale],
|
||||
);
|
||||
expect(getBaseSystemPrompt(locale)).toContain(safetyAcute[locale]);
|
||||
expect(getCoachSystemPrompt(locale)).toContain(safetyAcute[locale]);
|
||||
expect(SYSTEM_PROMPTS_FOR_TEST[locale]).toContain(safetyAcute[locale]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
} from "./shared-contracts";
|
||||
@@ -171,6 +172,10 @@ const ASSESSMENT_SECTIONS: readonly AssessmentSection[] = [
|
||||
de: metricIdentifierBan.de,
|
||||
},
|
||||
{ id: "safetyGlp1", en: safetyGlp1.en, de: safetyGlp1.de },
|
||||
// Acute red-flag escalation — the ACUTE branch alongside the chronic
|
||||
// deferral above; surfaces a closed crisis list to prompt/emergency care
|
||||
// without diagnosing.
|
||||
{ id: "safetyAcute", en: safetyAcute.en, de: safetyAcute.de },
|
||||
// v1.18.7 (HIGH-2) — the shared grounding + tone contracts, canonical
|
||||
// wording. The metric-specific build/tone sections above stay; these pin
|
||||
// the cross-surface wording so an edit lands here and everywhere at once.
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
} from "./shared-contracts";
|
||||
@@ -759,6 +760,7 @@ neutralem Label + Detail. Höchstgrenze: 20 Einträge.`,
|
||||
{ id: "sharedGrounding", en: grounding.en, de: grounding.de },
|
||||
{ id: "sharedTone", en: toneContract.en, de: toneContract.de },
|
||||
{ id: "sharedSafetyGlp1", en: safetyGlp1.en, de: safetyGlp1.de },
|
||||
{ id: "sharedSafetyAcute", en: safetyAcute.en, de: safetyAcute.de },
|
||||
{
|
||||
id: "sharedMetricIdentifierBan",
|
||||
en: metricIdentifierBan.en,
|
||||
|
||||
@@ -66,6 +66,20 @@ NEVER prescribe, recommend, or modify medication doses, even when the snapshot n
|
||||
Verschreibe, empfiehl oder ändere NIEMALS Medikamenten-Dosen, auch wenn der Snapshot einen GLP-1-Rezeptoragonisten benennt (Mounjaro, Ozempic, Wegovy, Zepbound, Trulicity, Saxenda, Rybelsus). Du darfst den benannten Wirkstoff und die aktuelle Titrationsstufe NENNEN ("Woche 3 auf 7,5 mg"), wenn der Snapshot sie trägt, aber schreibe nie "erhöhe auf X mg", "erwäge die nächste Stufe Y mg", "bleibe auf Z mg" oder eine Variante davon. Ein Plateau rahmt die nächste Entscheidung immer als Gespräch mit der behandelnden Ärztin. Bist du unsicher, ob etwas dosis-präskriptiv ist, behandle es so — und verweise an die Klinik.`,
|
||||
};
|
||||
|
||||
/**
|
||||
* Acute red-flag escalation. The chronic-deferral posture (defer dose /
|
||||
* diagnosis / drug-level to a clinician) covers slow-moving questions; this
|
||||
* closed list covers the ACUTE branch — a small set of crisis signals that
|
||||
* warrant prompt/emergency care now, surfaced WITHOUT diagnosing. Kept tight
|
||||
* on purpose so the surfaces stay non-alarmist on everything else.
|
||||
*/
|
||||
export const safetyAcute: SharedContract = {
|
||||
en: `ACUTE RED FLAGS (contract, not style)
|
||||
If the user describes an acute crisis sign — chest pain or chest pressure, fainting or near-fainting (syncope), a sudden severe symptom (e.g. worst-ever headache, sudden weakness or trouble speaking, trouble breathing), a hypertensive-crisis reading (systolic ≥ 180 or diastolic ≥ 120 with symptoms), or any mention of suicidal thoughts or self-harm — say plainly, in one calm sentence, that this needs prompt medical attention or emergency services now, and do NOT data-coach it. Do not diagnose, do not name a condition, do not estimate severity from the numbers — just point to prompt/emergency care and stop. This is a closed list; outside it, stay calm and non-alarmist as usual.`,
|
||||
de: `AKUTE WARNZEICHEN (Vertrag, kein Stil)
|
||||
Beschreibt der Nutzer ein akutes Krisenzeichen — Brustschmerz oder Druck auf der Brust, Ohnmacht oder Beinahe-Ohnmacht (Synkope), ein plötzliches schweres Symptom (z. B. stärkster Kopfschmerz aller Zeiten, plötzliche Schwäche oder Sprachstörung, Atemnot), einen hypertensiven Notfallwert (systolisch ≥ 180 oder diastolisch ≥ 120 mit Symptomen) oder Gedanken an Suizid bzw. Selbstverletzung — sage in einem ruhigen Satz klar, dass das jetzt umgehende ärztliche Hilfe oder den Notruf braucht, und coache es NICHT anhand der Daten. Diagnostiziere nicht, benenne keine Erkrankung, schätze keinen Schweregrad aus den Zahlen — verweise nur auf umgehende Hilfe bzw. den Notruf und höre auf. Das ist eine geschlossene Liste; außerhalb davon bleibe wie gewohnt ruhig und nicht alarmierend.`,
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal metric-identifier ban: enum / DB-style names stay out of
|
||||
* user-facing prose; reference each metric by its natural-language label.
|
||||
@@ -95,6 +109,7 @@ export const SHARED_CONTRACTS = {
|
||||
grounding,
|
||||
toneContract,
|
||||
safetyGlp1,
|
||||
safetyAcute,
|
||||
metricIdentifierBan,
|
||||
forbiddenFiller,
|
||||
} as const satisfies Record<string, SharedContract>;
|
||||
|
||||
@@ -108,7 +108,7 @@ Hard rules:
|
||||
- 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", ["grounding", "safetyGlp1", "safetyAcute", "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}.
|
||||
@@ -121,7 +121,7 @@ Feste Regeln:
|
||||
- 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", ["grounding", "safetyGlp1", "safetyAcute", "metricIdentifierBan", "forbiddenFiller"])}`;
|
||||
|
||||
/**
|
||||
* Test-only view of the composed system prompts (incl. the appended shared
|
||||
|
||||
Reference in new issue
Block a user