mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
- 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
98 lines
3.3 KiB
Python
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")]
|