fix(clinical): red flags, one crisis rule, callback after a crisis, one table of numbers
Clinical review of the questionnaires (ADR-013):
- Red-flag block first in stage 1 (config/instruments/red_flags.yaml); any «Да» stops the
questionnaire with an acute alert and the help screen of its category.
- One crisis rule: the crisis screen opens at a HIGH C-SSRS risk only; moderate stops and goes
to a psychiatrist (24 h as a guide), low is a flag with the safety plan. PHQ-9 item 9 with a
negative screener is a discrepancy flag; without a completed screener it asks the C-SSRS at once.
config/questionnaire.yaml: cssrs_positive_policy graded (C01).
- Every crisis leaves a callback task for the doctor on duty (post_crisis_call, one per patient)
and the app asks how the person is the next day, the safety plan one tap away.
- Routing: risk and flags first, scales second; PCL-5 >= 50 no longer means a priority
appointment (pcl5_severe is moderate on the dashboard too).
- Crisis numbers live only in config/texts/crisis.yaml; texts and the agent prompt refer to them
via {phone:<key>}. Fixes the children's helpline offered to adults by the chat agent.
Refs #92
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""Crisis numbers live in one table (config/texts/crisis.yaml); every other text refers to it by `{phone:<key>}`."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.safety import CRISIS_PATH, fill_phones
|
||||
from app.llm.agent.prompts import SYSTEM_PROMPT
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
# Any phone number except the universal short emergency numbers 112 / 101–103.
|
||||
NUMBER = re.compile(r"(\+7|\b8)[\s(-]*\d{3}[\s)-]*\d{3}[\s-]*\d{2}[\s-]*\d{2}")
|
||||
|
||||
|
||||
def test_no_number_outside_the_crisis_table():
|
||||
texts = [p for p in (ROOT / "config" / "texts").glob("*.yaml") if p != CRISIS_PATH]
|
||||
code = [*(ROOT / "app").rglob("*.py"), *(ROOT / "web" / "src").rglob("*.ts*")]
|
||||
offenders = [
|
||||
str(p.relative_to(ROOT))
|
||||
for p in [*texts, *code]
|
||||
if p.name != "privacy.py" # the PII scrubber documents the phone format with an example
|
||||
and NUMBER.search("\n".join(line for line in p.read_text(encoding="utf-8").splitlines() if "#" not in line))
|
||||
]
|
||||
assert offenders == []
|
||||
|
||||
|
||||
def test_references_resolve_and_unknown_keys_fail():
|
||||
assert "989-50-50" in fill_phones("{phone:helpline}") and "989-50-50" in SYSTEM_PROMPT
|
||||
with pytest.raises(ValueError):
|
||||
fill_phones("{phone:no_such_line}")
|
||||
Reference in New Issue
Block a user