From 40ed284b7a47bff91bf97a85e081d57604ebe645 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 28 Sep 2026 11:12:25 +1300 Subject: [PATCH] qet-mcp: qet_diff no longer reports untouched elements as changed QElectroTech writes an empty label as an empty and drops it on the next save. qet_diff compared the raw information bags, so re-saving grafcet.qet with no edit reported 9 of its 37 elements as info_changed ({"label": ""} -> {}), and Projet_vierge.qet 4. An empty field and a missing one now compare equal, and info_changed lists only fields that hold a value. The resave test now also requires an empty info_changed; it failed on grafcet.qet and Projet_vierge.qet before this change. Co-Authored-By: Claude Opus 5.5 --- misc/qet-mcp/qet_mcp.py | 9 +++++++-- misc/qet-mcp/test_qet_mcp.py | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index fef21791c..69cdeb31f 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -492,9 +492,14 @@ def tool_diff(before: str, after: str) -> dict: if a["label"] != b["label"]: relabelled.append({"uuid": k, "name": a["name"], "from": a["label"], "to": b["label"]}) - if a["info"] != b["info"]: + # An empty field and a missing one mean the same thing, and + # QElectroTech drops empty ones when it saves, so compare only the + # fields that hold a value. + a_info = {n: v for n, v in a["info"].items() if v} + b_info = {n: v for n, v in b["info"].items() if v} + if a_info != b_info: changed_info.append({"uuid": k, "name": a["name"], - "from": a["info"], "to": b["info"]}) + "from": a_info, "to": b_info}) a_co = {r["key"]: r for i, c, ix in _conductors(_root(before)) for r in [_conductor_row(i, c, ix)]} diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index b874a3356..4bf31e6cb 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -777,6 +777,26 @@ class Diff(unittest.TestCase): b = self.project("b.qet", version="0.200.1-dev") self.assertEqual(m.tool_diff(a, b)["folios"]["changed"], []) + def test_empty_information_and_a_missing_one_are_the_same(self): + """QElectroTech writes an empty label as + and drops it on the next save, so comparing the raw bags made every + unlabelled element of a re-saved project look edited.""" + def proj(infos): + return (f'' + f'{infos}' + f'') + a = self.dir / "a.qet" + b = self.dir / "b.qet" + c = self.dir / "c.qet" + a.write_text(proj('' + ' ')) + b.write_text(proj('')) + c.write_text(proj('K1')) + self.assertEqual(m.tool_diff(str(a), str(b))["elements"]["info_changed"], []) + # A value that really appears or disappears is still reported. + changed = m.tool_diff(str(b), str(c))["elements"]["info_changed"] + self.assertEqual([(x["from"], x["to"]) for x in changed], [({}, {"label": "K1"})]) + def test_element_text_field_moves_and_restyles_are_reported(self): def proj(x, size, frame, extra=""): return (f'' @@ -2747,6 +2767,7 @@ class CorpusIntegration(unittest.TestCase): if "unstable_keys" not in c: self.assertEqual((len(c["added"]), len(c["removed"])), (0, 0)) self.assertEqual(r["diff"]["elements"]["moved_count"], 0) + self.assertEqual(r["diff"]["elements"]["info_changed"], []) finally: sb.close()