- An acked crisis is not raised again for the same signal: re-raised only if a newer triggering scale exists
(seen live: AUDIT-C 0/0/0 re-fired the acked PHQ-9 item 9 crisis).
- In an open crisis every non-acute message gets a reply (agent for questions, support otherwise) plus
emergency contacts; acute messages still get the fixed script.
- Questions and medicine mentions without «?» are answered mid-questionnaire; free-text steps (check-in,
side effects, question states) keep parsing their input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A question asked mid-questionnaire, mid-check-in or during a crisis is answered right away (agent →
guidelines/instruction); the pending question is re-shown with its buttons (engine remembers the last
prompt), in a crisis the answer carries the emergency contacts and the crisis stays open until the doctor's
ack. Acute messages still get the fixed crisis script. OpenRouter blocks RF IPs: OPENROUTER_PROXY_URL routes
the agent through an HTTP proxy, as vobot does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gitea Actions: lint + tests, then infra/deploy/deploy.sh over ssh as the dedicated ptsr-deploy user
(secrets DEPLOY_SSH_KEY, DEPLOY_HOST), then an HTTPS smoke check. deploy.sh now ships a tar.gz via scp and
retries every remote step (the host drops some ssh connections); the web build runs as the deploy user so the
next release can clean up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenRouter returns 403 «Access denied by security policy» from the RF server, and parsing that string error
crashed the request (500 instead of the fallback answer). Error parsing is robust now and any agent exception
degrades to the extractive answer. GigaChat tool provider (GigaChat-2-Pro by default) converts the agent's
OpenAI-style history to function_call / role=function; auto mode prefers GigaChat when credentials are set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live run through the API (Andrey, real OpenRouter): onset questions now answered from the instruction
excerpt, «можно пиво?» reaches the agent although the check-in records alcohol, quit intent keeps the single
rule reaction, findings are mentioned only for intake/combination questions, one retry on a provider error,
provider error message logged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With OPENROUTER_API_KEY in a developer's .env the chat agent switched on in dialog tests (real API calls,
spent money, flaky results). An autouse fixture turns the agent off; agent tests pass explicit providers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live-tested on OpenRouter: Flash-Lite answers in 3–8 s with correct findings at ~$0.10/$0.40 per 1M tokens
(qwen3.7-flash and gpt-oss-120b took 26–32 s). Force a tool call on the first turn, give the model the
current prescription, let check_drug_for_patient default to it and return composition, intolerance check
and general interactions (alcohol); retry once on an empty final message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Free-text treatment and medication questions go to an LLM agent with read-only tools bound to the current
patient: guideline search (RAG, doses stripped), ЕМИАС record mock (diagnoses, comorbidities, intolerances,
other specialists' drugs, current prescription), drug info (composition, contraindications, interactions)
and a deterministic patient-specific check. Providers: OpenRouter (temporary, synthetic data only) and an
offline mock; GigaChat to follow. Scrubbed input only, forbidden-output filter, any failure falls back to
the existing extractive RAG / instruction answers. Off unless OPENROUTER_API_KEY or CHAT_AGENT=mock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Patient list sorted red -> yellow -> grey -> green with KPI row, trend arrows,
7-day adherence bars, discontinuation-risk badge with factor tooltip.
- Patient card: scales with severity bands from config/scales (unscheduled marked),
mood/sleep/alcohol small multiples, dose heat-strip, alerts feed with ack,
safety plan, AI summary with disclaimer.
- Live alerts: st.fragment(run_every=2.5s) polling, toast + pinned acute banner on
every page; full rerun on new alert only on doctor pages.
- Connect form -> POST /patients/connect -> code, QR, deep link.
- Thin httpx ApiClient over the frozen schemas with graceful "API недоступен".
- app.py discovers dashboard/pages/*.py automatically (st.navigation); demo pages
of #17 plug in by adding a file.
Refs #16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- personas degrade: /demo/personas → doctor's patients (hero first) → env token
- scenario file guessed from the transliterated first name (andrey.jsonl), like the backend
- «Подключить демо-пациента»: connects the synthetic hero via /patients/connect when no seed
- connect code prefilled from ?code= of the console link
Refs #17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- scripts/seed_demo.py: doctor doc-smirnova (on call), Andrey day 0 (connect code,
no consent) or day N with history; 7 patients with seeded-random histories.
Alerts and colours come from the real rules evaluated day by day.
- --reset drops/recreates both DBs and zeroes the demo clock; seed keeps the
persisted clock offset so dates match the running app.
- data/synthetic/andrey.jsonl: console scenario for days 0..13 generated from
config button labels (--write-scenario).
- tests: idempotency, modes, colours, scenario format/sync and a full replay of
the scenario through the real engine, scheduler and monitoring.
Refs #18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- pages/2_Демо-консоль.py: persona selector, virtual clock bar (+1 h, to 08:00/20:00,
next day, reset with confirm), fallback patient web chat (buttons from Reply.buttons,
kind styling, outbox drained after every action and on a 2 s fragment poll), scenario panel
- pages/3_Сплит-экран.py: patient chat or PATIENT_WEB_URL iframe on the left, compact doctor
panel (patients, live alerts, red banner on a new acute alert) on the right
- console/playback.py: data/synthetic/<persona>.jsonl runner (advance_to/press/text/
expect_color/start), date chaining between days, pause on failure with retry/skip/manual
- console/api.py: own HTTP client over the frozen API contract, ApiError only
Refs #17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Doctor list/card/alerts/ack/connect, dialog events/outbox/history and demo
controls now read the identity and clinical DBs. Rule-based discontinuation
risk index, trend, adherence; every doctor read is audited.
Refs #12
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- menu button «Вопрос о лечении» -> kb.question -> rag.answer (KR quote + source)
or «уточните у врача»; dose questions get a fixed doctor redirect
- free text in idle that looks like a question (no check-in signals, not a drug
question) -> knowledge base; no answer -> the usual support phrase
- routing order unchanged: consent/safety first, side effects/quit/alcohol -> check-in,
own drug -> med_qa (instruction only, SPEC §7 p.5)
- question cues, small talk and texts in config/texts/knowledge.yaml; scrub placeholders
before "что такое X" no longer hide the definition question
Refs #64
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- converter marks a paragraph glued across a page break with ⟦стр. N⟧; the chunker
strips the marker and extends the chunk's page range (wrong-page citations 24 -> 3,
the rest are scale-form table rows split by a page)
- [NAME]/[PHONE] placeholders from privacy.scrub() no longer count as unknown words
(a scrubbed question used to fall below the coverage gate)
- a question naming one disease boosts its guideline in retrieval and passage choice
("ТМС при депрессии" was answered from the PTSD guideline); "антидепрессант" is not a cue
Refs #62
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app/scheduler/demo.py: run_until(now) replays due and fresh events in time order: dose rows +
med_reminder, missed after 2 h without an answer → monitoring, daily_checkin, scale_due by the
config/modules.yaml schedules, daily monitoring pass (silence, missed doses) → reach_out/scale_due
- idempotent via scheduler_events (unique token+key); stale events are not sent; no contact without
consent or during an open crisis
- config/scheduler.yaml: times and windows; runs while the questionnaire release is off
- POST /demo/clock/advance replays the scheduler up to the new time
Refs #13
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- app/services/snapshot.py: PatientSnapshot from the clinical DB (field mapping, age, therapy day,
recent C-SSRS risk, weapon access from dialog facts)
- app/services/monitoring.py: RuleMonitoring registered via register_monitoring; persists alert/crisis
actions, crisis_acute audit, enhanced_monitoring flag, cached status_color, notifies the doctor (and
the doctor on duty for crises) after commit; a running crisis is not restarted
- demo clock offset persisted in clock_state, restored on scheduler start
- tests: Andrey's 13 days through the service → G G G Y Y G G G G Y Y Y R
Refs #38
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- No same-day C-SSRS repeat: a risk screen completed within cssrs_cooldown_hours is not queued again
after PHQ-9 item 9 >= 1 (scale crisis path, rule schedule_scale, scale_due); crisis script and alerts stay.
- Scale intro registry: register_scale_intro(scale_id, text_or_fn); start_scale(intro=None) uses it.
veteran_daily migrated from per-scale on_enter hooks.
- Prefix triggers "button:<prefix>:*" with trigger_payload(event); medication buttons carry the dose
(med:taken:<dose_id>, ...), med_pending dropped; payload-less legacy buttons answer as stale.
- Abandoned-step timeout: settings.abandon_after_hours (default 6 h) returns abandonable states
(daily.*, meds.*, scale.* not in onboarding) to idle before the next event; never in crisis or with intake on.
Refs #56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Daily check-in (mood → dose → sleep/nightmares → alcohol → free text with
extract_checkin, buttons win over the LLM, progressive upsert of
daily_checkins), medication reminder flow (taken / skip with reason /
side effects → doses + daily_checkins), medication question menu via
med_qa, med_info rule reactions from the drug instruction excerpt,
«записаться раньше» request recorded for lane B, friendly intros for
scheduled scales. actions.py: additive med_info handler hook.
Refs #37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Start screening of the veteran module: peer-tone intro, trauma exposure gate for
PC-PTSD-5, PCL-5/PHQ-9/AUDIT-C via scale sessions (sex asked if unknown), weapon
access question stored as facts for lane B with a safe-storage block on risk,
plain-language result, modules enabled in profile, FAQ, resume after a crisis.
Refs #36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deep-link /start <code> binds the chat via IdentityProvider, text and
button presses go to get_engine().handle(), replies render as inline
keyboards and are sent verbatim (no parse_mode). TelegramOutbox delivers
scheduler pushes to bound chats and falls back to the previous outbox.
No-op without TG_BOT_TOKEN; logs carry no text or chat ids.
Refs #11
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfbiLXFJswz2EK3qtWF7S7
build_structured() aggregates a PatientCard into scores, flags and
adherence (no patient text, no names); build_summary() sends it through
the LLM gateway, falls back to a config-driven template, keeps an open
acute alert first and caches per (token, provider, data hash).
Refs #19
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Topic from engine or keyword classifier, section via integrations.drug_info,
sentence retrieval by keyword overlap (dosing sentences dropped), LLM only
rephrases the excerpt through gateway.phrase("med_answer") with forbidden-output
and added-content guards; dose questions and missing answers -> None + fallback text.
Refs #15
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Single entry point app/llm/gateway.py: extract_item, extract_checkin,
risk_classify, phrase, summarize_for_doctor. Two-level design:
LLMProvider (transport, GigaChat) and LLMTasks (PromptedTasks over a
provider, or rule-based MockTasks). Shared policy in the gateway:
assert_scrubbed backstop, confidence threshold 0.5, risk failure -> NONE,
forbidden-output filter with config/texts/phrases.yaml fallbacks.
Refs #7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rule-based PII scrubber: phones, e-mails, SNILS (checksum-aware), passport,
INN, cards, dates of birth, addresses, URLs/handles and person names
(dictionary in config/privacy/names_ru.yaml + declension, patronymic and
surname heuristics). Idempotent via fixed-point iteration; false-positive
guards for ordinary clinical text. pseudo_id normalises SNILS to 11 digits
and returns HMAC-SHA256 truncated to 32 hex chars.
Refs #2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two DeclarativeBases on two engines (ADR-003): identity holds direct
identifiers, clinical holds token-keyed pseudonymised data plus audit_log.
Portable types (JSONB variant, tz-aware UTC timestamps rejecting naive
values) so tests run on SQLite. init_db() runs create_all at startup.
Guardrail #5 checked by AST and import-graph tests.
Refs #6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pydantic schema for config/scales/*.yaml, cached loader, score() with
severity bands (sex-specific for AUDIT-C), rule-based C-SSRS triage,
flags (PHQ-9 item 9, PCL-5 >= 33, PHQ-4 subscales) and delta().
Starting literature thresholds for phq9, gad7, pcl5, auditc, pcptsd5,
cssrs_screen, phq4; final values are approved by the psychiatrist (#8).
Refs #3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One Postgres with isolated identity/clinical databases and roles (ADR-003),
FastAPI /health, Streamlit placeholder, typed settings with offline defaults.
Refs #1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>