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>
This commit is contained in:
2026-10-04 15:22:29 +03:00
co-authored by Claude Opus 5.5
82 changed files with 9945 additions and 153 deletions
+12 -5
View File
@@ -40,7 +40,7 @@ async def _patient_with_code(code: str) -> None:
await identity_repo.create_connect_code(s, code=code, token=TOKEN, doctor_id="doc-1", now=clock.now())
async def test_anonymous_tokens_are_fresh_and_store_nothing(client: AsyncClient) -> None:
async def test_anonymous_tokens_are_fresh_and_store_no_personal_data(client: AsyncClient) -> None:
first = (await client.post("/api/v1/auth/anonymous")).json()
second = (await client.post("/api/v1/auth/anonymous")).json()
assert first["mode"] == "anonymous" and first["display_name"] is None
@@ -48,7 +48,9 @@ async def test_anonymous_tokens_are_fresh_and_store_nothing(client: AsyncClient)
async with session.get_identity_session() as s:
assert await identity_repo.get_patient(s, first["token"]) is None
async with session.get_clinical_session() as s:
assert await repo.get_profile(s, first["token"]) is None
# Only a doctor-less pseudonymous profile: the dialog API serves known profiles only.
profile = await repo.get_profile(s, first["token"])
assert profile.doctor_id == "" and profile.modules == []
async def test_anonymous_focus_picks_the_module_without_a_doctor(client: AsyncClient) -> None:
@@ -199,10 +201,10 @@ async def test_meds_reference_is_sourced_and_hides_rule_params(client: AsyncClie
assert all(ind["source"] for d in body["drugs"] for ind in d["indications"].values())
async def test_stage_one_starts_without_a_profile(client: AsyncClient, monkeypatch: pytest.MonkeyPatch) -> None:
# An anonymous token unknown to this database (e.g. after a reset) has no profile and no modules.
async def test_stage_one_starts_without_modules(client: AsyncClient, monkeypatch: pytest.MonkeyPatch) -> None:
# Stage 1 is the same for everyone: it does not wait for a module (it sets them itself).
monkeypatch.setenv("INTAKE_DEMO", "1")
token = "c" * 32
token = (await client.post("/api/v1/auth/anonymous")).json()["token"]
async def press(button: str | None = None) -> list[str]:
event = {"type": "button", "button": button} if button else {"type": "start"}
@@ -213,3 +215,8 @@ async def test_stage_one_starts_without_a_profile(client: AsyncClient, monkeypat
await press()
await press("consent:yes")
assert any(":phq9_1:" in b for b in await press("age:adult"))
async def test_patient_app_has_its_own_demo_personas(client: AsyncClient) -> None:
body = (await client.get("/api/v1/patient/demo-personas")).json()
assert [p["token"] for p in body] == [p.token for p in fixtures.DEMO_PERSONAS]