ERD¶
СУБД: PostgreSQL 16 (обоснование — в «Стеке»). Логи пишутся в ту же БД (FR-C-1). Персональные данные в patients — с ограниченным доступом и шифрованием чувствительных полей, см. «Безопасность».
Схема разбита на пять предметных областей — одна диаграмма на область читается, одна общая на 40 таблиц не читается.
Область 1 — Конфигурация и справочники¶
Основа тиражируемости: УЗ, отделения и вендоры — данные, а не код (FR-C-6).
erDiagram
FACILITIES ||--o{ DEPARTMENTS : "содержит"
FACILITIES ||--o{ PROVIDERS : "подключены к"
FACILITIES ||--o{ USERS : "работают в"
PROVIDERS ||--o{ PROVIDER_ENDPOINTS : "имеет"
PROVIDERS ||--o{ PROVIDER_CREDENTIALS : "имеет"
DEPARTMENTS ||--o{ WARDS : "содержит"
WARDS ||--o{ BEDS : "содержит"
USERS ||--o{ USER_ROLES : "имеет"
ROLES ||--o{ USER_ROLES : "назначена"
FACILITIES {
bigint id PK
string code
string name
string timezone
}
PROVIDERS {
bigint id PK
bigint facility_id FK
string code
enum axis "smp|geo|mis"
string adapter_class
boolean is_active
jsonb settings
}
DEPARTMENTS {
bigint id PK
bigint facility_id FK
string code
string name
string profile "профиль отделения"
}
Область 2 — «Триаж»: транспортный эпизод¶
Группы данных 1–5. Пациент едет; система принимает сообщения и следит за каретой.
erDiagram
PROVIDERS ||--o{ INBOUND_MESSAGES : "источник"
INBOUND_MESSAGES ||--o| BED_REQUESTS : "порождает"
INBOUND_MESSAGES ||--o| TRANSPORT_NOTIFICATIONS : "порождает"
INBOUND_MESSAGES ||--o{ VITAL_OBSERVATIONS : "порождает"
INBOUND_MESSAGES ||--o| DELIVERIES : "порождает"
PATIENTS ||--o{ TRANSPORT_NOTIFICATIONS : "везут"
AMBULANCES ||--o{ TRANSPORT_NOTIFICATIONS : "перевозит"
PRACTITIONERS ||--o{ TRANSPORT_NOTIFICATIONS : "бригада"
TRANSPORT_NOTIFICATIONS ||--o{ VITAL_OBSERVATIONS : "состояние в пути"
TRANSPORT_NOTIFICATIONS ||--o| DELIVERIES : "завершается"
TRANSPORT_NOTIFICATIONS ||--o{ TRANSPORT_EVENTS : "события статуса"
AMBULANCES ||--o{ AMBULANCE_LOCATIONS : "положение"
PROVIDERS ||--o{ LOCATION_REQUESTS : "опрашивается"
LOCATION_REQUESTS ||--o{ AMBULANCE_LOCATIONS : "результат"
INBOUND_MESSAGES {
bigint id PK
bigint provider_id FK
string message_uid UK "идемпотентность"
enum message_type
jsonb raw_body "сырой Bundle"
enum status
timestamptz received_at
}
BED_REQUESTS {
bigint id PK
string external_call_id
enum decision "accepted|rejected|conditional"
string decision_reason
bigint decided_by FK
}
TRANSPORT_NOTIFICATIONS {
bigint id PK
string external_call_id UK
bigint patient_id FK
bigint ambulance_id FK
enum status
enum smp_priority
string reason_text
timestamptz transport_started_at
}
VITAL_OBSERVATIONS {
bigint id PK
bigint notification_id FK
string loinc_code
numeric value_num
string unit_ucum
timestamptz effective_at "ключ партиции"
}
DELIVERIES {
bigint id PK
bigint notification_id FK
timestamptz delivered_at "отсечка СМП"
timestamptz accepted_at "приём в УЗ"
bigint accepted_by FK
}
Две отметки времени доставки, а не одна
delivered_at — момент по данным СМП (transport_complete), accepted_at — подтверждение медработником приёмного отделения. Разница между ними — время ожидания машины у приёмника, управленчески значимая метрика. Одно поле вместо двух сделало бы её неизмеримой. См. «Жизненный цикл».
Область 3 — «Триаж»: обращение, сортировка, очередь¶
Группы данных 6–7. Здесь сходятся оба потока — СМП и самообращения (ADR-5).
erDiagram
APPEALS ||--o| TRANSPORT_NOTIFICATIONS : "если по СМП"
PATIENTS ||--o{ APPEALS : "обращается"
APPEALS ||--o{ TRIAGE_ASSESSMENTS : "оценивается"
APPEALS ||--o| TRIAGE_QUEUE : "стоит в очереди"
APPEALS ||--o{ APPEAL_EVENTS : "события"
APPEALS ||--o| MIS_HANDOFFS : "передаётся в МИС"
APPEALS ||--o| MEDIK_TEAM_HANDOFFS : "сбор бригады"
TRIAGE_ASSESSMENTS }o--|| TRIAGE_RULE_VERSIONS : "по версии правил"
TRIAGE_RULES ||--o{ TRIAGE_RULE_VERSIONS : "версионируется"
TRIAGE_ASSESSMENTS ||--o{ TRIAGE_ASSESSMENT_INPUTS : "входные значения"
APPEALS {
bigint id PK
bigint facility_id FK
bigint patient_id FK "NULL до оформления"
bigint notification_id FK "NULL для самообращений"
enum source "smp|self"
enum status
timestamptz opened_at
timestamptz closed_at
}
TRIAGE_ASSESSMENTS {
bigint id PK
bigint appeal_id FK
enum color "red|yellow|green"
string ticket_code "К43|В34|П45"
enum mode "rules|manual|mass_casualty"
bigint rule_version_id FK
bigint assessed_by FK
timestamptz assessed_at
}
TRIAGE_QUEUE {
bigint id PK
bigint appeal_id FK,UK
enum color
int priority_rank
timestamptz enqueued_at
timestamptz called_at
timestamptz sla_deadline
}
TRIAGE_RULE_VERSIONS {
bigint id PK
bigint rule_id FK
int version
jsonb conditions
enum result_color
boolean is_active
bigint approved_by FK
}
MIS_HANDOFFS {
bigint id PK
bigint appeal_id FK
enum kind "appeal|team_request"
enum status "pending|sent|failed"
string mis_reference
int retry_count
}
Почему сортировка — отдельная таблица, а не поле в обращении
Пациента сортируют более одного раза: при поступлении, при ухудшении в очереди, при пересортировке в режиме массового поступления. История оценок с указанием версии правил и автора — единственный способ разобрать случай постфактум («почему ему дали жёлтый в 9:15»). Поле color в appeals хранило бы только последнее значение и уничтожало бы историю.
TRIAGE_ASSESSMENT_INPUTS фиксирует, какие значения были на входе правила — без этого воспроизвести решение невозможно, потому что витальные показатели меняются.
Область 6 — Журналы и аудит¶
erDiagram
USERS ||--o{ AUDIT_LOG : "действия"
PROVIDERS ||--o{ INTEGRATION_LOG : "обмен"
USERS ||--o{ SECURITY_EVENTS : "события ИБ"
AUDIT_LOG {
bigint id PK
bigint user_id FK
string action
string entity_type
bigint entity_id
jsonb before_state
jsonb after_state
inet ip_address
timestamptz created_at "ключ партиции"
}
INTEGRATION_LOG {
bigint id PK
bigint provider_id FK
enum direction "in|out"
string operation
int http_status
int duration_ms
text error_text
timestamptz created_at "ключ партиции"
}
SECURITY_EVENTS {
bigint id PK
enum event_type
bigint user_id FK
inet source_ip
boolean is_success
jsonb details
timestamptz created_at "ключ партиции"
}
[ТЗ] SECURITY_EVENTS — прямое требование: регистрация событий информационной безопасности с централизованным сбором и хранением не менее одного года (FR-C-2). Отдельно от AUDIT_LOG, потому что у них разные сроки хранения, разные потребители и разные права доступа.
Быстрорастущие таблицы¶
Три таблицы определяют объём БД и требуют партиционирования по времени:
| Таблица | Драйвер роста | Оценка |
|---|---|---|
AMBULANCE_LOCATIONS |
Карет в пути × частота опроса | Средняя |
VITAL_OBSERVATIONS |
Направлений × частота health_status |
Средняя |
SECURITY_EVENTS + AUDIT_LOG + INTEGRATION_LOG |
Все действия, хранение ≥ 1 года | Средняя |
Расчёт ёмкости и политики ретеншна — в «Нефункциональных требованиях». DDL с партициями — в «Схеме БД».