11 KiB
Запрос к Опусу — анализ и план развития Contracts App (v1.0.178)
Дата: 26.06.2025. Только инструкции, код не менять.
1. Контекст: что за сервис
Сервис «Сверка договоров» — обработка договоров облачного провайдера НУБЕС с контрагентами. Пользователь загружает ZIP-архивы с документами (договоры, допсоглашения, спецификации), система классифицирует, группирует по номерам договоров и сравнивает спецификации услуг — показывает diff (ADD/UPDATE/DELETE/UNRESOLVED).
Архитектура:
- Lucee (CFML) — фронтенд, домен contractor.luceek8s.dev.nubes.ru
- Python (VM) — бэкенд, домен contracts.kube5s.ru, порт 8766
- PostgreSQL — хранение
- nginx — прокси
- LLM — api.aillm.ru, модель gpt-oss-120b, HTTP/2 через httpx
Пайплайн:
- Upload (.docx/.pdf/.doc/.zip) → парсинг (python-docx/pdfplumber) → elements_json
- Classify — LLM определяет: тип (contract/supplement/specification/other), own_number, parent_number, counterparty, doc_date
- Group — Python normalize_number() + matching по номеру договора
- Compare — SSE, LLM сравнивает спецификации: ADD/UPDATE/DELETE/UNRESOLVED
2. Текущий код — ключевые файлы
2.1. Classify
Файл: contractor/deploy/services/classify.py
MAX_WORKERS = 4— параллельные запросы к LLM_smart_extract()— выжимка текста: заголовок ~1500 симв + regex-хиты по маркерам_call_llm_classify()— вызов LLM, парсинг JSON из ответаclassify_batch()— ThreadPoolExecutor, классифицирует все pending документы_safe_json_parse()— чинит битый JSON из LLM (markdown, trailing commas)
Промпт classify: llm_prompt.py → build_classify_prompt(header_text).
Запрашивает у LLM:
{"doc_type": "contract|supplement|specification|other",
"own_number": "XXX001-03700",
"parent_number": "XXX001-03700", // для допников/спек
"doc_date": "2025-01-01",
"counterparty": "ООО \"Ромашка\""}
Проблема classify: при 50+ файлах синхронный вызов таймаутился.
Сейчас: subprocess.Popen → classify_worker.py (async, 202 Accepted).
Но фронтенд (app.js runClassify()) не полностью синхронизирован с async-ответом.
2.2. Group
Файл: contractor/deploy/services/grouping.py
normalize_number()— uppercase + только буквы/цифры. «МЭС-123/2024» == «МЭС 123/2024» после нормализации.group_documents()— иерархический алгоритм:- contract → якорь группы
- supplement/spec → matching по parent_number (нормализованный)
- Оставшиеся → виртуальные группы по own_number
- Без номеров →
__unresolved__
apply_groups()— создаёт записи в DB (contracts + supplements)
Известные баги normalize_number:
- Кириллическая
Оvs цифра0— не различаются - Латинская
Cvs кириллическаяС— не различаются Ё→ выпадает
2.3. Compare
Файлы: contractor/deploy/convert_server.py (SSE endpoint /process-v2),
contractor/deploy/compare.js (фронтенд SSE-клиент).
Промпты compare: llm_prompt.py
FALLBACK_EXTRACT— для первого документа (базовый договор/спецификация): ADD всех строк.FALLBACK_DIFF— для допсоглашений: UPDATE/DELETE/ADD/UNRESOLVED. Поддерживает два режима:"mode": "partial"— точечные изменения"mode": "full_replace"— полная замена спецификации
Проблема compare: оба режима — «ADD всех строк». LLM не всегда корректно сопоставляет строки между v1 и v2 спецификации.
2.4. ZIP handling
Файлы: contractor/deploy/files.js (addZipFile()),
contractor/deploy/convert_server.py (_handle_unzip_upload()).
Текущая логика:
- ZIP загружается через FormData на
/unzip-upload - Бэкенд распаковывает, возвращает base64 каждого файла
- Фронтенд для каждого: base64 → Blob → File → upload (как addRegularFile)
- Дубликаты по имени: confirm-диалог, перезапись
Чего нет:
zip_source— связь файла с родительским ZIP- Группировка в таблице по ZIP-источнику
- ID файла =
zip_source + "/" + filename
2.5. Фронтенд
Ключевые JS-модули: contractor/deploy/ — state.js, files.js, groups.js, compare.js, app.js, app_utils.js
Таблица файлов: files.js renderFiles() — плоский список, без группировки по ZIP.
Уже есть max-height: 50vh; overflow-y: auto (скролл).
Степпер: app.js renderStepper() — 4 шага: Загрузка → Классификация → Группировка → Сравнение.
3. Что сказал заказчик (26.06.2025)
«А что касается вводных — файлы могут быть в зипах. Как правило, по 1 контрагенту, но я считаю, что по контрагенту может быть несколько зипов, и теоретически могу представить ситуацию, когда будет в одном зипе по нескольким как-то связанным контрагентам (какие-то агентские схемы). Наверно, вероятность того, что в одном зипе один контрагент — весьма высокая, но не 100%.»
Также обсуждалось:
- Привязка файлов к родительскому ZIP
- В таблице — ZIP как группирующий заголовок, ниже со сдвигом — вложенные файлы
- ID файла = имя зипа + "/" + имя файла
4. Что обсуждали мы (ключевые выводы)
- НУБЕС — всегда Исполнитель. Контрагенты — Заказчики.
- Один batch = один контрагент (почти всегда). Хоть 1 зип, хоть 5 зипов — всё по одному контрагенту.
- Агентские схемы — исключение, не основная логика.
zip_source— для визуальной группировки в таблице. Не влияет на логику classify/group/compare.- Два сценария UI:
- «Точный» (≤100 файлов) — текущий UI с таблицей и ручным контролем
- «Поток» (100+) — упрощённый UI: прогресс-бар, авто-пайплайн, сводный отчёт (без таблицы — 1000 DOM-строк вешают браузер)
- Коллизия одинаковых имён из разных ZIP:
- ID =
zip_source + "/" + filename→ разные сущности - Compare покажет diff между версиями
- ID =
5. Что нужно от Опуса
5.1. ПЛАН по zip_source
Как именно внедрить привязку файлов к родительскому ZIP на ВСЕХ слоях:
unzip.py→ возвращатьzip_sourcedb/documents.py→ полеzip_sourcefiles.jsrenderFiles()→ группировка + отступstate.js→ полеzip_sourceindex.cfm/ Lucee → надо ли менять?
Варианты отображения:
- Вариант А: ZIP как секция-заголовок, под ним файлы с отступом
- Вариант Б: древовидная структура (раскрывающиеся ZIP'ы)
- Вариант В: цветовая маркировка по ZIP
Какой лучше для пользователя и почему?
5.2. ПЛАН по двум сценариям UI
Как архитектурно разделить «Точный» и «Поток» режимы:
- Общий код (classify/group/compare не меняются)
- Разный UI: что показывать, что скрывать
- Как переключаться между режимами (авто по кол-ву файлов? ручной выбор?)
- «Поток» — сводный отчёт: структура, что в нём
5.3. АНАЛИЗ текущих промптов
Внимательно изучи llm_prompt.py:
- Есть ли проблемы в классификации (counterparty не определяется)
- Достаточно ли хорош diff-промпт (compare)
- Нужны ли разные варианты промптов для разных сценариев:
- Только НУБЕС-один контрагент (стандартный)
- Два контрагента (агентские схемы)
- Мусорные документы (акты сверки, счета)
- Нужна ли корректировка глоссария, примеров
5.4. РЕКОМЕНДАЦИИ по улучшению
Что ещё можно улучшить, исходя из анализа кода и требований заказчика:
- Приоритеты: что делать в первую очередь
- Риски: что может сломаться
- Оценка трудозатрат (грубо: маленькая/средняя/большая задача)
6. Ограничения
- Код не менять. Только инструкции и план.
- Ответ — в чат, подробно, с обоснованием каждого решения.
- Если есть несколько вариантов — перечислить с плюсами/минусами.