Browse Source
Расширенный README с разбивкой по фактическим эндпоинтам, моделям БД, сценарию auto-enrollment и отзыва согласия, переменным окружения и скоупу Фазы 1.main
1 changed files with 386 additions and 49 deletions
@ -1,78 +1,415 @@ |
|||||||
# Digital Reception — Фаза 1 |
# Digital Reception — Фаза 1 |
||||||
|
|
||||||
Видеоаналитика рецепции клиники: распознавание лиц, ручной enrollment, согласия, история визитов. Компонент Платформы цифровых сервисов клиники (ПЦС). |
Фаза 1 цифровой рецепции клиники ПЦС: фиксация треков людей по зонам, узнавание пациентов по лицу, ручной enrollment с бумажным согласием, история визитов, аудит биометрического доступа. |
||||||
|
|
||||||
|
Проект — монорепо на pnpm + Turborepo. Backend — NestJS (`apps/api`, `apps/polimed-mock`) и Python/FastAPI (`apps/face-service`). Frontend — Next.js 15 App Router + shadcn/ui (`apps/web-admin`). База — PostgreSQL 16 + pgvector через Prisma. Очереди — BullMQ поверх Redis. Хранилище кадров — MinIO. Распознавание лиц — InsightFace `buffalo_l`, 512-d, cosine. |
||||||
|
|
||||||
ТЗ: [`TZ (1).md`](./TZ%20(1).md). |
ТЗ: [`TZ (1).md`](./TZ%20(1).md). |
||||||
План разработки: `~/.claude/plans/plan-mode-reflective-wand.md`. |
|
||||||
|
## Что реализовано |
||||||
|
|
||||||
|
- Камеры и зоны на уровне БД/API: 8 камер, 5 зон (`A` вход, `B` коридор, `C` стойка ×4, `D` перед кабинетом, `E` в кабинете). |
||||||
|
- Эмбеддинги лиц через InsightFace `buffalo_l` (512-d, cosine). |
||||||
|
- Cross-camera re-id: top-K ближайших эмбеддингов с других камер в окне T минут, отдельный порог `REID_THRESHOLD`. |
||||||
|
- Узнавание пациента по лицу: поиск ближайшего эмбеддинга только среди записей с привязанным `patient_id` и активным согласием. |
||||||
|
- Auto-enrollment на `/capture`: страница ловит кадр с веб-камеры, отправляет в API, та проксирует во face-service. |
||||||
|
- Pgvector-индекс по 512-d эмбеддингам с фильтрами по времени/камере/пациенту. |
||||||
|
- Ручной enrollment: SENIOR_ADMIN сопоставляет трек с пациентом из Полимед-мока и регистрирует ссылку на бумажное согласие. |
||||||
|
- Отзыв согласия → отложенное удаление эмбеддингов: BullMQ-джоба `consent-revocation` с задержкой `CONSENT_REVOKE_DELAY_MS` (prod 24 ч, e2e 5 с). |
||||||
|
- История визитов пациента: события `arrived`/`waiting`/`service_started`/`service_ended`/`left_without_service` по треку, агрегаты длительности ожидания и обслуживания. |
||||||
|
- Dashboard управляющего: KPI за день, события по часам, journey-timeline активных треков, аватары распознанных пациентов, bbox overlay лиц. |
||||||
|
- Аудит биометрического доступа: `BiometryAccessLog` фиксирует каждый просмотр трека/визита, enrollment, отзыв согласия и публичный recognition-probe. |
||||||
|
- Авторизация через HttpOnly cookies (access 15 мин, refresh 30 дн), refresh-токены хранятся в БД с возможностью отзыва. |
||||||
|
- RBAC по ролям: MANAGER, SENIOR_ADMIN, SECURITY, SYSADMIN — на уровне декораторов NestJS. |
||||||
|
- Полимед-мок как отдельный NestJS-сервис: поиск пациентов, расписание приёмов на дату, карточка приёма. |
||||||
|
- Evidence-кадры: MinIO bucket с lifecycle-правилом `--expire-days 30`, presigned URL с TTL `EVIDENCE_PRESIGN_TTL_SECONDS`. |
||||||
|
- Fixtures-runner: e2e-сценарии треков, событий и эмбеддингов через публичные ingest-эндпоинты. |
||||||
|
- Prisma 5, миграции, сид (4 пользователя, 5 зон, 8 камер). |
||||||
|
|
||||||
|
Пока не реализованы: реальная МИС-интеграция Полимед, реальный RTSP/GPU/ByteTrack, поведенческие алерты (Max-бот), production compose, тесты ниже smoke-e2e. |
||||||
|
|
||||||
## Стек |
## Стек |
||||||
|
|
||||||
- **Монорепо:** pnpm workspaces + Turborepo. |
- pnpm workspaces 9 + Turborepo |
||||||
- **БД:** PostgreSQL 16 + pgvector, Prisma 5. |
- NestJS 10 (`apps/api`, `apps/polimed-mock`) |
||||||
- **Backend:** NestJS 10 (`apps/api`, `apps/polimed-mock`), Python + FastAPI (`apps/face-service`, `apps/video-ingest`). |
- Python 3.11 + FastAPI + Uvicorn (`apps/face-service`, `apps/video-ingest`) |
||||||
- **Frontend:** Next.js 15 (App Router) + shadcn/ui + Recharts (`apps/web-admin`). |
- Next.js 15 App Router + Tailwind + shadcn/ui + Recharts (`apps/web-admin`) |
||||||
- **Очереди:** BullMQ (Redis). |
- Prisma 5 |
||||||
- **Хранилище кадров:** MinIO (S3-совместимое). |
- PostgreSQL 16 + pgvector (`pgvector/pgvector:pg16`) |
||||||
- **Распознавание лиц:** InsightFace `buffalo_l`, 512-d cosine. |
- Redis 7 + BullMQ |
||||||
|
- MinIO (S3-совместимое) |
||||||
|
- InsightFace `buffalo_l` |
||||||
|
- JWT (jsonwebtoken) + bcrypt + HttpOnly cookies |
||||||
|
|
||||||
## Структура |
## Структура |
||||||
|
|
||||||
|
```text |
||||||
|
digital-reception/ |
||||||
|
├── apps/ |
||||||
|
│ ├── api/ # NestJS — auth, RBAC, ingest, enrollment, consents, visits, dashboard, audit |
||||||
|
│ ├── face-service/ # Python FastAPI — InsightFace + pgvector + re-id + recognize |
||||||
|
│ ├── polimed-mock/ # NestJS — мок МИС Полимед |
||||||
|
│ ├── video-ingest/ # Python — mp4-консьюмер (скаффолд под Ф0) |
||||||
|
│ ├── fixtures-runner/ # Node — e2e сценарии треков/событий/эмбеддингов |
||||||
|
│ └── web-admin/ # Next.js 15 — админка |
||||||
|
├── packages/ |
||||||
|
│ ├── db/ # Prisma schema, миграции, seed, экспорт типов |
||||||
|
│ ├── tsconfig/ # base / nest / next tsconfig пресеты |
||||||
|
│ └── eslint-config/ # общий ESLint |
||||||
|
├── docker/ |
||||||
|
│ ├── docker-compose.yml # postgres + redis + minio + minio-init (lifecycle) |
||||||
|
│ └── init.sql # CREATE EXTENSION vector |
||||||
|
├── package.json |
||||||
|
├── pnpm-workspace.yaml |
||||||
|
├── turbo.json |
||||||
|
└── README.md |
||||||
``` |
``` |
||||||
apps/ |
|
||||||
api/ # NestJS — auth, RBAC, треки, согласия, визиты, аудит |
|
||||||
face-service/ # Python FastAPI — InsightFace + pgvector + re-id |
|
||||||
polimed-mock/ # NestJS — мок МИС Полимед (вне скоупа Ф1) |
|
||||||
video-ingest/ # Python — минимальный mp4-консьюмер (скаффолд) |
|
||||||
fixtures-runner/ # Node — e2e сценарии треков и событий |
|
||||||
web-admin/ # Next.js 15 + shadcn/ui — админка |
|
||||||
packages/ |
|
||||||
db/ # Prisma schema, миграции, сид |
|
||||||
ui/ # shadcn/ui компоненты (shared) |
|
||||||
tsconfig/ # base / nest / next tsconfig пресеты |
|
||||||
eslint-config/ # общий ESLint |
|
||||||
docker/ |
|
||||||
docker-compose.yml # postgres + redis + minio |
|
||||||
init.sql # CREATE EXTENSION vector |
|
||||||
``` |
|
||||||
|
|
||||||
## Быстрый старт |
## Быстрый запуск |
||||||
|
|
||||||
|
1. Создайте `.env`: |
||||||
|
|
||||||
```bash |
```bash |
||||||
# 1. Инфраструктура |
|
||||||
cp .env.example .env |
cp .env.example .env |
||||||
pnpm docker:up # postgres+redis+minio |
``` |
||||||
|
|
||||||
# 2. Зависимости |
2. Поднимите инфраструктуру: |
||||||
|
|
||||||
|
```bash |
||||||
|
pnpm docker:up # postgres:5434 + redis:6380 + minio:9000/9001 |
||||||
|
``` |
||||||
|
|
||||||
|
3. Установите зависимости и накатите схему: |
||||||
|
|
||||||
|
```bash |
||||||
pnpm install |
pnpm install |
||||||
|
pnpm db:migrate # prisma migrate dev |
||||||
|
pnpm db:seed # 4 пользователя, 5 зон, 8 камер |
||||||
|
``` |
||||||
|
|
||||||
# 3. БД |
4. Запустите сервисы (каждый в своём терминале): |
||||||
pnpm db:migrate # создаёт схему |
|
||||||
pnpm db:seed # 4 юзера, 3 камеры, 3 зоны |
|
||||||
|
|
||||||
# 4. Сервисы (каждый в своём терминале) |
```bash |
||||||
pnpm --filter=@reception/api dev |
pnpm --filter=@reception/api dev # http://localhost:4000 |
||||||
pnpm --filter=@reception/polimed-mock dev |
pnpm --filter=@reception/polimed-mock dev # http://localhost:4100 |
||||||
pnpm --filter=@reception/web-admin dev |
pnpm --filter=@reception/web-admin dev # http://localhost:3000 |
||||||
# face-service: |
|
||||||
cd apps/face-service && uvicorn main:app --reload --port 8001 |
cd apps/face-service && uvicorn main:app --reload --port 8001 |
||||||
``` |
``` |
||||||
|
|
||||||
|
5. Прогон e2e-сценариев: |
||||||
|
|
||||||
|
```bash |
||||||
|
pnpm fixtures:run |
||||||
|
``` |
||||||
|
|
||||||
|
## URL |
||||||
|
|
||||||
|
- Web-admin: http://localhost:3000 |
||||||
|
- Login: http://localhost:3000/login |
||||||
|
- Capture (веб-камера → recognition probe): http://localhost:3000/capture |
||||||
|
- Пациенты: http://localhost:3000/patients |
||||||
|
- Карточка пациента: http://localhost:3000/patients/[id] |
||||||
|
- Enrollment: http://localhost:3000/enrollment |
||||||
|
- Enrollment по треку: http://localhost:3000/enrollment/[trackId] |
||||||
|
- Dashboard: http://localhost:3000/dashboard |
||||||
|
- Аудит: http://localhost:3000/audit |
||||||
|
- Инциденты (заглушка под Ф2): http://localhost:3000/incidents |
||||||
|
- API: http://localhost:4000 |
||||||
|
- API health: http://localhost:4000/health |
||||||
|
- Polimed mock: http://localhost:4100 |
||||||
|
- Face-service: http://localhost:8001 |
||||||
|
- Face-service health: http://localhost:8001/health |
||||||
|
- MinIO console: http://localhost:9001 (login: `minioadmin` / `minioadmin`) |
||||||
|
|
||||||
## Контроль доступа |
## Контроль доступа |
||||||
|
|
||||||
Сидер создаёт 4 dev-пользователя (пароли в `.env.example`): |
Сидер создаёт 4 dev-пользователя (пароли — из `SEED_PASSWORD_*` в `.env.example`): |
||||||
|
|
||||||
|
| Email | Роль | Что видит и делает | |
||||||
|
|-------------------|---------------|-----------------------------------------------------| |
||||||
|
| `manager@local` | MANAGER | Dashboard, история визитов, пациенты | |
||||||
|
| `senior@local` | SENIOR_ADMIN | Enrollment, отзыв согласий, всё что у MANAGER | |
||||||
|
| `security@local` | SECURITY | Лента инцидентов (под Ф2, сейчас заглушка) | |
||||||
|
| `admin@local` | SYSADMIN | Аудит биометрического доступа, всё что у MANAGER | |
||||||
|
|
||||||
|
Логин — `POST /auth/login`, ставит `access_token` (15 мин) и `refresh_token` (30 дн) как HttpOnly cookies. Refresh-токены хранятся в `RefreshToken` и могут быть отозваны на `logout`. |
||||||
|
|
||||||
|
## Backend API |
||||||
|
|
||||||
|
Базовый префикс — `http://localhost:4000`. Если не указано `Public`, эндпоинт требует валидный access-cookie. |
||||||
|
|
||||||
|
Auth: |
||||||
|
- `POST /auth/login` |
||||||
|
- `POST /auth/refresh` |
||||||
|
- `POST /auth/logout` |
||||||
|
- `GET /auth/me` |
||||||
|
|
||||||
|
Камеры и зоны: |
||||||
|
- `GET /cameras` — список из БД с кодом и названием зоны. |
||||||
|
|
||||||
|
Ingest (публичные, для fixtures-runner / video-ingest): |
||||||
|
- `POST /ingest/tracks` — создать трек по имени камеры и `firstSeenAt`. |
||||||
|
- `POST /ingest/track-events` — добавить событие (`arrived`/`waiting`/`service_started`/`service_ended`/`left_without_service`), опциональный `zoneCode` и `evidenceKey`. |
||||||
|
- `POST /ingest/capture-frame` — положить base64-кадр в MinIO и привязать к треку. |
||||||
|
|
||||||
|
Recognition (публичный, но логируется в `BiometryAccessLog`): |
||||||
|
- `POST /recognition/probe` — кадр + `cameraId` + `occurredAt`, проксируется во face-service `/recognize` и возвращает `patientId`/`confidence`/`distance`, если узнан. |
||||||
|
|
||||||
|
Треки и визиты (RBAC): |
||||||
|
- `GET /tracks?status=&shiftDate=` — список треков по фильтру (MANAGER/SENIOR_ADMIN/SYSADMIN). |
||||||
|
- `GET /tracks/:id` — карточка трека, пишется audit `view_track`. |
||||||
|
- `GET /patients` — пациенты, у которых есть визиты. |
||||||
|
- `GET /patients/:patientId/visits` — история визитов, пишется audit `view_patient_visits`. |
||||||
|
|
||||||
|
Enrollment и согласия: |
||||||
|
- `POST /enrollment` (SENIOR_ADMIN) — связать трек с пациентом Полимеда по `polimedPatientId` + `paperConsentRef`; audit `enroll`. |
||||||
|
- `POST /consents/:patientId/revoke` (SENIOR_ADMIN) — ставит `ConsentRevocationJob` со статусом `pending` и BullMQ-задачу с задержкой `CONSENT_REVOKE_DELAY_MS`; audit `consent_revoke`. |
||||||
|
|
||||||
|
Полимед-мок (RBAC): |
||||||
|
- `GET /polimed/patients/search?q=&limit=` |
||||||
|
- `GET /polimed/appointments?date=YYYY-MM-DD` |
||||||
|
- `GET /polimed/appointments/:id` |
||||||
|
|
||||||
|
Dashboard и аудит: |
||||||
|
- `GET /dashboard/overview?date=YYYY-MM-DD` (MANAGER/SENIOR_ADMIN/SYSADMIN). |
||||||
|
- `GET /audit/biometry?actorUserId=&subjectPatientId=&from=&to=&limit=` (SYSADMIN) — максимум 500 записей за один запрос. |
||||||
|
|
||||||
|
Health: |
||||||
|
- `GET /health` |
||||||
|
|
||||||
|
## Face-service |
||||||
|
|
||||||
|
Отдельный Python FastAPI-сервис на InsightFace `buffalo_l`. Модель грузится в `lifespan`. Если задан `SKIP_MODEL_LOAD=true`, frame-эндпоинты возвращают `null` — режим для CI/локальных тестов без CPU-нагрузки. |
||||||
|
|
||||||
|
Эндпоинты: |
||||||
|
|
||||||
|
```text |
||||||
|
GET /health |
||||||
|
POST /embed |
||||||
|
POST /track-embeddings |
||||||
|
POST /track-embeddings/raw |
||||||
|
POST /reid/search |
||||||
|
POST /recognize |
||||||
|
POST /recognize/embedding |
||||||
|
POST /enroll |
||||||
|
DELETE /patient/{patient_id}/embeddings |
||||||
|
GET /patient/{patient_id}/count |
||||||
|
``` |
||||||
|
|
||||||
|
Логика: |
||||||
|
|
||||||
|
- `/embed` — возвращает 512-d вектор лучшего лица на кадре без записи в БД. |
||||||
|
- `/track-embeddings` — сохраняет эмбеддинг с привязкой к треку/камере/моменту, опционально `patient_id`. |
||||||
|
- `/track-embeddings/raw` — сохраняет готовый вектор без детекции лица (только для `fixtures-runner` с синтетикой). |
||||||
|
- `/reid/search` — cross-camera re-id: top-K ближайших эмбеддингов за окно `window_minutes` (по умолчанию `REID_WINDOW_MINUTES=5`), фильтрует по `REID_THRESHOLD`, по умолчанию исключает ту же камеру. |
||||||
|
- `/recognize` — узнавание пациента: ближайший эмбеддинг среди записей с непустым `patient_id`, фильтр по `RECOGNITION_THRESHOLD`. |
||||||
|
- `/recognize/embedding` — то же, но по готовому вектору (для fixtures). |
||||||
|
- `/enroll` — привязывает все эмбеддинги трека к пациенту после согласия. |
||||||
|
- `DELETE /patient/{id}/embeddings` — физическое удаление при отзыве согласия (вызывается из BullMQ-джобы). |
||||||
|
|
||||||
|
Пороги настраиваются в `.env`: |
||||||
|
|
||||||
|
- `RECOGNITION_THRESHOLD=0.5` — узнавание пациента (после первой партии данных тюним по ТЗ §4.3). |
||||||
|
- `REID_THRESHOLD=0.35` — склейка треков cross-camera, строже узнавания, иначе ложные склейки. |
||||||
|
- `REID_WINDOW_MINUTES=5` — окно cross-camera re-id. |
||||||
|
|
||||||
|
## Камеры и зоны |
||||||
|
|
||||||
|
Сидер создаёт 5 зон и 8 камер: |
||||||
|
|
||||||
|
| Камера | Зона | |
||||||
|
|---------------------|-----------------------------------| |
||||||
|
| `cam-entrance` | A — Вход в клинику | |
||||||
|
| `cam-corridor` | B — Коридор / зона ожидания | |
||||||
|
| `cam-reception-1` | C — Стойка рецепции (рабочее место 1) | |
||||||
|
| `cam-reception-2` | C — Стойка рецепции (рабочее место 2) | |
||||||
|
| `cam-reception-3` | C — Стойка рецепции (рабочее место 3) | |
||||||
|
| `cam-reception-4` | C — Стойка рецепции (рабочее место 4) | |
||||||
|
| `cam-doctor-waiting`| D — Перед кабинетом врача | |
||||||
|
| `cam-doctor-office` | E — В кабинете врача | |
||||||
|
|
||||||
|
На рецепции 4 рабочих места — по камере на каждое, поэтому зона `C` содержит 4 камеры. Реальный RTSP-инжест и ByteTrack — в Ф0; в Ф1 потоки эмулируются `fixtures-runner` и страницей `/capture`. |
||||||
|
|
||||||
|
## Auto-enrollment и /capture |
||||||
|
|
||||||
|
`/capture` — страница для демо в браузере без RTSP. Берёт кадр с веб-камеры, отправляет в `POST /api/capture` на стороне Next, та проксирует в `POST /recognition/probe`. Backend кладёт пробу во face-service `/recognize`. Если confidence выше `RECOGNITION_THRESHOLD`, отрисовывается аватар-кружок и bbox распознанного лица; иначе — кадр идёт как новая точка трека и попадает в `enrollment`. |
||||||
|
|
||||||
|
Маршрут API проксирования: `apps/web-admin/src/app/api/capture/`. |
||||||
|
|
||||||
|
## Enrollment |
||||||
|
|
||||||
|
Ручное сопоставление выполняется на: |
||||||
|
|
||||||
|
```text |
||||||
|
http://localhost:3000/enrollment |
||||||
|
http://localhost:3000/enrollment/[trackId] |
||||||
|
``` |
||||||
|
|
||||||
|
Шаги: |
||||||
|
|
||||||
|
1. SENIOR_ADMIN выбирает трек (по умолчанию — новые за смену). |
||||||
|
2. Видит соседние треки через cross-camera re-id (face-service `/reid/search`). |
||||||
|
3. Ищет пациента в Полимед-моке (`GET /polimed/patients/search?q=`). |
||||||
|
4. Подтверждает наличие бумажного согласия и вводит `paperConsentRef`. |
||||||
|
5. `POST /enrollment` со схемой `{ trackId, polimedPatientId, polimedAppointmentId?, paperConsentRef }`. |
||||||
|
|
||||||
| Email | Роль | Что видит | |
Бэкенд: |
||||||
|-------------------|---------------|---------------------------------| |
|
||||||
| `manager@local` | MANAGER | Дашборд, история визитов | |
- создаёт/обновляет `Patient` (Polimed external id), `PatientConsent` (`ConsentAction.granted`) и `Visit`; |
||||||
| `senior@local` | SENIOR_ADMIN | Enrollment, согласия | |
- вызывает face-service `/enroll` — все эмбеддинги трека получают `patient_id`; |
||||||
| `security@local` | SECURITY | Лента инцидентов (Ф2) | |
- пишет `BiometryAccessLog` с `action='enroll'`. |
||||||
| `admin@local` | SYSADMIN | Аудит, пользователи, камеры | |
|
||||||
|
Без `paperConsentRef` запрос валидно отклоняется (`MinLength(1)`). |
||||||
|
|
||||||
|
## Согласия и отзыв |
||||||
|
|
||||||
|
Активное согласие хранится в `PatientConsent` с действием `granted` и ссылкой на бумажный документ. Отзыв: |
||||||
|
|
||||||
|
```text |
||||||
|
POST /consents/:patientId/revoke |
||||||
|
``` |
||||||
|
|
||||||
|
Поведение: |
||||||
|
|
||||||
|
- сразу создаётся запись `PatientConsent` (`ConsentAction.revoked`) и `ConsentRevocationJob` со статусом `pending`; |
||||||
|
- в BullMQ ставится задача с задержкой `CONSENT_REVOKE_DELAY_MS` (prod `86_400_000` = 24 ч, e2e `5000`); |
||||||
|
- `consent-revocation.processor` дожидается задержки, вызывает face-service `DELETE /patient/{id}/embeddings`, выставляет `ConsentRevocationJob.status = completed` и пишет audit; |
||||||
|
- при ошибке статус становится `failed`, задача повторно ставится в очередь. |
||||||
|
|
||||||
|
Прим.: до завершения задержки распознавание для этого пациента ещё может срабатывать — это сознательное поведение под ТЗ (окно на отмену отзыва на бумаге). |
||||||
|
|
||||||
|
## Аудит биометрического доступа |
||||||
|
|
||||||
|
`BiometryAccessLog` пишет каждое касание к биометрии: |
||||||
|
|
||||||
|
- `recognition_probe` — публичный probe из `/capture`; |
||||||
|
- `view_track`, `view_patient_visits` — просмотры в админке; |
||||||
|
- `enroll` — сопоставление трека с пациентом; |
||||||
|
- `consent_revoke` — старт процедуры отзыва. |
||||||
|
|
||||||
|
Запись содержит `actorUserId` (если был JWT), `subjectPatientId`, `action`, `metadata` (JSON), `occurredAt`. Просмотр доступен только SYSADMIN: |
||||||
|
|
||||||
|
```text |
||||||
|
GET /audit/biometry?actorUserId=&subjectPatientId=&from=&to=&limit= |
||||||
|
``` |
||||||
|
|
||||||
|
## Dashboard и история визитов |
||||||
|
|
||||||
|
Dashboard: |
||||||
|
|
||||||
|
```text |
||||||
|
http://localhost:3000/dashboard |
||||||
|
GET /dashboard/overview?date=YYYY-MM-DD |
||||||
|
``` |
||||||
|
|
||||||
|
Возвращает агрегаты по дате (по умолчанию — сегодня): количество визитов, средние ожидание/обслуживание, события по часам, активные треки для journey-timeline. |
||||||
|
|
||||||
|
История визитов пациента: |
||||||
|
|
||||||
|
```text |
||||||
|
http://localhost:3000/patients/[id] |
||||||
|
GET /patients/:patientId/visits |
||||||
|
``` |
||||||
|
|
||||||
|
В карточке пациента — визиты, длительность ожидания (от первого `waiting`/`arrived` до `service_started`), длительность обслуживания (от `service_started` до `service_ended`), эвиденс-кадры с presigned URL. |
||||||
|
|
||||||
|
## Полимед-мок |
||||||
|
|
||||||
|
`apps/polimed-mock` — отдельный NestJS-сервис на `:4100`. Имитирует МИС: 200+ пациентов, расписание приёмов на день, врачей и кабинеты. API: |
||||||
|
|
||||||
|
```text |
||||||
|
GET /polimed/patients/search?q=&limit= |
||||||
|
GET /polimed/appointments?date=YYYY-MM-DD |
||||||
|
GET /polimed/appointments/:id |
||||||
|
``` |
||||||
|
|
||||||
|
API основного сервиса проксирует к мок-сервису через `POLIMED_BASE_URL`. Реальной интеграции в Ф1 нет. |
||||||
|
|
||||||
|
## Переменные окружения |
||||||
|
|
||||||
|
Из `.env.example`: |
||||||
|
|
||||||
|
Postgres / Redis / MinIO: |
||||||
|
|
||||||
|
- `POSTGRES_HOST`, `POSTGRES_PORT=5434`, `POSTGRES_DB=reception`, `POSTGRES_USER`, `POSTGRES_PASSWORD` |
||||||
|
- `DATABASE_URL=postgresql://postgres:postgres@localhost:5434/reception` |
||||||
|
- `REDIS_HOST`, `REDIS_PORT=6380`, `REDIS_URL=redis://localhost:6380` |
||||||
|
- `MINIO_ENDPOINT=http://localhost:9000`, `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`, `MINIO_BUCKET=reception-evidence` |
||||||
|
|
||||||
|
Порты сервисов: |
||||||
|
|
||||||
|
- `API_PORT=4000`, `POLIMED_MOCK_PORT=4100`, `FACE_SERVICE_PORT=8001`, `WEB_ADMIN_PORT=3000` |
||||||
|
|
||||||
|
Service URLs (сервисы видят друг друга): |
||||||
|
|
||||||
|
- `FACE_SERVICE_URL=http://localhost:8001` |
||||||
|
- `POLIMED_BASE_URL=http://localhost:4100` |
||||||
|
- `WEB_ADMIN_ORIGIN=http://localhost:3000` |
||||||
|
- `API_BASE_URL=http://localhost:4000` |
||||||
|
|
||||||
|
Auth: |
||||||
|
|
||||||
|
- `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET` — обязательно поменять в проде. |
||||||
|
- `JWT_ACCESS_TTL=15m`, `JWT_REFRESH_TTL=30d`. |
||||||
|
- `COOKIE_SECURE=false` — для прода `true`. |
||||||
|
|
||||||
|
Сид и пороги: |
||||||
|
|
||||||
|
- `SEED_PASSWORD_MANAGER`, `SEED_PASSWORD_SENIOR`, `SEED_PASSWORD_SECURITY`, `SEED_PASSWORD_SYSADMIN` |
||||||
|
- `REID_THRESHOLD=0.35` — cross-camera re-id (ТЗ §4.3). |
||||||
|
- `RECOGNITION_THRESHOLD=0.5` — узнавание пациента (ТЗ §4.3). |
||||||
|
- `CONSENT_REVOKE_DELAY_MS=86400000` — задержка перед удалением эмбеддингов; для e2e `5000`. |
||||||
|
- `EVIDENCE_PRESIGN_TTL_SECONDS=900` — TTL presigned URL evidence-кадров. |
||||||
|
|
||||||
|
Не коммитьте `.env`, `JWT_*_SECRET`, реальные пароли, дампы БД и MinIO-данные. |
||||||
|
|
||||||
## Скоуп Фазы 1 |
## Скоуп Фазы 1 |
||||||
|
|
||||||
- ✅ `face-service` + pgvector + cross-camera re-id. |
- ✅ `face-service` + pgvector + cross-camera re-id + recognize. |
||||||
- ✅ Web-admin: ручной enrollment, согласия, история визитов, аудит. |
- ✅ Web-admin: ручной enrollment, согласия, история визитов, аудит, dashboard. |
||||||
- ✅ Фиксация бумажных согласий, отзыв → удаление эмбеддингов за 24 ч. |
- ✅ Бумажные согласия, отзыв → отложенное удаление эмбеддингов (24 ч prod / 5 с e2e). |
||||||
|
- ✅ RBAC по ролям MANAGER / SENIOR_ADMIN / SECURITY / SYSADMIN. |
||||||
|
- ✅ Auto-enrollment в `/capture`, journey-timeline, bbox overlay, аватары распознанных. |
||||||
- 🚫 **Полимед API** — замокан `apps/polimed-mock`, реальной интеграции нет. |
- 🚫 **Полимед API** — замокан `apps/polimed-mock`, реальной интеграции нет. |
||||||
- 🚫 **RTSP / GPU / ByteTrack** — работа Фазы 0, в Ф1 только минимальный mp4-скаффолд. |
- 🚫 **RTSP / GPU / ByteTrack** — Ф0; в Ф1 только mp4-скаффолд и `fixtures-runner`. |
||||||
- 🚫 **Поведенческие алерты (Max-бот)** — Фаза 2. |
- 🚫 **Поведенческие алерты, Max-бот, инциденты** — Ф2. |
||||||
|
- 🚫 **Production compose, метрики, rate limits** — Ф2. |
||||||
|
|
||||||
|
## Известные ограничения |
||||||
|
|
||||||
|
- InsightFace `buffalo_l` грузится ~1.5 ГБ моделей при первом старте; в CI используйте `SKIP_MODEL_LOAD=true`. |
||||||
|
- Пороги `REID_THRESHOLD` и `RECOGNITION_THRESHOLD` под ТЗ §4.3 — стартовые, тюним на реальных данных пилота. |
||||||
|
- `evidence_key` для кадров — base64 → MinIO; сырое видео не сохраняется. |
||||||
|
- Cross-camera re-id ограничен окном времени; вне окна треки не склеиваются. |
||||||
|
- Rate-limit и инциденты не реализованы — это работа Ф2. |
||||||
|
- В `packages/` есть только `db`, `eslint-config`, `tsconfig`. Общих UI-компонентов в shared-пакете пока нет; shadcn/ui подтягивается напрямую в `apps/web-admin`. |
||||||
|
|
||||||
|
## Структура БД (Prisma) |
||||||
|
|
||||||
|
Модели: `User`, `RefreshToken`, `Patient`, `PatientConsent`, `Zone`, `Camera`, `Track`, `TrackEvent`, `FaceEmbedding`, `Visit`, `BiometryAccessLog`, `ConsentRevocationJob`. |
||||||
|
|
||||||
|
Энумы: `Role`, `ConsentAction`, `ZoneCode` (`A`–`E`), `TrackStatus`, `TrackEventType`, `ConsentRevocationStatus`. |
||||||
|
|
||||||
|
Схема и миграции — в `packages/db/prisma/`. |
||||||
|
|
||||||
|
## Полезные команды |
||||||
|
|
||||||
|
```bash |
||||||
|
pnpm dev # turbo dev для всех приложений |
||||||
|
pnpm build # turbo build |
||||||
|
pnpm lint # turbo lint |
||||||
|
pnpm typecheck # turbo typecheck |
||||||
|
pnpm test # turbo test |
||||||
|
|
||||||
|
pnpm db:generate # prisma generate |
||||||
|
pnpm db:migrate # prisma migrate dev |
||||||
|
pnpm db:migrate:deploy # prisma migrate deploy (CI/prod) |
||||||
|
pnpm db:seed # 4 пользователя, 5 зон, 8 камер |
||||||
|
pnpm db:studio # Prisma Studio |
||||||
|
|
||||||
|
pnpm docker:up # postgres + redis + minio + minio-init |
||||||
|
pnpm docker:down # остановить инфраструктуру |
||||||
|
pnpm docker:logs # хвост логов compose-файла |
||||||
|
|
||||||
|
pnpm fixtures:run # e2e сценарии треков и эмбеддингов |
||||||
|
``` |
||||||
|
|||||||
Loading…
Reference in new issue