Технический проект. Документ 1: Модель данных
Проект: АС «Платформа ОПОРА РОССИИ»
СУБД: PostgreSQL 16
Дата: 26.09.2026
Статус: черновик к согласованию
1. Общие принципы
- Единое хранилище для бота и сайта (п. 4.1.1 ТЗ).
- Первичные ключи —
bigint (identity) либо uuid для внешне адресуемых сущностей (токены, сессии).
- Все таблицы содержат
created_at / updated_at (timestamptz).
- Мягкое удаление (
deleted_at) — для сущностей, где нужна история (карточки, задачи, артефакты).
- Перечисления — через
enum-типы PostgreSQL или text + CHECK (выбор на этапе реализации).
- Денормализованные агрегаты (прирост, рейтинг) — материализованные представления, пересчитываемые Celery-задачей.
- JSONB — для гибких полей (фильтры аудитории, payload аудита).
2. Перечисления (enum)
| Enum |
Значения |
messenger_type |
telegram, max |
user_status |
active, blocked, pending |
role_code |
admin, coordinator, region_head, member, deputy |
login_token_status |
active, used, expired, revoked |
snapshot_kind |
point0, current, history |
member_type |
member, resident |
task_status |
new, in_progress, review, done |
task_priority |
low, medium, high |
request_type |
join, access, event, support |
request_status |
new, in_progress, closed |
broadcast_type |
info, education, rating |
broadcast_status |
draft, scheduled, sending, sent, failed |
recipient_status |
pending, sent, failed |
actor_type |
user, assistant, system |
link_type |
community, chat |
3. Справочники
3.1. federal_districts — федеральные округа (8)
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| name |
text |
NOT NULL, UNIQUE |
«ЮФО», «ЦФО»… |
| code |
text |
UNIQUE |
краткий код |
| created_at |
timestamptz |
NOT NULL |
|
3.2. regions — регионы (89)
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| name |
text |
NOT NULL, UNIQUE |
«Донецкая Народная Республика» |
| federal_district_id |
bigint |
FK → federal_districts |
округ |
| status |
text |
NOT NULL, default active |
|
| created_at / updated_at |
timestamptz |
NOT NULL |
|
3.3. roles — роли
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| code |
role_code |
NOT NULL, UNIQUE |
| name |
text |
NOT NULL |
4. Пользователи и доступ
4.1. users — аккаунты
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| full_name |
text |
|
ФИО |
| messenger_type |
messenger_type |
NOT NULL |
|
| messenger_id |
text |
NOT NULL |
внешний id в мессенджере |
| phone |
text |
|
опционально |
| region_id |
bigint |
FK → regions, NULL |
привязка к региону |
| status |
user_status |
NOT NULL, default active |
|
| created_at / updated_at |
timestamptz |
NOT NULL |
|
Уникальность: UNIQUE (messenger_type, messenger_id).
4.2. user_roles — роли пользователей (с областью действия)
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| user_id |
bigint |
FK → users |
| role_id |
bigint |
FK → roles |
| region_id |
bigint |
FK → regions, NULL (область: своё отделение) |
| granted_by |
bigint |
FK → users, NULL |
| granted_at |
timestamptz |
NOT NULL |
Уникальность: UNIQUE (user_id, role_id, region_id).
4.3. login_tokens — одноразовые ссылки входа
| Поле |
Тип |
Ограничения |
Описание |
| id |
uuid |
PK |
|
| token |
text |
NOT NULL, UNIQUE |
случайный, достаточной длины |
| user_id |
bigint |
FK → users, NULL |
может быть выдан до регистрации |
| region_id |
bigint |
FK → regions, NULL |
|
| issued_by |
bigint |
FK → users (координатор) |
|
| status |
login_token_status |
NOT NULL, default active |
|
| expires_at |
timestamptz |
NOT NULL |
|
| used_at |
timestamptz |
NULL |
|
| created_at |
timestamptz |
NOT NULL |
|
Правило: повторное использование → status = used, ответ «Ссылка уже была использована…» (п. 4.1.5 ТЗ).
4.4. sessions — сессии
| Поле |
Тип |
Ограничения |
| id |
uuid |
PK |
| user_id |
bigint |
FK → users |
| token_hash |
text |
NOT NULL, UNIQUE |
| messenger_type |
messenger_type |
NOT NULL |
| created_at |
timestamptz |
NOT NULL |
| expires_at |
timestamptz |
NOT NULL |
| revoked_at |
timestamptz |
NULL |
5. Цели и метрики
5.1. goals — цели
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| name |
text |
NOT NULL |
«Рост базы членов» |
| description |
text |
|
|
| target_value |
numeric |
|
«+1096» |
| unit |
text |
|
«чел.» |
| is_active |
boolean |
NOT NULL, default true |
|
| created_at / updated_at |
timestamptz |
NOT NULL |
|
5.2. snapshots — срезы
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| goal_id |
bigint |
FK → goals |
|
| snapshot_date |
date |
NOT NULL |
дата среза |
| kind |
snapshot_kind |
NOT NULL |
point0 / current / history |
| source |
text |
|
upload / manual |
| created_at |
timestamptz |
NOT NULL |
|
Правило: для цели ровно один point0; current — последний по дате.
5.3. region_goal_values — значения по регионам
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| snapshot_id |
bigint |
FK → snapshots |
| region_id |
bigint |
FK → regions |
| value |
numeric |
NOT NULL |
| created_at |
timestamptz |
NOT NULL |
Уникальность: UNIQUE (snapshot_id, region_id).
5.4. data_uploads — загрузки выгрузок
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| goal_id |
bigint |
FK → goals |
| file_key |
text |
NOT NULL (MinIO) |
| uploaded_by |
bigint |
FK → users |
| status |
text |
uploaded / processed / failed |
| processed_at |
timestamptz |
NULL |
| created_at |
timestamptz |
NOT NULL |
5.5. Материализованное представление mv_region_metrics
Расчёт (п. 4.3.1 ТЗ):
point0_value — значение среза point0;
current_value — значение последнего current;
growth = current_value − point0_value;
has_dynamics = growth > 0;
freshness_days = now() − snapshot_date (для правила «не старше 7 дней»).
Поля: region_id, goal_id, point0_value, current_value, growth, has_dynamics, snapshot_date, freshness_days, updated_at.
5.6. mv_district_metrics
Агрегат по округу: district_id, goal_id, growth_sum, regions_with_dynamics, regions_total.
6. Карточка региона
6.1. region_cards
| Поле |
Тип |
Ограничения |
| region_id |
bigint |
PK, FK → regions |
| population |
integer |
численность |
| head_user_id |
bigint |
FK → users, NULL |
| updated_at |
timestamptz |
NOT NULL |
6.2. region_links — сообщества и чаты
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| region_id |
bigint |
FK → regions |
| type |
link_type |
NOT NULL |
| title |
text |
|
| url |
text |
NOT NULL |
| created_at / updated_at |
timestamptz |
NOT NULL |
6.3. region_card_history — история изменений
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| region_id |
bigint |
FK → regions |
| field |
text |
NOT NULL |
| old_value |
text |
|
| new_value |
text |
|
| changed_by |
bigint |
FK → users |
| changed_at |
timestamptz |
NOT NULL |
7. Члены организации
7.1. members
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| user_id |
bigint |
FK → users, NULL |
связь с аккаунтом |
| region_id |
bigint |
FK → regions |
|
| full_name |
text |
NOT NULL |
|
| type |
member_type |
NOT NULL, default member |
член / резидент |
| status |
text |
NOT NULL, default active |
|
| joined_at |
date |
|
|
| created_at / updated_at |
timestamptz |
NOT NULL |
|
8. Артефакты
8.1. artifacts
| Поле |
Тип |
Ограничения |
Описание |
| id |
bigint |
PK |
|
| title |
text |
|
|
| file_key |
text |
NOT NULL |
ключ в MinIO |
| file_name |
text |
NOT NULL |
|
| mime_type |
text |
|
|
| size_bytes |
bigint |
|
до 50 МБ (п. 4.2.8.1) |
| region_id |
bigint |
FK → regions, NULL |
привязка |
| goal_id |
bigint |
FK → goals, NULL |
привязка |
| task_id |
bigint |
FK → tasks, NULL |
привязка |
| uploaded_by |
bigint |
FK → users |
|
| deleted_at |
timestamptz |
NULL |
мягкое удаление |
| created_at |
timestamptz |
NOT NULL |
|
9. Задачи (трекер)
9.1. tasks
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| title |
text |
NOT NULL |
| description |
text |
|
| due_date |
date |
|
| priority |
task_priority |
NOT NULL, default medium |
| status |
task_status |
NOT NULL, default new |
| region_id |
bigint |
FK → regions, NULL |
| assignee_user_id |
bigint |
FK → users, NULL |
| assignee_role |
role_code |
NULL |
| created_by |
bigint |
FK → users |
| deleted_at |
timestamptz |
NULL |
| created_at / updated_at |
timestamptz |
NOT NULL |
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| task_id |
bigint |
FK → tasks |
| author_id |
bigint |
FK → users |
| body |
text |
NOT NULL |
| created_at |
timestamptz |
NOT NULL |
10. База знаний
10.1. knowledge_categories
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| name |
text |
NOT NULL, UNIQUE |
10.2. knowledge_articles
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| title |
text |
NOT NULL |
| body |
text |
NOT NULL |
| category_id |
bigint |
FK → knowledge_categories, NULL |
| created_by |
bigint |
FK → users |
| created_at / updated_at |
timestamptz |
NOT NULL |
11. Календарь событий
11.1. events
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| title |
text |
NOT NULL |
| description |
text |
|
| starts_at |
timestamptz |
NOT NULL |
| ends_at |
timestamptz |
|
| location |
text |
|
| created_by |
bigint |
FK → users |
| created_at / updated_at |
timestamptz |
NOT NULL |
11.2. event_reminders
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| event_id |
bigint |
FK → events |
| user_id |
bigint |
FK → users |
| remind_at |
timestamptz |
NOT NULL |
| sent_at |
timestamptz |
NULL |
12. Запросы (заявки)
12.1. requests
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| type |
request_type |
NOT NULL |
| user_id |
bigint |
FK → users |
| region_id |
bigint |
FK → regions, NULL |
| subject |
text |
|
| body |
text |
|
| status |
request_status |
NOT NULL, default new |
| assigned_to |
bigint |
FK → users, NULL |
| created_at / updated_at |
timestamptz |
NOT NULL |
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| request_id |
bigint |
FK → requests |
| author_id |
bigint |
FK → users |
| body |
text |
NOT NULL |
| created_at |
timestamptz |
NOT NULL |
13. Советы от регионов
13.1. advices
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| region_id |
bigint |
FK → regions |
| goal_id |
bigint |
FK → goals, NULL |
| author_user_id |
bigint |
FK → users |
| body |
text |
NOT NULL |
| status |
text |
published / hidden |
| created_at |
timestamptz |
NOT NULL |
14. Рассылки
14.1. broadcast_templates
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| code |
text |
NOT NULL, UNIQUE |
| name |
text |
NOT NULL |
| type |
broadcast_type |
NOT NULL |
| body_template |
text |
NOT NULL (с плейсхолдерами) |
| created_at / updated_at |
timestamptz |
NOT NULL |
14.2. broadcasts
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| template_id |
bigint |
FK → broadcast_templates, NULL |
| type |
broadcast_type |
NOT NULL |
| subject |
text |
|
| body |
text |
|
| audience_filter |
jsonb |
регион/округ/роль/подписка |
| status |
broadcast_status |
NOT NULL, default draft |
| scheduled_at |
timestamptz |
NULL |
| started_at / finished_at |
timestamptz |
NULL |
| created_by |
bigint |
FK → users |
| created_at |
timestamptz |
NOT NULL |
14.3. broadcast_recipients
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| broadcast_id |
bigint |
FK → broadcasts |
| user_id |
bigint |
FK → users |
| status |
recipient_status |
NOT NULL, default pending |
| sent_at |
timestamptz |
NULL |
| error |
text |
NULL |
Уникальность: UNIQUE (broadcast_id, user_id).
15. История платформы
15.1. platform_history
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| title |
text |
NOT NULL |
| body |
text |
|
| event_date |
date |
|
| is_public |
boolean |
NOT NULL, default true |
| created_by |
bigint |
FK → users |
| created_at |
timestamptz |
NOT NULL |
16. Аудит и ассистент
16.1. audit_log
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| actor_user_id |
bigint |
FK → users, NULL |
| actor_type |
actor_type |
NOT NULL |
| action |
text |
NOT NULL |
| entity_type |
text |
|
| entity_id |
text |
|
| payload |
jsonb |
|
| ip |
inet |
|
| created_at |
timestamptz |
NOT NULL |
16.2. assistant_configs
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| owner_user_id |
bigint |
FK → users, UNIQUE |
| enabled |
boolean |
NOT NULL, default false |
| permissions |
jsonb |
разрешённые действия |
| created_at / updated_at |
timestamptz |
NOT NULL |
16.3. assistant_actions
| Поле |
Тип |
Ограничения |
| id |
bigint |
PK |
| owner_user_id |
bigint |
FK → users |
| action |
text |
NOT NULL |
| entity_type |
text |
|
| entity_id |
text |
|
| payload |
jsonb |
|
| status |
text |
ok / failed |
| created_at |
timestamptz |
NOT NULL |
17. ERD (основные связи)
18. Индексы и производительность
| Таблица |
Индекс |
Назначение |
| users |
(messenger_type, messenger_id) UNIQUE |
поиск при входе |
| login_tokens |
(token) UNIQUE, (status, expires_at) |
проверка ссылки |
| region_goal_values |
(snapshot_id, region_id) UNIQUE |
целостность |
| region_goal_values |
(region_id) |
выборки по региону |
| tasks |
(region_id, status), (assignee_user_id, status) |
фильтры трекера |
| artifacts |
(region_id), (task_id), (goal_id) |
привязки |
| broadcast_recipients |
(broadcast_id, status) |
прогресс рассылки |
| audit_log |
(actor_user_id, created_at), (entity_type, entity_id) |
аудит |
| members |
(region_id, type) |
численность |
Материализованные представления mv_region_metrics, mv_district_metrics обновляются Celery-задачей после каждой загрузки выгрузки и по расписанию.
19. Открытые вопросы
- Члены vs пользователи — нужна ли отдельная сущность
members или достаточно users со статусом? (влияет на учёт численности)
- История срезов — хранить все срезы (
history) или только point0 + current?
- Резиденты — отдельная таблица или
member_type?
- Мультирегиональность пользователя — может ли пользователь быть привязан к нескольким регионам?
- Хранение артефактов — MinIO на том же диске (единый диск сервера).
20. Следующие документы
- Документ 2: контракты API (REST).
- Документ 3: макеты экранов (mobile-first).