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>
44 lines
2.1 KiB
Python
44 lines
2.1 KiB
Python
"""Follow-up after a crisis (clinical review 2026-10-04: «план безопасности и звонок после кризиса — обязательно»).
|
|
|
|
Every crisis — from the safety lexicon, a rule (PHQ-9 item 9, C-SSRS high) or the questionnaire (red flags, high
|
|
risk) — leaves a task for the doctor on duty: call the patient back, about 24 hours as a guide. The task is an alert
|
|
on the dashboard; «позвонили» is its acknowledgement (`repo.ack_alert`, audited). One open task per patient: a
|
|
repeated crisis signal does not pile up callbacks. On the next day the app itself asks how the person is, with the
|
|
safety plan at hand (`app/scheduler/jobs.py`).
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.types import AlertLevel
|
|
from app.db import repo
|
|
from app.db.clinical import Alert
|
|
|
|
POST_CRISIS_CALL = "post_crisis_call"
|
|
ON_DUTY = "on-duty"
|
|
CALL_WITHIN = timedelta(hours=24) # a guide, not a guarantee
|
|
NEXT_DAY_CHECK_AFTER = timedelta(hours=20)
|
|
|
|
|
|
async def schedule_post_crisis_call(session: AsyncSession, token: str, now: datetime, *, source: str) -> Alert | None:
|
|
if POST_CRISIS_CALL in await repo.open_alert_codes(session, token):
|
|
return None
|
|
return await repo.add_alert(
|
|
session,
|
|
token=token,
|
|
level=AlertLevel.HIGH,
|
|
code=POST_CRISIS_CALL,
|
|
reason="Перезвонить пациенту после кризиса — дежурный специалист, ориентир 24 ч. Отметка «позвонили» — "
|
|
"подтверждение этого сигнала.",
|
|
ts=now,
|
|
params={"due_at": (now + CALL_WITHIN).isoformat(), "assignee": ON_DUTY, "source": source},
|
|
)
|
|
|
|
|
|
def next_day_check_due(crisis: dict | None, now: datetime) -> bool:
|
|
"""The day after a crisis the app asks how the person is — once per crisis episode."""
|
|
if not crisis or not crisis.get("opened_at") or crisis.get("followup_sent"):
|
|
return False
|
|
return now - datetime.fromisoformat(crisis["opened_at"]) >= NEXT_DAY_CHECK_AFTER
|