104 lines
7.7 KiB
Markdown
104 lines
7.7 KiB
Markdown
# Запрос для Opus — План: группировка, прогресс, промежуточные результаты
|
||
|
||
## Что сказал заказчик (дословно)
|
||
|
||
> Не распознано (8 файлов)
|
||
> [supplement] допник-1-XXX002-01200_3.docx ... — нет базового договора №01219_3
|
||
> ...
|
||
> зачем нам базовый договор? Вся информация есть в допниках и спеках
|
||
|
||
> Было бы хорошо пояснить или показать процесс, что в каком порядке происходит. Так видно только текущую операцию
|
||
|
||
> наверно я захочу иметь возможность посмотреть любые промежуточные результаты
|
||
|
||
## Что нужно
|
||
|
||
Заказчик хочет три улучшения (без фанатизма, главное — устойчивость и понятный UI):
|
||
|
||
### #1 — Группировка без базовых договоров
|
||
|
||
**Проблема:** сейчас `group_documents()` требует contract-файл как якорь группы. Без него допники/спеки попадают в unresolved с текстом «нет базового договора №X». Заказчик: «зачем нам базовый договор? Вся информация есть в допниках и спеках».
|
||
|
||
**Нужно:** группировать документы по `parent_number` / `own_number`, даже если contract-файл отсутствует в загрузке. Создавать «виртуальную» группу без contract-файла.
|
||
|
||
**Вопросы:**
|
||
1. Алгоритм: что приоритетнее — `parent_number` от допника или `own_number` от спеки? Если оба ссылаются на один нормализованный номер — это одна группа?
|
||
2. Если два допника с одним `parent_number`, но разными `counterparty` — одна группа или разные?
|
||
3. Как назвать группу без contract-файла: `"№01300_2 — ЗАО XXX003"` из данных классификации? Достаточно?
|
||
4. Минимальный diff в `group_documents()` — чтобы не сломать текущую логику с contract-файлами?
|
||
|
||
### #2 — Прогресс пайплайна
|
||
|
||
**Проблема:** юзер видит только статус текущей операции. Непонятно что уже сделано, что предстоит.
|
||
|
||
**Нужно:** визуальная шкала этапов с иконками статуса.
|
||
|
||
**Вопросы:**
|
||
1. Достаточно 4 этапов: Загрузка → Классификация → Группировка → Сравнение? (Парсинг — подэтап загрузки, не показывать отдельно)
|
||
2. Где разместить: в топбаре (всегда видно, не скроллится) или в карточке с результатами?
|
||
3. При переклассификации после удаления/добавления файла — сбрасывать всю шкалу или только затрагиваемые этапы?
|
||
|
||
### #3 — Промежуточные результаты
|
||
|
||
**Проблема:** юзер хочет видеть что LLM вернула на каждом шаге: сырой ответ, как определился номер/тип/дата.
|
||
|
||
**Нужно:** раскрывающийся блок с деталями для каждого файла.
|
||
|
||
**Вопросы:**
|
||
1. Что хранить: сырой ответ LLM + распарсенный JSON? Достаточно двух новых полей в `documents`?
|
||
2. Где показывать: раскрывающийся блок под строкой файла в таблице? Или модалка при клике на статус?
|
||
3. Нужно ли для сравнения (process-v2 SSE) или только для классификации?
|
||
|
||
### #4 — Общие ограничения
|
||
|
||
Что из трёх самое трудозатратное и что можно упростить без потери юзабилити?
|
||
|
||
---
|
||
|
||
## Релевантные файлы (читать)
|
||
|
||
### Группировка (#1)
|
||
- `contractor/deploy/services/grouping.py` — `group_documents()`, `normalize_number()`, `apply_groups()`
|
||
- `contractor/deploy/db/documents.py` — поля `doc_type`, `own_number`, `parent_number`, `counterparty`, `classify_status`
|
||
- `contractor/deploy/db/contracts.py` — `insert()`, поля `number`, `client`
|
||
- `contractor/deploy/db/supplements.py` — `insert()`, `list_by_contract()`
|
||
|
||
### Прогресс-бар (#2) + Промежуточные результаты (#3)
|
||
- `contractor/index.cfm` — HTML-оболочка, топбар (`.topbar`, `position: sticky`), карточки, модалки
|
||
- `contractor/deploy/app.js` — весь фронтенд: загрузка, `runClassify()`, `loadGroups()`, `runCompareForGroup()`, `showClassifyBtn()`, рендеринг таблицы и групп
|
||
- `contractor/deploy/app_utils.js` — утилиты: `removeFile()`, `renderTable()`
|
||
- `contractor/deploy/convert_server.py` — роутер (`do_GET`, `do_POST`, `do_DELETE`), SSE (`_handle_process_v2`), эндпоинты `/api/classify-batch`, `/api/groups`, `/api/batch-progress`, `/api/sync`
|
||
|
||
### Общий контекст
|
||
- `contractor/deploy/services/classify.py` — `classify_batch()`, `_smart_extract()`, `_call_llm_classify()`, `_safe_json_parse()`
|
||
- `contractor/deploy/services/process.py` — `run_pipeline()` (SSE для сравнения: extract/diff)
|
||
- `contractor/deploy/services/grouping.py` — `group_documents()`, `apply_groups()`
|
||
- `contractor/deploy/llm_prompt.py` — `build_prompt()`, `build_classify_prompt()`
|
||
- `contractor/deploy/db/connection.py` — `query()`, `execute()`, `execute_returning()`
|
||
|
||
---
|
||
|
||
## Игнорировать (не относится к делу)
|
||
|
||
- `contracts-app/` — старый Python-бэкенд (Flask), не используется
|
||
- `contracts-vm/` — старые конфиги ВМ
|
||
- `DOC/`, `FILES/` — документация, заметки
|
||
- `dogovora/` — тестовые файлы договоров
|
||
- `history/`, `History/` — старые сессионные заметки (кроме этого файла)
|
||
- `contractor/*.cfm` кроме `index.cfm` — старый Lucee-код, не используется
|
||
- `contractor/deploy/nginx-contracts.conf` — конфиг nginx
|
||
- `contractor/deploy/convert_doc.py` — конвертер .doc → .docx
|
||
- `contractor/deploy/sync.sh` — скрипт деплоя
|
||
- `contractor/upload.cfm`, `contractor/process.cfm`, `contractor/parser.cfm` и т.д. — старый Lucee-код
|
||
|
||
---
|
||
|
||
## Архитектура (кратко)
|
||
|
||
- **Фронт:** `index.cfm` (Lucee, только HTML-оболочка) → грузит `app.js` + `app_utils.js` с ВМ (`https://contracts.kube5s.ru/static/`)
|
||
- **Бэкенд:** Python 3.12 `http.server` + `ThreadingMixIn` на ВМ (5.172.178.213), порт 8766, systemd-сервис `contracts`
|
||
- **Все endpoint'ы:** в `convert_server.py` (один файл-роутер, ~400 строк)
|
||
- **БД:** PostgreSQL 16, прямой доступ через `psycopg2`, connection pool
|
||
- **Таблицы:** `documents`, `supplements`, `contracts`, `spec_events`, `spec_current`, `prompts`
|
||
- **LLM:** gpt-oss-120b через `api.aillm.ru`, httpx с http2, `temperature=0.1`, `max_tokens=8000`
|