init: README, примеры договоров, история и архитектура проекта

This commit is contained in:
“Naeel”
2026-06-13 13:07:10 +04:00
commit 879e016754
10 changed files with 465 additions and 0 deletions
+282
View File
@@ -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"
}
```