feat(web): patient web app with ЕМИАС (demo) and anonymous sign-in

A second channel to the same dialog engine as the Telegram bot: consent,
onboarding, scales and check-ins come from app/dialog; the bot is unchanged.

- API: /auth/emias (doctor's one-time connect code via EmiasMock),
  /auth/emias/demo (synthetic personas), /auth/anonymous (random token,
  nothing stored in the identity DB), /crisis/resources from crisis.yaml
- web/: React + TS + Vite, mobile-first, safe areas, hash routing,
  tokens and texts isolated for a Capacitor mobile build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 07:57:00 +03:00
co-authored by Claude Opus 5.5
parent 4d331e4c05
commit b8a93ce9d4
28 changed files with 4697 additions and 5 deletions
+3 -5
View File
@@ -7,6 +7,7 @@ Lane B replaces bodies in #12 without changing paths or schemas (those are the f
from fastapi import APIRouter, HTTPException
from app.api import fixtures
from app.api.patient_web import router as patient_web_router
from app.api.questionnaire import router as questionnaire_router
from app.api.schemas import (
AckIn,
@@ -29,6 +30,7 @@ from app.scheduler import jobs as scheduler
router = APIRouter(prefix="/api/v1")
router.include_router(questionnaire_router)
router.include_router(patient_web_router)
# ------------------------------------------------------------------ doctor dashboard
@@ -114,8 +116,4 @@ async def demo_reset() -> dict[str, str]:
@router.get("/demo/personas", response_model=list[DemoPersona], tags=["demo"])
async def demo_personas() -> list[DemoPersona]:
return [
DemoPersona(
token="tok-andrey", display_name="Андрей С.", therapy_day=13, scenario="data/synthetic/andrey.jsonl"
)
]
return fixtures.DEMO_PERSONAS