From 0479afb44a82303e4807caddc7c09bfc029dc814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Mon, 24 Aug 2026 19:04:55 +0300 Subject: [PATCH] =?UTF-8?q?v0.0.71:=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81?= =?UTF-8?q?=D0=BE=D0=B2=20=E2=80=94=20=C2=AB=D0=BF=D1=80=D0=BE=D0=BF=D1=83?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=20(=D0=BB=D0=B8=D0=BC=D0=B8=D1=82)=C2=BB=20v?= =?UTF-8?q?s=20=C2=AB=D0=BD=D0=B5=20=D0=B8=D0=B7=D0=B2=D0=BB=D0=B5=D1=87?= =?UTF-8?q?=D1=91=D0=BD=C2=BB;=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B0=20?= =?UTF-8?q?=C2=AB=D0=9F=D1=80=D0=BE=D0=BF=D1=83=D1=89=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?(=D1=81=D0=B2=D0=B5=D1=80=D1=85=20=D0=BB=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D1=82=D0=B0)=C2=BB;=20=D1=81=D1=85=D0=B5=D0=BC=D0=B0=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=BA=D0=B8=20+=20=D0=BD=D0=B0=D0=B9=D0=B4=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=B1=D0=B0=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- History/README.md | 1 + .../infra/2026-08-24-upload-logic-schema.md | 43 +++++++++++++++++++ site/app.py | 2 +- site/templates/index.html | 18 +++++--- 4 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 History/infra/2026-08-24-upload-logic-schema.md diff --git a/History/README.md b/History/README.md index 4ab2320..0a0c138 100644 --- a/History/README.md +++ b/History/README.md @@ -106,6 +106,7 @@ - **2026-08-24-time-tickers-estimates.md** — v0.0.64–0.0.65: тикер текущего файла на всех этапах, мгновенные оценки времени по файлам и суммарно. - **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь). - **2026-08-24-folder-zip-not-lost.md** — v0.0.69: архивы из папки без документов не теряются (добавляются как есть); раскрытие zip с документами; склонение счётчика. +- **2026-08-24-upload-logic-schema.md** — подробная схема логики загрузки + найденные баги/несоответствия. - **2026-08-24-help-button-limits-text.md** — v0.0.70: заметная кнопка HELP (вместо «?») + пояснение статуса «пропущен» в ограничениях. - **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия». diff --git a/History/infra/2026-08-24-upload-logic-schema.md b/History/infra/2026-08-24-upload-logic-schema.md new file mode 100644 index 0000000..f55c83d --- /dev/null +++ b/History/infra/2026-08-24-upload-logic-schema.md @@ -0,0 +1,43 @@ +# Схема логики загрузки + найденные баги/несоответствия (2026-08-24) + +_infra. Подробная схема по коду: index.html (фронт), api_bp.py, session.py, obfuscator.py, extractor.py._ + +## Участники +- **Браузер** — index.html: выбор файлов/папки, PUT на ВМ, SSE-прогресс, статусы. +- **ВМ-буфер** — nginx dav `https://contracts.kube5s.ru/drhider-upload/` (CORS разрешён только для origin `https://drhider.pythonk8s.dev.nubes.ru`). +- **Бэк** — Flask (api_bp.py): upload_refs (pull), process_stream (SSE, воркер-поток), session.py (лимиты), drhider (extractor→scanner→replacer→builder). + +## Этап 0 — выбор файлов (браузер) +1. `fileInput` (multiple, accept) — для `.zip` → `listZipFiles` (раскрытие в браузере, allowedExt = .pdf .doc .docx .txt .md) → `addFileWithDedup`; иначе — как есть. +2. `folderInput` (`webkitdirectory`) — рекурсивно: `webkitRelativePath.slice(1)` (отбрасываем верхнюю папку), zip раскрывается с префиксом пути, документы — с относительным путём, прочее — пропуск. +3. `addFileWithDedup`: дедуп (имя+размер → пропуск; коллизия имени → суффикс `_2`); лимиты 50МБ/файл и 500МБ/сессия → `overNames` («🔥 не учитывается» в обычной таблице). + +## Этап 1 — загрузка (uploadFiles, фаза 1) +4. `toSend` = файлы БЕЗ `overNames`; over-файлы сразу получают статус «пропущен (лимит)» (v0.0.71). +5. Для каждого файла: **PUT** на `VM_UPLOAD_URL + token + '_' + k` (имя в URL не несётся) → прогресс % в ячейке → `✓ N KB/s`. `refs.push({name, size, url})`. Таймаут 300с. +6. **POST /api/upload_refs** {session, files: refs}: + - бэк: для каждого ref: `size > 50МБ` → delete+skip; `GET url` (pull egress, timeout 120с) → `content`; `content > 50МБ` → delete+skip; `add_file(sid, name, content)` (лимит сессии 500МБ); `delete url` с ВМ. + - ошибка сети/DNS → **502 «Pull failed»** (весь запрос падает). +7. Тест-режим `?upload-only=1` — стоп после фазы 1. + +## Этап 2 — обработка (SSE, фаза 2) +8. `EventSource /api/process_stream/`; воркер-поток → `obfuscate_files(all_files)`. +9. **obfuscator (проход 1)**: `expand_zips` (повторная распаковка zip, basename, защита от бомб) → `_dedupe_file_names` → для каждого: `extract_text` (исключение → `skipped`, событие `done` на этапе извлечения) → `scan_regex` → `extract_done` (total_chars, per_file) → `scan_llm_ner` (чанки: file_start/file_chunk/file_done; отмена → CancelRequested). +10. **проход 2**: `apply_replacements` для каждого (битые/скан → пропуск; при отмене — только `llm_done`) → `done` → `build_zip` + `build_mapping_csv` → событие `result`/`cancelled`. +11. Фронт: 3-секционная таблица (Обработанные / Текущий / Ожидают); статусы: `done` (до extract_done = битый → **skipped «не извлечён»** v0.0.71), `current`, `analyzed`, `pending`; `complete`/`cancelled` → статистика, кнопки ZIP/CSV, заморозка сессии. + +## Найденные баги и несоответствия +1. **[исправлено v0.0.71]** Статус «пропущен» использовался для ДВУХ разных причин: лимит и «не извлёкся». Теперь: «пропущен (лимит)» и «не извлечён» (пустой/битый/скан). +2. **[исправлено v0.0.71]** over-файлы во время обработки попадали в группу «Ожидают обработки» (нет procState → pending) с оценкой времени. Теперь — отдельная группа «⛔ Пропущены (сверх лимита)». +3. **[открыто]** `upload_refs` неатомарен: при ошибке pull (DNS/сеть) часть файлов уже добавлена в сессию и удалена с ВМ, но фронт получает 502 и бросает — сессия-сирота, файлы на ВМ частично остаются (инцидент 18:42). Нужны ретраи/атомарность. +4. **[открыто]** `upload_refs` при превышении суммарного лимита сессии отвечает «Session not found» (404) — add_file возвращает False и для отсутствия сессии, и для лимита; сообщение неверное. +5. **[открыто, риск]** Повторный `expand_zips` на бэке (с `os.path.basename` — обрезает пути) при отправке zip как есть (v0.0.69) ломает соответствие idx: количество файлов бэка ≠ фронта → `sendIdx[d.idx]` = undefined. Обычно не срабатывает (фронт сам раскрывает), но риск при расхождении allowedExt. +6. **[открыто]** allowedExt не совпадают: фронт раскрывает из zip только документы, бэк `expand_zips` берёт ВСЕ файлы (без фильтра) — расхождение поведения при zip «как есть». +7. **[открыто, косметика]** Счётчик «Добавлено из папки: N» завышается при дедупе (`added++` безусловно после `addFileWithDedup`). +8. **[открыто]** Три разных отображения одной причины (лимит) до обработки / при старте / в обработке — унифицировано в v0.0.71 (группа over). + +## Проверка +- `node --check` — OK, `py_compile` — OK. +- Локально (v0.0.71): группа «⛔ Пропущены (сверх лимита)» + «пропущен (лимит)» — работает. + Полный цикл локально невозможен из-за CORS ВМ-буфера (разрешён только origin прода). +- Версия 0.0.70 → 0.0.71. diff --git a/site/app.py b/site/app.py index 5a66ce3..7458893 100644 --- a/site/app.py +++ b/site/app.py @@ -21,7 +21,7 @@ if _sys_path_root not in sys.path: sys.path.insert(0, _sys_path_root) # Версия приложения (меняется при изменениях) -VERSION = "0.0.70" +VERSION = "0.0.71" def setup_logging(): diff --git a/site/templates/index.html b/site/templates/index.html index 8db9d80..e97617d 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -146,7 +146,7 @@

Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP с обезличенными копиями.
CSV с таблицей замен скачивается отдельно.
- Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх лимитов помечаются красным и не участвуют в обфускации: при запуске обработки они получают статус «пропущен» и не попадают в результат.
+ Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх лимитов помечаются красным (статус «пропущен») и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без текстового слоя получают статус «не извлечён» — они тоже не попадают в результат.
⏱ Время обработки каждого файла — ориентировочное (обновляется по факту).

