diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index 7c0b70431..448fb524f 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -2796,22 +2796,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 e979676c8..8358ef5b7 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -5875,6 +5875,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