- копирую модуль upload/ из drhider (слои 1-2) - blueprint: параметр sink (drhider-сессия по умолчанию, сверка — DB+парсинг) - upload_bp: contracts_upload_sink = _store_and_parse - routes: регистрирую create_upload_refs_blueprint(cfg, sink=...) - app.py: корень репо в sys.path (для import upload) - History: план переиспользования + ревью Соннета
6.6 KiB
Переиспользование модуля загрузки drhider в contracts-flask
Дата: 2026-08-26
Контекст
- Шлюз managed-кластера рвёт тела запросов >~64 КБ, egress не ограничен.
- Паттерн загрузки: браузер
PUTфайла на ВМ-буфер (WebDAV) → Flaskpull(egress GET) → обработка. - В drhider этот паттерн отлажен и вынесен в переиспользуемый модуль
upload/(слой 1 — выбор файлов/папок/архивов → таблица; слой 2 — закачка PUT→pull; слой 3 — логика приложения, НЕ в модуле). - Задача: взять из drhider выбор+загрузку, сшить с логикой сверки contracts-flask, НЕ меняя саму логику сверки (классификация/группы/сравнение).
Текущее состояние contracts-flask: v2.0.11 (рабочая загрузка через ВМ, написана вручную в этой сессии).
Ревью Соннета (итог)
Вердикт: «с оговорками» — одна критическая: слои 1+2 нельзя взять AS-IS для
фронт-части слоя 2. uploadViaVM.js шлёт {session, files:[...]}, а сверка ожидает
{batch_id, contract_id, zip_source, files}. Несовместимые форматы.
Ключевые находки Соннета
-
Привязка к in-memory сессии (blueprint.py, 4 точки):
create_session(),add_file(sid, name, content),get_files(sid) is None,file_count(sid). → заменить однимsink(name, content, **ctx), гдеctx = {batch_id, contract_id, zip_source}. -
Граница «логика сверки» — НЕЛЬЗЯ трогать:
Файл Функции pipeline_bp.pyprocess_v2,classify_batch_route(SSE + classify)services/process.pyrun_pipelineservices/classify.pyclassify_batch,_call_llm_classify, garbage filtersservices/grouping.pygroup_documents,apply_groups,normalize_numberdb/documents.pyВСЕ (контракт данных) db/supplements.pyВСЕ -
Риски:
- клиентский ZIP — полная распаковка в память браузера (у сверки PDF ~19 МБ);
allowedExtразный: drhider[.pdf,.doc,.docx,.txt,.md]vs сверка{pdf,docx,doc,zip};parse.pyуже пытается парсить.docнапрямую (elif ext=="doc": _parse_docx(data)) — sink должен перехватывать.docДОparse_file;- дедуп: name+size (слой 1) vs content-hash (sink) — не ошибка, двойная защита;
session(drhider) vsbatch_id(сверка,crypto.randomUUID()в state.js);zip_source— поле documents, одно на вызов (для UI-группировки).
-
Безопасный порядок (Соннет): расширить blueprint (sink) → extra-поля → contracts_sink → backend → frontend layer1 → frontend layer2 → удалить
/api/unzip_refs,/api/convert_refs.
Противоречия в плане (Соннет)
- «Слои 1+2 КАК ЕСТЬ» неверно для фронт-части слоя 2 (
{session}vs{batch_id,...}). .docв sink неполно описан (порядок «конвертация ДО parse»).allowedExtпри интеграции не упомянут.
Моё решение (3 этапа)
| Этап | Что | Риск |
|---|---|---|
| 1. Транспорт | скопировать upload/ в contracts-flask; заменить рукописные _safe_name + _pull_with_retries на модульные |
низкий |
| 2. Sink | create_upload_refs_blueprint(cfg, sink=...); sink = _store_and_parse + перехват .doc → внешний LibreOffice → parse_file |
средний |
| 3. UI | initUploadTable (файлы+папки+архивы) |
высокий, последним |
Принятые решения
- ZIP → серверный (оставить
/api/unzip_refs): у сверки крупные PDF, клиентская распаковка = регресс по памяти (осознанное отступление от «как в хайдере»). .doc→ конвертация в sink через внешний LibreOffice-сервис (CONVERT_SERVICE_URL), последовательно (один тяжёлый.docблокирует пакет; параллелить потом).- UI → последним, изолированно от «загрузка не работает».
- Реализация: я (спецификация + sink), субагент-младшая модель (механика этапа 1), я ревьюю дифф.
Что НЕ переносить из drhider
- in-memory сессию (сверка хранит в SQLite
documentsпоbatch_id/doc_id); - клиентскую распаковку ZIP;
- обфускацию/слой 3 drhider.
Находка при чтении транспортных файлов модуля (2026-08-26)
Модуль — это целостный Blueprint, а не набор drop-in функций:
safe_name(name)сохраняет подпапки и возвращает""при небезопасном имени. У сверки_safe_name— basename-only (rsplit) и возвращает"file.bin". НЕ 1:1.pull_file(client, url, ...)требуетhttpx.Client+ стриминг (client.stream). У сверки_pull_with_retries(url)—httpx.getвнутри. Разные сигнатуры.blueprint.pyжёстко завязан на сессию:create_session,add_file,get_files,file_count.
Следствие: «этап 1: заменить 2 функции 1:1» НЕ выполним. Переиспользование идёт на уровне blueprint через sink (этап 2), с адаптацией формы запроса/ответа.
Статус
- Скопировать
upload/в contracts-flask - Этап 2 — sink (доработка blueprint + contracts-sink + регистрация)
- Удалить рукописный транспорт (после этапа 2)
- Этап 3 — UI