fix(export): tolerate id-less persisted hyperedges in attach_hyperedges (#2775)

The semantic extractor emits hyperedges with no id and build.py persists them
verbatim, so a prior graph.json can contain id-less hyperedges. Seeding the
dedup set with a hard h["id"] raised KeyError: 'id' on every incremental
re-extract, silently failing the whole graph load. Guard the comprehension with
h.get("id"), symmetric with the incoming-set guard already below it; id-less
entries are retained in the graph, id-bearing dedup is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ousama Ben Younes
2026-08-16 18:27:34 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent 4fca621532
commit ae8d059231
2 changed files with 21 additions and 1 deletions
+7 -1
View File
@@ -163,7 +163,13 @@ _CONFIDENCE_SCORE_DEFAULTS = {"EXTRACTED": 1.0, "INFERRED": 0.5, "AMBIGUOUS": 0.
def attach_hyperedges(G: nx.Graph, hyperedges: list) -> None:
"""Store hyperedges in the graph's metadata dict."""
existing = G.graph.get("hyperedges", [])
seen_ids = {h["id"] for h in existing}
# Skip id-less persisted entries when seeding the dedup set (#2775): the
# semantic extractor emits hyperedges with no `id` and build.py persists them
# verbatim, so a prior graph.json can contain id-less hyperedges. A hard
# `h["id"]` here raised `KeyError: 'id'` on every incremental re-extract,
# symmetric with the `.get("id")` guard the loop below already applies to the
# incoming set.
seen_ids = {h["id"] for h in existing if h.get("id")}
for h in hyperedges:
if h.get("id") and h["id"] not in seen_ids:
existing.append(h)
+14
View File
@@ -137,6 +137,20 @@ def test_attach_hyperedges_skips_entry_without_id():
assert G.graph.get("hyperedges", []) == []
def test_attach_hyperedges_tolerates_id_less_persisted():
# Regression for #2775: the semantic extractor emits hyperedges with no `id`
# and build.py persists them verbatim, so a prior graph.json can carry id-less
# hyperedges. On the next (incremental) run, attach_hyperedges read that
# persisted set with a hard `h["id"]` and died with `KeyError: 'id'`, writing
# nothing. Reading the persisted set must tolerate missing ids.
G = nx.DiGraph()
G.graph["hyperedges"] = [{"nodes": ["a", "b"], "type": "project", "attributes": {}}]
attach_hyperedges(G, [{"id": "flow_a", "label": "Flow A", "nodes": ["A", "B"]}])
# No crash; the id-less persisted entry is retained and the new id-bearing
# incoming hyperedge is appended.
assert len(G.graph["hyperedges"]) == 2
# ---------------------------------------------------------------------------
# 3. to_json includes hyperedges key
# ---------------------------------------------------------------------------