Commit Graph
37 Commits
Author SHA1 Message Date
ElebobaandClaude Opus 5.5 7443b61974 feat(llm): speech-to-text for patient voice input via OpenRouter (+ offline mock)
CI / ruff · pytest (pull_request) Successful in 2m57s
POST /api/v1/me/{token}/stt takes a 16 kHz mono WAV and only transcribes:
the patient checks the text and sends it as an ordinary message, so safety,
scrub() and the LLM gateway still apply. Audio is neither stored nor logged.
OpenRouter STT is synthetic-data only (ADR-014); SaluteSpeech for the pilot is #121.

Refs #116

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:19:55 +03:00
ElebobaandClaude Opus 5.5 531bef1e73 feat: crisis message after the test and non-blocking; weapon answer triggers nothing; ЕМИАС demo = Никита
CI / ruff · pytest (pull_request) Successful in 2m33s
- CRISIS_BLOCKING (default off for the demo): a crisis no longer locks the dialog; the message and the
  doctor's acute alert still happen. The clinical protocol (blocking, immediate stop) stays behind the flag and
  keeps its tests.
- Crisis signals inside a questionnaire (intake red flags, high C-SSRS, PHQ-9 item 9) no longer stop it: the
  test is finished and the crisis message comes at the end; the doctor's alert is raised at once/at the end.
- Weapon access: asked and stored, triggers nothing (rule disabled, no red flag, no safety block).
- ЕМИАС demo persona is «Никита В.»; the one-click demo stays «Андрей С.».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 23:30:05 +03:00
ElebobaandClaude Opus 5.5 4b64086ad7 fix(bot): composition questions without «?» are questions
CI / ruff · pytest (pull_request) Successful in 2m44s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:41:43 +03:00
ElebobaandClaude Opus 5.5 dd67905626 fix: chat replies in any situation — no false crisis re-fire, answers in crisis, medicine questions anywhere
CI / ruff · pytest (pull_request) Successful in 2m22s
- 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>
2026-10-04 18:23:03 +03:00
ElebobaandClaude Opus 5.5 c489ddbdc4 feat(bot): chat answers questions in any state; OpenRouter via proxy from the RF server
CI / ruff · pytest (pull_request) Successful in 2m53s
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>
2026-10-04 17:42:03 +03:00
Pavel Eliseev 0743184f3d fix(api): provide pseudonymous patients access to doctor care 2026-10-04 16:47:06 +03:00
Arxip222 37b612b8c4 Merge remote-tracking branch 'origin/dev' into fix/clinical-review-safety
CI / ruff · pytest (pull_request) Successful in 2m22s
2026-10-04 16:21:24 +03:00
Arxip222andClaude Opus 5.5 44814b7293 fix(clinical): red flags, one crisis rule, callback after a crisis, one table of numbers
Clinical review of the questionnaires (ADR-013):
- Red-flag block first in stage 1 (config/instruments/red_flags.yaml); any «Да» stops the
  questionnaire with an acute alert and the help screen of its category.
- One crisis rule: the crisis screen opens at a HIGH C-SSRS risk only; moderate stops and goes
  to a psychiatrist (24 h as a guide), low is a flag with the safety plan. PHQ-9 item 9 with a
  negative screener is a discrepancy flag; without a completed screener it asks the C-SSRS at once.
  config/questionnaire.yaml: cssrs_positive_policy graded (C01).
- Every crisis leaves a callback task for the doctor on duty (post_crisis_call, one per patient)
  and the app asks how the person is the next day, the safety plan one tap away.
- Routing: risk and flags first, scales second; PCL-5 >= 50 no longer means a priority
  appointment (pcl5_severe is moderate on the dashboard too).
- Crisis numbers live only in config/texts/crisis.yaml; texts and the agent prompt refer to them
  via {phone:<key>}. Fixes the children's helpline offered to adults by the chat agent.

Refs #92

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:21:16 +03:00
Pavel Eliseev 7780b484f3 feat(bot): add MAX notifications adapter and operator handoff
CI / ruff · pytest (pull_request) Successful in 2m27s
Refs #83
2026-10-04 16:10:30 +03:00
Arxip222andClaude Opus 5.5 0106aefc33 Merge origin/dev into feat/rag-clinical-guidelines
CI / ruff · pytest (pull_request) Successful in 2m9s
Conflict in app/api/routes.py: /demo/personas keeps the dev version (demo console, DB patients
with scenarios); the patient app gets its own /patient/demo-personas (synthetic ЕМИАС accounts).
Anonymous sign-in now always creates a doctor-less pseudonymous profile, since the dialog API
of #12 serves known profiles only.

