Files
contracts-flask/History/2026-08-26-upload-module-reuse-plan.md
2026-08-26 11:40:03 +03:00

7.1 KiB
Raw Permalink Blame History

Переиспользование модуля загрузки drhider в contracts-flask

Дата: 2026-08-26

Контекст

  • Шлюз managed-кластера рвёт тела запросов >~64 КБ, egress не ограничен.
  • Паттерн загрузки: браузер PUT файла на ВМ-буфер (WebDAV) → Flask pull (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}. Несовместимые форматы.

Ключевые находки Соннета

  1. Привязка к 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}.

  2. Граница «логика сверки» — НЕЛЬЗЯ трогать:

    Файл Функции
    pipeline_bp.py process_v2, classify_batch_route (SSE + classify)
    services/process.py run_pipeline
    services/classify.py classify_batch, _call_llm_classify, garbage filters
    services/grouping.py group_documents, apply_groups, normalize_number
    db/documents.py ВСЕ (контракт данных)
    db/supplements.py ВСЕ
  3. Риски:

    • клиентский 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) vs batch_id (сверка, crypto.randomUUID() в state.js);
    • zip_source — поле documents, одно на вызов (для UI-группировки).
  4. Безопасный порядок (Соннет): расширить 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_namebasename-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 (commit db58a43)
  • Этап 2 — sink (blueprint + contracts_upload_sink + регистрация), проверено
  • Удалён рукописный /api/upload_refs (основной путь теперь через модуль)
  • Этап 3 — UI: выбор папок (webkitdirectory) + рекурсивный клиентский ZIP (commit d2894ad)
  • Осталось (cleanup, опционально): .doc→.docx в sink, удалить /api/unzip_refs//api/convert_refs + addZipFile/convertDoc

Примечание: /api/unzip_refs и /api/convert_refs пока оставлены как fallback (server-side ZIP и .doc), фронт addZipFile/convertDoc не удалены.