@@ -311,8 +311,9 @@ function procRow(i, stTxt) { } function renderProcTable() { - const groups = { done: [], current: [], pending: [] }; + const groups = { done: [], current: [], pending: [], over: [] }; for (let i = 0; i < sf.length; i++) { + if (overNames.has(sf[i].name)) { groups.over.push(i); continue; } const st = procState[i] ? procState[i].st : 'pending'; if (st === 'done' || st === 'skipped') groups.done.push(i); else if (st === 'current') groups.current.push(i); @@ -332,11 +333,17 @@ function renderProcTable() { for (const i of groups.done) { const p = procState[i]; const txt = p.st === 'skipped' - ? 'пропущен' + ? 'не извлечён' : '✓ ' + (p.elapsed ? p.elapsed.toFixed(1) : '0.0') + 'с'; rows.push(procRow(i, txt)); } } + if (groups.over.length) { + rows.push('⛔ Пропущены (сверх лимита) (' + groups.over.length + ')'); + for (const i of groups.over) { + rows.push(procRow(i, 'пропущен (лимит)')); + } + } if (groups.current.length) { rows.push('▶ Текущий файл'); for (const i of groups.current) { @@ -358,8 +365,7 @@ function renderProcTable() { p.est = Math.max(1, Math.round(szMB * k)); } let txt; - if (overNames.has(sf[i].name)) txt = '🔥 не учитывается'; - else if (p.st === 'analyzed') txt = 'анализ ✓'; + if (p.st === 'analyzed') txt = 'анализ ✓'; else if (p.st === 'current') txt = '⏳'; else if (p.est != null) txt = '~' + fmtSec(p.est) + ''; else txt = '—'; @@ -653,7 +659,7 @@ async function uploadFiles() { const sendIdx = []; // sendIdx[k] = индекс в sf для отправленного файла for (let i = 0; i < files.length; i++) { if (!overNames.has(files[i].name)) { toSend.push(files[i]); sendIdx.push(i); } - else { ss(i, 'пропущен'); } + else { ss(i, 'пропущен (лимит)'); } } // Обрубить всё что могло остаться от предыдущего раза if (activeES) { activeES.close(); activeES = null; }