Browse Source

docs: статус проекта, инструкция dev, обновление всех .md

- PROJECT_STATUS: что сделано (черновики, версии, разбор, каталог) и планы
- DEV_CONTOUR_USER_GUIDE: сценарии для проверяющих на dev-стенде
- README, ТЗ, card1, журнал, бэклоги, шаги 01–11+README, спринты, TEST_TABLES: ссылки и примечания
- backend/PROGRESS: ссылка на PROJECT_STATUS

Made-with: Cursor
dev
Константин Лебединский 2 weeks ago
parent
commit
a68331c86b
  1. 10
      README.md
  2. 54
      backend/PROGRESS.md
  3. 63
      docs/DEV_CONTOUR_USER_GUIDE.md
  4. 72
      docs/PROJECT_STATUS.md
  5. 380
      docs/TEST_TABLES_ANALYSIS.md
  6. 1
      docs/revision_task/BACKLOG.md
  7. 1
      docs/revision_task/BACKLOG_IDEAS.md
  8. 33
      docs/revision_task/TESTING_JOURNAL.md
  9. 24
      docs/revision_task/card1.md
  10. 3
      docs/revision_task/sprint-01-testing.md
  11. 2
      docs/revision_task/sprint-01.md
  12. 73
      docs/revision_task/sprint-02-testing.md
  13. 65
      docs/revision_task/sprint-02.md
  14. 2
      docs/revision_task/task.md
  15. 2
      docs/ТЗ.md
  16. 5
      docs/шаги/01-project-setup.md
  17. 5
      docs/шаги/02-database-design.md
  18. 5
      docs/шаги/03-auth.md
  19. 5
      docs/шаги/04-users-departments.md
  20. 6
      docs/шаги/05-test-management.md
  21. 5
      docs/шаги/06-test-assignment.md
  22. 5
      docs/шаги/07-test-taking.md
  23. 6
      docs/шаги/08-results-review.md
  24. 5
      docs/шаги/09-attempt-tracking.md
  25. 5
      docs/шаги/10-ai-assistant.md
  26. 5
      docs/шаги/11-settings.md
  27. 8
      docs/шаги/README.md

10
README.md

