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:
@@ -47,6 +47,7 @@ async def create_record(ctx, kind, *, task=None):
|
||||
sex = profile.sex if profile else None
|
||||
if sex is None and prior:
|
||||
sex = prior.details.get("sex")
|
||||
day = ctx.now.astimezone(ZoneInfo(runtime.timezone)).date()
|
||||
row = QuestionnaireRecord(
|
||||
id=uuid4().hex,
|
||||
token=ctx.token,
|
||||
@@ -54,7 +55,7 @@ async def create_record(ctx, kind, *, task=None):
|
||||
version=content()["version"],
|
||||
policy_version=load_policy().version,
|
||||
started_at=ctx.now,
|
||||
day=ctx.now.astimezone(ZoneInfo(runtime.timezone)).date(),
|
||||
day=day,
|
||||
status="active",
|
||||
answers={},
|
||||
details={
|
||||
@@ -69,6 +70,8 @@ async def create_record(ctx, kind, *, task=None):
|
||||
"weekly_pcl": kind == "pcl5" and bool((ctx.data.get("crisis") or {}).get("closed_at")),
|
||||
"answer_metadata": {},
|
||||
"task_id": task.id if task else None,
|
||||
# Days since stage 1: rotates the PHQ-9 items of the depression diary module (3 a day).
|
||||
"day_index": (day - prior.day).days if prior else 0,
|
||||
},
|
||||
)
|
||||
ctx.session.add(row)
|
||||
@@ -79,7 +82,7 @@ async def create_record(ctx, kind, *, task=None):
|
||||
return row
|
||||
|
||||
|
||||
def save_answer(row, question, value, now):
|
||||
def save_answer(row, question, value, now, *, derived=False):
|
||||
"""Anchor recall windows to administration, never to the day of a resumed answer."""
|
||||
day = row.day
|
||||
starts = {
|
||||
@@ -105,8 +108,8 @@ def save_answer(row, question, value, now):
|
||||
"period": question.period,
|
||||
"period_start": starts[question.period].isoformat() if question.period in starts else None,
|
||||
"period_end": day.isoformat(),
|
||||
"source": "patient",
|
||||
"derived": False,
|
||||
"source": "derived" if derived else "patient",
|
||||
"derived": derived,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -258,6 +261,16 @@ async def queue_task(session, token, kind, key, now):
|
||||
return row
|
||||
|
||||
|
||||
async def outstanding_task(session, token, kind):
|
||||
return await session.scalar(
|
||||
select(QuestionnaireTask).where(
|
||||
QuestionnaireTask.token == token,
|
||||
QuestionnaireTask.kind == kind,
|
||||
QuestionnaireTask.status.in_(("pending", "active")),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def lock_patient(session, token):
|
||||
# PostgreSQL serializes callbacks from different workers. SQLite tests additionally
|
||||
# use the engine's in-process lock. Consent and answers share this transaction.
|
||||
|
||||
Reference in New Issue
Block a user