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