"""diff_relations — compara dos snapshots de relaciones con key compuesta.""" def diff_relations( before: list[dict], after: list[dict], key: tuple[str, str, str] = ("source_id", "target_id", "relation_type"), ignore_fields: list[str] | None = None, compare_fields: list[str] | None = None, ) -> dict: """Compara relaciones entre dos snapshots usando key compuesta. Las relaciones se identifican por (source_id, target_id, relation_type) porque no tienen un ID unico propio. Detecta relaciones añadidas, eliminadas y modificadas (mismo source/target/type, distinta metadata). Args: before: Lista de relaciones del snapshot anterior. after: Lista de relaciones del snapshot posterior. key: Tupla de campos que forman la key compuesta. Default ("source_id", "target_id", "relation_type"). ignore_fields: Campos a excluir de la comparacion. Default ["created_at", "updated_at"]. compare_fields: Si se da, solo compara estos campos. Returns: Dict con keys: added, removed, modified, unchanged, summary. modified contiene lista de {"key": str, "changes": {"field": {"old": ..., "new": ...}}}. """ if ignore_fields is None: ignore_fields = ["created_at", "updated_at"] def make_key(rel: dict) -> str: return "|".join(str(rel.get(k, "")) for k in key) before_map = {make_key(r): r for r in before} after_map = {make_key(r): r for r in after} before_keys = set(before_map.keys()) after_keys = set(after_map.keys()) added = [after_map[k] for k in after_keys - before_keys] removed = [before_map[k] for k in before_keys - after_keys] modified = [] unchanged = 0 for k in before_keys & after_keys: b = before_map[k] a = after_map[k] if compare_fields is not None: fields_to_check = compare_fields else: all_fields = set(b.keys()) | set(a.keys()) key_set = set(key) fields_to_check = [f for f in all_fields if f not in ignore_fields and f not in key_set] changes = {} for field in fields_to_check: old_val = b.get(field) new_val = a.get(field) if old_val != new_val: changes[field] = {"old": old_val, "new": new_val} if changes: modified.append({"key": k, "changes": changes}) else: unchanged += 1 n_added = len(added) n_removed = len(removed) n_modified = len(modified) summary = f"{n_added} added, {n_removed} removed, {n_modified} modified, {unchanged} unchanged" return { "added": added, "removed": removed, "modified": modified, "unchanged": unchanged, "summary": summary, }