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

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

Made-with: Cursor
This commit is contained in:
Константин Лебединский
2026-04-24 22:12:06 +05:00
parent 4801ea9f19
commit a68331c86b
27 changed files with 842 additions and 12 deletions
+380
View File
@@ -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)