dedupe.py 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. """Deduplication helpers for acquisition -> decode -> ingest pipelines."""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. from typing import Iterable
  5. from acquisition.domain import CandidateItem
  6. @dataclass(frozen=True)
  7. class DedupeDecision:
  8. keep: bool
  9. key: str
  10. reason: str = ""
  11. def item_dedupe_key(item: CandidateItem) -> str:
  12. if item.unique_key:
  13. return f"unique_key:{item.unique_key}"
  14. if item.platform_item_id:
  15. return f"{item.platform}:id:{item.platform_item_id}"
  16. if item.canonical_url:
  17. return f"url:{item.canonical_url.strip().lower()}"
  18. return f"item:{item.id}"
  19. def dedupe_candidate_items(items: Iterable[CandidateItem]) -> list[CandidateItem]:
  20. seen: set[str] = set()
  21. out: list[CandidateItem] = []
  22. for item in items:
  23. key = item_dedupe_key(item)
  24. if key in seen:
  25. continue
  26. seen.add(key)
  27. out.append(item)
  28. return out
  29. def should_decode_item(
  30. item: CandidateItem,
  31. *,
  32. decoded_item_ids: set[str] | None = None,
  33. ) -> DedupeDecision:
  34. key = item_dedupe_key(item)
  35. decoded = decoded_item_ids or set()
  36. if str(item.id) in decoded or key in decoded:
  37. return DedupeDecision(keep=False, key=key, reason="already_decoded")
  38. if item.content_mode == "unsupported":
  39. return DedupeDecision(keep=False, key=key, reason="unsupported_content_mode")
  40. return DedupeDecision(keep=True, key=key)