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:
@@ -4,6 +4,7 @@ import math
|
||||
from dataclasses import asdict, replace
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from app.config import get_settings
|
||||
from app.core import interpretation
|
||||
from app.core.intake_questions import (
|
||||
DIARY_KINDS,
|
||||
@@ -15,6 +16,7 @@ from app.core.intake_questions import (
|
||||
group_answers,
|
||||
next_intake,
|
||||
red_flag_category,
|
||||
red_flag_triggers,
|
||||
scale_question,
|
||||
texts,
|
||||
)
|
||||
@@ -116,6 +118,39 @@ async def urgent(ctx, row=None, *, code="help_now", categories=None):
|
||||
return step
|
||||
|
||||
|
||||
def _defer_urgent(row, code, categories=None):
|
||||
"""Remember the first crisis signal of this questionnaire; the questionnaire goes on (team decision
|
||||
2026-10-04: the crisis message comes at the end and blocks nothing)."""
|
||||
if not row.details.get("pending_urgent"):
|
||||
row.details = {**row.details, "pending_urgent": {"code": code, "categories": categories or []}}
|
||||
|
||||
|
||||
async def _flush_urgent(ctx, row, step):
|
||||
"""Questionnaire over: acute alert + callback for the doctor, then the (non-blocking) crisis message."""
|
||||
pending = (row.details or {}).get("pending_urgent")
|
||||
if not pending or step.next_state == "intake.collect":
|
||||
return step
|
||||
row.details = {**row.details, "pending_urgent": None}
|
||||
code, categories = pending["code"], pending.get("categories") or []
|
||||
profile = await ctx.profile()
|
||||
if profile:
|
||||
profile.enhanced_monitoring = True
|
||||
await service.alert(
|
||||
ctx.session,
|
||||
ctx.token,
|
||||
f"{row.id}:{code}",
|
||||
code,
|
||||
ctx.now,
|
||||
level=AlertLevel.ACUTE,
|
||||
params={"record_id": row.id, "deferred": True},
|
||||
)
|
||||
await schedule_post_crisis_call(ctx.session, ctx.token, ctx.now, source="questionnaire:" + code)
|
||||
crisis = open_crisis(ctx, categories, source="questionnaire:" + code)
|
||||
for reply in crisis.replies:
|
||||
reply.buttons.append([Button(label=ctx.texts.safety_plan.title.rstrip(":"), data="safety_plan")])
|
||||
return step.then(crisis)
|
||||
|
||||
|
||||
async def item9_discrepancy(ctx, row):
|
||||
profile = await ctx.profile()
|
||||
if profile:
|
||||
@@ -170,18 +205,27 @@ async def manual_review(ctx, row, code):
|
||||
|
||||
|
||||
async def advance(ctx, row):
|
||||
return await _flush_urgent(ctx, row, await _advance(ctx, row))
|
||||
|
||||
|
||||
async def _advance(ctx, row):
|
||||
a = row.answers
|
||||
c = score_cssrs_recent(cssrs_answers(a))
|
||||
policy = load_policy()
|
||||
# Red flags (asked first): any «Да» stops the questionnaire with the help screen of its category.
|
||||
for item_id, value in a.items():
|
||||
if item_id.startswith("rf_") and value == 1:
|
||||
if item_id.startswith("rf_") and value == 1 and red_flag_triggers(item_id):
|
||||
category = red_flag_category(item_id)
|
||||
return await urgent(ctx, row, code="red_flag_" + item_id[3:], categories=[category] if category else [])
|
||||
if get_settings().crisis_blocking: # clinical protocol: stop at once
|
||||
return await urgent(ctx, row, code="red_flag_" + item_id[3:], categories=[category] if category else [])
|
||||
_defer_urgent(row, "red_flag_" + item_id[3:], [category] if category else [])
|
||||
break
|
||||
# One crisis rule for the whole app (ADR-013): the crisis screen opens at a HIGH C-SSRS risk only. Moderate →
|
||||
# stop + psychiatrist (about 24 h); low → flag, safety plan, continue.
|
||||
if c.risk_lower_bound == "high":
|
||||
return await urgent(ctx, row, code="suicide_signal")
|
||||
if get_settings().crisis_blocking:
|
||||
return await urgent(ctx, row, code="suicide_signal")
|
||||
_defer_urgent(row, "suicide_signal")
|
||||
if a.get("dphq_9", 0) > 0 and row.kind in DIARY_KINDS:
|
||||
# Diary depression module, item 9 ≥1 → the C-SSRS right away («Этап 2»); its risk level decides.
|
||||
return await item9_assessment(ctx, row)
|
||||
|
||||
Reference in New Issue
Block a user