feat: crisis message after the test and non-blocking; weapon answer triggers nothing; ЕМИАС demo = Никита
CI / ruff · pytest (pull_request) Successful in 2m33s
CI / ruff · pytest (pull_request) Successful in 2m33s
- CRISIS_BLOCKING (default off for the demo): a crisis no longer locks the dialog; the message and the doctor's acute alert still happen. The clinical protocol (blocking, immediate stop) stays behind the flag and keeps its tests. - Crisis signals inside a questionnaire (intake red flags, high C-SSRS, PHQ-9 item 9) no longer stop it: the test is finished and the crisis message comes at the end; the doctor's alert is raised at once/at the end. - Weapon access: asked and stored, triggers nothing (rule disabled, no red flag, no safety block). - ЕМИАС demo persona is «Никита В.»; the one-click demo stays «Андрей С.». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,8 @@ def _no_live_llm(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("CHAT_AGENT", "off")
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "")
|
||||
monkeypatch.setenv("LLM_PROVIDER", "mock")
|
||||
# Tests cover the clinical protocol by default (blocking crisis); demo-mode tests flip it explicitly.
|
||||
monkeypatch.setenv("CRISIS_BLOCKING", "true")
|
||||
get_settings.cache_clear()
|
||||
yield
|
||||
get_settings.cache_clear()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Demo mode (crisis_blocking off): a crisis signal inside a questionnaire does not stop it; the crisis message
|
||||
comes at the end and blocks nothing; the weapon question triggers nothing."""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db.clinical import Alert
|
||||
from tests.test_dialog_intake import answer_button, complete_existing, press, released, rows # noqa: F401
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def non_blocking(monkeypatch):
|
||||
from app.config import get_settings
|
||||
|
||||
monkeypatch.setattr(get_settings(), "crisis_blocking", False)
|
||||
|
||||
|
||||
async def test_red_flag_does_not_stop_the_questionnaire(released, non_blocking): # noqa: F811
|
||||
replies = await press(released, "intake:start")
|
||||
replies = await press(released, answer_button(replies, {"rf_psychosis": 0})) # «Да» on a red flag
|
||||
assert not any(r.kind == "crisis" for r in replies), "no crisis window in the middle of the test"
|
||||
assert any(r.buttons for r in replies), "the next question is asked"
|
||||
(row,) = await rows()
|
||||
assert row.status != "interrupted" and row.details["pending_urgent"]["code"] == "red_flag_psychosis"
|
||||
replies = await complete_existing(released, replies, {})
|
||||
assert any(r.kind == "crisis" for r in replies), "the crisis message comes at the end"
|
||||
assert any(a.code == "red_flag_psychosis" and a.level == "acute" for a in await rows(Alert))
|
||||
|
||||
|
||||
async def test_weapon_answer_triggers_nothing(released, non_blocking): # noqa: F811
|
||||
replies = await press(released, "intake:start")
|
||||
for _ in range(2): # psychosis, harm to others: «Нет»
|
||||
replies = await press(released, answer_button(replies))
|
||||
replies = await press(released, answer_button(replies, {"rf_weapons": 0})) # «Да» on weapons
|
||||
assert not any(r.kind == "crisis" for r in replies)
|
||||
(row,) = await rows()
|
||||
assert not row.details.get("pending_urgent")
|
||||
@@ -268,7 +268,7 @@ async def test_auditc_sex_declined_uses_default_threshold(dbs_nosex: None, engin
|
||||
assert (await results())["auditc"].severity == "positive" # unknown sex → sensitive threshold ≥ 3
|
||||
# Weapon at home + AUDIT-C positive = risk signal → safe-storage block.
|
||||
r = await press(engine, "vonb:weapon:self")
|
||||
assert VT.weapon.safety_block_self in [x.text for x in r]
|
||||
assert VT.weapon.safety_block_self not in [x.text for x in r] # the weapon answer affects nothing
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------- weapon / safety
|
||||
|
||||
+3
-2
@@ -125,8 +125,9 @@ def test_cssrs_levels() -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("weapon", "cssrs", "item9", "fires"),
|
||||
[
|
||||
(True, "moderate", None, True),
|
||||
(True, None, 1, True),
|
||||
# Team decision 2026-10-04: weapon access triggers nothing (rule disabled in config/rules.yaml).
|
||||
(True, "moderate", None, False),
|
||||
(True, None, 1, False),
|
||||
(True, "low", 0, False),
|
||||
(False, "high", 2, False),
|
||||
(None, "moderate", None, False),
|
||||
|
||||
@@ -223,10 +223,9 @@ async def test_weapon_access_from_dialog_facts(db: MemoryNotifier) -> None:
|
||||
assert snap is not None and snap.weapon_access is True
|
||||
await scale("cssrs_screen", 3, severity="moderate", scheduled=False)
|
||||
actions = await monitoring.RuleMonitoring().after_data_change(TOKEN)
|
||||
assert {a.code for a in actions if a.type == "crisis"} == {"weapon_access_risk"}
|
||||
plan = next(a for a in actions if a.type == "show_safety_plan")
|
||||
assert plan.params["block"] == "means_restriction"
|
||||
assert await color() == "red"
|
||||
# Team decision 2026-10-04: weapon access is stored but triggers nothing (no crisis, no safety block).
|
||||
assert "weapon_access_risk" not in {a.code for a in actions}
|
||||
assert not any(a.type == "show_safety_plan" and a.params.get("block") == "means_restriction" for a in actions)
|
||||
|
||||
|
||||
async def test_snapshot_mapping_and_unknown_patient(db: MemoryNotifier) -> None:
|
||||
|
||||
Reference in New Issue
Block a user