feat: Sprint 2 — test taking + results

Backend:
- Models: TestAttempt, AttemptAnswer (migration 002)
- POST /api/attempts: start attempt, shuffle questions/answers,
  hide is_correct, expose is_multiple for UI hints
- POST /api/attempts/{id}/submit: save answers, calculate score,
  strict matching (selected == correct), return full result
- GET /api/attempts/{id}/result: fetch saved result
- Register attempts router in main.py

Frontend:
- api/attempts.ts: types + API functions
- TestTake page: one question at a time, progress bar, timer
  with auto-submit, back navigation controlled by test setting,
  radio/checkbox based on is_multiple
- AttemptResult page: score, pass/fail, per-question breakdown
  with correct/selected/missed answer highlighting
- App.tsx: add /tests/:testId/take and /attempts/:id/result routes
- TestDetail: add "Пройти тест" button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aleksey Razorvin
2026-03-21 12:53:11 +05:00
parent 5551202d6f
commit d5f6abb5ad
12 changed files with 973 additions and 4 deletions
+97
View File
@@ -0,0 +1,97 @@
# ШАГ 007 — Спринт 2: Прохождение теста + результат
**Дата:** 2026-03-21
**Контекст:** Мастер-класс по разработке системы тестирования сотрудников клиники.
---
## Запрос
> запускай реализацию спринта 2
---
## Ответ
Реализован Спринт 2: прохождение теста с таймером и навигацией, результат с разбором ошибок.
---
## Новые файлы
```
backend/app/models/attempt.py ← TestAttempt, AttemptAnswer
backend/app/schemas/attempt.py ← схемы для старта, сдачи и результата
backend/app/api/attempts.py ← 3 эндпоинта
backend/alembic/versions/002_attempts.py ← миграция
frontend/src/api/attempts.ts ← типы и запросы
frontend/src/pages/TestTake/ ← страница прохождения теста
frontend/src/pages/AttemptResult/ ← страница результата
```
---
## API эндпоинты (новые)
| Метод | URL | Описание |
|-------|-----|----------|
| POST | `/api/attempts` | Начать попытку → возвращает вопросы перемешанные, без правильных ответов |
| POST | `/api/attempts/{id}/submit` | Сдать тест → подсчитать и вернуть результат |
| GET | `/api/attempts/{id}/result` | Получить результат сохранённой попытки |
---
## Схема БД (добавлено)
```
test_attempts
id, test_id → tests.id, started_at, finished_at,
score, passed, correct_count, total_count, status
attempt_answers
id, attempt_id → test_attempts.id,
question_id → questions.id, answer_id → answers.id
```
Одна строка `attempt_answers` = один выбранный вариант ответа.
Для вопросов с несколькими правильными ответами — несколько строк.
---
## Логика прохождения теста
**Старт попытки:**
- Создаётся запись `TestAttempt` со статусом `in_progress`
- Вопросы и ответы внутри каждого вопроса перемешиваются случайно
- Поле `is_correct` **не передаётся** на фронт — нельзя смошенничать через DevTools
- Поле `is_multiple: bool` говорит фронту: показывать радио-кнопки или чекбоксы
**Сдача теста:**
- Фронт отправляет `[{ question_id, answer_ids[] }]` для каждого вопроса
- Вопрос засчитывается правильным только если `selected_ids == correct_ids` (точное совпадение)
- Балл = (правильных / всего) × 100
- Зачёт: балл ≥ порогу из теста
**Разбор ошибок:**
- Для каждого вопроса: `is_answered_correctly`
- Для каждого варианта: `is_correct` + `is_selected` → фронт показывает что выбрал и что было правильно
---
## UX прохождения теста
- Вопросы по одному, прогресс-бар сверху
- Таймер: если задан — обратный отсчёт, при `< 60 сек` — предупреждение, при `0` — автосабмит
- Кнопка «Назад» заблокирована если `allow_navigation_back = false`
- Чекбоксы для `is_multiple`, радио-кнопки для одиночного ответа
---
## Следующие шаги
- [x] Спринт 1: Инфраструктура + Создание тестов
- [x] Спринт 2: Прохождение теста + результат
- [ ] Спринт 3: Трекер результатов
- [ ] Спринт 4: Авторизация и роли
- [ ] Спринт 5: Уведомления в MAX