Files
contracts-flask/History/opus-dedup-2026-06-28.md
T

114 lines
6.7 KiB
Markdown
Raw 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.
# Запрос Opus — логика дедупликации в пайплайне
Дата: 28.06.2026
## Проблема
Сейчас:
1. **Загрузка:** дубликат файла определяется только по имени (`filename`). Тот же файл с другим именем → новый документ.
2. **Сравнение (ADD):** при добавлении строки в `spec_current` не проверяется `name_hash`. Одна и та же услуга добавится дважды если пришла из двух разных допников с одинаковым содержанием.
3. **Позиции:** `name_hash = md5(нормализованное_имя || date_start)`. Хеш есть, но используется только для UPDATE (поиск target_hash). При ADD — не проверяется.
## Вопросы
### 1. Дедупликация документов при загрузке
Сейчас: `delete_by_document(contract_id, filename)` — только по совпадению имени.
**Нужно ли:**
- Сравнивать по хешу содержимого (sha256 original_bytes)?
- Сравнивать по `elements_json`?
- Или только по имени + предупреждение?
### 2. Дедупликация строк в spec_current (ADD → UPSERT)
Сейчас: ADD всегда INSERT. Если та же услуга приходит из другого допника — будет дубликат.
**Предложение:** перед INSERT проверять `name_hash`:
```
есть name_hash? → UPDATE (как обычный UPDATE)
нет → INSERT
```
**Вопросы:**
- Правильно ли это? В каких случаях ADD должен ОСТАТЬСЯ INSERT даже при совпадении name_hash?
- Как обрабатывать если ADD и UPDATE приходят в одном наборе ops и пересекаются по name_hash?
### 3. Связанные краевые случаи
- Два допника с одинаковым содержанием, разные имена → сейчас: две обработки, возможны дубликаты. Как надо?
- Один допник добавляет услугу X, другой её же удаляет. Порядок имеет значение? Как обеспечить?
- ZIP содержит файлы с теми же именами что уже загружены. Перезаписывать? Пропускать? Спрашивать?
- Два одинаковых договора (разные имена) в одном батче. Как группировать?
## Ожидаемый ответ
На каждый сценарий: **как ДОЛЖНО быть** и **как проще всего реализовать сейчас** (MVP). Приоритет: не сломать существующее поведение.
---
## Ответ Opus
### ⚠️ Поправки к посылкам
1. **«ADD всегда INSERT → дубликат»** — неверно. `spec_events.py` уже делает UPSERT по `(contract_id, name_hash)`: совпадение → UPDATE, иначе INSERT. Дублей в `spec_current` нет.
2. **«name_hash = md5(имя ‖ date_start)»** — неверно. Реально: `sha256(name.strip().lower())[:16]` — только имя. Услуги с одинаковым именем но разным периодом схлопываются.
---
### Вопрос 1. Дедупликация документов при загрузке
**Сейчас:** `delete_by_document(contract_id, filename)` — по имени. Ошибки глотаются в `try/except: pass`. Хеша содержимого нет.
**Как ДОЛЖНО быть:**
- `sha256(original_bytes)``documents.content_hash`
- Идентичность в рамках договора/батча — по `content_hash`
- Сравнение по `elements_json` не нужно (нестабилен при смене парсера)
- Ошибки удаления логировать, не глотать
**MVP:**
- Колонка `content_hash` + индекс `(contract_id/batch_id, content_hash)`
- В `store_document`: если уже есть документ с таким хешем → пропустить, вернуть `{duplicate_of: doc_id}`
---
### Вопрос 2. ADD → UPSERT
UPSERT по имени уже есть. Реальная проблема: **last-write-wins** — второй ADD молча перезаписывает первый (та же услуга, другая цена).
**Как ДОЛЖНО быть:**
- ADD = новая позиция. При коллизии ключа различать UPDATE vs реально разные позиции (одно имя, разный период)
- Ключ должен включать `date_start`/период
**MVP — вариант B (рекомендован):**
- Расширить ключ: `_hash(name + "|" + str(date_start))`
- Одна услуга в разные периоды = разные строки
- Риск: если допник меняет цену без смены `date_start`, LLM должен прислать UPDATE, а не ADD
---
### Вопрос 3. Краевые случаи
**a) Два допника с одинаковым содержанием, разные имена:**
Сейчас: два документа → два прогона LLM. Дублей в spec_current нет (UPSERT), но раздувается история.
**MVP:** дедуп по `content_hash` на загрузке (Вопрос 1).
**b) ADD X, затем DELETE X — порядок:**
Применение по `created_at` или `order_ids`. Не по хронологии допников.
**MVP:** сортировать по `doc_date` (классификация уже извлекает): `ORDER BY doc_date NULLS LAST, created_at`.
**c) ZIP с уже загруженными именами:**
**MVP:** в рамках `batch_id` отсеивать по `content_hash`. Пропускать с предупреждением, не перезаписывать молча.
**d) Два одинаковых договора в батче:**
**MVP:** `content_hash`-дедуп решает «одинаковые»; для «разные файлы, один номер» — флаг конфликта в UI.
---
### Сквозной MVP (что делать)
1. `documents.content_hash` + дедуп по нему на загрузке
2. Сортировка допников по `doc_date`
3. Опционально: `date_start` в `name_hash`