12 KiB
12 KiB
Дизайн: TTL-фикс + прерывание с сохранением + оценка времени + UI (2026-08-24)
План реализации фичи по итогам тестов 24.08 (см.
2026-08-24-tests-upload-and-obfuscation.md). Статус: ПЛАН, код не менялся. Порядок: TTL → прерывание → ETA → UI.
0. Исходная проблема (что тесты вскрыли)
- TTL-баг: сессия живёт 30 мин (
TTL_SECONDS = 30*60), таймер запускается при создании и не продлевается во время обработки. Обфускация 101 файла заняла 1995с (>30 мин) → сессия удалена TTL →store_resultмолчаFalse→download/csv= 404, результат потерян. - Нет UX для долгих прогонов: юзер не знает, сколько ждать, и не может прервать с сохранением.
1. Требования (от пользователя)
- Показывать, что обработка долгая «потому что много данных».
- Возможность прерывания с сохранением уже обработанного в выходном ZIP + CSV.
- Прерывание не мгновенное — «пусть завершает, сколько требуется» (мягкий добор).
- Юзер точно знает, что будет сохранено (до нажатия и во время).
- В начале — ориентировочное время обработки всего пакета, чтобы знать, чего ожидать.
2. Блок A — TTL-фикс (фундамент, обязателен первым)
Файл: site/session.py
Текущее поведение
TTL_SECONDS = 30 * 60
def _start_timer(sid):
def _clean(): _sessions.pop(sid, None)
timer = threading.Timer(TTL_SECONDS, _clean); timer.daemon=True; timer.start(); return timer
Таймер одноразовый, не продлевается → долгий прогон убивает сессию.
Новое поведение
- Добавить
def touch(sid): отменяет старый таймер и запускает новый (продлевает жизнь). - При старте обработки (
process_stream) — отменить таймер сессии (во время обработки сессия живёт «вечно», пока идёт воркер). - При завершении (
complete/error/cancelled) — запустить таймер заново (результат доступен ещё 30 мин после окончания). add_file,get_files— можно тожеtouch()для единообразия (не обязательно).
Итог: долгий прогон больше не теряет сессию; результат доступен 30 мин после завершения.
3. Блок B — прерывание с сохранением (мягкая остановка)
3.1. Модель поведения (согласовано с пользователем)
| Где нажата «Прервать» | Поведение |
|---|---|
| Во время LLM-анализа | Анализ добарается до конца (без полного mapping нельзя сохранить ни одного документа). Прерывание срабатывает на границе фазы замены. |
| Во время фазы замены | Дорабатывается текущий файл → стоп. В ZIP+CSV попадают все готовые файлы + полная таблица замен. |
| После завершения | Обычный полный результат (прерывание не актуально). |
- Флаг отмены проверяется только на границах файлов фазы замены → результат всегда осмысленный.
- LLM-фаза не прерывается (добор), что соответствует «пусть завершает, сколько требуется».
3.2. Изменения по файлам
site/session.py
- В словаре сессии добавить
"cancel": threading.Event()при создании. - Хелперы:
request_cancel(sid),cancel_requested(sid).
site/routes/api_bp.py
- Новый эндпоинт
POST /api/cancel/<sid>:request_cancel(sid)→{ok:true}(или 404 если нет сессии). process_stream:- При старте —
cancel_event= событие из сессии, таймер TTL отменяется. - Воркер передаёт
cancel_eventвobfuscate_files. - Обработка исключения отмены → сборка частичного результата:
- ZIP из файлов, прошедших замену (исключая
mapping.csv) + полныйmapping.csv. store_result+store_csv(сейчасstore_resultсохраняет только полный zip).
- ZIP из файлов, прошедших замену (исключая
- Новое SSE-событие
cancelled:data: {"saved": <кол-во готовых>, "total": <всего>, ...}. - После завершения/отмены — перезапуск TTL-таймера.
- При старте —
drhider/obfuscator.py
obfuscate_files(..., cancel_event=None).- В цикле фазы замены (по файлам):
if cancel_event and cancel_event.is_set(): raise CancelRequested(). - В LLM-цикле отмену НЕ проверяем (добор до конца).
CancelRequested— новое исключение (вdrhider/obfuscator.pyили общий модуль).- Для сборки частичного ZIP: функция должна уметь вернуть уже обработанные файлы
(список
(имя, bytes)заменённых) — либо прогресс-колбеком, либо исключение с данными.
3.3. Частичный результат — детали
- Каждый файл, прошедший замену, гарантированно консистентен: глобальный mapping один для всех.
- Частичный ZIP = готовые файлы +
mapping.csv(полная таблица — она собрана к концу LLM). - Если прервано до конца LLM — прерывание не сработает (добор), значит частичный результат будет как минимум с теми файлами, которые успели замениться после LLM.
4. Блок C — оценка времени (ETA)
4.1. Уровень 1 — статическая оценка в начале
- Фронт знает
Nфайлов и объёмS(МБ) до обработки. - Эмпирический коэффициент из замеров: 24.08 — 101 файл / ~150 КБ текста → LLM ≈ 1924с (~12.8 с/КБ текста); 23.08 — 100 файлов → LLM 841–985с.
- Коэффициент задать константой на фронте/бэке (можно в
config.py), формула:оценка ≈ S_текста(КБ) × K + константа. Для бинарных/сканов текста нет — оценка грубая, поэтому это «ориентировочно», с оговоркой в UI.
4.2. Уровень 2 — после извлечения (бэк знает объём текста)
obfuscate_filesпосле фазы извлечения знает суммарный объём текста (total_chars).- Через новый колбек (или расширенный
progress_cb) отдать{phase:"extract_done", chars:N}. - Тогда оценка точнее:
tokens ≈ chars × фактор,время ≈ tokens / скорость(истор.).
4.3. Уровень 3 — адаптивный ETA во время LLM
- В LLM-цикле меряем фактическую скорость: обработано символов / прошедшее время.
- Оставшийся объём =
total_chars − processed_chars. ETA = оставшиеся символы / скорость.- Отдавать в существующем heartbeat
llm:data: {"active":..., "elapsed":..., "tokens":..., "eta_sec": N, "done_chars":..., "total_chars":...}. - Фронт обновляет «осталось ~X мин» в live-блоке.
4.4. Ограничения (честно)
- Оценка ориентировочная (LLM-вывод варьируется). Показывать как «≈», обновлять адаптивно.
- Для сканов/PDF без текстового слоя — извлечённый текст мал, LLM-этап быстрый, оценка завышена.
5. Блок D — UI (index.html)
5.1. Сообщение «много данных»
- После загрузки (перед обработкой): «Загружено N файлов (X МБ). Идёт обработка — это может занять ~M мин.»
- Держать в статус-строке и в live-блоке.
5.2. Кнопка «Прервать»
- Появляется/активна на этапе обработки (Фаза 2).
- По нажатию — диалог-подтверждение:
«Остановить обработку? Будет сохранено: документы, уже прошедшие обработку (сейчас готово X из N), и таблица замен. Текущий анализ будет доведён до конца. [Остановить] [Отмена]»
- После подтверждения —
POST /api/cancel/<sid>. - Во время добора: счётчик «готово X/N» + «завершаем текущий этап…».
5.3. Показ частичного результата
- SSE-событие
cancelled→ статус «Сохранено X из N документов + таблица замен». - Кнопки «Скачать ZIP» / «Скачать CSV» работают как обычно (ведут на download/csv).
5.4. ETA в live-блоке
- «Обработка… осталось ~X мин» — из
eta_secхартбита. - Статическая оценка — сразу после старта обработки.
6. Порядок реализации и проверка
- TTL-фикс (
session.py+api_bp.py): тест — долгий прогон >30 мин, скачивание после. - Прерывание (
obfuscator.py+api_bp.py+session.py): тест — прервать в фазе замены, проверить частичный ZIP+CSV. - ETA (
obfuscator.py/api_bp.py+ фронт): тест — сверка оценки с фактом. - UI (кнопка, диалог, счётчик): ручной тест в браузере.
Версия: бамп до 0.0.63 после реализации (всех блоков).
7. Затронутые файлы
site/session.py— TTLtouch, флаг отмены.site/routes/api_bp.py—POST /api/cancel/<sid>, событиеcancelled, ETA в heartbeat, частичный результат.drhider/obfuscator.py—cancel_event,CancelRequested, передача объёма текста, сборка частичного результата.site/templates/index.html— сообщение, кнопка, диалог, счётчик, ETA.drhider/config.py(опц.) — коэффициент оценки времени.
8. Открытые вопросы
- Точное место сбора «готовых файлов» в
obfuscate_files(где хранить заменённые байты для частичного ZIP). - Формат события
cancelled(поляsaved/total+ причина). - Значение коэффициента оценки (уточнить по ещё паре замеров).