Files
TestingWebApp/docs/migration-to-tgflaskform.md
T

176 lines
20 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.
# Перенос TestingWebApp на стек HR_TG_Bot / tgFlaskForm
**Тот же план простым языком (две базы, люди, этапы):** [migration-to-tgflaskform-plain.md](migration-to-tgflaskform-plain.md).
**Назначение документа:** зафиксировать целевую архитектуру, **спринтовый план** доведения функциональности до паритета и **порядок миграции данных** из отдельного приложения (`Express` + `React` + БД `clinic_tests`) в кабинет **`tgFlaskForm`** (Flask, шаблоны, общая БД `hr_bot_test`, таблицы `testing_*`).
**Связанные материалы:** [PROJECT_STATUS.md](PROJECT_STATUS.md), [README.md](../README.md), [TEST_TABLES_ANALYSIS.md](TEST_TABLES_ANALYSIS.md), код модуля в репозитории HR: `HR_TG_Bot/tgFlaskForm/webApp/interfaces/testing/`, модели: `HR_TG_Bot/tgFlaskForm/db/models.py`.
**Каркас нового контура в этом репозитории:** [../flask_app/README.md](../flask_app/README.md).
---
## 0. Стратегия переходного периода (отдельное приложение, тот же стек)
**Решение:** переписывание с Node/React на **тот же стек, что у мини-приложения и кабинета HR** — Python 3, **Flask**, шаблоны (Jinja2), статический JS, работа с PostgreSQL в духе `tgFlaskForm`. При этом сервис **пока живёт отдельно**: свой процесс, свой URL/порт, **не** обязан совпадать с деплоем полного `HR_TG_Bot/tgFlaskForm`.
**Зачем так:** быстрее выйти на паритет по UX и данным, **без** риска «большого взрыва» в едином кабинете; позже либо встраиваете модуль в кабинет (общий `webApp`), либо оставляете отдельный вход — стек уже совпадает.
**Обязательно зафиксировать продуктово:**
| Вопрос | Рекомендация |
|--------|----------------|
| Где **пишут** тесты и попытки, пока два контура? | Один «канонический» контур на запись; второй read-only или только пилот — иначе разъедутся данные. |
| База | Либо по-прежнему **`clinic_tests`** в новом Flask до ETL, либо сразу **`hr_bot_test`** + `testing_*` (как в кабинете) — одно из двух, не смешивать без миграции. |
| ETL | Скрипт `HR_TG_Bot/tgFlaskForm/tools/migrate_clinic_tests_to_hr.py`: бэкап → `--dry-run` → проверка на копии → короткое окно → `--apply`. |
**Технически:** в репозитории TestingWebApp заведён каталог **`flask_app/`** — минимальное приложение-заготовка; развитие переноса идёт там (или копированием готовых модулей из `HR_TG_Bot/tgFlaskForm`).
---
## 1. Зачем переносить
| Аспект | Сейчас (TestingWebApp) | Цель (tgFlaskForm) |
|--------|------------------------|---------------------|
| Стек | Node.js (Express), React (Vite), отдельный деплой | Python 3, Flask, Jinja/PyPug, статический JS в шаблонах — **единый кабинет** с остальным HR |
| База | PostgreSQL, схема `clinic_tests`, UUID-ключи, локальные `users` | Та же инфраструктура Postgres, БД **`hr_bot_test`**, целочисленные `id`, связь с **`staff_members`** |
| Авторизация | Собственные логин/JWT + опция `HR_AUTH` | Сессии кабинета, RBAC через HR (`testing_head_positions`, флаги HR и т.д.) |
| Модуль тестирования | Полный цикл в одном репозитории | В **`tgFlaskForm` уже есть** blueprint `/cabinet/testing`, запросы в `db/queries/testing_queries.py` — задача переноса = **паритет фич + данные + вывод из эксплуатации** старого UI/API |
Итог после **полной** консолидации: один вход для сотрудника, одна БД «истины» по людям, меньше дублирования интеграций с HR. На переходном этапе допустим **отдельный** Flask-инстанс с тем же стеком (см. §0).
---
## 2. Исходный и целевой стек (кратко)
**Исходный (TestingWebApp):**
- Backend: `express`, `pg`, миграции SQL в `backend/src/db/migrations/`.
- Frontend: `react`, `react-router-dom`, `vite`.
- Данные: цепочки `tests``test_versions``questions``answer_options`; назначения с `test_assignment_targets` (отдел/пользователь); попытки `test_attempts`, ответы `user_answers` (массив UUID вариантов).
**Целевой (`HR_TG_Bot/tgFlaskForm`) и отдельный контур в этом репозитории (`flask_app/`):**
- Приложение: `Flask`, точка входа `web_run.py`, фабрика/приложение `webApp/__init__.py`.
- Шаблоны: `webApp/templates/cabinet/testing/*.html`, клиентский JS в `templates/static/js/cabinet/testing_*`.
- ORM/запросы: SQLAlchemy-модели `TestingTest`, `TestingTestVersion`, `TestingQuestion`, `TestingAnswer`, `TestingAssignment`, `TestingAttempt`, `TestingAttemptAnswer`, `TestingSetting`, `TestingHeadPosition` в `db/models.py`; бизнес-запросы — `db/queries/testing_queries.py`.
- Сервер: dev `flask run`, prod типично `waitress` (см. `web_run.py`).
- **Отдельный деплой в TestingWebApp:** каталог `flask_app/``run.py`, шаблоны в `flask_app/app/templates/` (см. §0).
---
## 3. Спринтовый план (переписывание = паритет + миграция + снятие стенда)
Длительность спринта ориентировочно **2 календарные недели**; границы можно сжимать/растягивать под состав команды. Нумерация условная: **Спринт 0** — подготовка, далее функциональные слои.
### Спринт 0 — Инвентаризация и критерии готовности
**Цель:** зафиксировать разрыв «TestingWebApp ↔ tgFlaskForm» и правила миграции.
- Составить **матрицу сценариев** по [ТЗ.md](ТЗ.md) и [PROJECT_STATUS.md](PROJECT_STATUS.md): редактор теста, версии, назначения, прохождение, разбор, трекер, настройки модуля, AI.
- Зафиксировать отличия схемы: UUID vs integer, модель назначений (цель: каждая строка `TestingAssignment` = один `staff_id`).
- Решение по **импорту из PDF/DOCX** (в Node-версии есть извлечение текста для черновика): либо перенос в Python (`tgFlaskForm`), либо явный scope «после миграции».
- **Критерий выхода:** подписанный чек-лист паритета + утверждённый порядок миграции (раздел 4 этого документа).
### Спринт 1 — Данные и идентификаторы
**Цель:** подготовить перенос без потери смысла связей.
- Убедиться, что у всех значимых пользователей `clinic_tests.users` есть сопоставление с **`staff_members.id`** (колонка `staff_id` и/или правила сопоставления по логину из HR).
- Спроектировать **таблицы соответствия** для одноразового ETL (например временные таблицы или JSON-маппинги: `old_test_uuid → testing_tests.id`, `old_version_uuid → testing_test_versions.id`, и т.д.).
- Реализовать **скрипт миграции** — в репозитории HR: [`HR_TG_Bot/tgFlaskForm/tools/migrate_clinic_tests_to_hr.py`](../../HR_TG_Bot/tgFlaskForm/tools/migrate_clinic_tests_to_hr.py) (Python, `psycopg2`, два URL). Режимы: `--dry-run` (только отчёт) и `--apply` (одна транзакция `COMMIT` в `hr_bot_test`). Переменные или флаги: `CLINIC_TESTS_URL`, `HR_BOT_URL`; опция `--skip-missing-staff` пропускает цепочки, у автора нет `users.staff_id`.
- **Критерий выхода:** dry-run на копии прод-дампа `clinic_tests` + smoke-проверки количества строк (тесты, версии, вопросы, попытки).
### Спринт 2 — Паритет бизнес-логики в Flask
**Цель:** закрыть расхождения поведения, а не только UI.
- Версионирование: правила «первая правка без попыток / новая версия после попыток», активная версия — согласовать с уже реализованным в `testing_queries.py` и довести до полного соответствия ТЗ при необходимости.
- Назначения: если в `clinic_tests` остались назначения **на отдел**, описать стратегию **разворачивания** в N строк `TestingAssignment` (по списку `staff_id` отдела на дату миграции) или доработать модель в HR (отдельное решение продукт-оунера).
- Прохождение: таймер, лимит попыток, дедлайн, случайный порядок вопросов (`question_seed`) — сверка с ТЗ и доработка в Python при расхождении.
- **Критерий выхода:** автоматические тесты на критичные запросы (где их ещё нет) + ручной прогон чек-листа из спринта 0.
### Спринт 3 — UI/UX кабинета и интеграция в меню
**Цель:** пользователь не возвращается к старому хосту.
- Пункты меню кабинета, бейджи «назначенные тесты», единый стиль с `cabinet/base.html`.
- Довести страницы: список «мои тесты», редактор, назначение, прохождение, результат/разбор, трекер, настройки — по чек-листу.
- Импорт документов (если включён в scope спринта 0): эндпоинт + UI в шаблоне, ключи API только на сервере (`TestingSetting` / env).
- **Критерий выхода:** UX-приёмка на стенде, совпадающий с ТЗ сценарий для HR / руководителя / сотрудника.
### Спринт 4 — Миграция prod, cutover, архив TestingWebApp
**Цель:** переключить реальных пользователей и зафиксировать артефакты.
- Заморозка записи в TestingWebApp (режим только чтение или техническое окно).
- Прогон ETL на прод-копии → валидация → прогон на боевой БД в согласованное окно.
- Обновление ссылок (внутренние порталы, документация, docker-compose): вместо `:3107` / отдельного сервиса — URL кабинета HR с `/cabinet/testing/...`.
- Репозиторий TestingWebApp: ветка **`legacy/clinic-tests-node`**, в README — ссылка на этот документ и дата end-of-life API/UI.
- **Критерий выхода:** мониторинг ошибок (например Sentry уже в `webApp/__init__.py`), отсутствие P1 по тестам в первую неделю после cutover.
---
## 4. Как происходит миграция данных (пошагово)
### 4.1 Предпосылки
1. Доступ к **двум** базам с одной машины (или логическое копирование дампа): `clinic_tests` и `hr_bot_test`.
2. Маппинг **пользователь → сотрудник:** для каждой строки `users` в `clinic_tests` должен быть известен **`staff_members.id`**. Если `staff_id` пустой — заранее ручной/полуавтоматический справочник соответствий (логин, email, ФИО).
3. Зафиксированная **версия кода** `tgFlaskForm`, в которой пройдены регрессионные тесты модуля тестирования.
### 4.2 Порядок загрузки сущностей (чтобы не нарушить FK)
Рекомендуемый порядок транзакций/батчей:
1. **`testing_tests`** — из цепочек `tests`: `title`, `description`, `created_by``users.staff_id`, `is_active`, `created_at` (по политике: локальное время vs UTC).
2. **`testing_test_versions`** — из `test_versions`: связь `test_id` через маппинг; `version_number``version`; `passing_score_percent` ← порог из версии/цепочки (в старой схеме часть полей была на `tests` — нормализовать в версию как в SQLAlchemy-модели); `time_limit_minutes`, `allow_back_navigation`, `is_active_version`, флаг единственной активной версии на цепочку.
3. **`testing_questions`** — из `questions`: текст, тип (`single`/`multiple` из `has_multiple_answers`), `sort_order``question_order`.
4. **`testing_answers`** — из `answer_options`: текст, `is_correct`, порядок.
5. **`testing_assignments`** — из `test_assignments` + `test_assignment_targets`:
- для целей типа **пользователь:** одна строка на пару (тест, `staff_id`);
- для целей **отдел:** развернуть в множество строк по сотрудникам отдела на момент миграции (с явным логом «создано из department_id=…»);
- `assigned_by``staff_id` постановщика; `deadline`, `max_attempts`, `assigned_at`.
6. **`testing_attempts`** — из `test_attempts`: связь с новым `assignment_id` (если в старой модели попытка шла от `user_id` без отдельного assignment — потребуется **восстановление** или создание синтетических назначений; зафиксировать правило в спринте 0).
7. **`testing_attempt_answers`** — из `user_answers`: каждый выбранный UUID варианта → строка с новым `answer_id` (через маппинг `answer_options.id``testing_answers.id`).
Везде, где в старой БД использовались **UUID**, скрипт хранит таблицу **`public._clinic_tests_migration_map`** (`entity`, `old_uuid``new_id`) в `hr_bot_test` для идемпотентного повторного прогона.
**Замечание по назначениям:** в текущей версии скрипта строки `clinic_tests.test_assignments` / `test_assignment_targets` **не** переносятся пакетно; для каждой пары (тест HR, сотрудник) при переносе **попыток** создаётся или находится строка `testing_assignments` (синтетическое назначение, `max_attempts = 99`). Полный импорт истории назначений из clinic — отдельная доработка при необходимости.
### 4.3 Валидация после ETL
- Сравнение **агрегатов:** число тестов, версий, вопросов, назначений, завершённых попыток, строк ответов.
- Выборочная сверка: 5–10 последних попыток — ручной разбор «вопрос / выбранные варианты / балл» в старом и новом UI.
- Проверка уникальности «одна активная версия на тест» и отсутствия «висячих» FK.
### 4.4 Cutover (переключение)
1. Объявить **окно**: остановка записи в TestingWebApp.
2. Инкрементальный дамп изменений с последней реплики (если делали пробный перенос ранее) или финальный полный перенос.
3. Прогон ETL в транзакции (или по крупным батчам с чекпоинтами) → `VACUUM ANALYZE` при необходимости.
4. Включить пользователям ссылку на **кабинет**; проверить права `can_create_tests` / HR.
5. Сохранить **бэкап** `clinic_tests` и лог миграции минимум на срок, определённый политикой клиники (типично 30–90 дней).
### 4.5 Откат
- Если после cutover обнаружен блокирующий дефект: вернуть пользователей на временный старый стенд **только для чтения** при наличии бэкапа; новые данные в `hr_bot_test` после cutover при откате не синхронизируются автоматически — риск фиксируется заранее (короткое окно, «freeze» повторных действий).
---
## 5. Риски и как их снимать
| Риск | Мера |
|------|------|
| Неполное сопоставление `users``staff_members` | Закрыть в спринте 1; не начинать ETL без процента покрытия, согласованного с заказчиком |
| Разная семантика назначений (отдел, версия) | Явные правила в спринте 0 + лог развёртки отделов |
| Потеря истории попыток из-за смены модели assignment | Моделирование на копии БД в спринте 1–2 |
| Дублирование разработки UI | Опираться на уже существующий модуль в `tgFlaskForm`, не переписывать с нуля параллельный SPA |
---
## 6. Итог
Переписывание в данном контексте — это не «ещё один greenfield на Flask», а **консолидация** уже начатого модуля в `tgFlaskForm` с **одноразовой миграцией** из `clinic_tests` и выводом из эксплуатации связки React + Express. Спринты 0–4 дают сквозной маршрут от анализа до cutover; детали ETL должны быть закреплены в коде скрипта и журнале прогона к концу **спринта 1**.
**См. также:** если пользователи жалуются на медленную загрузку страниц кабинета/Flask — пошаговый план измерений и правок: [performance-flask-mini-app.md](performance-flask-mini-app.md).