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

Made-with: Cursor
2026-04-24 22:12:06 +05:00

110 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Карта задач: Card 1 — история прохождений, документ, авторизация HR
**Связь со спринтами:** основная масса пунктов **V.x****Спринт 1** (версионирование + инфра); **D.x** — загрузка документа (может пересекаться со **Спринтом 2** / AI, если без LLM черновик не делается); **A.x** — авторизация **базой** `Postgres_TG_Bots` (выполнять после/параллельно с V.1, чтобы не плодить дубли пользователей на долгий срок).
**Фактический стек репозитория [TestingWebApp](../../README.md):** Node.js (backend), PostgreSQL, Docker; фронт — SPA в `frontend/`. План доработок **не** привязывать к FastAPI, если в коде API на Express/Node.
### Хранение, связь с сотрудниками, RBAC (зафиксировано)
- **Один кластер PostgreSQL** (как в [Postgres_TG_Bots](../../../Postgres_TG_Bots)): отдельные **базы****`clinic_tests`** (тесты, назначения, попытки, миграции модуля) и **`hr_bot_test`** (штат, справочники, уже реализованный **RBAC**). Таблицы модуля тестов **не** вешаем в `hr_bot_test`, чтобы не конфликтовать по именам (`users`, `departments` и т.д. уже заняты смыслами HR).
- **Сотрудник в бизнес-процессах модуля тестирования** идентифицируется по **`staff_members.id`** (БД `hr_bot_test` / экосистема [hr_web_viewer](../../../Postgres_TG_Bots/hr_web_viewer)). В `clinic_tests` храним **суррогатные ссылки** на сотрудника (например `staff_id` / UUID той же сущности), без дублирования ФИО и кадровых данных в долгую.
- **`telegram_id`** в данных сотрудника — **только справка** (в т.ч. для исторических выгрузок, отображения). **Ни назначения, ни проверок прав, ни выбор сотрудника в сценариях модуля** от `telegram_id` **не** зависят и не должны зависеть.
- **RBAC:** единая цель — опираться на **уже существующую** в клинике модель (роли, привязки к сотруднику, permissions). Проверка «можно ли действие» в конечном варианте — через **HR API** / общий auth-контур и/или согласованное чтение RBAC-таблиц; в `clinic_tests` **не** строим параллельную полную матрицу ролей. На переходных этапах допустимы упрощения (MVP-флаги), пока в контракте не зафиксировано иное.
---
## Часть V — Версионирование (цель: корректная история при правках автора)
**Правила (приёмка):**
1. Пока по **цепочке теста** (`tests.id`) **не было ни одной** попытки в `test_attempts` (через любую `test_version_id` этой цепочки) — автор **редактирует на месте** текущую единственную строку `test_versions` и дочерние вопросы/ответы; поле `version` **не** увеличивается.
2. Как только появилась **хотя бы одна** попытка — **каждое** сохранение с изменениями контента создаёт **новую** строку `test_versions`: `version = max+1`, `parent_id` → id предыдущей версии, прежняя версия `is_active = false`, новая `is_active = true`; старые вопросы/ответы **копируются** в новую версию.
3. Все версии одной цепочки — общий `test_id`; цепочка линейна по `parent_id` (и/или по монотонному `version` при одном `test_id`).
4. `test_attempts.test_version_id` **NOT NULL** — попытка всегда на снимок версии, разбор старых результатов не «плывёт» при новых правках.
5. Списки тестов для **сотрудников** и **авторов**: только **активная** версия цепочки (`test_versions.is_active` и `tests` не скрыт деактивацией цепочки).
6. **История версий** в UI: просмотр, **ручной** выбор активной версии; при выборе в транзакции: у всех версий данного `test_id` `is_active = false`, у выбранной `is_active = true`. Новые старты попыток — по **текущей** активной версии.
7. **Деактивация теста целиком** — флаг на уровне `tests` (скрыть цепочку из списков, **без** удаления строк).
**Задачи (детализация):**
| ID | Задача | Критерий |
| --- | --- | --- |
| V.1 | Миграция БД: `test_versions.parent_id` (FK на `test_versions.id`, ON DELETE NO ACTION/RESTRICT), частичный уникальный индекс: не более **одной** `is_active = true` на `test_id` (если СУБД поддерживает; иначе — уникальный индекс + проверка в сервисе) | `migrate` отрабатывает пусто на повтор |
| V.2 | Сервис `hasAnyAttemptForTest(testId)` + unit-тесты | Покрыты кейсы: 0 попыток / 1+ по любой версии цепочки |
| V.3 | `saveTestDraft(author, testId, payload)`: ветвление in-place **vs** `forkNewVersion` (копия вопросов/опций) | Соответствие правилам 1–2 |
| V.4 | Старт попытки: в `test_attempts` писать `test_version_id` **той** версии, что была **активна** в момент старта | Нет перезаписи `version_id` позже |
| V.5 | API: `GET /tests` (роль) — только активные цепочки; `GET /tests/:id/versions`; `POST /tests/:id/versions/:vid/activate` | 403/404 по политике |
| V.6 | API: `PATCH /tests/:id` (деактивация цепочки `tests.is_active` или отдельное поле) | Список пустеет, данные на месте |
| V.7 | UI автора: номер/метка версии, предупреждение при «после первой попытки», экран **история версий**, кнопка **сменить активную** (с confirm) | Смоук `sprint-01-testing.md` |
| V.8 | UI списки сотрудника/автора: **один** ряд на цепочку, без дублей версий | `GET /tests/:id/summary` + упрощённая карточка для не-автора; список `GET /tests` с JOIN на активную версию |
| V.9 | Интеграционные тесты API + регресс «разбор старой попытки» по старым `question_id` | `backend/src/integration/v9card1.test.js` при `CLINIC_TESTS_INTEGRATION=1` и миграциях; без БД — skip |
| V.10 | *Продукт:* при новой версии `test_assignments` **не** переносим на новый `test_version_id`; старт попытки — по **активной** версии (см. [task.md §2.6](task.md)) | Зафиксировано в ТЗ |
---
## Часть D — Загрузка документа → черновик теста
**Цель:** загрузить файл (PDF, DOCX — перечислить лимиты), извлечь текст, **сгенерировать** структуру вопросов (логично тянуть **LLM** из ТЗ §4.2) или дать мастер «вставьте текст».
| ID | Задача | Критерий |
| --- | --- | --- |
| D.1 | Эндпоинт `POST /tests/import/document` (multipart), валидация размера/типа, сохранение во временное хранилище | 413/400 при нарушении |
| D.2 | Извлечение текста: PDF (библиотека на выбор), DOCX (zip/xml) | Юнит на фикстуре |
| D.3 | Вызов слоя **генерации** (если **есть** ключ DeepSeek — → промпт; иначе — заглушка «только вручную») с ответом JSON по схеме вопросов/ответов | Согласовано с §4.2 |
| D.4 | UI: кнопка «Из документа», превью, применение → дальше **тот же** поток сохранения, что и ручной редактор (в т.ч. V.1–V.3) | — |
| D.5 | Удаление временных файлов после обработки / TTL | — |
---
## Часть A — Авторизация по паролю, БД [Postgres_TG_Bots](../../../Postgres_TG_Bots)
**Контекст:** в `Postgres_TG_Bots`/`hr_web_viewer` учёт `users` с полями `username`, `password_hash` (Werkzeug `pbkdf2:sha256` / `scrypt` через `werkzeug.security` — см. `hr_web_viewer/models/user_models.py`). **Идентичность сотрудника** для сценариев тестов — по **`staff_members.id`** (см. блок «Хранение…» выше); **`telegram_id` не используем** в логике входа, назначений и прав.
| ID | Задача | Критерий |
| --- | --- | --- |
| A.1 | Вторичное соединение: `HR_DATABASE_URL` (или DSN) к **`hr_bot_test`**, read-only **или** отдельный пользователь с `SELECT` на `users` и `staff_members` (для связки логин → `staff_id`, подразделение и т.д.) **отдельно** от `DATABASE_URL` в **`clinic_tests`** | Пример `.env` в `TestingWebApp` |
| A.2 | Реализация `verifyPassword` в Node, **совместимой** с `check_password_hash` (использовать пакет, понимающий префиксы `pbkdf2:sha256:` и `scrypt:`) | Тест: тот же хеш, что сгенерировал Werkzeug |
| A.3 | Логин: по `username``users` в `hr_bot_test`; при успехе — токен **TestingWebApp** с привязкой к **`staff_members.id`** (и при необходимости к локальному `users` в `clinic_tests` только как технический mirror **без** отдельного жизненного цикла пароля). Пароли **только** в HR-таблице `users` | Нет дублирования паролей в долгую |
| A.4 | Отключить/не использовать регистрацию с локальным `password_hash` в прод-режиме, если включён `HR_AUTH=1` | Флаг в `.env` |
| A.5 | Маппинг ролей: взять из **существующей** RBAC-модели HR (см. `staff_role_assignments` / roles & permissions) **или** согласованный вызов **HR API**; MVP — ограниченный набор, без опоры на `telegram_id` | [README — данные и интеграция](../../README.md#данные-сотрудники-интеграция-с-hr) |
---
## Порядок работ (рекомендация)
1. **V.1****V.2****V.3** (ядро версий).
2. **A.1**–**A.3** параллельно или сразу после **V.1** (нужен стабильный логин для стенда).
3. **V.4****V.9** и UI **V.7****V.8**.
4. **D.*** после появления клиента LLM (или D.1–D.2 + ручной встав текста без LLM).
5. **V.10** — решение по назначениям до **V.5** если назначения уже в проде.
---
## Ссылки
- ТЗ: [task.md](task.md) §4.1, §4.2
- Спринт 1: [sprint-01.md](sprint-01.md)
- Проверки и журнал: [TESTING_JOURNAL.md](TESTING_JOURNAL.md)
- Старый чек-лист: [sprint-01-testing.md](sprint-01-testing.md)
- Анализ таблиц (если ведёте): [TEST_TABLES_ANALYSIS.md](../TEST_TABLES_ANALYSIS.md)
---
## Статус реализации (сводно, 2026-04-25+)
| ID | Статус | Комментарий |
| --- | --- | --- |
| V.1V.6, V.7, V.10 | Код + API + UI приёмка | [TESTING_JOURNAL S1-10, S1-11](TESTING_JOURNAL.md); публикация, скрытые, история. |
| V.8 | Расширен MVP | `GET /api/tests`**только свои** (`created_by`) **и** тесты с **назначением** на `users.id` (`test_accessService.js`). Карточка/попытка/starter/chain — та же логика. Старт **«Пройти»** в списке. Назначение: `POST /api/tests/:id/assign` (только **автор**), при production — `CLINIC_ASSIGNMENT_ENABLED=1` (`featureFlags.js`); в `development` включено. UI: блок «Назначение сотрудникам» + `GET /api/auth/dev/assignment-directory` при `assignmentUi` из `/api/auth/me`. |
| V.9 | Частично | + `v9card1.test.js`: 0 попыток → in-place; после попытки → форк, старая попытка на старых `version_id` / `question_id`. Запуск: `CLINIC_TESTS_INTEGRATION=1` + `DATABASE_URL` (или DB_*), `npm run migrate`. |
| D.1 | Готово | `POST /tests/import/document`, 400/413, multipart. |
| D.2 | Готово | `documentExtractService.js`: PDF (`pdf-parse`), DOCX (`mammoth`), TXT/MD. |
| D.3 | MVP (импорт) | `documentGenService.js`: вызов Chat Completions (DeepSeek по умолчанию или OpenAI), JSON-черновик → `generation.draft` в `POST /import/document`; `LLM_NO_JSON=1` при несовместимости API. |
| D.4 | MVP | Карточка теста: выбор файла, превью, «Вставить в поле вопроса» → тот же черновик. |
| D.5 | Готово | Временный файл удаляется после чтения; Nginx `client_max_body_size 10m`. |
| A.1A.4 | Код + compose | `HR_AUTH` + `HR_DATABASE_URL` в `docker-compose.dev.yml`. |
| A.5 | MVP | `mapHrRoleToApp` без полного RBAC из HR-таблиц. |
| UI | 2026-04+ | Список тестов: подпись **автора** (Вы / Фамилия И. О.); шапка: **Фамилия И. О.** Разбор попыток: API + UI после `submit` и маршрут `/tests/:id/attempts/:aid/review` ([PROJECT_STATUS.md](../PROJECT_STATUS.md)). |
**Следующий шаг по card1:** **V.9** — довести supertest/HTTP-регресс при необходимости; **D.3+** — отдельные кнопки в редакторе (сгенерировать/проверить/улучшить), ключ в БД, `/settings` (см. [sprint-02](sprint-02.md)); **A.5**`staff_role_assignments` / HR API; по желанию назначения по **отделу**. Навигация по сценариям: [DEV_CONTOUR_USER_GUIDE.md](../DEV_CONTOUR_USER_GUIDE.md).