Files
semantica/tests/context/test_distance_intelligence.py
KaifAhmad1 dd016744ce feat(context): add distance intelligence across context, API, and Explorer (#502)
- ContextGraph.get_neighbors() gains include_distance_metadata flag (backward-compat)
- get_neighbor_distances() returns neighbors sorted by hop and confidence decay
- AgentContext.retrieve/find_precedents support proximity-weighted blending
- FR-4: path enrichment (decay, similarity, coherence, bottleneck, interpretation)
- FR-6: POST /api/graph/distance-matrix (hops/weighted/semantic, upper-triangle)
- FR-3: GET /api/graph/node/{id}/semantic-neighborhood
- FR-8: GET /api/decisions/causal-distance (causal-edge-only BFS)
- FR-9: GET /api/temporal/distance-history (convergence/divergence events)
- FR-10: POST /api/export/distance-enriched (CSV/JSONL, 200-node cap)
- Explorer: PathDistanceIntelPanel, Ego Mode, Structural/Semantic overlay, Heatmap
- Fix 13 Qodo review issues: API param mismatch, O(E*L) decay, breaking change,
  schema key inconsistency, datetime arithmetic, id overwrite, sweep race,
  node_subset DoS, full-matrix redundancy, effect race, silent exceptions, duplication
- 57 new tests in test_distance_intelligence.py; 18 regression tests in _smoke_review_fixes.py
2026-04-27 11:07:27 +05:30

98 lines
3.3 KiB
Python

from semantica.context.context_graph import ContextGraph
def test_get_neighbor_distances_tracks_path_decay_and_band():
graph = ContextGraph(advanced_analytics=False)
graph.add_node("A", "entity", "Anchor")
graph.add_node("B", "entity", "Bridge")
graph.add_node("C", "decision", "Decision")
graph.add_edge("A", "B", "influences", weight=0.9)
graph.add_edge("B", "C", "influences", weight=0.7)
neighbors = graph.get_neighbor_distances("A", hops=2, min_confidence=0.5)
c_neighbor = next(item for item in neighbors if item["id"] == "C")
assert c_neighbor["hop"] == 2
assert c_neighbor["distance_band"] == "near"
assert c_neighbor["confidence_decay"] == 0.63
assert c_neighbor["path_to_anchor"] == ["A", "B", "C"]
def test_trace_decision_causality_returns_auditable_chain_dicts():
graph = ContextGraph(advanced_analytics=False)
first = graph.record_decision(
category="risk",
scenario="Approve initial risk policy",
reasoning="Baseline risk controls look sound",
outcome="approved",
confidence=0.8,
entities=["account_123"],
)
second = graph.record_decision(
category="risk",
scenario="Approve follow-up risk exception",
reasoning="Prior account controls still apply",
outcome="approved",
confidence=0.9,
entities=["account_123"],
)
graph._decisions[first]["timestamp"] = 1
graph._decisions[second]["timestamp"] = 2
chains = graph.trace_decision_causality(second, max_depth=2)
assert chains
assert chains[0]["hop_count"] == 1
assert chains[0]["distance_band"] == "direct"
assert chains[0]["weakest_link"]["from"] == first
assert chains[0]["hops"][0]["to"] == second
assert "confidence" in chains[0]["interpretation"]
assert list(chains[0])[0]["from"] == first
def test_analyze_decision_influence_exposes_score_breakdown():
graph = ContextGraph(advanced_analytics=False)
source = graph.record_decision(
category="loan",
scenario="Approve secured loan",
reasoning="Collateral and income verified",
outcome="approved",
confidence=0.9,
entities=["borrower_1"],
)
graph.record_decision(
category="loan",
scenario="Review related refinance",
reasoning="Same borrower and collateral",
outcome="review",
confidence=0.8,
entities=["borrower_1"],
)
result = graph.analyze_decision_influence(source)
assert result["influence_scores"]
score = result["influence_scores"][0]
assert set(score["score_breakdown"]) == {
"entity_overlap",
"category_match",
"temporal_proximity",
}
assert score["is_direct"] is True
def test_cross_graph_path_traverses_link_boundary():
left = ContextGraph(advanced_analytics=False)
right = ContextGraph(advanced_analytics=False)
left.add_node("A", "entity", "Left")
right.add_node("B", "entity", "Right")
left.link_graph(right, "A", "B")
path = left.cross_graph_path("A", right, "B")
assert path["reachable"] is True
assert path["hop_count"] == 1
assert path["cross_graph_links_used"] == 1
assert path["distance_band"] == "direct"
assert path["path"] == [(left.graph_id, "A"), (right.graph_id, "B")]