You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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.
  • Данные: цепочки teststest_versionsquestionsanswer_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 Предпосылки

  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_byusers.staff_id, is_active, created_at (по политике: локальное время vs UTC).
  2. testing_test_versions — из test_versions: связь test_id через маппинг; version_numberversion; passing_score_percent ← порог из версии/цепочки (в старой схеме часть полей была на tests — нормализовать в версию как в SQLAlchemy-модели); time_limit_minutes, allow_back_navigation, is_active_version, флаг единственной активной версии на цепочку.
  3. testing_questions — из questions: текст, тип (single/multiple из has_multiple_answers), sort_orderquestion_order.
  4. testing_answers — из answer_options: текст, is_correct, порядок.
  5. testing_assignments — из test_assignments + test_assignment_targets:
    • для целей типа пользователь: одна строка на пару (тест, staff_id);
    • для целей отдел: развернуть в множество строк по сотрудникам отдела на момент миграции (с явным логом «создано из department_id=…»);
    • assigned_bystaff_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.idtesting_answers.id).

Везде, где в старой БД использовались UUID, скрипт хранит таблицу public._clinic_tests_migration_map (entity, old_uuidnew_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. Риски и как их снимать

Риск Мера
Неполное сопоставление usersstaff_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.