20 KiB
Перенос TestingWebApp на стек HR_TG_Bot / tgFlaskForm
Тот же план простым языком (две базы, люди, этапы): migration-to-tgflaskform-plain.md.
Назначение документа: зафиксировать целевую архитектуру, спринтовый план доведения функциональности до паритета и порядок миграции данных из отдельного приложения (Express + React + БД clinic_tests) в кабинет tgFlaskForm (Flask, шаблоны, общая БД hr_bot_test, таблицы testing_*).
Связанные материалы: PROJECT_STATUS.md, README.md, TEST_TABLES_ANALYSIS.md, код модуля в репозитории HR: HR_TG_Bot/tgFlaskForm/webApp/interfaces/testing/, модели: HR_TG_Bot/tgFlaskForm/db/models.py.
Каркас нового контура в этом репозитории: ../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 и 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(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 Предпосылки
- Доступ к двум базам с одной машины (или логическое копирование дампа):
clinic_testsиhr_bot_test. - Маппинг пользователь → сотрудник: для каждой строки
usersвclinic_testsдолжен быть известенstaff_members.id. Еслиstaff_idпустой — заранее ручной/полуавтоматический справочник соответствий (логин, email, ФИО). - Зафиксированная версия кода
tgFlaskForm, в которой пройдены регрессионные тесты модуля тестирования.
4.2 Порядок загрузки сущностей (чтобы не нарушить FK)
Рекомендуемый порядок транзакций/батчей:
testing_tests— из цепочекtests:title,description,created_by←users.staff_id,is_active,created_at(по политике: локальное время vs UTC).testing_test_versions— изtest_versions: связьtest_idчерез маппинг;version_number←version;passing_score_percent← порог из версии/цепочки (в старой схеме часть полей была наtests— нормализовать в версию как в SQLAlchemy-модели);time_limit_minutes,allow_back_navigation,is_active_version, флаг единственной активной версии на цепочку.testing_questions— изquestions: текст, тип (single/multipleизhas_multiple_answers),sort_order←question_order.testing_answers— изanswer_options: текст,is_correct, порядок.testing_assignments— изtest_assignments+test_assignment_targets:- для целей типа пользователь: одна строка на пару (тест,
staff_id); - для целей отдел: развернуть в множество строк по сотрудникам отдела на момент миграции (с явным логом «создано из department_id=…»);
assigned_by←staff_idпостановщика;deadline,max_attempts,assigned_at.
- для целей типа пользователь: одна строка на пару (тест,
testing_attempts— изtest_attempts: связь с новымassignment_id(если в старой модели попытка шла отuser_idбез отдельного assignment — потребуется восстановление или создание синтетических назначений; зафиксировать правило в спринте 0).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 (переключение)
- Объявить окно: остановка записи в TestingWebApp.
- Инкрементальный дамп изменений с последней реплики (если делали пробный перенос ранее) или финальный полный перенос.
- Прогон ETL в транзакции (или по крупным батчам с чекпоинтами) →
VACUUM ANALYZEпри необходимости. - Включить пользователям ссылку на кабинет; проверить права
can_create_tests/ HR. - Сохранить бэкап
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.