v0.0.71: разделение статусов — «пропущен (лимит)» vs «не извлечён»; группа «Пропущены (сверх лимита)»; схема логики загрузки + найденные баги
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This commit is contained in:
@@ -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 (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия».
|
||||
|
||||
|
||||
@@ -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/<sid>`; воркер-поток → `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.
|
||||
+1
-1
@@ -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():
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<p class="sub">
|
||||
Загрузите документы (.docx, .doc, .pdf, .txt, .md, .zip) — получите ZIP с обезличенными копиями.<br>
|
||||
CSV с таблицей замен скачивается отдельно.<br>
|
||||
<span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх лимитов помечаются красным и не участвуют в обфускации: при запуске обработки они получают статус «пропущен» и не попадают в результат.</span><br>
|
||||
<span style="color:#c0392b;">Ограничения: один файл не более 50 МБ, суммарно не более 500 МБ. Файлы сверх лимитов помечаются красным (статус «пропущен») и не участвуют в обфускации. Пустые, повреждённые файлы и сканы без текстового слоя получают статус «не извлечён» — они тоже не попадают в результат.</span><br>
|
||||
<span style="color:#7d3c98;">⏱ Время обработки каждого файла — ориентировочное (обновляется по факту).</span>
|
||||
</p>
|
||||
<div class="file-input-wrap">
|
||||
@@ -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'
|
||||
? '<span style="color:#c0392b;">пропущен</span>'
|
||||
? '<span style="color:#c0392b;" title="Не удалось прочитать файл: пустой, повреждённый или скан без текста">не извлечён</span>'
|
||||
: '<span style="color:#22c55e;">✓ ' + (p.elapsed ? p.elapsed.toFixed(1) : '0.0') + 'с</span>';
|
||||
rows.push(procRow(i, txt));
|
||||
}
|
||||
}
|
||||
if (groups.over.length) {
|
||||
rows.push('<tr class="grp-row"><td colspan="4">⛔ Пропущены (сверх лимита) (' + groups.over.length + ')</td></tr>');
|
||||
for (const i of groups.over) {
|
||||
rows.push(procRow(i, '<span style="color:#c0392b;">пропущен (лимит)</span>'));
|
||||
}
|
||||
}
|
||||
if (groups.current.length) {
|
||||
rows.push('<tr class="grp-row"><td colspan="4">▶ Текущий файл</td></tr>');
|
||||
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 = '<span style="color:#c0392b;">🔥 не учитывается</span>';
|
||||
else if (p.st === 'analyzed') txt = '<span style="color:#7d3c98;">анализ ✓</span>';
|
||||
if (p.st === 'analyzed') txt = '<span style="color:#7d3c98;">анализ ✓</span>';
|
||||
else if (p.st === 'current') txt = '<span style="color:#2563eb;">⏳</span>';
|
||||
else if (p.est != null) txt = '<span style="color:#999;">~' + fmtSec(p.est) + '</span>';
|
||||
else txt = '<span style="color:#999;">—</span>';
|
||||
@@ -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, '<span style="color:#c0392b;">пропущен</span>'); }
|
||||
else { ss(i, '<span style="color:#c0392b;">пропущен (лимит)</span>'); }
|
||||
}
|
||||
// Обрубить всё что могло остаться от предыдущего раза
|
||||
if (activeES) { activeES.close(); activeES = null; }
|
||||
|
||||
Reference in New Issue
Block a user