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

6.7 KiB
Raw Blame History

Запрос 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