Skip to content

Commit eefce79

Browse files
sandeep-agamiclaude
andcommitted
reconcile: identical column names are one column set when the row counts differ
Spec: ACE-128 Sandeep: "Identical column names read as a column difference. Both sides return exactly ['department', 'pending_items'], yet columns grades defect. Because the row counts differ (13 vs 759), value-based pairing fails, and the new 'compare by values, never by name' rule then reports identical names as different columns." Two vectors of different length are never equal, so with differing counts every column read unpaired: a fact about the counts, not the columns. The comparator now reports no pairs and no extras when the counts differ, and the items verb compares columns by data only when a values comparison ran (same counts, and a pairing or an unmatched golden column); otherwise it falls back to names, so identical names are one column set and the rows check alone carries the difference. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 60c0596 commit eefce79

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

packages/agami-core/src/semantic_model/comparator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,11 @@ def _column_pairs(
642642
reports nothing rather than failing a score that already ran."""
643643
if match not in ("exact", "values") or not golden.rows or not generated.rows:
644644
return (), ()
645+
if len(golden.rows) != len(generated.rows):
646+
# Pairing is by value vectors, and two vectors of different length are never equal, so
647+
# every column would read unpaired: not a fact about the columns, only about the counts,
648+
# which the score already reports. Nothing is claimed here.
649+
return (), ()
645650
try:
646651
pairing, _unmatched = match_columns(
647652
golden.columns, golden.rows, generated.columns, generated.rows,

plugins/agami/scripts/reconcile.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,13 +1713,17 @@ def add(key, state, yours=None, agami=None, note=None, yours_hi=None, agami_hi=N
17131713
ac = list(((rec.get("recorded") or {}).get("columns")) or [])
17141714
pairs = [tuple(p) for p in (result_set.get("column_pairs") or []) if isinstance(p, (list, tuple)) and len(p) == 2]
17151715
if yc or ac:
1716-
if pairs or "unmatched_generated_columns" in result_set:
1716+
same_count = result_set.get("golden_row_count") == result_set.get("generated_row_count")
1717+
values_compared = same_count and (bool(pairs) or bool(result_set.get("unmatched_golden_columns")))
1718+
if values_compared:
17171719
# Columns are compared by the values they carry, never by name: the comparator says
17181720
# which of yours paired with which of agami's, and a renamed column is the same column.
1719-
only_yours = [c for c in (result_set.get("unmatched_golden_columns") or []) if c in yc] or [c for c in yc if c not in {p[0] for p in pairs}]
1721+
only_yours = [c for c in (result_set.get("unmatched_golden_columns") or []) if c in yc]
17201722
only_agami = list(result_set.get("unmatched_generated_columns") or [])
17211723
renamed = [f"{a}{b}" for a, b in pairs if a != b]
1722-
else: # an older score file: names are all there is
1724+
else:
1725+
# No values comparison ran (the row counts differ, or an older score file): names are
1726+
# all there is. Identical names are one column set; the rows check carries the counts.
17231727
only_yours, only_agami, renamed = _only(yc, ac), _only(ac, yc), []
17241728
if not only_yours and not only_agami:
17251729
col_state = "held"

tests/test_comparator_column_pairs.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,11 @@ def test_a_scalar_pair_serialises_and_other_levels_report_nothing():
2727
assert list(map(list, d["column_pairs"])) == [["revenue", "total"]] and d["unmatched_generated_columns"] == ()
2828
shape = compare_result_sets(ExecResult(columns=["a"], rows=[(1,)]), ExecResult(columns=["b"], rows=[(2,)]), match="shape")
2929
assert shape.column_pairs == () and shape.unmatched_generated_columns == ()
30+
31+
32+
def test_different_row_counts_report_no_pairs_and_no_extras():
33+
golden = ExecResult(columns=["department", "pending_items"], rows=[("a", 1), ("b", 2)])
34+
generated = ExecResult(columns=["department", "pending_items"], rows=[("a", 1), ("b", 2), ("c", 3)])
35+
score = compare_result_sets(golden, generated, match="values")
36+
assert score.accuracy == 0.0 and score.column_pairs == () and score.unmatched_generated_columns == () and score.unmatched_golden_columns == ()
37+

tests/test_reconcile_result_and_fix.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,18 @@ def test_the_change_text_the_prefill_and_the_fix_come_from_one_source(tmp_path):
125125
defect = items[2]
126126
assert defect["fix"] == "query" and defect["prefill"]["fix"] == "value orders.status='Delivered'"
127127

128+
129+
def test_identical_column_names_with_different_row_counts_are_one_column_set(tmp_path):
130+
"""Values cannot pair when the row counts differ, so the columns fall back to names: identical
131+
names read as the same columns, and only the rows check carries the difference."""
132+
rec = dict(TABLE_DIFF, row=1, statement_recorded={"columns": ["department", "pending_items"], "row_count": 13},
133+
recorded={"columns": ["department", "pending_items"], "rows": []},
134+
comparison={"result_set": {"accuracy": 0.0, "reason": "the answer key has 13 rows and the generated result has 759",
135+
"unmatched_golden_columns": [], "column_pairs": [], "unmatched_generated_columns": [],
136+
"golden_row_count": 13, "generated_row_count": 759}})
137+
item = reconcile.report_items(_run(tmp_path, [rec]))[0]
138+
rows = {r["key"]: r for r in item["diff"]}
139+
assert rows["rows"]["state"] == "defect" and rows["rows"]["yours"] == "13 rows" and rows["rows"]["agami"] == "759 rows"
140+
assert rows["columns"]["state"] == "held" and not rows["columns"].get("yours_hi") and not rows["columns"].get("agami_hi")
141+
assert item["result"]["data"] == "differs" and "columns" not in item["result"]["differs_in"]
142+

0 commit comments

Comments
 (0)