feat(dashboard): demo console with web chat, demo clock, scenario playback and split screen
- 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>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""Demo console pages (#17) render without crashing even when the API is down (robustness of the live demo)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from streamlit.testing.v1 import AppTest
|
||||
|
||||
PAGES = Path(__file__).resolve().parents[1] / "dashboard" / "pages"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def api_down(monkeypatch):
|
||||
monkeypatch.setenv("API_BASE_URL", "http://127.0.0.1:9") # nothing listens on the discard port
|
||||
monkeypatch.setenv("DEMO_PERSONA_TOKEN", "tok-andrey")
|
||||
monkeypatch.delenv("PATIENT_WEB_URL", raising=False)
|
||||
|
||||
|
||||
def _page(name: str) -> AppTest:
|
||||
return AppTest.from_file(str(PAGES / name), default_timeout=30)
|
||||
|
||||
|
||||
def test_console_page_survives_api_down_and_button_clicks(api_down, tmp_path):
|
||||
scenario = tmp_path / "s.jsonl"
|
||||
scenario.write_text('{"day": 1, "note": "утро", "actions": [{"advance_to": "08:00"}]}\n', encoding="utf-8")
|
||||
at = _page("2_Демо-консоль.py")
|
||||
at.session_state["console_scenario_path:tok-andrey"] = str(scenario)
|
||||
at.run()
|
||||
assert not at.exception
|
||||
assert any("Часы недоступны" in w.value for w in at.warning)
|
||||
labels = [b.label for b in at.button]
|
||||
assert {"+1 час", "Следующий день", "🚀 Начать", "▶ Проиграть день 1"} <= set(labels)
|
||||
|
||||
at.button(key="console-clock-d1").click().run() # «Следующий день» with the API down
|
||||
assert not at.exception
|
||||
at.button(key="console-chat-start").click().run()
|
||||
assert not at.exception
|
||||
next(b for b in at.button if b.label == "▶ Проиграть день 1").click().run()
|
||||
assert not at.exception
|
||||
state = at.session_state["console_playback:tok-andrey"]
|
||||
assert state.failed is not None and state.done == [] # playback paused, page alive
|
||||
at.run()
|
||||
assert not at.exception
|
||||
assert any("Повторить шаг" in b.label for b in at.button), [b.label for b in at.button]
|
||||
|
||||
|
||||
def test_console_page_without_scenario_file(api_down):
|
||||
at = _page("2_Демо-консоль.py")
|
||||
at.session_state["console_scenario_path:tok-andrey"] = "data/synthetic/__missing__.jsonl"
|
||||
at.run()
|
||||
assert not at.exception
|
||||
assert any("Сценарий не загружен" in i.value for i in at.info)
|
||||
|
||||
|
||||
def test_split_screen_survives_api_down_and_uses_iframe_when_configured(api_down, monkeypatch):
|
||||
at = _page("3_Сплит-экран.py")
|
||||
at.run()
|
||||
assert not at.exception
|
||||
assert any("Алерты недоступны" in w.value for w in at.warning)
|
||||
|
||||
monkeypatch.setenv("PATIENT_WEB_URL", "http://localhost:5173")
|
||||
at = _page("3_Сплит-экран.py")
|
||||
at.run()
|
||||
assert not at.exception
|
||||
assert any("http://localhost:5173" in c.value for c in at.caption)
|
||||
Reference in New Issue
Block a user