From e14b9f9523dc459f5bb00b8584aae32a49bd66eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Tue, 16 Jun 2026 15:01:18 +0400 Subject: [PATCH] =?UTF-8?q?session-05:=20SSE-=D0=BF=D0=B0=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D0=BD=D0=B3,=20=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=20=D0=BE?= =?UTF-8?q?=D1=82=20LLM,=20ZIP-=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- History/session-05-sse-parsing.md | 113 ++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 History/session-05-sse-parsing.md diff --git a/History/session-05-sse-parsing.md b/History/session-05-sse-parsing.md new file mode 100644 index 0000000..b82796f --- /dev/null +++ b/History/session-05-sse-parsing.md @@ -0,0 +1,113 @@ +# Сессия #5 — SSE-парсинг, интерактивный прогресс, отказ от LLM + +_Дата: 2026-06-16_ +_Версия: v1.0 → v1.5 (5 повышений)_ +_Коммиты: fa4c7fe → 435f617 (6 шт.)_ + +--- + +## Контекст + +Пользователь (Наиль) захотел: +1. Видеть **интерактивный прогресс** при обработке файлов (имя + таймер в секундах) +2. После обработки — **сводка**: общее время, байты +3. **Никакого LLM** пока — только парсинг +4. Вспомнил что очередь файлов (JS-слой) уже была реализована в коммите `17c4841` + +--- + +## Шаг 1: SSE-поток для LLM (v1.1, fa4c7fe) + +**Что сделано:** +- `_process` в app.py переделан с блокирующего POST на SSE-поток (`text/event-stream`) +- Маршрут `/process/` сменён на GET (нужно для `EventSource`) +- События: `file_start` → `file_done`/`file_error` → `summary` +- В шаблоне — JavaScript с `EventSource`, живые таймеры (каждые 500мс) + +**Ошибка:** Назвал деплой «поды в Kubernetes» — пользователь поправил: это **managed service** на pythonk8s.services.ngcloud.ru, деплой = `git push master`. + +**Ошибка:** Забыл повысить версию → v1.1 отдельным коммитом. + +--- + +## Шаг 2: Полный отказ от LLM (v1.2, 3cffca7) + +**Причина:** Пользователь сказал «НЕ НАДО ЛЛМ и на экране тоже». + +**Что сделано:** +- `_process` → `_parse` — только парсинг, без `extractor.extract()` и `differ.diff()` +- `_upload_files` — только сохраняет файлы в БД, без парсинга на этом этапе +- `POST /` возвращает JSON `{"contract_id": "..."}` вместо редиректа +- Весь LLM (`extractor`, `differ`, `llm_client`) убран из основного потока + +**Интерфейс:** +- Файлы → сразу в таблицу (имя, дата изменения, размер) +- ZIP — JSZip показывает содержимое в браузере +- Кнопка **«Парсинг»** (не «Обработать LLM») +- Клиент: POST файлов через `fetch`, затем SSE `/parse/` +- Прогресс в столбце «Статус»: ⏳ Xс → ✓ X.Xс +- Итоговая строка: ✓ Готово: N файлов | размер | время + +--- + +## Шаг 3: ZIP — файлы внутри не парсились (v1.3, 21f8c0b) + +**Проблема:** ZIP-файл парсился как единое целое (✓ 0.2с), а файлы внутри него (`допник-1.doc`, `спецификация.docx`...) оставались с `—` в статусе. + +**Причина:** В `_parse` парсер просто сливал все elements из ZIP в один документ. Файлы из JSZip-превью были только в браузере, сервер про них не знал. + +**Решение:** +- Сервер сам открывает ZIP (`zipfile.ZipFile`) +- Для каждого файла внутри: создаёт отдельный `documents` + `supplements`, парсит, шлёт SSE-события +- JSZip и сервер используют одинаковые имена → `findRowByName` находит строку +- ZIP-строка показывает `↗ N файлов` вместо галочки + +--- + +## Шаг 4: Подпапки в ZIP (v1.4, d5858d7) + +**Проблема:** Если в ZIP есть подпапки, и в разных папках файлы с одинаковыми именами — конфликт. + +**Решение:** Использовать полный относительный путь (`подпапка/файл.docx`) вместо basename. + +Изменено в двух местах: +- **Клиент**: `relativePath` вместо `relativePath.split('/').pop()` +- **Сервер**: `zname` вместо `zbasename` + +--- + +## Шаг 5: Только PDF и Word (v1.5, 435f617) + +**Проблема:** Из ZIP парсились ВСЕ файлы (включая `.txt`, `.xlsx`), давая пустые результаты. + +**Решение:** Фильтр по MIME-типу: +- Сервер: `if zmime not in (pdf, docx, doc): continue` +- Клиент (JSZip): `if ['docx','doc','pdf'].indexOf(ext) === -1: return` + +--- + +## Архитектура (текущая) + +``` +app.py: + POST / → _upload_files() → сохранить файлы в БД, вернуть JSON {contract_id} + GET / → _index() → render_template("upload.html") + GET /parse/ → _parse() → SSE-поток парсинга + +_parse(): + 1. Для каждого документа договора: + - Обычный файл → parser_mod.parse() → сохранить parsed_text + - ZIP → zipfile.ZipFile() → для каждого PDF/Word внутри: + создать document + supplement → parser_mod.parse() → сохранить + 2. SSE-события: file_start → file_done/file_error → zip_expanded → summary +``` + +--- + +## Выученные уроки + +1. **⛔ Не фантазировать про инфраструктуру.** Managed service ≠ Kubernetes. Деплой = `git push`. +2. **Версия — всегда.** После каждого изменения кода — bump. +3. **ZIP требует особой обработки.** Недостаточно просто распарсить — нужно создать документы для каждого файла внутри. +4. **Имена должны совпадать** между клиентом (JSZip) и сервером (zipfile) — иначе `findRowByName` не находит строку. +5. **`replace_string_in_file` портит файлы** — для больших правок использовать heredoc (`cat > file`).