feat(dashboard): console fallbacks for the real API (#12)
CI / ruff · pytest (pull_request) Successful in 1m37s

- 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>
This commit is contained in:
2026-10-04 12:50:42 +03:00
co-authored by Claude Opus 5.5
parent 260422138a
commit 8d12aa3d08
5 changed files with 130 additions and 13 deletions
+31 -4
View File
@@ -135,24 +135,51 @@ class ConsoleApi:
params = {"since_id": since_id, "open_only": str(open_only).lower()}
return list(self._request("GET", f"/api/v1/doctors/{doctor}/alerts", params=params))
def connect(self, body: dict[str, Any]) -> dict[str, Any]:
"""`POST /patients/connect` (ConnectPatientIn) → {token, code, deep_link}."""
return dict(self._request("POST", "/api/v1/patients/connect", json=body, timeout=30))
def ack(self, alert_id: int, doctor: str) -> dict[str, Any]:
return dict(self._request("POST", f"/api/v1/alerts/{alert_id}/ack", json={"doctor_id": doctor}))
def personas_or_fallback(api: ConsoleApi) -> tuple[list[dict[str, Any]], str | None]:
"""Demo personas from the API; on failure a single fallback persona (env token) and the error text."""
"""Demo personas for the selector, degrading step by step so the console always has someone to talk as.
1. `GET /demo/personas` (patients with a scenario file);
2. the demo doctor's patient list (real tokens; no scenario file yet, e.g. before #18 lands);
3. a single persona with the token from `DEMO_PERSONA_TOKEN` (fixtures use `tok-andrey`).
Returns the personas and a note for the presenter when a fallback was used.
"""
try:
personas = [p for p in api.personas() if isinstance(p, dict) and p.get("token")]
if personas:
return personas, None
error = "API вернул пустой список персон"
error = "API не вернул персон со сценарием"
except ApiError as exc:
error = str(exc)
try:
patients = [p for p in api.patients(doctor_id()) if isinstance(p, dict) and p.get("token")]
except ApiError:
patients = []
if patients:
# The demo hero first: the scenario file is guessed from the name later (`default_scenario_path`).
patients.sort(key=lambda p: not str(p.get("display_name", "")).startswith("Андрей"))
personas = [
{
"token": p["token"],
"display_name": p.get("display_name") or p["token"],
"therapy_day": p.get("therapy_day", 0),
"scenario": None,
}
for p in patients
]
return personas, f"{error}; показаны пациенты врача"
token = fallback_token()
fallback = {
"token": token,
"display_name": "Андрей С." if token == DEFAULT_TOKEN else token,
"therapy_day": 0,
"scenario": "data/synthetic/andrey.jsonl" if token == DEFAULT_TOKEN else None,
"scenario": None,
}
return [fallback], error
return [fallback], f"{error}; используется токен из окружения"