commit 879e0167540bbcf3281401c0b21ab674c5a1b2e9 Author: “Naeel” Date: Sat Jun 13 13:07:10 2026 +0400 init: README, примеры договоров, история и архитектура проекта diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9db60d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +FILES +contracts-app/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f80552d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Сверка договоров (Contracts) + +Проект автоматизированной обработки и сверки договоров с помощью ИИ. + +## Задача + +Разбирать спецификации договоров и допников (docx/pdf) в структурированный вид, +отслеживать изменения между версиями, восстанавливать историю договора во времени. + +## Состав + +| Папка | Что | +|---|---| +| `contracts-app/` | Flask-сервис (основной код) | +| `dogovora/` | Примеры договоров и допников | +| `FILES/` | Заметки, ключи LLM | + +## Конфиденциальность + +Данные договоров строго конфиденциальны. LLM — только собственная модель. diff --git a/dogovora/примеры_договоров_для_ИИ.zip b/dogovora/примеры_договоров_для_ИИ.zip new file mode 100755 index 0000000..2164dba Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ.zip differ diff --git a/dogovora/примеры_договоров_для_ИИ/допник-1-XXX002-01200_3.doc b/dogovora/примеры_договоров_для_ИИ/допник-1-XXX002-01200_3.doc new file mode 100755 index 0000000..47a8c87 Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ/допник-1-XXX002-01200_3.doc differ diff --git a/dogovora/примеры_договоров_для_ИИ/допник-1-XXX003-01300_2.docx b/dogovora/примеры_договоров_для_ИИ/допник-1-XXX003-01300_2.docx new file mode 100755 index 0000000..8c2e5e7 Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ/допник-1-XXX003-01300_2.docx differ diff --git a/dogovora/примеры_договоров_для_ИИ/спецификация-XXX001-03700.docx b/dogovora/примеры_договоров_для_ИИ/спецификация-XXX001-03700.docx new file mode 100755 index 0000000..21a3b9f Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ/спецификация-XXX001-03700.docx differ diff --git a/dogovora/примеры_договоров_для_ИИ/спецификация-XXX003-01300_2.docx b/dogovora/примеры_договоров_для_ИИ/спецификация-XXX003-01300_2.docx new file mode 100755 index 0000000..e3d9c15 Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ/спецификация-XXX003-01300_2.docx differ diff --git a/dogovora/примеры_договоров_для_ИИ/спецификация-ХХХ002-01200_3.docx b/dogovora/примеры_договоров_для_ИИ/спецификация-ХХХ002-01200_3.docx new file mode 100755 index 0000000..6c33e6e Binary files /dev/null and b/dogovora/примеры_договоров_для_ИИ/спецификация-ХХХ002-01200_3.docx differ diff --git a/history/architecture.md b/history/architecture.md new file mode 100644 index 0000000..cb0629f --- /dev/null +++ b/history/architecture.md @@ -0,0 +1,161 @@ +# Архитектура Contracts App + +## Обзор + +Сервис **Сверка договоров** — автоматизированная обработка договоров и допников (docx/pdf) +с извлечением структурированных данных, отслеживанием изменений и восстановлением +истории договора во времени. + +## Принципы + +1. **НЕ МОНОЛИТ** — каждый слой независим, отдельный файл, своя зона ответственности +2. **Данные не покидают облако** — всё в PostgreSQL внутри кластера +3. **Ничего не терять** — парсер отдаёт полный слепок документа, LLM решает что важно +4. **Исключения — не фантазировать** — нерешаемые подзадачи отмечать явно + +--- + +## Слои приложения + +``` +┌─────────────────────────────────────────────┐ +│ app.py │ +│ ContractsApp (сборка) │ +├──────────┬──────────┬──────────┬────────────┤ +│ db.py │ parser.py│ test_ │ (будущие) │ +│ (БД) │ (парсинг)│ routes.py │ llm.py │ +│ │ │ (/test) │ upload.py│ +└──────────┴──────────┴──────────┴────────────┘ +``` + +| Слой | Файл | Что делает | Статус | +|---|---|---|---| +| Ядро | `app.py` | Flask-приложение, инициализация, регистрация Blueprint | ✅ | +| БД | `db.py` | `connect()`, `query()`, `_pg_connect()` | ✅ | +| Тесты | `test_routes.py` | Blueprint `/test` — мост к БД извне | ✅ | +| Парсер | `parser.py` | `parse(bytes, mime) → elements` для docx/pdf/doc/zip | ✅ | +| LLM | `llm.py` | Нормализация строк через aillm.ru (120B) | ⬜ | +| Загрузка | `upload.py` | Приём файлов, сохранение в БД | ⬜ | + +--- + +## Поток обработки документа + +``` +Пользователь + │ + ▼ +POST /upload (файл .docx/.pdf/.doc/.zip) + │ + ▼ +upload.py: сохранить в contract_docs (original_bytes) + │ + ▼ +parser.py: parse(bytes, mime) → elements JSON + │ + ▼ +db.py: сохранить parsed_json в contract_docs + │ + ▼ +llm.py: отправить elements → LLM → нормализованные spec_rows + │ + ▼ +db.py: сохранить в spec_rows, сравнить с предыдущими → spec_history + │ + ▼ +GET /contract/{id}/history → полная история изменений +``` + +--- + +## Схема БД (план) + +``` +contract_docs — исходные файлы + сырой парсинг + id UUID PK + contract_id → contracts.id + filename TEXT + mime_type TEXT + original_bytes BYTEA ← сам файл + parsed_json JSONB ← выдача parser.py + created_at TIMESTAMPTZ + +contracts — договоры + id UUID PK + number TEXT ← номер договора + client TEXT ← клиент + date DATE + status TEXT + +supplements — допники + id UUID PK + contract_id → contracts.id + number TEXT + date DATE + type TEXT ← новый / изменение / расторжение + doc_id → contract_docs.id + +spec_rows — строки спецификаций + id UUID PK + supplement_id → supplements.id + row_num INT + name TEXT ← наименование услуги + price NUMERIC + qty NUMERIC + sum NUMERIC + date_start DATE + date_end DATE + +spec_history — история изменений + id UUID PK + spec_row_id → spec_rows.id + supplement_id → supplements.id + change_type TEXT ← added / changed / deleted / unchanged + old_values JSONB + new_values JSONB +``` + +--- + +## API эндпоинты + +| Метод | Путь | Слой | Что | +|---|---|---|---| +| GET | `/` | app.py | Главная (HTML) | +| GET | `/health` | app.py | Health check → "OK" | +| GET | `/test` | test_routes | Статус БД + список команд | +| POST | `/test` `{"action":"status"}` | test_routes | Статус БД | +| POST | `/test` `{"action":"createdb"}` | test_routes | Создать БД contracts | +| POST | `/test` `{"action":"tables"}` | test_routes | Список таблиц | +| POST | `/test` `{"action":"sql","sql":"..."}` | test_routes | Произвольный SQL | + +--- + +## Технологии + +| Компонент | Выбор | +|---|---| +| Язык | Python 3.12 | +| Фреймворк | Flask | +| БД | PostgreSQL (внутрикластерный) | +| Парсинг docx | python-docx | +| Парсинг .doc | LibreOffice (headless) | +| Парсинг PDF | pdfplumber | +| LLM | aillm.ru API (120B модель) | +| Деплой | pythonk8s.services.ngcloud.ru | +| Репозиторий | gitea.services.ngcloud.ru/Nail/contracts-app.git | + +--- + +## Конфигурация (переменные окружения) + +| Переменная | Назначение | +|---|---| +| `DB_HOST` | Хост PostgreSQL | +| `DB_PORT` | Порт (5432) | +| `DB_NAME` | Имя БД (contracts) | +| `DB_USER` | Пользователь | +| `DB_PASS` | Пароль | +| `DB_SSLMODE` | SSL mode (disable) | +| `LLM_API_KEY` | Ключ aillm.ru (будет) | +| `LLM_API_URL` | URL LLM API (будет) | diff --git a/history/session-01-init.md b/history/session-01-init.md new file mode 100644 index 0000000..cff587b --- /dev/null +++ b/history/session-01-init.md @@ -0,0 +1,282 @@ +# История проекта Contracts (Сверка договоров) + +## 2026-06-13 — Сессия #1: Инициация и развёртывание + +### Участники +- Сергей Мищук — постановщик задачи +- Владимир Крупский — тех. консультация +- Наиль Тазетдинов — разработка +- GitHub Copilot (DeepSeek V4 Pro) — AI-ассистент + +--- + +## Задача (из fromTelega.md) + +**Сверка договоров** — автоматизированная обработка договоров и допников: + +1. Разобрать спецификации (docx/pdf) до структурированного вида +2. Собрать кумулятивный статус договора по цепочке допников (во времени) +3. (опционально) Сопоставить артикулы с каталогом услуг через LLM + +**Ограничения:** +- Данные строго конфиденциальны +- LLM — только своя модель +- Исключения не фантазировать, отмечать как нерешаемые + +--- + +## Решения по технологиям + +| Компонент | Выбор | Причина | +|---|---|---| +| Язык | Python 3.12 | python-docx, psycopg2, requests — лучшая экосистема для docx + БД + LLM | +| Фреймворк | Flask | Платформа поддерживает, шаблон Baldurs-Gate-test | +| БД | PostgreSQL | Существующий кластер postgresqlk8s | +| LLM | aillm.ru (120B) | Ключ sk-ucI5YvOticoOQK9ujK5m9Q, резервный DeepSeek Flash | +| Деплой | pythonk8s.services.ngcloud.ru | Платформа Flask-сервисов | +| Репозиторий | gitea.services.ngcloud.ru/Nail/contracts-app.git | Публичный, мастер-ветка | + +**Отклонённые варианты:** +- Node.js — слабый парсинг docx (mammoth → HTML) +- Lucee/CFML — нет инструментов для docx/LLM +- SQLite — не подходит для облачного сервиса +- 3060 (личный сервер) — это личное, не для сервиса + +--- + +## Архитектура + +**Принцип: НЕ МОНОЛИТИТЬ. Независимые слои = отдельные API.** + +``` +contracts-app/ +├── Dockerfile +├── requirements.txt +├── .env.example +└── site/ + ├── app.py ← главный, собирает слои (класс ContractsApp) + ├── db.py ← слой БД (connect, query) + ├── test_routes.py ← слой /test (Blueprint) — мост к БД извне + ├── static/css/ + └── templates/ +``` + +### Слои (текущие) + +| Слой | Файл | Что делает | +|---|---|---| +| Ядро | `app.py` | Класс ContractsApp, регистрирует Blueprint'ы | +| БД | `db.py` | `connect()` и `query(sql, params)` | +| Тест | `test_routes.py` | Blueprint `/test` — статус БД, список таблиц, SQL | + +--- + +## Конфигурация (production) + +```json +// startupConfiguration +{ "resourceRealm": "k8s-4-ext-nubes-ru" } + +// clusterConfiguration +{ "cpu": "500", "memory": "1024", "replicas": "1" } + +// accessConfiguration +{ "domain": "contractor" } + +// appConfiguration +{ + "gitPath": "https://gitea.services.ngcloud.ru/Nail/contracts-app.git", + "version": "3.12", + "healthPath": "/health" +} + +// jsonEnv +{ + "DB_HOST": "postgresqlk8s-master.xxx.svc.cluster.local", + "DB_NAME": "contracts", + "DB_PASS": "xnm9KHLibBvT5lYuQzaBVFPYJQHfTxbS4YeEWMcFvtXuXfLaXvgogqJ6uW0nbBUB", + "DB_PORT": "5432", + "DB_USER": "constracts", + "DB_SSLMODE": "disable" +} +``` + +⚠️ **DB_HOST** всё ещё `xxx` — нужно заменить на реальный: +`postgresqlk8s-master.60bdf3e3-5087-41ff-b760-fe6ea544a80e.svc.cluster.local` + +⚠️ **DB_USER** написано `constracts` (опечатка) — должно быть `contracts` + +--- + +## Хронология деплоев + +### Попытка #1 — 11:34, Таймаут health check (478 сек) +- **Причина:** `debug=True` в app.run(), жёсткие версии в requirements.txt +- **Решение:** убрал debug, смягчил версии, health → plain text + +### Попытка #2 — 12:12, Под(ы) не работают (ImportError) +- **Причина:** `from . import db` — относительные импорты не работают при `python app.py` из папки site/ +- **Ошибка:** `ImportError: attempted relative import with no known parent package` +- **Решение:** заменил на абсолютные: `import db`, `from test_routes import test_bp` + +### Попытка #3 — 12:18, Успех ✅ +- Health: OK +- DB: fail (переменные не заданы) + +### Попытка #4 — 12:??, (ожидается) +- Добавлены переменные БД + +--- + +## Платформа: требования Flask-сервиса + +Из `flask_manual.md`: +- `requirements.txt` в корне +- Код в `site/` +- Запуск: `python app.py` +- Python: 3.12-slim +- HealthCheck: задаётся параметром +- Git: публичный доступ + +--- + +## API эндпоинты + +| Метод | Путь | Что | +|---|---|---| +| GET | `/` | Главная (HTML, статус БД) | +| GET | `/health` | Health check → "OK" | +| GET | `/test` | Статус БД + список команд | +| POST | `/test` `{"action":"tables"}` | Список таблиц | +| POST | `/test` `{"action":"sql","sql":"..."}` | Произвольный SQL | + +--- + +## План дальнейших работ + +1. ✅ Flask-заготовка деплоится +2. ✅ Слои db.py + test_routes +3. ✅ Подключить БД (переменные заданы) +4. ✅ Создать базу contracts (через /test createdb) +5. ✅ Парсер docx/pdf/doc/zip — parser.py +6. ⬜ Загрузка документов в БД (contract_docs) +7. ⬜ LLM-нормализация строк +8. ⬜ Схема таблиц (contracts, spec_rows, spec_history) +9. ⬜ DIFF допников, кумулятивная история +10. ⬜ Fuzzy-match с каталогом услуг + +--- + +## Хронология деплоев (полная) + +### Попытка #1 — 11:34, Таймаут health check (478 сек) +- **Причина:** `debug=True` в app.run(), жёсткие версии в requirements.txt +- **Решение:** убрал debug, смягчил версии, health → plain text + +### Попытка #2 — 12:12, Под(ы) не работают (ImportError) +- **Причина:** `from . import db` — относительные импорты не работают при `python app.py` из папки site/ +- **Ошибка:** `ImportError: attempted relative import with no known parent package` +- **Решение:** заменил на абсолютные: `import db`, `from test_routes import test_bp` + +### Попытка #3 — 12:18, Успех, но БД fail +- Health: OK +- DB: fail — переменные не заданы + +### Попытка #4 — ~12:31, БД fail — password auth failed +- Хост резолвится (10.102.125.70), порт доступен +- Ошибка: `password authentication failed for user "contracts"` +- Пароль от ipwhitelist не подходит для пользователя contracts + +### Попытка #5 — 12:35, db.connect() возвращает ошибки +- Улучшена диагностика — теперь /test показывает точную ошибку + +### Попытка #6 — ~12:38, БД fail — database "contracts" does not exist +- Аутентификация прошла (пароль исправлен) +- База не существует + +### Попытка #7 — 12:41, добавлен /test createdb +- Создан endpoint для создания БД через API + +### Попытка #8 — 12:49, БАЗА СОЗДАНА ✅ +- `POST /test {"action":"createdb"}` → `DB 'contracts' created` +- `/test` → `db: "ok"` +- Таблицы: только pg_stat_* (служебные) + +--- + +## Решения по хранению (принято) + +- ❌ S3 / приватная репа / файловая система — данные уходят за пределы облака +- ❌ SQLite — не подходит для облачного сервиса +- ✅ **PostgreSQL BYTEA + JSONB** — всё внутри кластера, без внешних зависимостей + - `contract_docs` — исходные файлы (original_bytes BYTEA) + сырой парсинг (parsed_json JSONB) + +--- + +## Парсер (parser.py) + +Принцип: **ничего не фильтровать, не терять ни символа**. + +``` +docx ──▶ python-docx ──▶ elements: [{paragraph, style, text}, {table, rows}, ...] +.doc ──▶ libreoffice ──▶ docx ──▶ python-docx +pdf ──▶ pdfplumber ──▶ страницы → текст + таблицы +zip ──▶ zipfile ──▶ рекурсивно parse() каждый файл +``` + +Результат: полный слепок документа → в БД → дальше LLM разбирает. + +Протестирован на реальном примере (спецификация-XXX001-03700.docx): +- 12 элементов (4 таблицы + 8 параграфов) +- Все стили сохранены +- Ничего не потеряно + +--- + +## Текущая структура проекта + +``` +contracts-app/ +├── Dockerfile +├── requirements.txt ← flask, gunicorn, python-docx, requests, psycopg2-binary, python-dotenv, pdfplumber +├── .env.example +├── README.md +└── site/ + ├── __init__.py + ├── app.py ← класс ContractsApp, сборка слоёв + ├── db.py ← слой БД: connect(), query(), _pg_connect(), ensure_db() + ├── test_routes.py ← слой /test: статус, tables, sql, createdb + ├── parser.py ← слой парсера: parse() для docx/pdf/doc/zip + ├── static/css/ + └── templates/ +``` + +--- + +## Конфигурация (актуальная) + +```json +// clusterConfiguration +{ "cpu": "500", "memory": "1024", "replicas": "1" } + +// accessConfiguration +{ "domain": "contractor" } +// URL: https://contractor.pythonk8s.services.ngcloud.ru + +// appConfiguration +{ + "gitPath": "https://gitea.services.ngcloud.ru/Nail/contracts-app.git", + "version": "3.12", + "healthPath": "/health" +} + +// jsonEnv +{ + "DB_HOST": "postgresqlk8s-master.60bdf3e3-5087-41ff-b760-fe6ea544a80e.svc.cluster.local", + "DB_NAME": "contracts", + "DB_PASS": "****", + "DB_PORT": "5432", + "DB_USER": "contracts", + "DB_SSLMODE": "disable" +} +```