# Пайплайн обработки договора ## Схема ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 👤 ПОЛЬЗОВАТЕЛЬ │ │ Открывает / → выбирает файлы → жмёт «Обработать» │ └──────────────────────────┬──────────────────────────────────────────┘ │ multipart/form-data (files) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ app.py: _upload_page() POST │ │ Принимает файлы, запускает пайплайн │ └──────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼ file.docx file.pdf file.zip │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ① parser.py │ │ docx → python-docx → elements JSON │ │ pdf → pdfplumber → elements JSON │ │ doc → libreoffice → docx → python-docx │ │ zip → zipfile → каждый файл рекурсивно │ │ │ │ Выход: [{type:"paragraph", style, text}, {type:"table", rows}] │ │ НИЧЕГО не фильтрует, не теряет │ └──────────────────────────┬──────────────────────────────────────────┘ │ elements JSON ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ② textify.py │ │ elements → линейный текст │ │ │ │ [No Spacing] Приложение № 1 │ │ [Heading 3] Состав и стоимость Услуг │ │ --- Таблица (9×6) --- │ │ | № | Наименование | Цена | Объем | Сумма | Дата | │ │ | 1 | Аренда стойко-места | 213 905 | 3 | 641 716 | 01.04.2026 | │ └──────────────────────────┬──────────────────────────────────────────┘ │ текст (строка) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ③ extractor.py + llm_client.py │ │ текст → промпт → LLM (gpt-oss-120b) → JSON │ │ │ │ Промпт: «Найди таблицы услуг, извлеки строки в JSON» │ │ Ответ: [{"row_num":1, "name":"Аренда...", "price":213905, │ │ "qty":3, "sum":641716, "date_start":"2026-04-01"}, ...] │ └──────────────────────────┬──────────────────────────────────────────┘ │ строки спецификации ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ④ differ.py │ │ Сравнение с предыдущим допником │ │ │ │ rows_old vs rows_new → changes: │ │ added: новая услуга │ │ changed: цена 213905 → 250000, дата 01.04 → 25.04 │ │ deleted: услуга убрана │ │ unchanged: без изменений │ └──────────────────────────┬──────────────────────────────────────────┘ │ изменения ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ⑤ Сохранение в PostgreSQL │ │ │ │ documents ← исходный файл + распарсенный текст │ │ contracts ← договор │ │ supplements ← допник │ │ spec_rows ← строки спецификации │ │ spec_history ← история изменений │ └──────────────────────────┬──────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Ответ пользователю: HTML-страница с таблицей результатов │ │ «Извлечено 14 строк, добавлено 14, изменено 0, удалено 0» │ │ + полная таблица услуг с ценами │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Кратко ``` файлы → parser → textify → LLM → differ → PostgreSQL → HTML-страница ``` ## Слои | # | Слой | Что делает | |---|---|---| | ① | `parser.py` | docx/pdf/doc/zip → JSON | | ② | `textify.py` | JSON → текст | | ③ | `extractor.py` + `llm_client.py` | текст → LLM → строки | | ④ | `differ.py` | сравнение с предыдущим | | ⑤ | `db.py` + `schema.py` | сохранение в БД | | — | `app.py` | сборка, приём формы, отдача HTML |