From 8631a2600647719ddc0b40d72fca66223c10dde0 Mon Sep 17 00:00:00 2001 From: woogi Date: Mon, 10 Aug 2026 10:47:34 -0500 Subject: [PATCH] feat: surface disputed extracted values to critic and specialist prompts --- backend/agents/disputes.py | 2 +- backend/agents/runner.py | 4 ++++ backend/pipeline/_serialize.py | 2 ++ tests/agents/test_disputes.py | 27 +++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/backend/agents/disputes.py b/backend/agents/disputes.py index 3db7ae9..56dd24b 100644 --- a/backend/agents/disputes.py +++ b/backend/agents/disputes.py @@ -12,7 +12,7 @@ from typing import Dict, List def _norm(value) -> str: - return re.sub(r"\s+", " ", str(value or "").strip().lower()) + return re.sub(r"\s+", " ", ("" if value is None else str(value)).strip().lower()) def find_disputes(assertions: List[Dict]) -> List[Dict]: diff --git a/backend/agents/runner.py b/backend/agents/runner.py index bacd107..538a0ec 100644 --- a/backend/agents/runner.py +++ b/backend/agents/runner.py @@ -11,6 +11,7 @@ from backend.agents.code_agent import CodeAgent, build_code_scopes from backend.agents.completeness import CompletenessAgent, build_sheet_summaries from backend.agents.conflict_critic import ConflictCriticAgent from backend.agents.construct_agent import ConstructabilityAgent, build_construct_scopes +from backend.agents.disputes import annotate_clusters from backend.agents.extractors import ( JurisdictionAgent, SheetExtractorAgent, @@ -113,6 +114,9 @@ def run_agent_pipeline( for artifact in result.artifacts ][:config.CLUSTER_MAX] object_graph = build_object_graph(clusters) + disputed_count = annotate_clusters(clusters) + if disputed_count: + orchestrator.stage(f"[Link] {disputed_count} clusters carry disputed extracted values") memory.replace("clusters", clusters) memory.replace("object_graph", object_graph) memory.dump("03-link.json") diff --git a/backend/pipeline/_serialize.py b/backend/pipeline/_serialize.py index 815ad41..371a111 100644 --- a/backend/pipeline/_serialize.py +++ b/backend/pipeline/_serialize.py @@ -54,6 +54,8 @@ def slim_clusters(clusters: List[Dict]) -> List[Dict]: "location": c.get("location"), "disciplines": c.get("disciplines"), "kind": c.get("kind"), + **({"disputed_attributes": c["disputed_attributes"]} + if c.get("disputed_attributes") else {}), "assertions": [slim_assertion(a) for a in c.get("assertions", [])], } for c in clusters diff --git a/tests/agents/test_disputes.py b/tests/agents/test_disputes.py index bdcbe8f..b607598 100644 --- a/tests/agents/test_disputes.py +++ b/tests/agents/test_disputes.py @@ -40,3 +40,30 @@ def test_annotate_clusters_writes_disputed_attributes(): assert annotate_clusters(clusters) == 1 assert clusters[0]["disputed_attributes"][0]["attribute"] == "stud_pack_size" assert "disputed_attributes" not in clusters[1] + + +def test_slim_clusters_preserves_disputed_attributes(): + from backend.pipeline._serialize import slim_clusters + cluster = {"key": "c1", "assertions": [], + "disputed_attributes": [{"attribute": "a", "values": ["1", "2"], + "assertion_ids": ["x", "y"]}]} + slim = slim_clusters([cluster])[0] + assert slim["disputed_attributes"][0]["values"] == ["1", "2"] + + +def test_find_disputes_handles_none_and_zero_values(): + # None value/attribute -> skipped; numeric 0 is a real value, not blank + assertions = [ + {"id": "a1", "attribute": "count", "value": 0}, + {"id": "a2", "attribute": "count", "value": 1}, + {"id": "a3", "attribute": None, "value": "x"}, + {"id": "a4", "attribute": "count", "value": None}, + ] + disputes = find_disputes(assertions) + assert len(disputes) == 1 + assert disputes[0]["values"] == ["0", "1"] + + +def test_find_disputes_empty_input(): + assert find_disputes([]) == [] + assert annotate_clusters([]) == 0