Files
fn_registry/python/functions/datascience/diff_relations.py
T
egutierrez 63a9cb5273 feat: funciones Python datascience, finance, cybersecurity y pipelines
Datascience: aggregate_by_group, deduplicate_entities/relations, detect_drift,
diff_entities/relations, extract_entities/relations_llm, hotness_score, melt,
merge_graphs, pivot, build_entity/relation_schema_prompt.
Finance: avellaneda_stoikov_quotes, generate_gbm_prices, generate_taker_order,
hawkes_intensity + módulo finance.py.
Cybersecurity: envelope_encrypt/decrypt + módulo cybersecurity.py.
Pipelines: extraction_pipeline, monte_carlo_market, run_market_sim.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:11:32 +02:00

83 lines
2.8 KiB
Python

"""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,
}