Browse Source
- PROJECT_STATUS: что сделано (черновики, версии, разбор, каталог) и планы - DEV_CONTOUR_USER_GUIDE: сценарии для проверяющих на dev-стенде - README, ТЗ, card1, журнал, бэклоги, шаги 01–11+README, спринты, TEST_TABLES: ссылки и примечания - backend/PROGRESS: ссылка на PROJECT_STATUS Made-with: Cursordev
27 changed files with 842 additions and 12 deletions
@ -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 |
||||||
@ -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, БД, переменные окружения. |
||||||
@ -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) |
||||||
@ -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) |
||||||
@ -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:** дата __________, комментарий _________________________ |
||||||
@ -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`. |
||||||
@ -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…
Reference in new issue