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>
This commit is contained in:
2026-10-04 17:42:03 +03:00
co-authored by Claude Opus 5.5
parent 683a74b004
commit c489ddbdc4
7 changed files with 97 additions and 2 deletions
+45
View File
@@ -0,0 +1,45 @@
"""Questions are answered in any state (side answer): mid-scale the pending question comes back with its
buttons; in a crisis the answer carries the emergency contacts and the crisis stays open."""
from app.core import clock
from app.db import repo, session
from tests.test_dialog_knowledge import TOKEN, buttons, engine, joined, press, say, state # noqa: F401
async def test_question_mid_scale_is_answered_and_question_returns(engine): # noqa: F811
await press(engine, "menu:checkin") # some flow with buttons; then force a scale for determinism
async with session.get_clinical_session() as s:
await repo.set_dialog_state(s, token=TOKEN, state="idle", data={"consent": True}, now=clock.now())
from app.dialog.contracts import Event
first = await engine.handle(TOKEN, Event(type="system", system="scale_due", params={"scale": "phq9"}))
assert buttons(first), "scale question with buttons expected"
st_before, _ = await state()
replies = await say(engine, "Через сколько начнёт действовать лекарство?")
st_after, data = await state()
assert st_after == st_before # still inside the scale
assert "2–4 недели" in joined(replies) or "врач" in joined(replies).lower()
assert buttons(replies), "the pending question is shown again with its buttons"
assert "Вернёмся к вопросу" in joined(replies)
assert data.get("last_prompt")
async def test_question_in_crisis_gets_answer_and_contacts(engine): # noqa: F811
async with session.get_clinical_session() as s:
await repo.set_dialog_state(
s, token=TOKEN, state="crisis", data={"consent": True, "crisis": {"open": True}}, now=clock.now()
)
replies = await say(engine, "Через сколько начнёт действовать лекарство?")
st, _ = await state()
assert st == "crisis"
kinds = [r.kind for r in replies]
assert "crisis" in kinds and len(replies) >= 2
assert "112" in joined(replies)
async def test_non_question_mid_scale_still_goes_to_the_scale(engine): # noqa: F811
from app.dialog.contracts import Event
await engine.handle(TOKEN, Event(type="system", system="scale_due", params={"scale": "phq9"}))
replies = await say(engine, "ну так себе")
assert "Вернёмся к вопросу" not in joined(replies)