chore(core): stage 1 contracts — dialog, API schemas + fixtures, integrations, monitoring, modules
CI / ruff · pytest (pull_request) Successful in 22s
CI / ruff · pytest (pull_request) Successful in 22s
Freeze interfaces between lanes A (bot), B (backend), C (dashboard) so three developers work in parallel without touching each other's files: dialog Event/Reply + engine registry with echo stub, integration protocols with in-memory defaults, MonitoringService with noop stub, /api/v1 schemas with fixture-backed routes, frozen main.py wiring, config/modules.yaml + due_scales(), DAR-5, tzdata, Andrey trajectory, lanes in WORKFLOW, ADR-010. Refs #34 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
"""Deterministic fixture data returned by stub routes until #12 implements them (synthetic only).
|
||||
|
||||
Lets the dashboard (lane C) be built against the frozen schemas right away. Mirrors the demo cabinet from
|
||||
docs/product/01_demo_persona.md: veteran Andrey on day 13 (red) plus seven other patients.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from app.api.schemas import (
|
||||
AlertOut,
|
||||
DiscontinuationRisk,
|
||||
DoseOut,
|
||||
PatientCard,
|
||||
PatientListItem,
|
||||
SeriesPoint,
|
||||
)
|
||||
from app.integrations.base import Prescription
|
||||
|
||||
TZ = ZoneInfo("Europe/Moscow")
|
||||
TODAY = date(2026, 10, 3)
|
||||
DOCTOR_ID = "doc-smirnova"
|
||||
|
||||
|
||||
def _risk(score: int, factors: list[str]) -> DiscontinuationRisk:
|
||||
level = "high" if score >= 60 else "medium" if score >= 30 else "low"
|
||||
return DiscontinuationRisk(score=score, level=level, factors=factors)
|
||||
|
||||
|
||||
PATIENTS: list[PatientListItem] = [
|
||||
PatientListItem(
|
||||
token="tok-andrey",
|
||||
display_name="Андрей С.",
|
||||
age=34,
|
||||
modules=["veteran", "depression"],
|
||||
drug="сертралин 50 мг",
|
||||
therapy_day=13,
|
||||
next_visit=TODAY + timedelta(days=15),
|
||||
status_color="red",
|
||||
top_alert_level="acute",
|
||||
open_alerts=3,
|
||||
phq9_last=18,
|
||||
pcl5_last=44,
|
||||
trend="up",
|
||||
adherence_7d=1.0,
|
||||
discontinuation_risk=_risk(55, ["хотел бросить (день 5)", "алкоголь 3 дня", "настроение ≤ 3"]),
|
||||
last_event="PHQ-9 п.9 = 2 — кризисный протокол",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-dmitry",
|
||||
display_name="Дмитрий В.",
|
||||
age=41,
|
||||
modules=["veteran"],
|
||||
drug="сертралин 100 мг",
|
||||
therapy_day=45,
|
||||
status_color="yellow",
|
||||
top_alert_level="moderate",
|
||||
open_alerts=1,
|
||||
phq9_last=11,
|
||||
pcl5_last=48,
|
||||
trend="up",
|
||||
adherence_7d=0.86,
|
||||
discontinuation_risk=_risk(25, ["бессонница"]),
|
||||
last_event="кошмары, сон < 4 ч 3 ночи",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-sergey",
|
||||
display_name="Сергей П.",
|
||||
age=37,
|
||||
modules=["veteran"],
|
||||
drug="пароксетин 20 мг",
|
||||
therapy_day=30,
|
||||
status_color="yellow",
|
||||
top_alert_level="moderate",
|
||||
open_alerts=1,
|
||||
phq9_last=12,
|
||||
pcl5_last=41,
|
||||
trend="flat",
|
||||
adherence_7d=0.57,
|
||||
discontinuation_risk=_risk(70, ["пропуски 3 из 7", "нет ответа 2 дня"]),
|
||||
last_event="пропуски 3 из 7 дней",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-nikolay",
|
||||
display_name="Николай Ф.",
|
||||
age=52,
|
||||
modules=["veteran"],
|
||||
drug="сертралин 50 мг",
|
||||
therapy_day=14,
|
||||
status_color="yellow",
|
||||
top_alert_level="moderate",
|
||||
open_alerts=1,
|
||||
phq9_last=13,
|
||||
pcl5_last=39,
|
||||
trend="flat",
|
||||
adherence_7d=0.86,
|
||||
discontinuation_risk=_risk(40, ["алкоголь 4 из 7"]),
|
||||
last_event="алкоголь 4 из 7 дней",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-anna",
|
||||
display_name="Анна Л.",
|
||||
age=23,
|
||||
modules=["depression"],
|
||||
drug="флуоксетин 20 мг",
|
||||
therapy_day=8,
|
||||
status_color="yellow",
|
||||
top_alert_level="info",
|
||||
open_alerts=1,
|
||||
phq9_last=15,
|
||||
trend="flat",
|
||||
adherence_7d=1.0,
|
||||
discontinuation_risk=_risk(30, ["беспокойство"]),
|
||||
last_event="возраст < 25: усиленный мониторинг",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-irina",
|
||||
display_name="Ирина К.",
|
||||
age=34,
|
||||
modules=["depression"],
|
||||
drug="эсциталопрам 10 мг",
|
||||
therapy_day=21,
|
||||
status_color="green",
|
||||
phq9_last=9,
|
||||
trend="down",
|
||||
adherence_7d=1.0,
|
||||
discontinuation_risk=_risk(5, []),
|
||||
last_event="PHQ-9 16 → 9",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-olga",
|
||||
display_name="Ольга Н.",
|
||||
age=29,
|
||||
modules=["depression"],
|
||||
drug="сертралин 50 мг",
|
||||
therapy_day=10,
|
||||
status_color="green",
|
||||
phq9_last=12,
|
||||
trend="down",
|
||||
adherence_7d=1.0,
|
||||
discontinuation_risk=_risk(10, []),
|
||||
last_event="тошнота прошла",
|
||||
),
|
||||
PatientListItem(
|
||||
token="tok-pavel",
|
||||
display_name="Павел Р.",
|
||||
age=45,
|
||||
modules=["depression"],
|
||||
drug="эсциталопрам 10 мг",
|
||||
therapy_day=60,
|
||||
status_color="green",
|
||||
phq9_last=4,
|
||||
trend="down",
|
||||
adherence_7d=1.0,
|
||||
discontinuation_risk=_risk(5, []),
|
||||
last_event="ремиссия, PHQ-9 = 4",
|
||||
),
|
||||
]
|
||||
|
||||
# Andrey's 13 days (docs/product/01_demo_persona.md): mood, sleep hours (bucket midpoint), alcohol 0/1/2.
|
||||
_ANDREY_DAYS = [
|
||||
(4, 7, 0),
|
||||
(4, 5, 0),
|
||||
(4, 5, 0),
|
||||
(3, 5, 0),
|
||||
(3, 5, 0),
|
||||
(4, 7, 0),
|
||||
(4, 7, 0),
|
||||
(4, 3, 1),
|
||||
(4, 3, 2),
|
||||
(4, 3, 2),
|
||||
(3, 5, 1),
|
||||
(3, 5, 0),
|
||||
(2, 3, 0),
|
||||
]
|
||||
|
||||
|
||||
def _dt(d: date, hh: int = 9) -> datetime:
|
||||
return datetime.combine(d, time(hh), TZ)
|
||||
|
||||
|
||||
def andrey_card() -> PatientCard:
|
||||
start = TODAY - timedelta(days=12)
|
||||
series: list[SeriesPoint] = []
|
||||
for i, (mood, sleep, alc) in enumerate(_ANDREY_DAYS):
|
||||
d = start + timedelta(days=i)
|
||||
series += [
|
||||
SeriesPoint(day=d, metric="mood", value=mood),
|
||||
SeriesPoint(day=d, metric="sleep_hours", value=sleep),
|
||||
SeriesPoint(day=d, metric="alcohol", value=alc),
|
||||
]
|
||||
series += [
|
||||
SeriesPoint(day=start - timedelta(days=3), metric="phq9", value=14),
|
||||
SeriesPoint(day=start - timedelta(days=3), metric="pcl5", value=46),
|
||||
SeriesPoint(day=start - timedelta(days=3), metric="auditc", value=3),
|
||||
SeriesPoint(day=start + timedelta(days=6), metric="phq9", value=13),
|
||||
SeriesPoint(day=start + timedelta(days=6), metric="pcl5", value=44),
|
||||
SeriesPoint(day=TODAY, metric="phq9", value=18, scheduled=False),
|
||||
]
|
||||
doses = [
|
||||
DoseOut(planned_at=_dt(start + timedelta(days=i), 8), drug="сертралин", status="skipped" if i == 4 else "taken")
|
||||
for i in range(13)
|
||||
]
|
||||
alerts = [
|
||||
AlertOut(
|
||||
id=3,
|
||||
token="tok-andrey",
|
||||
patient_name="Андрей С.",
|
||||
ts=_dt(TODAY, 21),
|
||||
level="acute",
|
||||
color="red",
|
||||
code="phq9_item9",
|
||||
reason="PHQ-9 п.9 = 2 (мысли о смерти/самоповреждении)",
|
||||
),
|
||||
AlertOut(
|
||||
id=2,
|
||||
token="tok-andrey",
|
||||
patient_name="Андрей С.",
|
||||
ts=_dt(start + timedelta(days=9), 21),
|
||||
level="moderate",
|
||||
color="yellow",
|
||||
code="alcohol_3d",
|
||||
reason="Алкоголь 3 дня подряд на фоне СИОЗС",
|
||||
),
|
||||
AlertOut(
|
||||
id=1,
|
||||
token="tok-andrey",
|
||||
patient_name="Андрей С.",
|
||||
ts=_dt(start + timedelta(days=9), 21),
|
||||
level="moderate",
|
||||
color="yellow",
|
||||
code="sleep_lt4_3n",
|
||||
reason="Сон < 4 ч три ночи подряд",
|
||||
),
|
||||
AlertOut(
|
||||
id=0,
|
||||
token="tok-andrey",
|
||||
patient_name="Андрей С.",
|
||||
ts=_dt(start + timedelta(days=4), 21),
|
||||
level="high",
|
||||
color="yellow",
|
||||
code="quit_intent",
|
||||
reason="Намерение прекратить приём",
|
||||
acked_by=DOCTOR_ID,
|
||||
acked_at=_dt(start + timedelta(days=5), 11),
|
||||
),
|
||||
]
|
||||
return PatientCard(
|
||||
patient=PATIENTS[0],
|
||||
prescriptions=[Prescription(drug="сертралин", dose="50 мг", instructions="утром после еды", start_date=start)],
|
||||
series=series,
|
||||
doses=doses,
|
||||
alerts=alerts,
|
||||
safety_plan=[
|
||||
"Мои сигналы: не сплю, пью, «всё бессмысленно»",
|
||||
"Что помогает: прогулка с собакой, душ",
|
||||
"Люди и места: брат Олег, спортзал",
|
||||
"Кому звонить: жена, сослуживец Миша",
|
||||
"Специалисты: д-р Смирнова, 8-800-2000-122, 112",
|
||||
"Безопасная среда: ружьё хранится у брата",
|
||||
],
|
||||
summary="За 13 дней: приверженность 12/13, эпизод намерения бросить на 5-й день (снят после звонка врача). "
|
||||
"С 8-го дня сон < 4 ч и алкоголь; к 13-му дню настроение 2/10, внеплановый PHQ-9 = 18 (рост с 13), "
|
||||
"п.9 = 2. Обсудить: безопасность, коррекция терапии сна, алкоголь.",
|
||||
)
|
||||
|
||||
|
||||
def card(token: str) -> PatientCard | None:
|
||||
if token == "tok-andrey":
|
||||
return andrey_card()
|
||||
item = next((p for p in PATIENTS if p.token == token), None)
|
||||
if item is None:
|
||||
return None
|
||||
return PatientCard(patient=item, prescriptions=[], series=[], doses=[], alerts=[], summary=item.last_event)
|
||||
|
||||
|
||||
def all_alerts() -> list[AlertOut]:
|
||||
return andrey_card().alerts
|
||||
Reference in New Issue
Block a user