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:
@@ -7,6 +7,7 @@ import type { DialogButton, Reply } from "../api/types";
|
||||
|
||||
export type Step =
|
||||
| { kind: "idle"; notes: string[] } // engine is back at the menu: the flow is over
|
||||
| { kind: "ack"; text: string[] } // a short confirmation without a question: a toast, then home
|
||||
| { kind: "consent"; lead: string[]; title: string; buttons: DialogButton[] }
|
||||
| { kind: "crisis"; text: string; buttons: DialogButton[] }
|
||||
| { kind: "plan"; lead: string[]; title: string; lines: string[]; buttons: DialogButton[] }
|
||||
@@ -26,7 +27,7 @@ export interface Survey {
|
||||
title: string; // the item text, verbatim
|
||||
footnotes: string[]; // «*…» explanations printed under the item on the paper form
|
||||
options: DialogButton[];
|
||||
input: "choice" | "number" | "hours" | "text";
|
||||
input: "choice" | "number" | "hours" | "portions" | "text";
|
||||
anchors?: string[]; // «0 — самое плохое», «10 — самое хорошее»: the scale ends as printed in the item
|
||||
asOf?: string; // ISO date the recall period counts from
|
||||
skip?: DialogButton;
|
||||
@@ -107,7 +108,10 @@ function survey(batch: Reply[], qIndex: number): Survey | null {
|
||||
? "choice"
|
||||
: skip
|
||||
? "text"
|
||||
: "hours";
|
||||
: // Numeric items carry no buttons: diary alcohol is counted in standard drinks, everything else in hours.
|
||||
/порци/i.test(title)
|
||||
? "portions"
|
||||
: "hours";
|
||||
return { kind: "survey", itemId, instruction: [...before, ...instruction], title, footnotes, options, input, anchors, asOf, skip, help, pause };
|
||||
}
|
||||
|
||||
@@ -142,6 +146,9 @@ export function classify(batch: Reply[]): Step | null {
|
||||
const qIndex = batch.map((r) => r.buttons.length > 0).lastIndexOf(true);
|
||||
const texts = batch.map((r) => r.text);
|
||||
if (qIndex === -1) {
|
||||
// Without buttons the engine waits for free text only when it marks the reply as a question; anything else
|
||||
// («отметил приём») is a confirmation.
|
||||
if (!batch.some((r) => r.kind === "question")) return { kind: "ack", text: texts.filter(Boolean) };
|
||||
const { lead, title } = titled(texts);
|
||||
return { kind: "text", lead, title };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user