Files
contracts-flask/History/2026-08-27-classify-stuck-at-68.md
“Naeel” e5c7c88073
Deploy contracts-flask / validate (push) Canceled after 0s
Avoid false garbage classification for UPD mentions
2026-08-27 10:44:48 +03:00

138 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Классификация застряла на 68/84: диагностика 2026-08-27
## Наблюдение
На экране классификация остаётся на `68/84`, хотя контейнер продолжает работать.
Проверка через ВМ:
```text
GET /api/batch-progress?batch=acbdffe5-e3ec-43f4-ab61-84861cac35bd
{"counts":{"classified":68,"garbage":16},"ok":true,"total":84}
```
Повторный запрос вернул то же значение. Kubernetes показывает pod
`pythonk8s-574b5cf9b4-z4wxz` в состоянии `Running`, `Ready 1/1`, `RESTARTS 0`.
Событий Kubernetes нет. На ВМ `contracts.service` активен, `convert_server.py`
запущен в одном экземпляре.
## Причина в коде
`site/routes/api_bp.py` возвращает раздельные счётчики `classified`, `failed` и
`garbage`, а `total` равен числу всех документов батча.
`site/static/app.js` в `runClassify()` считает завершённые документы так:
```javascript
var done = (d.counts.classified || 0) + (d.counts.failed || 0);
```
`garbage` в эту сумму не включён. Для текущего батча:
```text
done = 68 classified + 0 failed = 68
total = 84
garbage = 16
фактически завершено = 68 + 0 + 16 = 84
```
Поэтому условие `done >= total` никогда не выполняется, polling не
останавливается, а кнопка остаётся на `68/84`. Это ошибка фронтендового
подсчёта прогресса, а не зависание Kubernetes или LLM на 68-м файле.
## Исправление
В `site/static/app.js` к числу завершённых документов добавлен конечный статус
`garbage`:
```javascript
var done = (d.counts.classified || 0)
+ (d.counts.failed || 0)
+ (d.counts.garbage || 0);
```
Версия приложения повышена с `2.0.17` до `2.0.18`; cache-buster статических
скриптов обновлён в `site/templates/index.html`.
Kubernetes проверялся только командами чтения через ВМ:
`kubectl get`, `kubectl describe`, `kubectl top`, `kubectl logs`.
## Дополнительная проверка загрузки ZIP
При проверке загрузки разными способами обнаружен отдельный дефект связи
файлов с архивом. `expandZipClient()` создаёт для каждого распакованного файла
поле `zip_source`, а `uploadFile(file, onProgress, zipSource)` умеет передавать
его в `/api/upload_refs`. Однако вызов `uploadFile()` в цикле `onFilesSelected()`
передавал только два аргумента. Поэтому backend получал `zip_source=null`, и
связь с исходным ZIP терялась.
Проверены архивы:
- `testgen/out/zips/duplicates_inside.zip` — целый ZIP, два файла с одинаковым именем;
- `testgen/out/zips/mixed_with_error.zip` — целый ZIP, корректный и повреждённый DOCX.
Исправление применено в `site/static/files.js`: третьим аргументом передаётся
`entry.zip_source`. Для обычных файлов значение `null`, поэтому их поведение не
изменяется. Версия приложения повышена с `2.0.18` до `2.0.19`, cache-buster
обновлён в `site/templates/index.html`.
## Проверки исправления
- `node --check site/static/files.js` — успешно;
- frontend upload-тесты — успешно;
- layer 2 upload-тесты — успешно;
- архивы `duplicates_inside.zip` и `mixed_with_error.zip` прошли `unzip -t`.
## Реальный end-to-end тест сверки
27.08.2026 выполнен тест через deployed API `v2.0.19` на batch
`0dd0d3a7-3e52-4888-95eb-1b6d3e37f40e`.
Загружены через VM-поток два реальных DOCX:
- `договор-XXX001-03700.docx` — распарсен, 20 элементов;
- `допник-1-XXX001-03700.docx` — распарсен, 16 элементов.
Классификация завершилась `2/2`, но результат классификации неожиданен:
- допсоглашение попало в группу `03700`;
- базовый договор попал в `__unresolved__` со статусом `garbage` и причиной
отсутствия matching-договора.
Это функциональный дефект/проблема классификации тестовой пары: без базового
договора группа не проверяет корректное сравнение договора с приложением.
Тем не менее реальный pipeline сверки для сформированной группы проверен:
- `/api/apply-groups``200`, создан contract ID
`b859e813-bac9-4eb9-82b5-6b5666f2ba4a`;
- `/process-v2` — SSE завершён событием `complete`;
- LLM вернул `6` операций в режиме `partial`;
- применено: `added=6`, `updated=0`, `deleted=0`;
- ошибок соединения и SSE не было;
- время pipeline: `9.4с`.
Следующий обязательный тест для проверки matching — загрузить базовый договор с
точным именем/содержимым, которое классификатор ожидает как базовый, и повторить
ту же пару. Код после этого прогона не изменялся.
## Исправление ложного garbage для базового договора
При повторной проверке реального файла `договор-XXX001-03700.docx` установлена
точная причина ошибочной классификации: второй garbage-фильтр искал подстроку
`УПД` в первых 2000 символах. В договоре эта аббревиатура встречается в
обычном условии оплаты: «на основании УПД и счета». Из-за этого договор
получал `garbage=header_keywords`, хотя его заголовок начинается со слова
«Договор».
Исправление в `site/services/classify.py`:
- удалено общее substring-срабатывание для `УПД`;
- добавлено распознавание `УПД` и полного названия только с начала строки,
когда это заголовок самого документа.
Добавлены регрессионные тесты: упоминание УПД внутри договора не является
garbage, а заголовок документа `УПД № ...` является garbage.
Версия приложения повышена до `2.0.20`, cache-buster обновлён в
`site/templates/index.html`.