"""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, DemoPersona, DiscontinuationRisk, DoseOut, PatientCard, PatientListItem, SeriesPoint, ) from app.core.safety import fill_phones from app.integrations.base import Prescription TZ = ZoneInfo("Europe/Moscow") TODAY = date(2026, 10, 3) DOCTOR_ID = "doc-smirnova" # Synthetic personas for the demo console and the demo ЕМИАС sign-in of the patient web app. DEMO_PERSONAS: list[DemoPersona] = [ DemoPersona(token="tok-andrey", display_name="Андрей С.", therapy_day=13, scenario="data/synthetic/andrey.jsonl"), ] 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=[ "Мои сигналы: не сплю, пью, «всё бессмысленно»", "Что помогает: прогулка с собакой, душ", "Люди и места: брат Олег, спортзал", "Кому звонить: жена, сослуживец Миша", fill_phones("Специалисты: д-р Смирнова, {phone:helpline}, 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