Refs #77

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:22:29 +03:00
Arxip222andClaude Opus 5.5 290b0d6151 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>
2026-10-04 15:14:09 +03:00
ElebobaandClaude Opus 5.5 b72af71e66 feat(bot): answer treatment questions from the clinical-guideline knowledge base
CI / ruff · pytest (pull_request) Successful in 1m30s
- 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>
2026-10-04 11:27:42 +03:00
ElebobaandClaude Opus 5.5 fe29ebd629 fix(llm): RAG citation pages, scrub placeholders, disease-aware document choice
CI / ruff · pytest (pull_request) Successful in 1m2s
- 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>
2026-10-04 04:31:59 +03:00
ElebobaandClaude Opus 5.5 6a77831603 feat(llm): RAG knowledge base over clinical guidelines with cited extractive answers
CI / ruff · pytest (pull_request) Successful in 1m30s
- docs/source/rag: PDF sources (KR PTSD 2023, KR depression 2024, meds module spec),
  pdf->txt converter (tables as markdown, page markers, hand-transcribed figures),
  word-level check against pdftotext; fixes HAM-D/MADRS fake headers and split rows
- config/rag: corpus txt + sources.yaml (documents, audiences, retrieval params, synonyms)
- app/llm/rag: Snowball stemmer, section-aware chunker with page citations,
  BM25 + synonym expansion + densest-passage reranking, extractive answer()
  with coverage gate, patient dose/prescribing filter, optional LLM rephrasing
  rejected when it adds content; CLI python -m app.llm.rag
- tests: gold retrieval set (hit@3 >= 15/18), off-topic, safety, faithfulness
- ADR-011