@ -6,10 +6,13 @@
**Дата:** 2026-03-21 **Дата:** 2026-03-21
**Статус:** Согласовано **Статус:** Согласовано
**Актуальное состояние кода (не ТЗ, а «что уже есть»):** [docs/PROJECT_STATUS.md](docs/PROJECT_STATUS.md) · [инструкция для проверяющих на dev](docs/DEV_CONTOUR_USER_GUIDE.md).
--- ---
## Содержание ## Содержание
- [Состояние реализации (сводка)](#состояние-реализации-сводка)
- [Функциональные возможности](#функциональные-возможности) - [Функциональные возможности](#функциональные-возможности)
- [Роли и права доступа](#роли-и-права-доступа) - [Роли и права доступа](#роли-и-права-доступа)
- [Установка и запуск](#установка-и-запуск) - [Установка и запуск](#установка-и-запуск)
@ -19,6 +22,13 @@
--- ---
## Состояние реализации (сводка)
Коротко и по-человечески: [docs/PROJECT_STATUS.md](docs/PROJECT_STATUS.md) (черновики и версии, разбор попыток, список тестов, dev-стенд).
Как пользоваться локальным **dev** без чтения кода: [docs/DEV_CONTOUR_USER_GUIDE.md](docs/DEV_CONTOUR_USER_GUIDE.md).
---
## Функциональные возможности ## Функциональные возможности
### Управление пользователями и подразделениями ### Управление пользователями и подразделениями

54
backend/PROGRESS.md

@ -0,0 +1,54 @@
# Progress — миграция `001_initial` (историческая заметка)
*Актуальное описание продукта и сценариев: [../docs/PROJECT_STATUS.md](../docs/PROJECT_STATUS.md).*
# Progress - Шаг 2: Проектирование базы данных
## Статус: ✅ ЗАВЕРШЕНО
### Выполненные задачи:
1. ✅ **Создание SQL-миграции** (`backend/src/db/migrations/001_initial.sql`)
- Созданы все таблицы:
- `departments` (Подразделения)
- `users` (Пользователи)
- `tests` (Тесты)
- `test_versions` (Версии тестов)
- `questions` (Вопросы)
- `answer_options` (Варианты ответов)
- `test_assignments` (Назначения тестов)
- `test_assignment_targets` (Получатели назначений)
- `test_attempts` (Попытки прохождения)
- `user_answers` (Ответы пользователя)
- `settings` (Настройки)
- Созданы ENUM типы: `user_role`, `target_type`, `attempt_status`
- Созданы индексы для оптимизации запросов
- Добавлены начальные данные в таблицу `settings`
2. ✅ **Создание скрипта миграции** (`backend/src/db/migrate.js`)
- Поддержка выполнения SQL-миграций
- Отслеживание выполненных миграций в таблице `migrations`
- Транзакционное выполнение миграций
- Логирование процесса выполнения
3. ✅ **Создание db.js** (`backend/src/db/db.js`)
- Подключение к PostgreSQL с использованием пула соединений
- Функции: `query()`, `transaction()`, `getClient()`
- Обработка ошибок пула
- Логирование запросов в режиме разработки
4. ✅ **Применение миграций к БД**
- Миграция `001_initial.sql` успешно выполнена
- Все таблицы созданы в базе данных `clinic_tests`
### Созданные файлы:
```
backend/src/db/
├── migrations/
│ └── 001_initial.sql # SQL-миграция с созданием всех таблиц
├── migrate.js # Скрипт для выполнения миграций
└── db.js # Модуль подключения к PostgreSQL
```
### Дата выполнения: 2026-03-21

63
docs/DEV_CONTOUR_USER_GUIDE.md

@ -0,0 +1,63 @@
# Как пользоваться стендом **dev** (простыми словами)
**Для кого:** тот, кто **проверяет** интерфейс на своей машине или на общем dev-сервере, без погрузки в код.
**Адрес по умолчанию:** [http://localhost:8080](http://localhost:8080) — если вы подняли проект командой `docker compose -f docker-compose.dev.yml up` из корня репозитория. Страница и API с одного адреса: запросы к `/api/…` идут на бэкенд за прокси.
Если кто-то дал **другой URL** (например, внутренний хост клиники) — откройте его; логика та же.
---
## 1. Вход
1. Откройте в браузере адрес стенда. Должна открыться **страница входа** (логин, пароль, кнопка «Войти»).
2. Введите **логин и пароль**, которые вам выдали для **этой** базы `clinic_tests` (или HR, если включён `HR_AUTH` — смотрите, что сказал разработчик).
3. После успешного входа вы попадаете в раздел **«Тесты»**. В **шапке** справа: **Фамилия с инициалами** и роль; слева — название портала. **«Выйти»** завершает сессию.
Если не пускает — не подбирайте пароль: напишите тому, кто администрирует БД или `.env` на стенде.
---
## 2. Список «Тесты»
- Каждая **строка** — один тест (одна **цепочка**; номер версии в подписи внизу строки).
- **Слева****название** (клик открывает **карточку** теста: настройки, вопросы, назначения — если вы автор, или краткую информацию — если вам только **назначили**).
- **Справа** — кнопка **«Пройти»**: начать попытку **сразу** по **текущей активной** версии (именно с неё, а не с «старой из назначения»).
- Под названием: **Автор: Вы** — если вы создали тест; **Автор: Фамилия И. О.** — если тест чужой, но вам **назначен**.
Пустой список: либо вам **ничего не назначили** и вы **не создавали** тесты, либо всё **скрыто** из списка (у автора внизу может быть блок «Скрытые вами»).
---
## 3. Карточка теста (автор)
- **Содержание:** название, порог зачёта, вопросы и варианты, отметка верных ответов. **Сохранить черновик** — записывает правки. Если по тесту **уже были прогоны**, при изменении **содержимого** система заведёт **новую версию** (и предупредит, что так и задумано).
- **История версий** — посмотреть все версии, **сделать активной** другую (с подтверждением). Новые «Пройти» пойдут с активной.
- **Публикация** — скрыть цепочку из общего списка или вернуть обратно.
- **Прогоны и разбор** (если есть завершённые попытки) — таблица; можно открыть **разбор** по вопросам.
- **Импорт из файла** (если на стенде настроен ключ к LLM) — загрузить документ, получить **черновик**, вставить в редактор, затем снова **сохранить черновик** по правилам версий.
Автор **не** запускает экзамен **с карточки** в том же сценарии, что сотрудник: для самопрохождения — **«Пройти»** в **списке** «Тесты».
---
## 4. Прохождение и разбор (сотрудник или самопроверка автора)
1. В списке нажмите **«Пройти»** у нужного теста.
2. Ответьте на вопросы, нажмите **«Завершить тест»**.
3. Увидите **сводку** (сколько верно, процент, зачёт/незачёт) и **разбор по вопросам** (что отмечено, что было верно). Есть ссылка на **отдельную страницу разбора** — удобно, если нужно вернуться позже.
---
## 5. Что сказать разработчику, если «что-то не так»
- **«Белый экран» / ошибка** — сделайте скриншот и опишите, **на какой странице** (например, «после Войти» или «после Пройти»).
- **«Не вижу тест»** — уточните, вы **автор** или вам должны были **назначить**; проверьте блок **скрытых** тестов.
- **«Сохранил, а версия не та»** — скажите, были ли **уже** попытки у других: после первой попытки **любая** смена содержимого **увеличивает** номер версии **специально**, чтобы старые ответы не «переписывались».
---
## 6. Где почитать подробности для разработки
- [PROJECT_STATUS.md](PROJECT_STATUS.md) — что в целом сделано и что в планах.
- [../README.md](../README.md) — Docker, БД, переменные окружения.

72
docs/PROJECT_STATUS.md

@ -0,0 +1,72 @@
# Состояние проекта (человеческий обзор)
**Репозиторий:** [TestingWebApp](https://git.pirogov.ai/l_konstantin/TestingWebApp) · ветка разработки: **`dev`**
**Дата среза:** 2026-04-24
Этот документ — не дублирование ТЗ, а **короткое объяснение**, что уже работает в коде и что логично делать дальше. Подробные задачи: [revision_task/card1.md](revision_task/card1.md), [revision_task/BACKLOG.md](revision_task/BACKLOG.md).
---
## Что уже сделано (как это устроено)
### Вход и роли
- Сотрудник входит по **логину и паролю** (сессия через cookie + JWT).
- В шапке показываются **роль** и **Фамилия с инициалами** (например, *Иванов И. О.*), полное ФИО — во всплывающей подсказке.
- В **режиме разработки** (`NODE_ENV=development`) у удобного тестирования могут быть дополнительные кнопки (например, создание теста сотрудником — `devUi` в ответе `/api/auth/me`).
### «Цепочка» теста и черновики
- У каждого теста есть **одна логическая цепочка** в базе: все правки вопросов относятся к ней, но **версия контента** (`v1`, `v2`, …) может расти.
- **Пока никто не проходил** этот тест — автор правит **на месте**: сохраняет черновик, и меняется текущая активная версия **без** лишнего дублирования строк в истории.
- **Как только по цепочке появилась хотя бы одна завершённая попытка** — каждое **содержательное** сохранение с изменениями создаёт **новую версию** (новый номер, старая остаётся в истории). Старые результаты остаются привязаны к **той** версии, с которой человек реально отвечал.
- **Активная версия** — та, с которой сейчас стартуют новые попытки. Автор может **вручную** переключить активную версию в таблице истории (с подтверждением), если бизнесу так нужно.
- **Публикация:** тест можно **скрыть из общего списка** (цепочка остаётся в базе; автор видит скрытые в отдельном блоке и может вернуть в список).
### Список тестов и доступ
- В каталоге **«Тесты»** видны цепочки, где вы **автор**, и тесты, **назначенные вам** (через назначение на пользователя; в dev назначения обычно **включены**).
- Под названием показывается **«Автор: Вы»** для своих тестов и **«Автор: Фамилия И. О.»** для чужих (назначенных).
- **Пройти** тест — кнопка **справа** в строке; **карточка** теста — клик по названию **слева** (попытка с карточки не стартует сама).
### Прохождение и результат
- Открывается экран вопросов (один или несколько верных вариантов); после **«Завершить тест»** — итог: сколько верно, процент, **зачёт** по порогу.
- **Разбор:** после сдачи показывается, по **каждому вопросу**, что выбрал пользователь и какие варианты верны. Отдельная страница разбора доступна по ссылке; **автор** на карточке теста видит раздел **«Прогоны и разбор»** по завершённым попыткам.
### Импорт и ИИ (MVP)
- Можно загрузить **файл** (PDF, DOCX, текст): сервер **извлекает текст** и при настроенном ключе **LLM** (например, `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` в окружении) предлагает **черновик** вопросов. Дальше тот же поток, что и при ручном редактировании: правки → **сохранить черновик** (с учётом правил версий выше).
- **Полный** набор сценариев из ТЗ (отдельная страница настроек ключа, «проверить тест целиком», модалки с чекбоксами и т.д.) — в [sprint-02](revision_task/sprint-02.md); часть уже заложена в сервисах, UI доводится.
### Назначения (MVP)
- **Автор** на карточке теста может **назначить** сотрудников из справочника (в dev — через поиск/каталог, если фича включена в `docker-compose` / `.env`). Назначение **не** перепривязывается автоматически к каждой новой версии контента: **старт попытки** всегда берёт **текущую активную** версию на момент нажатия **«Пройти»**.
### Интеграция с HR (в зачатке)
- Поддержан сценарий **входа через учётки HR** (`HR_AUTH` + `HR_DATABASE_URL`) для проверок на одном кластере Postgres с экосистемой `Postgres_TG_Bots` (см. [README — установка](../README.md)).
- Целевой **RBAC** из HR-таблиц — [card1, часть A](revision_task/card1.md#часть-a--авторизация-по-паролю-бд-postgres_tg_bots); сейчас — упрощённое сопоставление ролей.
---
## Что в планах (логичный следующий слой)
| Направление | Суть |
|-------------|------|
| **AI по ТЗ §4.2** | Ключ в настройках (не на клиенте), кнопки «сгенерировать/проверить/улучшить» с превью и подтверждением, регресс с версиями. |
| **Дашборды (ТЗ этап 2)** | Единая картина по отделу / клинике, фильтры, история. |
| **MAX / мини-приложение** | Встраивание в общий HR-контур клиники. |
| **Таймер, подсказки, медиа в вопросах** | Режимы прохождения и вложения — отдельные этапы ТЗ. |
| **E2E и интеграционные тесты** | Расширение `V.9`, стабильный CI. |
| **Назначения** | Сроки, лимит попыток, назначения «по отделу» (частично в бэклоге [BACKLOG_IDEAS](revision_task/BACKLOG_IDEAS.md)). |
Журнал приёмок и чек-листы: [TESTING_JOURNAL.md](revision_task/TESTING_JOURNAL.md).
---
## Связанные файлы
- [Руководство пользователя dev-контура](DEV_CONTOUR_USER_GUIDE.md)
- [README с установкой](../README.md)
- [Карта задач card1](revision_task/card1.md)

380
docs/TEST_TABLES_ANALYSIS.md

@ -0,0 +1,380 @@
# Анализ таблиц для тестирования сотрудников
*Модуль **TestingWebApp** использует отдельную БД `clinic_tests` (см. [PROJECT_STATUS.md](PROJECT_STATUS.md) и [README.md](../README.md)). Ниже — разбор **наследуемых** / смежных сущностей в другой схеме, для сравнения и миграционных дискуссий.*
## Обзор существующих таблиц
В базе данных существуют следующие таблицы, связанные с тестированием:
### 1. [`training_questions`](hr_web_viewer/models.py) - Вопросы обучения
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `position` | text | Должность/категория |
| `test_type` | text | Тип темы/теста |
| `question` | text | Текст вопроса |
| `answer_1` - `answer_12` | text | Варианты ответов |
| `answer_count` | smallint | Количество правильных ответов |
**Проблемы:**
- Отсутствует явное указание правильного ответа
- Нет типа вопроса (одиночный/множественный выбор, текстовый, сопоставление)
- Нет баллов за вопрос
- Нет порядка вопросов
- Поле `position` используется как категория, но не связано с должностями
### 2. [`training_results`](hr_web_viewer/models.py) - Результаты обучения
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `telegram_id` | bigint | ID сотрудника |
| `correct_answers` | integer | Правильные ответы |
| `total_questions` | integer | Всего вопросов |
| `score` | integer | Балл |
| `completed_at` | timestamp | Дата завершения |
| `passed` | boolean | Пройден/не пройден |
**Индексы:**
- `idx_training_results_telegram_id` - по telegram_id
**Проблемы:**
- Нет связи с конкретным тестом (test_type)
- Нет количества попыток
- Нет детализации по ответам
### 3. [`training_settings`](hr_web_viewer/models.py) - Настройки обучения
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `position` | varchar(100) | Должность |
| `question_count` | integer | Количество вопросов (по умолчанию 10) |
| `passing_score` | integer | Проходной балл (по умолчанию 70) |
| `time_limit` | integer | Ограничение времени в минутах (по умолчанию 30) |
| `active` | boolean | Активен/неактивен |
**Индексы:**
- `idx_training_settings_position` - по position
**Проблемы:**
- Нет связи с категорией теста
- Нет ограничения количества попыток
- Нет настройки случайного порядка вопросов
### 4. [`test_assignments`](hr_web_viewer/models.py) - Назначения тестов
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `la_name` | text | Название адаптации |
| `intern_fio` | text | ФИО стажера |
| `user_credentials` | text | Учетные данные |
| `test_theme` | text | Тема теста |
| `test_subtheme` | text | Подтема теста |
| `attempts_allowed` | integer | Количество попыток |
| `passing_score` | integer | Проходной балл |
| `la_id` | integer | Ссылка на адаптацию |
| `intern_id` | bigint | ID сотрудника (staff_members.id) |
| `deadline` | timestamp | Срок сдачи |
**Внешние ключи:**
- `intern_id` -> `staff_members(id)`
- `la_id` -> `learning_adaptations(id)`
**Проблемы:**
- Назначения привязаны к конкретным сотрудникам, а не к должностям
- Нет статуса прохождения
- Нет связи с результатами
### 5. [`test_table`](hr_web_viewer/models.py) - Таблица тестов
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `name` | varchar(100) | Название теста |
**Проблемы:**
- Минимальная структура, практически не используется
### 6. [`corp_groups_tester`](hr_web_viewer/models.py) - Тестировщики (корпоративные группы)
| Колонка | Тип | Описание |
|---------|-----|----------|
| `id` | integer | Первичный ключ |
| `fio` | text | ФИО |
| `telegram_id` | varchar(20) | Telegram ID |
| `position` | varchar(200) | Должность |
| `department` | varchar(200) | Отдел |
| `phone` | varchar(20) | Телефон |
| `email` | varchar(100) | Email |
| `hire_date` | date | Дата приема |
**Проблемы:**
- Дублирует данные staff_members
- Не используется в текущей системе
---
## Рекомендуемая расширенная схема для ClinicTestingApp
### Новые таблицы
#### 1. `tests` - Основные тесты
```sql
CREATE TABLE tests (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, -- Название теста
description TEXT, -- Описание
category VARCHAR(100), -- Категория (тема)
position VARCHAR(100), -- Должность (nullable - для всех)
question_count INTEGER DEFAULT 10, -- Количество вопросов в тесте
time_limit_minutes INTEGER, -- Ограничение времени (null = без ограничений)
attempts_allowed INTEGER DEFAULT 3, -- Количество попыток
passing_score_percent INTEGER DEFAULT 70, -- Проходной процент
random_questions BOOLEAN DEFAULT FALSE, -- Случайный порядок вопросов
is_active BOOLEAN DEFAULT TRUE, -- Активен
created_by INTEGER, -- ID администратора
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_tests_category ON tests(category);
CREATE INDEX idx_tests_position ON tests(position);
```
#### 2. `test_questions` - Вопросы тестов
```sql
CREATE TABLE test_questions (
id SERIAL PRIMARY KEY,
test_id INTEGER NOT NULL REFERENCES tests(id) ON DELETE CASCADE,
question_text TEXT NOT NULL, -- Текст вопроса
question_type VARCHAR(50) NOT NULL, -- single_choice, multiple_choice, text, matching, ordering
points INTEGER DEFAULT 1, -- Баллы за вопрос
question_order INTEGER, -- Порядок вопроса
explanation TEXT, -- Пояснение к ответу
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_test_questions_test_id ON test_questions(test_id);
```
#### 3. `test_answers` - Ответы на вопросы
```sql
CREATE TABLE test_answers (
id SERIAL PRIMARY KEY,
question_id INTEGER NOT NULL REFERENCES test_questions(id) ON DELETE CASCADE,
answer_text TEXT NOT NULL, -- Текст ответа
is_correct BOOLEAN DEFAULT FALSE, -- Правильный ответ
answer_order INTEGER, -- Порядок (для сопоставления/порядка)
points_if_correct INTEGER DEFAULT 1 -- Баллы (если отличаются от question.points)
);
CREATE INDEX idx_test_answers_question_id ON test_answers(question_id);
```
#### 4. `test_assignments_extended` - Расширенные назначения тестов
```sql
CREATE TABLE test_assignments_extended (
id SERIAL PRIMARY KEY,
test_id INTEGER NOT NULL REFERENCES tests(id) ON DELETE CASCADE,
staff_id INTEGER NOT NULL REFERENCES staff_members(id) ON DELETE CASCADE,
assigned_by INTEGER, -- ID администратора
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deadline TIMESTAMP, -- Срок сдачи
attempts_allowed INTEGER, -- Переопределение количества попыток (null = из теста)
status VARCHAR(50) DEFAULT 'pending', -- pending, in_progress, completed, expired
UNIQUE(test_id, staff_id)
);
CREATE INDEX idx_test_assignments_test_id ON test_assignments_extended(test_id);
CREATE INDEX idx_test_assignments_staff_id ON test_assignments_extended(staff_id);
```
#### 5. `test_attempts` - Попытки прохождения
```sql
CREATE TABLE test_attempts (
id SERIAL PRIMARY KEY,
assignment_id INTEGER NOT NULL REFERENCES test_assignments_extended(id) ON DELETE CASCADE,
attempt_number INTEGER NOT NULL, -- Номер попытки
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
completed_at TIMESTAMP, -- Завершена
score_points INTEGER, -- Набрано баллов
score_percent NUMERIC(5,2), -- Процент
passed BOOLEAN, -- Пройден/не пройден
time_spent_seconds INTEGER -- Потраченное время
);
CREATE INDEX idx_test_attempts_assignment_id ON test_attempts(assignment_id);
```
#### 6. `test_answers_given` - Ответы пользователя
```sql
CREATE TABLE test_answers_given (
id SERIAL PRIMARY KEY,
attempt_id INTEGER NOT NULL REFERENCES test_attempts(id) ON DELETE CASCADE,
question_id INTEGER NOT NULL REFERENCES test_questions(id) ON DELETE CASCADE,
given_answer_ids INTEGER[], -- ID выбранных ответов (для choice)
given_text TEXT, -- Текстовый ответ
is_correct BOOLEAN, -- Правильный/неправильный
points_earned INTEGER, -- Полученные баллы
answered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_test_answers_given_attempt_id ON test_answers_given(attempt_id);
```
#### 7. `test_categories` - Категории тестов
```sql
CREATE TABLE test_categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
parent_id INTEGER REFERENCES test_categories(id),
is_active BOOLEAN DEFAULT TRUE
);
```
#### 8. `test_reports` - Сформированные отчеты
```sql
CREATE TABLE test_reports (
id SERIAL PRIMARY KEY,
report_type VARCHAR(50) NOT NULL, -- department, employee, category
parameters JSONB, -- Параметры отчета
file_path VARCHAR(500), -- Путь к файлу
format VARCHAR(10), -- pdf, xlsx
generated_by INTEGER, -- ID администратора
generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
---
## Расширение существующих таблиц (миграции)
### training_questions
```sql
-- Добавить тип вопроса
ALTER TABLE training_questions ADD COLUMN IF NOT EXISTS question_type VARCHAR(50) DEFAULT 'single_choice';
-- Добавить баллы
ALTER TABLE training_questions ADD COLUMN IF NOT EXISTS points INTEGER DEFAULT 1;
-- Добавить правильный ответ (индекс)
ALTER TABLE training_questions ADD COLUMN IF NOT EXISTS correct_answer_index INTEGER;
-- Добавить порядок
ALTER TABLE training_questions ADD COLUMN IF NOT EXISTS sort_order INTEGER;
-- Добавить пояснение
ALTER TABLE training_questions ADD COLUMN IF NOT EXISTS explanation TEXT;
```
### training_results
```sql
-- Добавить связь с тестом
ALTER TABLE training_results ADD COLUMN IF NOT EXISTS test_id INTEGER REFERENCES tests(id);
-- Добавить номер попытки
ALTER TABLE training_results ADD COLUMN IF NOT EXISTS attempt_number INTEGER DEFAULT 1;
-- Добавить время прохождения
ALTER TABLE training_results ADD COLUMN IF NOT EXISTS time_spent_seconds INTEGER;
-- Добавить детализацию ответов (JSON)
ALTER TABLE training_results ADD COLUMN IF NOT EXISTS answers_detail JSONB;
```
### training_settings
```sql
-- Добавить связь с тестом
ALTER TABLE training_settings ADD COLUMN IF NOT EXISTS test_id INTEGER REFERENCES tests(id);
-- Добавить категорию
ALTER TABLE training_settings ADD COLUMN IF NOT EXISTS category VARCHAR(100);
-- Добавить случайный порядок
ALTER TABLE training_settings ADD COLUMN IF NOT EXISTS random_order BOOLEAN DEFAULT FALSE;
```
---
## Связь с staff_members
Текущая проблема: используется `telegram_id` для связи с сотрудниками.
Решение: перейти на использование `staff_members.id` как универсального идентификатора:
```sql
-- Добавить staff_id в training_results
ALTER TABLE training_results ADD COLUMN IF NOT EXISTS staff_id INTEGER REFERENCES staff_members(id);
-- Миграция данных
UPDATE training_results tr
SET staff_id = sm.id
FROM staff_members sm
WHERE tr.telegram_id = sm.telegram_id;
-- Создать внешний ключ после миграции
ALTER TABLE training_results
ADD CONSTRAINT training_results_staff_id_fkey
FOREIGN KEY (staff_id) REFERENCES staff_members(id);
```
---
## Типы вопросов
| Тип | Код | Описание |
|-----|-----|----------|
| Одиночный выбор | `single_choice` | Один правильный ответ из нескольких |
| Множественный выбор | `multiple_choice` | Несколько правильных ответов |
| Текстовый ответ | `text` | Свободный текст |
| Сопоставление | `matching` | Сопоставление пар |
| Порядок элементов | `ordering` | Расстановка в правильном порядке |
---
## API Endpoints (рекомендуемые)
### Тесты
- `GET /api/tests` - Список тестов
- `POST /api/tests` - Создать тест
- `GET /api/tests/{id}` - Получить тест с вопросами
- `PUT /api/tests/{id}` - Обновить тест
- `DELETE /api/tests/{id}` - Удалить тест
### Вопросы
- `GET /api/tests/{test_id}/questions` - Список вопросов
- `POST /api/tests/{test_id}/questions` - Добавить вопрос
- `PUT /api/questions/{id}` - Обновить вопрос
- `DELETE /api/questions/{id}` - Удалить вопрос
### Назначения
- `GET /api/assignments` - Список назначений
- `POST /api/assignments` - Назначить тест
- `GET /api/employees/{id}/assignments` - Назначения сотрудника
### Прохождение
- `POST /api/tests/{id}/start` - Начать тест
- `POST /api/attempts/{id}/answer` - Ответить на вопрос
- `POST /api/attempts/{id}/complete` - Завершить тест
### Отчеты
- `GET /api/reports/department` - Отчет по отделениям
- `GET /api/reports/employee/{id}` - Отчет по сотруднику
- `GET /api/reports/category/{id}` - Отчет по категории
- `GET /api/reports/export` - Экспорт отчета (PDF/Excel)

1
docs/revision_task/BACKLOG.md

@ -5,6 +5,7 @@
**Карта больших кусков работ:** [card1.md](card1.md) (версии **V**, документ **D**, авторизация **HR A**). **Карта больших кусков работ:** [card1.md](card1.md) (версии **V**, документ **D**, авторизация **HR A**).
**Идеи и пожелания (простой язык):** [BACKLOG_IDEAS.md](BACKLOG_IDEAS.md) **Идеи и пожелания (простой язык):** [BACKLOG_IDEAS.md](BACKLOG_IDEAS.md)
**Журнал проверок по спринтам (авто + ручные шаги для заказчика):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md) **Журнал проверок по спринтам (авто + ручные шаги для заказчика):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
**Сводка «что сделано / что дальше» (простым языком):** [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · [инструкция для dev-стенда](../DEV_CONTOUR_USER_GUIDE.md)
**Этап 1 (ТЗ §4)** — пять фич: 4.1–4.5 (части можно параллелить). **Этап 1 (ТЗ §4)** — пять фич: 4.1–4.5 (части можно параллелить).
**Этап 2 (ТЗ §5)** — дашборды. **Этап 2 (ТЗ §5)** — дашборды.

1
docs/revision_task/BACKLOG_IDEAS.md

@ -3,6 +3,7 @@
*Язык простой, без жаргона разработки. Сюда попадает всё, что всплыло в обсуждениях и ещё не вошло в жёсткое ТЗ.* *Язык простой, без жаргона разработки. Сюда попадает всё, что всплыло в обсуждениях и ещё не вошло в жёсткое ТЗ.*
**Как пользоваться:** приоритеты и «да/нет» фиксируем отдельно; пункты **не** удаляем — переносим в раздел **Решено** с кратким итогом, если идея закрыта или отклонена. **Как пользоваться:** приоритеты и «да/нет» фиксируем отдельно; пункты **не** удаляем — переносим в раздел **Решено** с кратким итогом, если идея закрыта или отклонена.
**Что уже в продукте (кратко):** [../PROJECT_STATUS.md](../PROJECT_STATUS.md).
--- ---

33
docs/revision_task/TESTING_JOURNAL.md

@ -8,9 +8,13 @@
Ниже в разделе B таблица — **журнал уже прошедших** шагов. Новые шаги приходят **сначала в чат**, потом дублируются сюда. Ниже в разделе B таблица — **журнал уже прошедших** шагов. Новые шаги приходят **сначала в чат**, потом дублируются сюда.
**Ветка / коммит последней привязки:** `dev` (обновлять при релизе на проверку) **Ветка / коммит последней привязки:** `dev` (обновлять при релизе на проверку; актуализация документации 2026-04-24 — [../PROJECT_STATUS.md](../PROJECT_STATUS.md))
**Адрес стенда (когда появится):** *(заполнить)* **Адрес стенда:** `http://localhost:8080` (UI; при стеке `docker compose -f docker-compose.dev.yml up` — тот же origin для `/api/…`).
**Актуальный UI (после 2026-04-24):** старт прохождения — **не** с карточки теста, а со **списка «Тесты»**: в каждой строке **справа** кнопка **«Пройти»**; **слева** — ссылка на карточку. Под названием — **«Автор: Вы»** или **«Автор: Фамилия И. О.»**; в шапке — **Фамилия И. О.**, полное ФИО в подсказке. После **«Завершить тест»** — **разбор** по вопросам; у автора в карточке — **«Прогоны и разбор»** по завершённым попыткам. Шаги **S1-07** и **S1-13** в таблице ниже описывают **старый** вариант («Старт/Начать попытку» на карточке) — оставлены в журнале как история. Регресс по новому потоку — **S1-14** и далее.
**Текущий шаг для ручной проверки (код в чате = тот же номер):** **S1-14** — см. раздел B.
--- ---
@ -24,7 +28,7 @@
|---|----------------|--------|------| |---|----------------|--------|------|
| A1 | В проекте есть миграция базы: связь версий «родитель» (`parent_id`) и правило «только одна активная версия на тест» | [x] `002_…sql` | 2026-04-24 | | A1 | В проекте есть миграция базы: связь версий «родитель» (`parent_id`) и правило «только одна активная версия на тест» | [x] `002_…sql` | 2026-04-24 |
| A2 | Линтер (`npm run lint`): **0 errors**; остаются **warnings** `no-console` в существующих файлах | готово (errors) | 2026-04-24 | | A2 | Линтер (`npm run lint`): **0 errors**; остаются **warnings** `no-console` в существующих файлах | готово (errors) | 2026-04-24 |
| A3 | `npm test` в `backend/`: hasAny + проверка Werkzeug-совместимых хешей (`src/**/*.test.js`) | [x] готово | 2026-04-25 | | A3 | `npm test` в `backend/`: hasAny, Werkzeug, V.9 smoke, **D.2** `documentExtract``src/**/*.test.js` (10+ тестов) | [x] готово | 2026-04-25 |
| A4 | Запрос «здоров ли сервер» по адресу `/api/health` при запущенном backend | [x] `{"status":"ok"}` | 2026-04-24 | | A4 | Запрос «здоров ли сервер» по адресу `/api/health` при запущенном backend | [x] `{"status":"ok"}` | 2026-04-24 |
| A5 | Реализация card1: API тестов/версий, черновик, HR-login (опц.), D.1 upload, UI списка/версий/черновика (в `dev`) | [x] код | 2026-04-25 | | A5 | Реализация card1: API тестов/версий, черновик, HR-login (опц.), D.1 upload, UI списка/версий/черновика (в `dev`) | [x] код | 2026-04-25 |
@ -38,11 +42,26 @@
|-----|------------------------------|-----------|------| |-----|------------------------------|-----------|------|
| S1-00 | Открыть `TESTING_JOURNAL.md`, просмотреть верх и раздел B; в таблице — строка S1-00 «ожидает…» | ОК | 2026-04-23 | | S1-00 | Открыть `TESTING_JOURNAL.md`, просмотреть верх и раздел B; в таблице — строка S1-00 «ожидает…» | ОК | 2026-04-23 |
| S1-01 | Открыть `card1.md`, убедиться, что есть блок про V.1 / V.2 / V.3 (сохранение / форк) | ОК | 2026-04-24 | | S1-01 | Открыть `card1.md`, убедиться, что есть блок про V.1 / V.2 / V.3 (сохранение / форк) | ОК | 2026-04-24 |
| | *(дальше — по мере выдачи шагов в переписке)* | | | | S1-02 | Открыть в браузере `http://localhost:8080` — должна загрузиться **страница входа** (заголовок «Клинические тесты» / «Войдите в систему», поля логин и пароль, кнопка «Войти») | ОК | 2026-04-23 |
| S1-03 | В браузере открыть `http://localhost:8080/api/health` — в ответе виден JSON c полем `status` со значением `ok` (страница не «404» и не пустая ошибка) | ОК | 2026-04-23 |
| S1-04 | С экрана входа войти: учётка **вашей** среды (локальный `users` в `clinic_tests` **или** при `HR_AUTH=1` — логин HR). После **«Войти»** должен открыться экран **«Тесты»** с **шапкой** (слева бренд, справа **Фамилия И. О.**/роль и кнопка **«Выйти»**; не обязательно полное тройное ФИО в одну строку). Список тестов может быть пустым. | ОК | 2026-04-24 |
| S1-05 | На экране «Тесты» в поле **«Новый тест — название»** ввести любое имя, нажать **«Создать»**. Должен открыться экран карточки теста (ссылка «← к списку», блок **Версии**, черновик и т.д.). | ОК | 2026-04-24 |
| S1-06 | На карточке теста в блоке **«Черновик (V.3)»** (при необходимости изменить текст вопроса) нажать **«Сохранить черновик»**. Под кнопкой появляется пояснение (например, что черновик применён) или пусто без ошибки на красном. Раздел **Версии** остаётся / обновляется без сообщения «Доступ запрещён». | ОК | 2026-04-24 |
| S1-07 | В блоке **«Прохождение (V.4)»** нажать **«Старт попытки»**. Под кнопкой/рядом появляется сообщение, что **попытка стартовала** (с id или без), без «Доступ запрещён» / без красного текста с ошибкой API. | ОК | 2026-04-24 |
| S1-08 | Нажать **«← к списку»** и убедиться, что **ваш тест** отображается в списке (название, строка с v… и фрагментом id активной версии). | ОК | 2026-04-24 |
| S1-09 (опц.) | В шапке нажать **«Выйти»** — должен открыться экран входа. Снова **«Войти»** с теми же данными — снова экран **«Тесты»** (список на месте). | ОК | 2026-04-24 |
| S1-10 | **История версий** (card1 V.7): в карточке теста видны **заголовок**, таблица версий (версия, активна, дата). Если **≥2** версий — нажать **«сделать активной»** на неактивной, согласиться в confirm; в таблице **текущая** переносится; в списке **«Тесты»** в метке строки обновился **фрагмент id** активной версии. | ОК | 2026-04-25 |
| S1-11 | **Публикация / V.6**: **«Скрыть из списка»** — в верхнем списке теста нет; на странице «Тесты» внизу блок **«Скрытые вами из списка»** — открыть карточку — **«Снова показать в списке»** — тест снова в верхнем списке. | ОК | 2026-04-25 |
| S1-12 | В блоке **«Содержание: вопросы…»** задать вопрос(ы) и варианты, отметить верные, **«Сохранить черновик»** — без красной ошибки; **История версий** / заголовок обновляются при необходимости. | ОК | 2026-04-24 |
| S1-13 | **«Начать попытку»** — открывается экран с вопросами (радио/чекбоксы); **«Завершить тест»** — виден результат: правильно из N, %, сравнение с порогом, без 400 «нет вопросов» при сохранённых вопросах. | ОК | 2026-04-24 |
| S1-14 | Экран **«Тесты»** (`/tests`): у строки с тестом **справа** видна кнопка **«Пройти»**; **слева** клик по названию открывает **карточку** без автоматического старта попытки. | *ожидает* | — |
| S1-15 | Со **списка** нажать **«Пройти»** у теста с сохранёнными вопросами: открывается экран попытки; **«Завершить тест»** — результат (корректно из N, %, порог), без красной ошибки API. | *ожидает* | — |
| S1-16 | **Карточка в режиме «не автор»** (сотрудник / другой пользователь): **нет** кнопки «Начать попытку»; есть короткий текст, что пройти тест из **каталога** кнопкой «Пройти» справа. | *ожидает* | — |
| S1-17 (опц.) | **Автор** в карточке своего теста: раздела **«Прохождение»** с «Начать попытку» **нет**; после **«Сохранить черновик»** сообщение о статусе — **под** кнопками в блоке **«Содержание: название, порог, вопросы»**. | *ожидает* | — |
*Старые номера S1-01… сведём к той же таблице, когда появятся экраны; формулировки шагов вы получите **только** в чате, по одному.* *Старые номера S1-01… сведём к той же таблице, когда появятся экраны; формулировки шагов вы получите **только** в чате, по одному.*
**Итог спринта 1:** дата __________ комментарий заказчика одной фразой: _________________________ **Итог спринта 1:** дата **2026-04-25** комментарий заказчика одной фразой: **смоук + V.6–V.7 (S1-02…S1-11) и сценарий черновик→прохождение (S1-12, S1-13) пройдены; карточка card1 в объёме приёмки сценария закрыта, остаётся бэклог D.2+ / V.9 E2E**
--- ---
@ -66,11 +85,11 @@
| Спринт | Тема простыми словами | Раздел A | Раздел B | | Спринт | Тема простыми словами | Раздел A | Раздел B |
|--------|------------------------|----------|----------| |--------|------------------------|----------|----------|
| 1 | Версии, история прогонов | приём (код в dev) | 2 + очередь S1-02+ | | 1 | Версии, история прогонов | приём (код в dev) | S1-02…S1-13 ОК; **регресс UI:** S1-14… *(в процессе)* |
| 2 | *(по мере появления)* | | | | 2 | *(по мере появления)* | | |
--- ---
*Связанные файлы: [sprint-01-testing.md](sprint-01-testing.md) (черновик чек-листа), [card1.md](card1.md) (задачи).* *Связанные файлы: [sprint-01-testing.md](sprint-01-testing.md) (черновик чек-листа), [card1.md](card1.md) (задачи).*
**Очередь ручного приёма card1 (шаги по одному в чате, затем в таблицу B):** S1-02 — миграции и старт; S1-03 — сценарий тест (черновик / попытка / смена активной); S1-04 — (при `HR_AUTH=1`) вход HR. Первый шаг после внедрения: см. **одно** задание в чате от ассистента. **Очередь (по запросу / спринт 2):** закрыть **S1-14**–**S1-17** (новый сценарий «Пройти»); затем — регресс после релизов; **D.2–D.5**; **V.9** E2E; углубление V.8 (назначения / «мои тесты») по card1.

24
docs/revision_task/card1.md

@ -36,8 +36,8 @@
| V.5 | API: `GET /tests` (роль) — только активные цепочки; `GET /tests/:id/versions`; `POST /tests/:id/versions/:vid/activate` | 403/404 по политике | | V.5 | API: `GET /tests` (роль) — только активные цепочки; `GET /tests/:id/versions`; `POST /tests/:id/versions/:vid/activate` | 403/404 по политике |
| V.6 | API: `PATCH /tests/:id` (деактивация цепочки `tests.is_active` или отдельное поле) | Список пустеет, данные на месте | | V.6 | API: `PATCH /tests/:id` (деактивация цепочки `tests.is_active` или отдельное поле) | Список пустеет, данные на месте |
| V.7 | UI автора: номер/метка версии, предупреждение при «после первой попытки», экран **история версий**, кнопка **сменить активную** (с confirm) | Смоук `sprint-01-testing.md` | | V.7 | UI автора: номер/метка версии, предупреждение при «после первой попытки», экран **история версий**, кнопка **сменить активную** (с confirm) | Смоук `sprint-01-testing.md` |
| V.8 | UI списки сотрудника/автора: **один** ряд на цепочку, без дублей версий | | | V.8 | UI списки сотрудника/автора: **один** ряд на цепочку, без дублей версий | `GET /tests/:id/summary` + упрощённая карточка для не-автора; список `GET /tests` с JOIN на активную версию |
| V.9 | Интеграционные тесты API + регресс «разбор старой попытки» по старым `question_id` | — | | V.9 | Интеграционные тесты API + регресс «разбор старой попытки» по старым `question_id` | `backend/src/integration/v9card1.test.js` при `CLINIC_TESTS_INTEGRATION=1` и миграциях; без БД skip |
| V.10 | *Продукт:* при новой версии `test_assignments` **не** переносим на новый `test_version_id`; старт попытки — по **активной** версии (см. [task.md §2.6](task.md)) | Зафиксировано в ТЗ | | V.10 | *Продукт:* при новой версии `test_assignments` **не** переносим на новый `test_version_id`; старт попытки — по **активной** версии (см. [task.md §2.6](task.md)) | Зафиксировано в ТЗ |
--- ---
@ -87,3 +87,23 @@
- Проверки и журнал: [TESTING_JOURNAL.md](TESTING_JOURNAL.md) - Проверки и журнал: [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
- Старый чек-лист: [sprint-01-testing.md](sprint-01-testing.md) - Старый чек-лист: [sprint-01-testing.md](sprint-01-testing.md)
- Анализ таблиц (если ведёте): [TEST_TABLES_ANALYSIS.md](../TEST_TABLES_ANALYSIS.md) - Анализ таблиц (если ведёте): [TEST_TABLES_ANALYSIS.md](../TEST_TABLES_ANALYSIS.md)
---
## Статус реализации (сводно, 2026-04-25+)
| ID | Статус | Комментарий |
| --- | --- | --- |
| V.1–V.6, V.7, V.10 | Код + API + UI приёмка | [TESTING_JOURNAL S1-10, S1-11](TESTING_JOURNAL.md); публикация, скрытые, история. |
| V.8 | Расширен MVP | `GET /api/tests`**только свои** (`created_by`) **и** тесты с **назначением** на `users.id` (`test_accessService.js`). Карточка/попытка/starter/chain — та же логика. Старт **«Пройти»** в списке. Назначение: `POST /api/tests/:id/assign` (только **автор**), при production — `CLINIC_ASSIGNMENT_ENABLED=1` (`featureFlags.js`); в `development` включено. UI: блок «Назначение сотрудникам» + `GET /api/auth/dev/assignment-directory` при `assignmentUi` из `/api/auth/me`. |
| V.9 | Частично | + `v9card1.test.js`: 0 попыток → in-place; после попытки → форк, старая попытка на старых `version_id` / `question_id`. Запуск: `CLINIC_TESTS_INTEGRATION=1` + `DATABASE_URL` (или DB_*), `npm run migrate`. |
| D.1 | Готово | `POST /tests/import/document`, 400/413, multipart. |
| D.2 | Готово | `documentExtractService.js`: PDF (`pdf-parse`), DOCX (`mammoth`), TXT/MD. |
| D.3 | MVP (импорт) | `documentGenService.js`: вызов Chat Completions (DeepSeek по умолчанию или OpenAI), JSON-черновик → `generation.draft` в `POST /import/document`; `LLM_NO_JSON=1` при несовместимости API. |
| D.4 | MVP | Карточка теста: выбор файла, превью, «Вставить в поле вопроса» → тот же черновик. |
| D.5 | Готово | Временный файл удаляется после чтения; Nginx `client_max_body_size 10m`. |
| A.1–A.4 | Код + compose | `HR_AUTH` + `HR_DATABASE_URL` в `docker-compose.dev.yml`. |
| A.5 | MVP | `mapHrRoleToApp` без полного RBAC из HR-таблиц. |
| UI | 2026-04+ | Список тестов: подпись **автора** (Вы / Фамилия И. О.); шапка: **Фамилия И. О.** Разбор попыток: API + UI после `submit` и маршрут `/tests/:id/attempts/:aid/review` ([PROJECT_STATUS.md](../PROJECT_STATUS.md)). |
**Следующий шаг по card1:** **V.9** — довести supertest/HTTP-регресс при необходимости; **D.3+** — отдельные кнопки в редакторе (сгенерировать/проверить/улучшить), ключ в БД, `/settings` (см. [sprint-02](sprint-02.md)); **A.5**`staff_role_assignments` / HR API; по желанию назначения по **отделу**. Навигация по сценариям: [DEV_CONTOUR_USER_GUIDE.md](../DEV_CONTOUR_USER_GUIDE.md).

3
docs/revision_task/sprint-01-testing.md

@ -2,6 +2,8 @@
**Актуальный ведённый журнал (авто + ручные шаги, ответы ОК/не ОК):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md) — *этот файл остаётся как подробный черновик сценариев*. **Актуальный ведённый журнал (авто + ручные шаги, ответы ОК/не ОК):** [TESTING_JOURNAL.md](TESTING_JOURNAL.md) — *этот файл остаётся как подробный черновик сценариев*.
**Сводка «что в коде»:** [../PROJECT_STATUS.md](../PROJECT_STATUS.md).
Ниже: **автоматизировано / проверено при разработке** и **ручная приёмка** — дублирует структуру; статусы переносите в `TESTING_JOURNAL.md`. Ниже: **автоматизировано / проверено при разработке** и **ручная приёмка** — дублирует структуру; статусы переносите в `TESTING_JOURNAL.md`.
**Окружение:** зафиксировать ветку/коммит, URL стенда, тестовые учётки (роль автора, роль сотрудника). **Окружение:** зафиксировать ветку/коммит, URL стенда, тестовые учётки (роль автора, роль сотрудника).
@ -64,4 +66,3 @@ _Сценарии для прогона в «боевом» темпе, без
--- ---
**Итог приёмки спринта 1:** дата __________, подпись/комментарий _________________________

2
docs/revision_task/sprint-01.md

@ -10,6 +10,8 @@
**Данные:** БД `clinic_tests` на общем кластере; сотрудник в сценариях — `staff_members.id`; `telegram_id` — только справка; RBAC — из HR. См. [card1 (вступление)](card1.md#хранение-связь-с-сотрудниками-rbac-зафиксировано). **Данные:** БД `clinic_tests` на общем кластере; сотрудник в сценариях — `staff_members.id`; `telegram_id` — только справка; RBAC — из HR. См. [card1 (вступление)](card1.md#хранение-связь-с-сотрудниками-rbac-зафиксировано).
**Текущая реализация (сводка):** [../PROJECT_STATUS.md](../PROJECT_STATUS.md).
--- ---
## Цель спринта ## Цель спринта

73
docs/revision_task/sprint-02-testing.md

@ -0,0 +1,73 @@
# Спринт 2 — тестирование (AI-помощники)
**Состояние продукта:** [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · чек-лист импорта и API — ниже.
**Предпосылка:** спринт 1 (версии) принят.
**Секреты:** для стенда допускается отдельный ключ DeepSeek; в логах/скриншотах **не** светить полный ключ.
**MVP 2026-04 (импорт + LLM, до полного спринта 2):** в `backend` задать `DEEPSEEK_API_KEY` *или* `OPENAI_API_KEY``POST /api/tests/import/document` при загрузке файла возвращает `generation.draft` → в карточке теста, блок «Импорт из файла», кнопка **«Применить сгенерированный черновик»** → затем **«Сохранить черновик»**. Юнит-тесты: `documentGenService.test.js`.
---
## 1. Автоматизировано и self-check (разработка)
_Отмечайте [ ] → [x] по мере выполнения._
### 1.1 Безопасность и API
- [ ] Эндпоинты настроек не возвращают сырой ключ на клиент
- [ ] Ошибка при отсутствии/невалидном ключе — структурированная, с кодом/текстом для UI
- [ ] «Проверить подключение» при валидном ключе — успех; при фейле — сообщение об ошибке сети/401 и т.д.
### 1.2 Моки / контракты (по возможности)
- [ ] Парсинг JSON-ответов LLM: при невалидном JSON — пользователю сообщение, не 500 без текста
- [ ] Unit-тесты маппинга «ответ LLM → черновик теста/вопроса» (на фикстурах)
### 1.3 Смоук перед передачей
- [ ] Сгенерировать тест: только с названием; превью; применить — вопросы в редакторе
- [ ] Проверить тест: модалка с текстом
- [ ] Улучшить вопрос: чекбоксы, применение частично
- [ ] Дистракторы: к существующим ответам добавилось 3 варианта
- [ ] После применения AI-изменений сохранение теста согласовано с правилами **версий** (если были попытки)
---
## 2. Ручная приёмка (я / заказчик)
### 2.1 Настройки
- [ ] Сохранить ключ, обновить страницу — приложение не показывает ключ, но AI-функции работают
- [ ] Очистить/испортить ключ — AI показывает ошибку и **есть** переход/ссылка на настройки
- [ ] «Проверить подключение» отражает реальное состояние (успех/ошибка)
### 2.2 Уровень теста
- [ ] **Сгенерировать тест** недоступен при пустом названии; при заполненном — выдаёт осмысленный черновик, применяется **целиком** по кнопке
- [ ] **Проверить тест** — рекомендации читаемы, модалка закрывается, данные теста не портит без явного применения
- [ ] **Предложить улучшение** — сравнение было/стало, выбор чекбоксами, применяется только отмеченное
### 2.3 Уровень вопроса
- [ ] **Улучшить вопрос** — нет молчаливой перезаписи; подтверждение через чекбоксы/применить
- [ ] **Дистракторы** — три новых **не** заменяют старые ответы
- [ ] **Сгенерировать подсказку** — текст появляется в поле, можно отредактировать и сохранить
### 2.4 Версионирование + AI (регресс)
- [ ] На тесте **без** попыток: массовое применение AI не создаёт лишних версий бессмысленно (ожидание как в спринте 1)
- [ ] На тесте **с** попытками: осмысленные сохранения ведут себя по правилам 4.1 (новая версия при изменении)
### 2.5 Дизайн
- [ ] Кнопки, модалки, превью, состояния загрузки/ошибок **визуально** в одном ряду с остальным модулем (как спринт 1)
### 2.6 Качество UX
- [ ] Долгий ответ LLM: индикатор ожидания, нельзя «задвоить» запросы без контроля
- [ ] Понятные сообщения при сбое сети или API DeepSeek
---
**Итог приёмки спринта 2:** дата __________, комментарий _________________________

65
docs/revision_task/sprint-02.md

@ -0,0 +1,65 @@
# Спринт 2 — Редактор тестов: AI-помощники (desktop web)
**Формат:** отдельное веб desktop-приложение (этап 1, фича §4.2).
**Граница спринта:** начинается **после** приёмки спринта 1; заканчивается готовностью **всех** функций AI-помощника из ТЗ (настройки ключа, проверка подключения, сценарии уровня теста и уровня вопроса) на базе **DeepSeek** и сохранения **идентичности дизайна** с остальным приложением.
**Предпосылка:** версионирование (спринт 1) работает; сгенерированные/изменённые черновики сохраняются в модель **текущей редактируемой версии** согласно правилам 4.1.
**Стек (целевой в ТЗ):** Python, FastAPI — **в репозитории TestingWebApp фактически Node.js + Express**, LLM через HTTP (OpenAI-совместимый API, в т.ч. DeepSeek); ключ в окружении или (в целевом виде спринта) в БД, не на клиенте. Сводка MVP: [../PROJECT_STATUS.md](../PROJECT_STATUS.md).
---
## Цель спринта
Реализовать **§4.2 ТЗ**: интеграция DeepSeek, страница настроек, все табличные функции уровня теста и вопроса, обработка отсутствия ключа, UI с превью и подтверждением (без «тихой» перезаписи там, где ТЗ требует сравнения с чекбоксами).
---
## Функции (контрольный список из ТЗ)
### Интеграция и настройки
- Ключ DeepSeek на `/settings`, хранение в БД, не отдаётся на фронт
- «Проверить подключение» — тестовый запрос
- Все AI-действия при отсутствии ключа — понятная ошибка + ссылка на настройки
### Уровень теста
| Функция | Критерий |
| --- | --- |
| Сгенерировать тест | Только при заполненном названии; превью; применение целиком |
| Проверить тест | Модалка с рекомендациями |
| Предложить улучшение всего теста | Постатейно было → стало, чекбоксы, применение выбранного |
### Уровень вопроса
| Функция | Критерий |
| --- | --- |
| Улучшить вопрос | Модалка, было/стало по частям, чекбоксы, **без** прямой замены без подтверждения |
| Дистракторы | +3 неправдоподобных варианта **добавляются**, не заменяют |
| Сгенерировать подсказку | Текст в поле подсказки; автор правит/удаляет (связь с §4.4 в следующих спринтах) |
---
## Технические подзадачи
| # | Задача |
| --- | --- |
| 1 | Модель настроек (ключ), API save/test, маскирование в ответах |
| 2 | Промпты и контракты JSON для каждой функции; валидация ответа LLM |
| 3 | Эндпоинты/сервисы: 6 сценариев + единая обёртка ошибок/квот |
| 4 | UI: кнопки в редакторе, модалки, превью «сгенерировать тест», дифы с чекбоксами |
| 5 | Соблюдение правил 4.1 при сохранении применённых AI-изменений |
| 6 | Логи без утечки ключа; rate-limit/таймауты по best effort |
---
## Вне спринта 2
- Медиа (§4.3), полноценное поведение подсказок в прохождении (§4.4 + §4.5) — отдельные спринты, если не входят в минимум для кнопки «подсказка» в редакторе
- Дашборды (этап 2 ТЗ), HR-интеграция, MAX
---
## Документ тестирования
Чек-лист: `sprint-02-testing.md`.

2
docs/revision_task/task.md

@ -10,6 +10,8 @@
| Адресат | Константин Л. (разработчик) | | Адресат | Константин Л. (разработчик) |
| Базовый репозиторий | https://git.pirogov.ai/l_konstantin/TestingWebApp | | Базовый репозиторий | https://git.pirogov.ai/l_konstantin/TestingWebApp |
**Состояние репозитория (не ТЗ, а факт):** ветка `dev` — [PROJECT_STATUS.md](../PROJECT_STATUS.md); как пользоваться локальным стендом — [DEV_CONTOUR_USER_GUIDE.md](../DEV_CONTOUR_USER_GUIDE.md).
## 1. Контекст и зачем это делается ## 1. Контекст и зачем это делается
### Зачем клинике система тестирования ### Зачем клинике система тестирования

2
docs/ТЗ.md

@ -5,6 +5,8 @@
**Дата:** 2026-03-21 **Дата:** 2026-03-21
**Статус:** Согласовано **Статус:** Согласовано
*Реализация в ветке `dev` (не дословно ТЗ):* [PROJECT_STATUS.md](PROJECT_STATUS.md) · [инструкция dev-стенда](DEV_CONTOUR_USER_GUIDE.md).
--- ---
## 1. Назначение системы ## 1. Назначение системы

5
docs/шаги/01-project-setup.md

@ -42,3 +42,8 @@
- Работающий сервер с подключением к БД - Работающий сервер с подключением к БД
- Структура проекта готова для разработки - Структура проекта готова для разработки
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/02-database-design.md

@ -137,3 +137,8 @@
- Созданы все таблицы с связями - Созданы все таблицы с связями
- Накатанные миграции - Накатанные миграции
- Схема БД готова - Схема БД готова
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/03-auth.md

@ -54,3 +54,8 @@ function requireDepartment(departmentId: string) {
- Работающий вход по логину/паролю - Работающий вход по логину/паролю
- Защищённые API роуты - Защищённые API роуты
- Разграничение прав по ролям - Разграничение прав по ролям
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/04-users-departments.md

@ -47,3 +47,8 @@
- Админка для управления сотрудниками - Админка для управления сотрудниками
- Справочник подразделений - Справочник подразделений
- Назначение ролей сотрудникам - Назначение ролей сотрудникам
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

6
docs/шаги/05-test-management.md

@ -47,8 +47,14 @@
--- ---
*Фактические маршруты API в `TestingWebApp`: префикс `/api/tests`, версии и черновик — см. [../PROJECT_STATUS.md](../PROJECT_STATUS.md).*
## Результат ## Результат
- Полноценный конструктор тестов - Полноценный конструктор тестов
- Версионирование с сохранением истории - Версионирование с сохранением истории
- Управление вопросами и ответами - Управление вопросами и ответами
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/06-test-assignment.md

@ -48,3 +48,8 @@
- Назначение тестов подразделениям или сотрудникам - Назначение тестов подразделениям или сотрудникам
- Ограничение по дедлайну и попыткам - Ограничение по дедлайну и попыткам
- Список назначений для сотрудника - Список назначений для сотрудника
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/07-test-taking.md

@ -70,3 +70,8 @@
- Таймер с автозавершением - Таймер с автозавершением
- Сохранение прогресса - Сохранение прогресса
- Навигация по вопросам - Навигация по вопросам
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

6
docs/шаги/08-results-review.md

@ -4,6 +4,8 @@
Реализовать отображение результатов теста и разбора ответов сотруднику. Реализовать отображение результатов теста и разбора ответов сотруднику.
**Реализация в dev:** ответ `POST …/attempts/:id/submit` включает поле `review`; отдельно `GET /api/tests/:testId/attempts/:attemptId/review` (см. [../PROJECT_STATUS.md](../PROJECT_STATUS.md)).
--- ---
## Задачи ## Задачи
@ -76,3 +78,7 @@
- Итоговый балл и процент - Итоговый балл и процент
- Статус зачёта - Статус зачёта
- Полный разбор по каждому вопросу - Полный разбор по каждому вопросу
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/09-attempt-tracking.md

@ -51,3 +51,8 @@
- Таблица попыток с фильтрами - Таблица попыток с фильтрами
- Ограничение данных по роли - Ограничение данных по роли
- Полная история прохождений - Полная история прохождений
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/10-ai-assistant.md

@ -87,3 +87,8 @@
- AI-генерация вопросов - AI-генерация вопросов
- Улучшение формулировок - Улучшение формулировок
- Рекомендации по качеству - Рекомендации по качеству
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

5
docs/шаги/11-settings.md

@ -61,3 +61,8 @@
- Страница `/settings` - Страница `/settings`
- Ввод и сохранение API ключа - Ввод и сохранение API ключа
- Проверка подключения к DeepSeek - Проверка подключения к DeepSeek
---
*Актуальная привязка к коду: [../PROJECT_STATUS.md](../PROJECT_STATUS.md) · индекс шагов: [README.md](README.md).*

8
docs/шаги/README.md

@ -0,0 +1,8 @@
# Пошаговая спецификация (`docs/шаги/`)
Файлы **01**–**11** — **проектные шаги** (целевое поведение и API), а не автоматическая копия кода. Фактическое состояние фич, сценарии «как у пользователя» и ветка **`dev`** описаны в:
- [../PROJECT_STATUS.md](../PROJECT_STATUS.md) — что сделано и что в планах;
- [../DEV_CONTOUR_USER_GUIDE.md](../DEV_CONTOUR_USER_GUIDE.md) — инструкция для проверки на dev-стенде.
Журнал приёмки: [../revision_task/TESTING_JOURNAL.md](../revision_task/TESTING_JOURNAL.md).
Loading…
Cancel
Save