feat(web): questionnaires, diary and medication module in the patient app

- Focus screen renders versioned questionnaire items (iq: buttons): section chip, position
  (adaptive forms without "of N"), recall date, pause / help-now actions, optional skip,
  sleep-hours input, 0-10 scale anchors, RUS-AUDIT item 2 standard-drink table.
- Paused / held-for-review questionnaires get their own screen.
- Today: intake and diary tasks, review notice, therapy-day card on days 1/7/14.
- Drug card /meds/:ref from data/meds/meds_reference.json (draft built from the Minzdrav
  guidelines: depression 2024, PTSD 2023) — expected effect, course, side effects, red flags;
  no doses shown. "Report a side effect" always available (med:side_effects:<ref>).
- API: /instruments/catalog, /meds/reference; today gains questionnaire state, reference id,
  indication and therapy day; demo persona seeds the dashboard dose history.
- INTAKE_DEMO=1 (demo clock only) opens the questionnaires on synthetic data without touching
  the clinical release gate in config/intake_runtime.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 08:53:13 +03:00
co-authored by Claude Opus 5.5
parent 40c242bb1a
commit 464b474abd
22 changed files with 3393 additions and 58 deletions
+542
View File
@@ -0,0 +1,542 @@
"""Builds data/meds/meds_reference.json — the drug reference of the medication module.
Source of every value: the two Minzdrav clinical guidelines in config/rag/corpus/ —
* «Депрессивный эпизод, рекуррентное депрессивное расстройство» (2024): прил. А3 табл. 2 (дозы),
табл. 3 (профили побочных эффектов), разд. 3.1–3.6, прил. Б5, В;
* «Посттравматическое стрессовое расстройство» (2023, ID 753): разд. 3.2, прил. А3 (дозы).
Patient side-effect items: effects rated «++»/«+++» in табл. 3 (antidepressants) or named in the КР text
(other drugs), worded for a patient. Nothing here is a recommendation: the app only follows the doctor's
prescription (module spec, «Границы»). Texts and draft parameters must be reviewed by a psychiatrist.
Run: uv run python -m scripts.build_meds_reference
"""
import json
from pathlib import Path
OUT = Path(__file__).resolve().parents[1] / "data" / "meds" / "meds_reference.json"
DEP = "КР депрессия 2024"
PTSD = "КР ПТСР 2023 (ID 753)"
# Patient wording of the КР табл. 3 columns (and of effects named in the КР text).
SE = {
"anticholinergic": "Сухость во рту или запоры",
"nausea": "Тошнота",
"sedation": "Сонливость днём",
"insomnia": "Бессонница или беспокойство",
"sexual": "Сексуальные проблемы",
"orthostatic": "Головокружение при вставании",
"weight": "Прибавка веса",
"headache": "Головная боль",
"dizziness": "Головокружение",
"coordination": "Нарушение координации",
"memory": "Снижение внимания и памяти",
"akathisia": "Беспокойство, невозможность усидеть на месте",
"eps": "Скованность или дрожь в теле",
"skin": "Кожная сыпь или зуд",
}
RF_BLEEDING = "Необычные кровотечения или синяки"
RF_EARLY = "Мысли о самоубийстве или резкое усиление тревоги в первые недели приёма"
RF_JAUNDICE = "Желтушность кожи или глаз"
RF_RASH = "Кожная сыпь"
RF_PRIAPISM = "Длительная болезненная эрекция"
RF_UTERINE = "Маточное кровотечение"
TCA_MON = "Артериальное давление и ЭКГ"
AP_MON = "Вес и признаки метаболического синдрома"
def ad(id_, name, cls, start, lo, hi, items, *, ptsd=None, red=(), mon=(), withdrawal=None, overdose=False, note=None):
"""Antidepressant: doses from КР депрессия табл. 2, side effects from табл. 3."""
d = {
"id": id_,
"name_ru": name,
"group": "Антидепрессант",
"class": cls,
"indications": {
"depression": {
"start_mg": start,
"min_mg": lo,
"max_mg": hi,
"off_label": False,
"expected_response": "3–4 недели",
"course": "После улучшения лечение продолжают в той же дозе ещё 4–6 месяцев",
"source": f"{DEP}, прил. А3 табл. 2; разд. 3.1",
}
},
"diary_items": [SE[i] for i in items],
"diary_items_source": f"{DEP}, прил. А3 табл. 3 (++/+++)",
"red_flags": [RF_EARLY, *red],
"monitoring": list(mon),
"withdrawal_risk": withdrawal,
"overdose_danger": overdose,
"max_course_days": None,
}
if ptsd:
d["indications"]["ptsd"] = ptsd
if note:
d["note"] = note
return d
def ptsd_ind(lo, hi, off_label, start=None):
return {
"start_mg": start,
"min_mg": lo,
"max_mg": hi,
"off_label": off_label,
"expected_response": "4–6 недель",
"course": "Обычно 6–12 месяцев после стабилизации",
"source": f"{PTSD}, разд. 3.2; прил. А3",
}
def other(
id_, name, cls, lo, hi, off_label, items, *, red=(), mon=(), withdrawal=None, course_days=None, src, note=None
):
"""Non-antidepressant used for PTSD: doses from КР ПТСР прил. А3 / разд. 3.2, effects named in the text."""
ind = ptsd_ind(lo, hi, off_label)
ind["expected_response"] = None
ind["course"] = None
d = {
"id": id_,
"name_ru": name,
"group": next(g for key, g in GROUPS.items() if key in cls),
"class": cls,
"indications": {"ptsd": ind},
"diary_items": [SE[i] for i in items],
"diary_items_source": src,
"red_flags": list(red),
"monitoring": list(mon),
"withdrawal_risk": withdrawal,
"overdose_danger": False,
"max_course_days": course_days,
}
if note:
d["note"] = note
return d
# Patient-facing drug group by the КР class wording.
GROUPS = {
"бензодиазепин": "Анксиолитик (транквилизатор)",
"Анксиолитик": "Анксиолитик",
"Антипсихотическое": "Антипсихотик",
"Противоэпилептический": "Противоэпилептический препарат",
}
SSRI = ["nausea", "insomnia", "sexual", "headache"] # табл. 3 + головная боль из разд. 3.1 (СИОЗС)
BENZO = ["sedation", "dizziness", "coordination", "memory"] # КР ПТСР 3.2: седация, головокружение, координация…
BENZO_SRC = f"{PTSD}, разд. 3.2 (комментарии о производных бензодиазепина)"
AP_SRC = f"{PTSD}, разд. 3.2: акатизия, экстрапирамидные расстройства, метаболический синдром"
DRUGS = [
ad(
"paroxetine",
"Пароксетин",
"СИОЗС",
20,
20,
50,
SSRI,
ptsd=ptsd_ind(20, 50, False),
red=[RF_BLEEDING],
withdrawal="high",
),
ad("sertraline", "Сертралин", "СИОЗС", 50, 50, 200, SSRI, ptsd=ptsd_ind(25, 200, False), red=[RF_BLEEDING]),
ad(
"fluvoxamine",
"Флувоксамин",
"СИОЗС",
50,
100,
300,
SSRI,
red=[RF_BLEEDING],
note="Стартовая доза по табл. 2: 50–100 мг",
),
ad(
"fluoxetine",
"Флуоксетин",
"СИОЗС",
20,
20,
80,
SSRI,
ptsd=ptsd_ind(20, 40, True),
red=[RF_BLEEDING],
withdrawal="low",
),
ad("citalopram", "Циталопрам", "СИОЗС", 20, 20, 40, SSRI, red=[RF_BLEEDING]),
ad("escitalopram", "Эсциталопрам", "СИОЗС", 10, 10, 20, SSRI, red=[RF_BLEEDING]),
ad(
"venlafaxine",
"Венлафаксин",
"СИОЗСН",
75,
75,
375,
["nausea", "insomnia", "sexual"],
ptsd=ptsd_ind(75, 225, True),
mon=["Артериальное давление"],
withdrawal="high",
note="Депрессия: >225 мг/сут — в стационарных условиях",
),
ad("duloxetine", "Дулоксетин", "СИОЗСН", 60, 60, 120, ["nausea", "insomnia"]),
ad(
"amitriptyline",
"Амитриптилин",
"ТЦА",
50,
150,
300,
["anticholinergic", "sedation", "orthostatic", "weight"],
ptsd=ptsd_ind(75, 150, True),
mon=[TCA_MON],
withdrawal="high",
overdose=True,
note="Депрессия: старт 50–75 мг; >150 мг/сут — в стационарных условиях",
),
ad(
"imipramine",
"Имипрамин",
"ТЦА",
25,
150,
300,
["anticholinergic", "insomnia", "orthostatic", "weight"],
ptsd=ptsd_ind(75, 250, True),
mon=[TCA_MON],
withdrawal="high",
overdose=True,
note="Депрессия: старт 25–75 мг; >200 мг/сут — в стационарных условиях",
),
ad(
"clomipramine",
"Кломипрамин",
"ТЦА",
50,
100,
250,
["anticholinergic", "sexual", "orthostatic", "weight"],
mon=[TCA_MON],
withdrawal="high",
overdose=True,
note="Старт 50–75 мг; летальность при передозировке средняя",
),
ad(
"pipofezine",
"Пипофезин",
"ТЦА (АТХ — другие антидепрессанты)",
25,
150,
500,
[],
note="В табл. 3 КР не описан — побочные эффекты уточнять у врача; старт 25–50 мг",
),
ad("mianserin", "Миансерин", "НаССА", 30, 60, 90, ["sedation"], mon=["Формула крови (по КР — изменения редко)"]),
ad(
"mirtazapine",
"Миртазапин",
"НаССА",
15,
15,
45,
["sedation", "weight"],
ptsd=ptsd_ind(30, 60, True),
note="Старт 15–30 мг",
),
ad(
"trazodone",
"Тразодон",
"Специфический серотонинергический АД",
100,
150,
600,
["sedation", "sexual"],
red=[RF_PRIAPISM],
note=">450 мг/сут — в стационарных условиях",
),
ad(
"pirlindole",
"Пирлиндол",
"Ингибитор МАО-А",
50,
150,
400,
[],
note="В табл. 3 КР не описан — побочные эффекты уточнять у врача; старт 50–75 мг",
),
ad(
"agomelatine",
"Агомелатин",
"Мелатонинергический АД",
25,
25,
50,
[],
red=[RF_JAUNDICE],
mon=["Билирубин, АСТ, АЛТ, щелочная фосфатаза — в начале лечения и при повышении дозы"],
),
ad("vortioxetine", "Вортиоксетин", "Серотонинергический модулятор", 10, 10, 20, ["nausea", "insomnia"]),
# КР ПТСР: анксиолитики, антипсихотические средства, противоэпилептические препараты
other(
"diazepam",
"Диазепам",
"Производное бензодиазепина",
5,
15,
False,
BENZO,
withdrawal="high",
course_days=28,
src=BENZO_SRC,
note="Курс не более 4 недель, в тяжёлых случаях до 8–12 недель",
),
other(
"lorazepam",
"Лоразепам",
"Производное бензодиазепина",
2,
10,
False,
BENZO,
withdrawal="high",
course_days=21,
src=BENZO_SRC,
note="Дозы — из текста разд. 3.2 (в табл. А3 не приведён)",
),
other(
"clonazepam",
"Клоназепам",
"Производное бензодиазепина",
1,
4,
False,
BENZO,
withdrawal="high",
course_days=21,
src=BENZO_SRC,
note="Дозы — из текста разд. 3.2 (в табл. А3 не приведён)",
),
other(
"alprazolam",
"Алпразолам",
"Производное бензодиазепина",
0.25,
4.5,
False,
BENZO,
withdrawal="high",
course_days=90,
src=BENZO_SRC,
note="Курс до 3 месяцев; отмена в течение 2–6 недель",
),
other(
"tofisopam",
"Тофизопам",
"Производное бензодиазепина (дневной транквилизатор)",
50,
300,
False,
[],
course_days=84,
src=f"{PTSD}, разд. 3.2",
note="По КР не вызывает физической зависимости и седации; курс 4–12 недель",
),
other(
"etifoxine",
"Этифоксин",
"Анксиолитик",
150,
200,
False,
["sedation", "skin"],
red=[RF_JAUNDICE, RF_UTERINE],
course_days=42,
src=f"{PTSD}, разд. 3.2",
note="Курс от нескольких дней до 4–6 недель",
),
other(
"hydroxyzine",
"Гидроксизин",
"Анксиолитик",
25,
100,
True,
["sedation"],
course_days=56,
src=f"{PTSD}, разд. 3.2",
note="Курс обычно 4–8 недель",
),
other(
"buspirone",
"Буспирон",
"Анксиолитик",
20,
30,
False,
[],
src=f"{PTSD}, разд. 3.2",
note="Эффект с 7–14 дня, максимум к 4-й неделе",
),
other(
"alimemazine",
"Алимемазин",
"Антипсихотическое средство",
15,
80,
False,
[],
mon=[AP_MON],
src=f"{PTSD}, разд. 3.2",
note="По КР побочные эффекты редки и слабо выражены",
),
other(
"risperidone",
"Рисперидон",
"Антипсихотическое средство",
0.5,
8,
True,
["akathisia", "eps", "weight"],
mon=[AP_MON],
src=AP_SRC,
),
other(
"quetiapine",
"Кветиапин",
"Антипсихотическое средство",
50,
300,
True,
["akathisia", "eps", "weight"],
mon=[AP_MON],
src=AP_SRC,
),
other(
"olanzapine",
"Оланзапин",
"Антипсихотическое средство",
5,
20,
True,
["weight", "sedation"],
mon=[AP_MON],
src=f"{DEP}, разд. 3.3 (увеличение веса, седация); {PTSD}, разд. 3.2",
),
other(
"tiapride", "Тиаприд", "Антипсихотическое средство", 200, 300, False, [], mon=[AP_MON], src=f"{PTSD}, разд. 3.2"
),
other(
"chlorprothixene",
"Хлорпротиксен",
"Антипсихотическое средство",
25,
150,
True,
[],
mon=[AP_MON],
src=f"{PTSD}, разд. 3.2",
note="Расхождение в КР ПТСР: в тексте 15–100 мг, в табл. А3 — 25–150 мг",
),
other(
"lamotrigine",
"Ламотриджин",
"Противоэпилептический препарат",
25,
500,
True,
[],
red=[RF_RASH],
src=f"{PTSD}, разд. 3.2",
note="Частые кожные высыпания вплоть до токсического эпидермального некролиза",
),
other("topiramate", "Топирамат", "Противоэпилептический препарат", 200, 400, True, [], src=f"{PTSD}, разд. 3.2"),
]
TEXTS = {
"expect_effect_depression": (
"Лекарства от депрессии действуют не сразу. Первые изменения иногда заметны уже через 1–2 недели, "
"а эффект врач оценивает через 3–4 недели. Принимайте препарат по схеме, даже если пока не чувствуете разницы."
),
"expect_effect_ptsd": (
"Эффект препарата при ПТСР развивается постепенно — его оценивают через 4–6 недель. В первую неделю тревога "
"может временно усилиться: это известная реакция, сообщите о ней врачу. Не прекращайте приём самостоятельно."
),
"dont_stop_alone": (
"Не прекращайте приём самостоятельно: резкая отмена может ухудшить самочувствие, а лечение — потерять эффект. "
"Если препарат мешает жить или вы в нём сомневаетесь, обсудите это с врачом — он может изменить дозу "
"или подобрать другой."
),
"why_continue_depression": (
"Даже когда становится лучше, лечение продолжают ещё несколько месяцев в той же дозе: так ниже риск, "
"что депрессия вернётся."
),
"withdrawal_missed": (
"Вы пропустили приём два дня подряд. При резком прекращении этого препарата бывают неприятные ощущения: "
"головокружение, раздражительность, нарушения сна, состояние как при гриппе. Вернитесь к обычной схеме "
"и сообщите врачу, если стало хуже."
),
"ran_out": (
"Лекарство закончилось? Не откладывайте — попросите у врача новый рецепт: "
"перерыв в приёме может ухудшить самочувствие."
),
"red_flag": "Этот симптом нужно обсудить с врачом сегодня. Если состояние тяжёлое — вызывайте скорую: 103 или 112.",
"side_effects_general": (
"Спасибо, что сообщили. Побочные эффекты часто ослабевают в первые недели. Если они мешают жить — это повод "
"обсудить с врачом дозу или замену препарата. Не меняйте схему сами."
),
"instruction_fallback": "Уточните у врача или в инструкции к препарату.",
}
RULES = [
{"id": "M1", "params": {"missed_days_in_row": 2}, "status": "черновик"},
{"id": "M2", "params": {}, "status": "черновик"},
{"id": "M3", "params": {"ran_out_days_in_row": 2}, "status": "черновик"},
{"id": "M4", "params": {"default_max_course_days": 21}, "status": "КР ПТСР 3.2"},
{
"id": "M5",
"params": {"check_day": 28, "baseline_lookback_days": 14, "phq9_min_drop_pct": 50, "pcl5_min_drop": 10},
"status": "черновик",
},
{"id": "M6", "params": {}, "status": "КР депрессия 3.1.3"},
{"id": "M7", "params": {}, "status": "черновик"},
{"id": "M8", "params": {"fibser_burden_min": 5, "side_effect_misses_in_7_days": 2}, "status": "черновик"},
{"id": "M9", "params": {}, "status": "в сводке врачу"},
{"id": "M10", "params": {}, "status": "приложения А3 обеих КР"},
]
def main() -> None:
OUT.parent.mkdir(parents=True, exist_ok=True)
doc = {
"version": "draft-2026-10-04",
"status": "черновик: собран по КР Минздрава, тексты и параметры вычитывает психиатр",
"sources": [
{
"id": "kr_depression_2024",
"title": "Депрессивный эпизод, рекуррентное депрессивное расстройство",
"year": 2024,
"file": "docs/source/rag/kr_depression.pdf",
},
{
"id": "kr_ptsd_2023",
"title": "Посттравматическое стрессовое расстройство",
"year": 2023,
"kr_id": 753,
"file": "docs/source/rag/kr_ptsd.pdf",
},
],
"drugs": DRUGS,
"texts": TEXTS,
"rules": RULES,
}
OUT.write_text(json.dumps(doc, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"{OUT}: {len(DRUGS)} drugs")
if __name__ == "__main__":
main()