feat(web): two-stage questionnaires, screening result and diary by result
- Sign-in: centred ЕМИАС card without the sidebar; anonymous entry asks «Депрессия / ПТСР».
- Stage 1 is the same for everyone and follows the fixed order of the MVP questionnaire:
PHQ-2 → trauma filter/TSQ → RUS-AUDIT-S → C-SSRS → context; stage 2 only where stage 1
is positive. Starts without a profile.
- After stage 1 the patient sees a preliminary result (screening, not a diagnosis), stored
with the record; it switches on the diary modules (depression / PTSD).
- Stage 2 diary: morning (sleep, sleep quality, alcohol) and evening (mood, medication,
3 PHQ-9 items in rotation, short PCL-5); C-SSRS since last contact only under risk;
flags on weekly means against the personal norm; scheduled full scales with change
(remission, response, worsening).
- Questionnaire log: GET /me/{token}/results and the «Мои результаты» screen.
- Questionnaires centred without the sidebar and without scale names; «Продолжить позже»
goes home; confirmations are toasts; free-text comments are optional.
- Demo policy config/questionnaire_demo.yaml (INTAKE_DEMO only, not a clinical approval).
ADR-012.
Refs #77
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+22
-9
@@ -15,7 +15,9 @@ from sqlalchemy import select, text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from app.core import clock
|
||||
from app.core.intake_questions import DIARY_KINDS
|
||||
from app.core.intake_runtime import due_scales, intake_enabled, load_runtime
|
||||
from app.core.questionnaire import load_policy
|
||||
from app.db import repo
|
||||
from app.db.clinical import PatientProfile
|
||||
from app.db.intake import QuestionnaireTask
|
||||
@@ -45,16 +47,18 @@ async def plan():
|
||||
initial = next((r for r in reversed(rows) if r.kind == "intake" and r.status == "complete"), None)
|
||||
if initial is None:
|
||||
continue
|
||||
last_activity = max([r.day for r in rows if r.kind == "diary" and r.status == "complete"] + [initial.day])
|
||||
diaries = [r.day for r in rows if r.kind in DIARY_KINDS and r.status == "complete"]
|
||||
last_activity = max([*diaries, initial.day])
|
||||
high_risk = profile.enhanced_monitoring or bool(state.data.get("crisis"))
|
||||
if high_risk and (local.date() - last_activity).days >= p.high_risk_silence_days:
|
||||
await service.alert(session, profile.token, f"silence:{last_activity}", "high_risk_silence", now)
|
||||
if (state.data.get("crisis") or {}).get("open") or state.data.get("intake_review_required"):
|
||||
continue
|
||||
if local.hour >= p.daily_hour and not any(
|
||||
r.kind == "diary" and r.day == local.date() and r.status == "complete" for r in rows
|
||||
):
|
||||
await service.queue_task(session, profile.token, "diary", f"diary:{local.date()}", now)
|
||||
done = {r.kind for r in rows if r.day == local.date() and r.status == "complete"}
|
||||
# Stage 2 diary: morning block after waking, evening block at evening_hour («Этап 2», p.2).
|
||||
for kind, hour in (("diary_morning", p.morning_hour), ("diary_evening", p.evening_hour)):
|
||||
if local.hour >= hour and kind not in done:
|
||||
await service.queue_task(session, profile.token, kind, f"{kind}:{local.date()}", now)
|
||||
last = {}
|
||||
for row in rows:
|
||||
if row.status != "complete":
|
||||
@@ -82,12 +86,21 @@ async def plan():
|
||||
after_crisis=crisis_date,
|
||||
policy=p,
|
||||
)
|
||||
if scales or local.hour >= p.daily_hour:
|
||||
# Suicide-risk module: C-SSRS «since last contact» every day, only under risk monitoring.
|
||||
if profile.enhanced_monitoring and local.hour >= p.evening_hour and "cssrs_since_contact" not in done:
|
||||
await service.queue_task(session, profile.token, "cssrs_since_contact", f"cssrs:{local.date()}", now)
|
||||
totals = initial.details.get("totals", {})
|
||||
modules = set(profile.modules or [])
|
||||
policy = load_policy()
|
||||
# PHQ-9 only with depression or a positive PHQ-2; PCL-5 only with PTSD or a positive TSQ («Этап 2», p.3).
|
||||
wanted = {
|
||||
"phq9": "depression" in modules or bool(totals.get("phq9", {}).get("complete")),
|
||||
"pcl5": "veteran" in modules or (totals.get("tsq", {}).get("total") or 0) >= policy.tsq_expand_at,
|
||||
"rus_audit_s": True,
|
||||
}
|
||||
for sid in scales:
|
||||
if sid == "pcl5" and initial.answers.get("trauma") != 1:
|
||||
continue
|
||||
await service.queue_task(session, profile.token, sid, f"{sid}:{local.date()}", now)
|
||||
if wanted[sid]:
|
||||
await service.queue_task(session, profile.token, sid, f"{sid}:{local.date()}", now)
|
||||
|
||||
|
||||
async def deliver():
|
||||
|
||||
Reference in New Issue
Block a user