Пайплайн: загрузка → парсинг → классификация → группировка → сравнение → аудит
Пользователь выбирает .docx / .pdf / .doc / .zip.
pdfplumber извлекает текст и таблицы с сохранением структуры колонокpython-docx читает XML — извлекает параграфы и таблицы построчноunknown_formatРезультат: elements_json — массив элементов двух типов:
{type: "paragraph", text: "...", style: "..."}{type: "table", rows: [["колонка1", "колонка2"], ...]}Документ сохраняется в БД, считается content_hash (SHA-256) — если в этом же батче уже есть файл с таким содержимым, он помечается как duplicate и не дублируется.
📋 Заказчик: «100+ файлов за раз. Файлы: .docx/.pdf, возможно ZIP.
Загрузка из локали (файловая шара / облачный диск), НЕ S3 (конфиденциальность).»
Каждый загруженный файл проходит трёхэтапный фильтр.
📋 Заказчик: «Оставлять только: договоры, доп. соглашения, спецификации.
Игнорировать: акты сверки, счета/счета-фактуры/УПД, акты услуг, платёжки.»
Проверка имени файла на мусорные ключевые слова: «счёт», «акт», «платёж», «УПД», «сверка», «invoice» и др. Совпадение → garbage, дальше не идёт.
Проверка первых 2000 символов текста на заголовки: «СЧЕТ-ФАКТУРА», «АКТ СВЕРКИ», «АКТ ОКАЗАННЫХ УСЛУГ» и т.п. Совпадение → garbage.
📋 Заказчик: «Названия документов неинформативные. Есть лишние документы.
По всем контрагентам.»
Оставшиеся документы отправляются в LLM. Модель получает выжимку текста (первые 1500 символов + строки с маркерами «договор», «соглашение», «спецификация») и возвращает JSON:
doc_type — contract / supplement / specification / otherown_number — номер этого документаparent_number — номер родительского договора (для допников и спецификаций)doc_date — дата документа YYYY-MM-DDcounterparty — название контрагента📋 Заказчик: «НУБЕС = Исполнитель во всех целевых договорах.»
→ counterparty = другая сторона (Заказчик).
Детерминированная (без LLM). Документы группируются по номерам договоров.
📋 Заказчик: «Система сама должна разбираться, кто к кому.
зачем нам базовый договор? Вся информация есть в допниках и спеках.»
«Я не должен распознавать ничего. Система должна сделать всё.»
parent_number попадают в одну виртуальную группу — даже если сам договор не загруженДля каждой группы запускается последовательная обработка.
📋 Заказчик: «Важно. В произвольной, не повторяющейся.» (порядок допников)
«Главное — точность разбора, не UI. Сначала базовая функция → потом юзабельность.»
Документы внутри группы сортируются по doc_date (из классификации), затем по времени загрузки.
extractdiffADD — новая услугаUPDATE — изменение цены/количества/суммыDELETE — услуга исключенаЕсли допник говорит «изложить в новой редакции» — LLM возвращает полный новый список (full_replace). Все старые строки заменяются новыми.
ADD без имени услуги → UNRESOLVED (не применяется, помечается для ручной проверки)UPDATE/DELETE с пустым идентификатором → UNRESOLVEDsum = price × qty — расхождение → флагcontent_hash — один и тот же файл дважды не обрабатывается01.03.2026 → 2026-03-01 (авто-нормализация)1 000,50 → 1000.50 (авто-нормализация)📋 Заказчик: «кривых документов можно ожидать.
Если не может разобраться — пусть зовет на помощь юзера.»
📋 Заказчик: «Наверно я захочу иметь возможность посмотреть любые промежуточные результаты.
Было бы хорошо пояснить или показать процесс, что в каком порядке происходит.»
ADD/UPDATE/DELETE/UNRESOLVED)Три роли промптов, хранятся в БД с версионированием:
| Роль | Назначение |
|---|---|
extract |
Извлечение строк спецификации из договора |
diff |
Сравнение допников с текущей спецификацией |
classify |
Определение типа/номера/даты/контрагента документа |
Встроенный редактор с историей версий: сохранил новую версию → она сразу используется LLM. Старые версии остаются в истории — можно откатиться.
| Компонент | Где | Технология |
|---|---|---|
| Веб-интерфейс | Managed Python (Nubes) | Python 3.12, Jinja2, ванильный JS |
| Механизм обработки | Выделенная VM | Python 3.12, httpx, pdfplumber, python-docx |
| База данных | VM PostgreSQL 15 | JSONB, UUID, advisory locks |
| LLM | api.aillm.ru | gpt-oss-120b (бесплатно, OpenAI API) |
| Деплой | Managed Python + VM | Dockerfile, Nginx + Let's Encrypt |
📋 Заказчик: «Конечная цель: построчное сравнение CRM ↔ фискальная система.
Особый фокус: даты начала услуг.
PAYG (суффикс -m) — особый случай. Сверку с CRM тоже можно делегировать AI.»
Текущий пайплайн завершается на этапе извлечения спецификации из договоров и отслеживания изменений по допникам. Модуль сравнения с CRM — спроектирован как CanonicalRow адаптер, ожидает формата данных от заказчика.
📋 Заказчик: «Это всё пока опыты и набивание шишек.
Не надеюсь с первого захода на идеальный результат.»