Refs #60

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 04:11:06 +03:00
ElebobaandClaude Opus 5.5 251f946691 feat(backend): demo scheduler on the virtual clock — doses, missed, check-ins, scales, silence
CI / ruff · pytest (pull_request) Successful in 1m3s
- 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>
2026-10-04 03:25:44 +03:00
ElebobaandClaude Opus 5.5 d06720fdab fix(bot): close dialog engine gaps (C-SSRS repeat, scale intros, button payloads, step timeout)
CI / ruff · pytest (pull_request) Successful in 1m19s
- 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>
2026-10-03 19:00:47 +03:00
ElebobaandClaude Opus 5.5 2b13d54ed7 feat(bot): veteran daily check-in, medication reminders and rule reactions
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>
2026-10-03 18:45:53 +03:00
Eleboba 4c6e150162 Merge pull request 'feat(questionnaire): revised intake, diaries and follow-up monitoring' (#53) from feat/52-questionnaire into dev
CI / ruff · pytest (push) Successful in 59s
Reviewed-on: #53
2026-10-03 18:41:31 +03:00
ElebobaandClaude Opus 5.5 0a92266bab feat(bot): veteran onboarding — exposure, PC-PTSD-5, PCL-5, PHQ-9, AUDIT-C, weapon access
CI / ruff · pytest (pull_request) Successful in 32s
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>
2026-10-03 18:37:46 +03:00
Pavel Eliseev c42ef621e5 feat(questionnaire): integrate revised intake and follow-up monitoring
CI / ruff · pytest (pull_request) Successful in 55s
Add gated TSQ, Russian C-SSRS Recent, durable responses and notifications, diaries, schedules and audited clinical review. Preserve manual assessment when the Russian follow-up source is unavailable.

Refs #52
2026-10-03 18:19:34 +03:00
Pavel Eliseev 57b5ad1eb2 feat(core): add verified Russian C-SSRS Recent source content
CI / ruff · pytest (pull_request) Successful in 31s
2026-10-03 17:24:48 +03:00
Pavel Eliseev 3cd7e2fd75 feat(core): add review-gated intake questionnaire rules
Correct RUS-AUDIT-S composition, preserve C-SSRS lifetime behavior, and prepare clinical review materials.

Refs #52
2026-10-03 16:13:36 +03:00
Eleboba f6e4828f01 Merge pull request 'feat(bot): Telegram-адаптер поверх движка диалога' (#51) from feat/11-telegram into dev
CI / ruff · pytest (push) Successful in 31s
2026-10-03 15:45:34 +03:00
ElebobaandClaude Opus 5.5 8fd1202063 feat(bot): Telegram channel over the dialog engine
CI / ruff · pytest (pull_request) Successful in 39s
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
2026-10-03 15:43:51 +03:00
ElebobaandClaude Opus 5.5 fa40be3d94 feat(bot): dialog engine — scrub → safety → FSM → monitoring, consent, age gate, crisis
CI / ruff · pytest (pull_request) Successful in 25s
- Engine.handle registered via register_engine; lexicon-first safety (no LLM on ACUTE),
  fixed crisis script once per open crisis, POSSIBLE → C-SSRS screen
- FSM registry (@state/@on_enter/@trigger) with auto-loaded flows; core flow:
  new → consent → age_gate → module onboarding → idle, crisis, consent_revoked
- reusable scale session (buttons, extract_item + clarify, item 9 / C-SSRS buttons-only,
  crisis flags), execution of monitoring dialog actions, texts in config/texts/dialog.yaml

Refs #10

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:42:36 +03:00
Eleboba 67cfe29c58 Merge pull request 'feat(bot): LLM-саммари динамики для врача' (#49) from feat/19-doctor-summary into dev
CI / ruff · pytest (push) Successful in 24s
2026-10-03 15:34:46 +03:00
ElebobaandClaude Opus 5.5 49d5f8a4e5 feat(bot): add doctor summary built from structured card data
CI / ruff · pytest (pull_request) Successful in 24s
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>
2026-10-03 15:33:37 +03:00
ElebobaandClaude Opus 5.5 51cc259002 feat(bot): med_qa answers about the drug only from ГРЛС excerpts
CI / ruff · pytest (pull_request) Successful in 21s
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>
2026-10-03 15:32:48 +03:00
ElebobaandClaude Opus 5.5 b4731fb1d6 chore(core): stage 1 contracts — dialog, API schemas + fixtures, integrations, monitoring, modules
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>
2026-10-03 15:22:25 +03:00
Eleboba a86008f248 Merge pull request 'feat(core): safety-детектор, лексикон риска и кризисный скрипт' (#33) from feat/4-safety into dev
CI / ruff · pytest (push) Successful in 22s
2026-10-03 15:03:11 +03:00
Eleboba ef2f92816e Merge pull request 'feat(bot): LLM-шлюз — GigaChat + mock, extract / risk / phrase / summarize' (#32) from feat/7-llm-gateway into dev
CI / ruff · pytest (push) Successful in 25s
2026-10-03 15:01:52 +03:00
ElebobaandClaude Opus 5.5 97f3173c9f feat(core): safety detector with RU risk lexicon and fixed crisis script
- check(text, llm_signal) -> SafetyResult: normalisation (ё, homoglyphs, translit,
  repeats), stem regexes per category, negation window (acute -> possible only),
  weapon+risk combination, final level = max(lexicon, llm)
- crisis_reply(categories, audience) loads config/texts/crisis.yaml (patient/family,
  domestic violence and harm-to-others variants)
- 99-phrase eval set; acute recall 100%, < 5 ms per message

Refs #4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:01:35 +03:00
ElebobaandClaude Opus 5.5 924a51c483 feat(bot): LLM gateway with GigaChat and offline mock providers
CI / ruff · pytest (pull_request) Successful in 23s
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>
2026-10-03 15:00:41 +03:00
ElebobaandClaude Opus 5.5 585f0af273 feat(core): implement scrub() and pseudo_id() with tests
CI / ruff · pytest (pull_request) Successful in 24s
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>
2026-10-03 15:00:21 +03:00
ElebobaandClaude Opus 5.5 711c646876 feat(core): rule engine for alerts/agent actions and virtual demo clock
CI / ruff · pytest (pull_request) Successful in 18s
- app/core/clock.py: now()/today(), virtual offset (advance/set_now/reset),
  pluggable offset persistence hook (ADR-005)
- config/rules.yaml + app/core/rules.py: YAML declares rules (params, actions,
  levels, reason_ru), Python evaluators implement predicates by id; dedup of
  open alerts, crisis never suppressed; ADR-006 colours (ADR-007)
- tests: per-rule unit tests, dedup, clock, 13-day demo persona trajectory

Refs #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 14:58:19 +03:00
ElebobaandClaude Opus 5.5 0c8cedc0c6 feat(core): scale engine with YAML schema and deterministic scoring
CI / ruff · pytest (pull_request) Successful in 23s
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>
2026-10-03 14:56:40 +03:00
ElebobaandClaude Opus 5.5 107b8dba51 chore(devops): project skeleton — uv, docker compose, Makefile, CI
CI / ruff · pytest (pull_request) Successful in 1m50s
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>
2026-10-03 14:46:01 +03:00