a68331c86b
- PROJECT_STATUS: что сделано (черновики, версии, разбор, каталог) и планы - DEV_CONTOUR_USER_GUIDE: сценарии для проверяющих на dev-стенде - README, ТЗ, card1, журнал, бэклоги, шаги 01–11+README, спринты, TEST_TABLES: ссылки и примечания - backend/PROGRESS: ссылка на PROJECT_STATUS Made-with: Cursor
381 lines
16 KiB
Markdown
381 lines
16 KiB
Markdown
# Анализ таблиц для тестирования сотрудников
|
||
|
||
*Модуль **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)
|