fix(build): normalize legacy numeric edge confidences to INFERRED on load
A pre-enum graph.json stored a numeric edge confidence (a float) instead of a string tag, which tripped the per-edge 'invalid confidence' validator warning on every incremental reload. _fold_edge_aliases now moves a numeric confidence into confidence_score (when none is present) and sets the tag to INFERRED — never EXTRACTED, since a recovered legacy float is not structural provenance. Modern string tags are untouched (no churn) and the raw float survives in confidence_score. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
558df6d57d
commit
5ad221c3f4
@@ -210,9 +210,23 @@ def _fold_edge_aliases(edge: dict) -> None:
|
||||
is not provenance) and never a threshold mapping of the float. The
|
||||
``confidence_score`` key itself is NOT popped: it is a legitimate companion
|
||||
field that the edge loop sanitizes and to_json round-trips.
|
||||
|
||||
A NUMERIC ``confidence`` (pre-enum graphs stored the LLM pass's float —
|
||||
1.0/0.95/0.9/0.85 — directly in the field) normalizes to ``INFERRED``:
|
||||
numeric confidences only ever came from the LLM semantic pass, and
|
||||
LLM-derived edges are INFERRED by definition. The original float moves to
|
||||
``confidence_score`` unless an explicit one is already present (the
|
||||
companion field is the authority). Without this fold, every reload of a
|
||||
pre-enum graph re-warns once per legacy edge, forever. ``bool`` is
|
||||
excluded despite subclassing ``int``: ``True`` is not a score.
|
||||
"""
|
||||
if not edge.get("relation") and isinstance(edge.get("type"), str) and edge["type"]:
|
||||
edge["relation"] = edge.pop("type")
|
||||
_conf = edge.get("confidence")
|
||||
if isinstance(_conf, (int, float)) and not isinstance(_conf, bool):
|
||||
if edge.get("confidence_score") is None:
|
||||
edge["confidence_score"] = float(_conf)
|
||||
edge["confidence"] = "INFERRED"
|
||||
if not edge.get("confidence") and edge.get("confidence_score") is not None:
|
||||
edge["confidence"] = "INFERRED"
|
||||
|
||||
|
||||
@@ -172,6 +172,62 @@ def test_legacy_edge_type_confidence_score_aliases_folded():
|
||||
assert "type" not in data
|
||||
|
||||
|
||||
def test_legacy_numeric_confidence_normalized_to_inferred(capsys):
|
||||
"""Pre-enum graphs stored the LLM pass's float directly in `confidence`
|
||||
(1.0/0.95/0.9/0.85). Reloading such a graph must not warn once per edge on
|
||||
every load — the number normalizes to INFERRED (numeric confidences only
|
||||
ever came from the LLM semantic pass, and LLM-derived edges are INFERRED
|
||||
by definition) with the original float preserved in confidence_score."""
|
||||
ext = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
|
||||
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"},
|
||||
{"id": "n3", "label": "C", "file_type": "code", "source_file": "c.py"}],
|
||||
"edges": [{"source": "n1", "target": "n2", "relation": "calls",
|
||||
"confidence": 0.95, "source_file": "a.py"},
|
||||
{"source": "n2", "target": "n3", "relation": "references",
|
||||
"confidence": 1.0, "source_file": "b.py"}],
|
||||
"input_tokens": 0, "output_tokens": 0}
|
||||
G = build_from_json(ext)
|
||||
err = capsys.readouterr().err
|
||||
assert "invalid confidence" not in err
|
||||
assert "Extraction warning" not in err
|
||||
d12 = edge_data(G, "n1", "n2")
|
||||
assert d12["confidence"] == "INFERRED"
|
||||
assert d12["confidence_score"] == 0.95
|
||||
d23 = edge_data(G, "n2", "n3")
|
||||
assert d23["confidence"] == "INFERRED"
|
||||
assert d23["confidence_score"] == 1.0
|
||||
|
||||
|
||||
def test_legacy_numeric_confidence_existing_score_wins():
|
||||
"""A numeric `confidence` next to an explicit `confidence_score` must not
|
||||
overwrite the explicit score — the companion field is the authority."""
|
||||
ext = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
|
||||
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"}],
|
||||
"edges": [{"source": "n1", "target": "n2", "relation": "calls",
|
||||
"confidence": 0.9, "confidence_score": 0.4, "source_file": "a.py"}],
|
||||
"input_tokens": 0, "output_tokens": 0}
|
||||
G = build_from_json(ext)
|
||||
data = edge_data(G, "n1", "n2")
|
||||
assert data["confidence"] == "INFERRED"
|
||||
assert data["confidence_score"] == 0.4
|
||||
|
||||
|
||||
def test_legacy_numeric_confidence_links_spelling_reload(capsys):
|
||||
"""The on-disk shape of the defect: a NetworkX-serialized graph.json
|
||||
(`links` spelling) from a pre-enum version reloads without a validator
|
||||
warning per edge and its edges land INFERRED."""
|
||||
raw = {"nodes": [{"id": "n1", "label": "A", "file_type": "code", "source_file": "a.py"},
|
||||
{"id": "n2", "label": "B", "file_type": "code", "source_file": "b.py"}],
|
||||
"links": [{"source": "n1", "target": "n2", "relation": "calls",
|
||||
"confidence": 0.85, "source_file": "a.py"}]}
|
||||
G = build_from_json(raw)
|
||||
err = capsys.readouterr().err
|
||||
assert "invalid confidence" not in err
|
||||
data = edge_data(G, "n1", "n2")
|
||||
assert data["confidence"] == "INFERRED"
|
||||
assert data["confidence_score"] == 0.85
|
||||
|
||||
|
||||
def test_node_alias_canonical_field_wins():
|
||||
"""#2194: when both the canonical field and its alias are present, the
|
||||
canonical value wins and the alias key is left untouched."""
|
||||
|
||||
Reference in New Issue
Block a user