From bedf32e07ca0e556cb2257c0193dd60d31fbda38 Mon Sep 17 00:00:00 2001 From: rajashidattapy Date: Tue, 11 Aug 2026 20:09:59 +0100 Subject: [PATCH] fix(wiki): count each incident edge once in the audit trail (#2635) --- graphify/wiki.py | 23 +++++++++++++++++------ tests/test_wiki.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/graphify/wiki.py b/graphify/wiki.py index cb9c6cf3..d283552a 100644 --- a/graphify/wiki.py +++ b/graphify/wiki.py @@ -73,12 +73,23 @@ def _community_article( top_nodes = sorted(nodes, key=lambda n: G.degree(n), reverse=True)[:25] cross = _cross_community_links(G, nodes, cid, labels, node_community or {}) - # Edge confidence breakdown - conf_counts: Counter = Counter() - for nid in nodes: - for neighbor in G.neighbors(nid): - ed = edge_data(G, nid, neighbor) - conf_counts[ed.get("confidence", "EXTRACTED")] += 1 + # Edge confidence breakdown, over every edge INCIDENT to the community (the + # cross-community ones included — those are disproportionately the uncertain + # edges, and AMBIGUOUS is what ARCHITECTURE.md flags for human review). + # + # ``G.edges(nbunch)`` reports each incident edge exactly once. Walking + # ``nodes x G.neighbors`` instead visited an edge once per endpoint inside + # the community, so an intra-community edge was counted TWICE while a + # crossing one was counted once. Intra-community edges are overwhelmingly + # the high-confidence EXTRACTED ones — that is what makes a community — so + # the split was biased towards confidence and understated the review + # burden. On a MultiGraph this also counts parallel edges individually + # rather than collapsing them to the first (``edge_data``), which is the + # same understatement: an AMBIGUOUS edge parallel to an EXTRACTED one used + # to be invisible here. + conf_counts: Counter = Counter( + d.get("confidence", "EXTRACTED") for *_, d in G.edges(nodes, data=True) + ) total_edges = sum(conf_counts.values()) or 1 sources = sorted({G.nodes[n].get("source_file") or "" for n in nodes} - {""}) diff --git a/tests/test_wiki.py b/tests/test_wiki.py index 289a0a2b..18013da6 100644 --- a/tests/test_wiki.py +++ b/tests/test_wiki.py @@ -96,11 +96,42 @@ def test_community_article_shows_cohesion(tmp_path): def test_community_article_has_audit_trail(tmp_path): + """Each incident edge is counted exactly ONCE (#2633). + + The Parsing Layer (n1, n2) touches two edges: n1-n2 (EXTRACTED, both + endpoints inside) and n1-n3 (INFERRED, crossing out). Counting + ``nodes x G.neighbors`` visited the intra-community edge from both ends and + the crossing one from only one, reporting EXTRACTED 2 (67%) / INFERRED 1 + (33%) — biased towards confidence, which understates the AMBIGUOUS review + burden the section exists to surface. + """ G = _make_graph() to_wiki(G, COMMUNITIES, tmp_path, community_labels=LABELS) parsing = (tmp_path / "Parsing_Layer.md").read_text() - assert "EXTRACTED" in parsing - assert "INFERRED" in parsing + audit = parsing[parsing.index("## Audit Trail"):] + assert "- EXTRACTED: 1 (50%)" in audit, audit + assert "- INFERRED: 1 (50%)" in audit, audit + assert "- AMBIGUOUS: 0 (0%)" in audit, audit + + +def test_audit_trail_counts_parallel_edges_individually(tmp_path): + """On a MultiGraph each parallel edge is its own row in the split (#2633). + + Collapsing them to the first (``edge_data``) hid an AMBIGUOUS edge behind an + EXTRACTED one running between the same pair — the same understatement as the + double-count above. + """ + G = nx.MultiGraph() + G.add_node("n1", label="parse", file_type="code", source_file="parser.py") + G.add_node("n2", label="validate", file_type="code", source_file="parser.py") + G.add_edge("n1", "n2", relation="calls", confidence="EXTRACTED", weight=1.0) + G.add_edge("n1", "n2", relation="references", confidence="AMBIGUOUS", weight=1.0) + + to_wiki(G, {0: ["n1", "n2"]}, tmp_path, community_labels={0: "Parsing Layer"}) + audit = (tmp_path / "Parsing_Layer.md").read_text() + audit = audit[audit.index("## Audit Trail"):] + assert "- EXTRACTED: 1 (50%)" in audit, audit + assert "- AMBIGUOUS: 1 (50%)" in audit, audit def test_god_node_article_has_connections(tmp_path):