diff --git a/site/app.py b/site/app.py index 48a148a..7ef0431 100644 --- a/site/app.py +++ b/site/app.py @@ -93,6 +93,11 @@ def prompts_activate(): return jsonify({"ok": False, "error": str(e)}) +@app.route("/architect") +def architect(): + return render_template("architect.html") + + @app.route("/health") def health(): return {"ok": True, "service": "contracts-flask"} diff --git a/site/templates/architect.html b/site/templates/architect.html new file mode 100644 index 0000000..a79b857 --- /dev/null +++ b/site/templates/architect.html @@ -0,0 +1,303 @@ + + +
+ + +Пайплайн: загрузка → парсинг → классификация → группировка → сравнение → аудит
+ + + +Пользователь выбирает .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 Flask (k8s) | +Python 3.12, Jinja2, ванильный JS | +
| Механизм обработки | +VM (5.172.178.213) | +Python 3.12, httpx, pdfplumber, python-docx | +
| База данных | +VM PostgreSQL 15 | +JSONB, UUID, advisory locks | +
| LLM | +api.aillm.ru | +gpt-oss-120b (бесплатно, OpenAI API) | +
| Деплой | +Managed k8s + VM | +Dockerfile, Nginx + Let's Encrypt | +
+📋 Заказчик: «Конечная цель: построчное сравнение CRM ↔ фискальная система.+ +
+Особый фокус: даты начала услуг.
+PAYG (суффикс -m) — особый случай. Сверку с CRM тоже можно делегировать AI.» +
Текущий пайплайн завершается на этапе извлечения спецификации из договоров и отслеживания изменений по допникам. Модуль сравнения с CRM — спроектирован как CanonicalRow адаптер, ожидает формата данных от заказчика.
+📋 Заказчик: «Это всё пока опыты и набивание шишек.+ +
+Не надеюсь с первого захода на идеальный результат.» +