From f325b8001e45ef68a24f7f7a9733638d6118bfb8 Mon Sep 17 00:00:00 2001 From: DaTweety <129504390+DaTweety@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:51:57 +0200 Subject: [PATCH] qet-mcp: count duplicate labels per plant and location qet_check reported -F1 as a duplicate master when one sits in +UV1 and one in +UV2, although they are two devices: the place is part of the name, as the %M/%LM label formulas show it and as IEC 81346 tools treat it. Group the two duplicate-label checks by label and the effective place, the element's own plant/location or else its folio's. Co-Authored-By: Claude Opus 5.5 --- misc/qet-mcp/qet_mcp.py | 24 ++++++++++++++++++------ misc/qet-mcp/test_qet_mcp.py | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index 7de720d62..8746ca58a 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -2772,22 +2772,34 @@ def tool_element_search(directory: str, query: str = "", link_type: str | None = # either would bury the real findings. # - "simple" elements are checked for duplicates too but only as a warning: # industrial.qet reuses V1..V6 across folios on purpose. +# +# A label names one device per place: the same "-F1" in two distribution +# boards (+UV1-F1, +UV2-F1) is two devices, as in IEC 81346 / EPLAN. The +# place is the element's own plant/location, else its folio's -- what the +# label formulas %M/%LM show. element_nomenclature_view carries only the +# element's, so these two checks read the tables. +_LABELS_PER_PLACE_SQL = ( + "SELECT ei.label AS label, " + "COALESCE(NULLIF(ei.plant,''), di.plant, '') AS plant, " + "COALESCE(NULLIF(ei.location,''), di.locmach, '') AS location, COUNT(*) AS n " + "FROM element_info ei JOIN element e ON ei.element_uuid = e.uuid " + "JOIN diagram_info di ON di.diagram_uuid = e.diagram_uuid " + "WHERE COALESCE(ei.label,'') <> '' AND e.type = '{type}' " + "AND COALESCE(LOWER(TRIM(ei.exclude_from_bom)), '') NOT IN ('true', '1', 'yes', 'on') " + "GROUP BY ei.label, 2, 3 HAVING n > 1 ORDER BY n DESC, ei.label") + CHECKS = { "duplicate_master_labels": { "severity": "error", "note": "Two master elements with the same label are ambiguous in every " "report that keys on it (BOM, cross-references, wiring list).", - "sql": "SELECT label, COUNT(*) AS n FROM element_nomenclature_view " - "WHERE COALESCE(label,'') <> '' AND element_type = 'master' " - "GROUP BY label HAVING n > 1 ORDER BY n DESC, label", + "sql": _LABELS_PER_PLACE_SQL.format(type="master"), }, "duplicate_simple_labels": { "severity": "warning", "note": "Legitimate when a label is reused on purpose across folios " "(industrial.qet does); worth a look otherwise.", - "sql": "SELECT label, COUNT(*) AS n FROM element_nomenclature_view " - "WHERE COALESCE(label,'') <> '' AND element_type = 'simple' " - "GROUP BY label HAVING n > 1 ORDER BY n DESC, label", + "sql": _LABELS_PER_PLACE_SQL.format(type="simple"), }, "unlabelled_masters": { "severity": "warning", diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 3ada780c3..0e3b3caee 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -5828,6 +5828,22 @@ class Integration(unittest.TestCase): line = [l for l in (out["stdout"] + out["stderr"]).splitlines() if l.startswith("DB ")][0] self.assertEqual(json.loads(line[3:]), [{"wire_number": "W1"}]) + def test_duplicate_labels_are_counted_per_location(self): + """-F1 in two distribution boards is two devices: the folio's + location (+UV1, +UV2) is part of the name, as %LM shows it. KM1 on + +UV1 and +UV2 is fine, a second KM1 on +UV2 is the finding.""" + base = self.sb.new(folios=3) + ops = [] + for folio, ort in enumerate(("UV1", "UV2", "UV2")): + ops += [{"op": "set_folio", "folio": folio, "property": "locmach", "value": ort}, + {"op": "add_element", "id": f"k{folio}", "folio": folio, "path": COIL, + "x": 100, "y": 100}, + {"op": "set_label", "folio": folio, "element": f"$k{folio}", "label": "KM1"}] + r = self.ok(self.sb.edit(base, ops)) + found = {f["check"]: f for f in m.tool_check(BINARY, r["output"])["findings"]} + self.assertEqual(found["duplicate_master_labels"]["rows"], + [{"label": "KM1", "plant": "", "location": "UV2", "n": 2}]) + def test_check_fires_on_a_project_with_known_defects(self): """Newly placed elements store NULL where a loaded file stores '', and `col = ''` matches only the second: the first version of these checks