# -*- coding: utf-8 -*- """Tests fuer den Ablauf Anlagen-JSON -> (BricsCAD) -> SCL. - stabiler Schluessel (DerivedTro.key): Korrekturen ueberleben eine Umbenennung - Verbindungen aus BricsCAD (edit_connections, tro_flow.apply_connection_edits, tro_annotate._read_connections) - Pruefung vor der SCL (validate_anlage) - Zeichnung aus der JSON (tro_annotate.analysis_from_document) Fixture: Testanlage test_system (wie test_tro_edits.py). """ from __future__ import annotations import os from pathlib import Path import pytest from material_flow import build_graph, read_elements from model import AnlageDocument, Binding, TroDocument from scl_context import jam_override, jam_stable_key from tro_annotate import analysis_from_document, format_slots, read_tros, tro_slots from tro_flow import analyse_tros from tro_overrides import ConnectionEdit, TroEdit, load_overrides, save_edits from validate_anlage import FEHLER, validate FIXTURE = Path(__file__).parent / "fixtures" / "test_system_export.csv" CSV = FIXTURE.name @pytest.fixture(scope="module") def layout(): elements = read_elements(FIXTURE, []) return elements, build_graph(elements, CSV, []) def _analyse(layout, edits=None, connection_edits=None): elements, graph = layout return analyse_tros(graph, elements, edits=edits, connection_edits=connection_edits) def _tro_kante(analysis): """Eine abgeleitete Kante zwischen zwei TROs (keine Station).""" ids = {t.tro_id for t in analysis.tros} return next((s, d) for s, d in analysis.edges if s in ids and d in ids) def _markers(analysis): return [{"ID": t.tro_id, "KEY": analysis.edit_key(t.tro_id), "TYPE": t.type_name, "UID": f"{n:04d}", "SLOTS": format_slots(*tro_slots(t)), "_BLOCK": f"TRO_SYM_{t.type_name}"} for n, t in enumerate(analysis.tros, start=100)] def _arrows(analysis, markers): uid = {m["ID"]: m["UID"] for m in markers} ids = {t.tro_id for t in analysis.tros} return [{"FROM_TRO": s, "TO_TRO": d, "FROM_UID": uid[s], "TO_UID": uid[d], "KIND": "flow", "_BLOCK": "CONNECTION_ARROW"} for s, d in analysis.edges if s in ids and d in ids] # --------------------------------------------------------------------------- # Stabiler Schluessel # --------------------------------------------------------------------------- def test_umbenannter_tro_traegt_seinen_schluessel(layout): raw = _analyse(layout) key = raw.tros[0].tro_id doc = TroDocument.from_analysis(_analyse(layout, [TroEdit(key=key, name="Neu_1")]), CSV) tro = doc.by_id("Neu_1") assert tro.key == key and tro.stable_key == key assert doc.resolve(key) is tro and doc.resolve("Neu_1") is tro # Staubereichs-Korrektur unter dem alten Schluessel gilt weiter area = next(a for a in doc.jam_areas_or_derived() if a.tro == "Neu_1") assert jam_stable_key(area, doc) == f"{key}.{area.entry}" assert jam_override(area, doc, {f"{key}.{area.entry}": ("0042", 3)}) == ("0042", 3) # unveraenderte TROs: Schluessel = ID assert all(t.key == t.tro_id for t in doc.tros if t.tro_id != "Neu_1") # --------------------------------------------------------------------------- # Verbindungen # --------------------------------------------------------------------------- def test_connection_edit_hin_und_zurueck(tmp_path): c = ConnectionEdit.parse("+ TRO01 -> TRO05 | weiche") assert (c.add, c.src, c.dst, c.kind) == (True, "TRO01", "TRO05", "weiche") assert ConnectionEdit.parse(c.render()) == c assert not ConnectionEdit.parse("- TRO01 -> TRO05").add with pytest.raises(ValueError): ConnectionEdit.parse("TRO01 -> TRO05") save_edits(CSV, [TroEdit(key="TRO02", name="X")], tmp_path, [c, ConnectionEdit(add=False, src="TRO03", dst="TRO04")]) o = load_overrides(CSV, tmp_path) assert o.connection_edits == [c, ConnectionEdit(add=False, src="TRO03", dst="TRO04")] assert [e.key for e in o.edits] == ["TRO02"] save_edits(CSV, [], tmp_path, []) assert "edit_connections" not in (tmp_path / "tro_overrides.ini").read_text(encoding="utf-8") def test_verbindungen_aendern_vorgaenger_und_nachfolger(layout): raw = _analyse(layout) s, d = _tro_kante(raw) andere = next(t.tro_id for t in raw.tros if t.tro_id not in (s, d) and (t.tro_id, d) not in raw.edges) analysis = _analyse(layout, connection_edits=[ ConnectionEdit(add=False, src=s, dst=d), ConnectionEdit(add=True, src=andere, dst=d), ConnectionEdit(add=True, src="GIBTSNICHT", dst=d)]) assert (s, d) not in analysis.edges and (andere, d) in analysis.edges assert analysis.edge_kinds[(andere, d)] == "transfer" ziel = analysis.by_id(d) assert andere in ziel.predecessors and s not in ziel.predecessors assert d not in analysis.by_id(s).successors assert any("GIBTSNICHT gibt es nicht" in f for f in analysis.findings) # die Staubereiche folgen den Vorgaengern doc = TroDocument.from_analysis(analysis, CSV) eingang = next(a for a in doc.jam_areas if a.tro == d and a.entry == 1) assert andere in eingang.predecessors def test_verbindung_ueber_schluessel_nach_umbenennung(layout): raw = _analyse(layout) s, d = _tro_kante(raw) analysis = _analyse(layout, [TroEdit(key=s, name="Quelle_1")], [ConnectionEdit(add=False, src=s, dst=d)]) assert ("Quelle_1", d) not in analysis.edges def test_pfeile_der_zeichnung_werden_zu_verbindungskorrekturen(layout, tmp_path): elements, _ = layout raw = _analyse(layout) markers = _markers(raw) arrows = _arrows(raw, markers) # unveraendert: keine Korrektur _, meldungen = read_tros(markers, raw, elements, CSV, tmp_path, arrows=arrows) assert load_overrides(CSV, tmp_path).connection_edits == [] s, d = _tro_kante(raw) andere = next(t.tro_id for t in raw.tros if t.tro_id not in (s, d) and (t.tro_id, d) not in raw.edges) arrows = [a for a in arrows if (a["FROM_TRO"], a["TO_TRO"]) != (s, d)] arrows.append({"FROM_TRO": andere, "TO_TRO": d, "FROM_UID": "", "TO_UID": "", "KIND": "flow", "_BLOCK": "CONNECTION_ARROW"}) # ein Marker umbenannt, seine Pfeile tragen noch den alten Namen -> ueber UID quelle = next(m for m in markers if m["ID"] == s) quelle["ID"] = "Quelle_1" read_tros(markers, raw, elements, CSV, tmp_path, arrows=arrows) o = load_overrides(CSV, tmp_path) assert o.connection_edits == [ConnectionEdit(add=False, src=s, dst=d), ConnectionEdit(add=True, src=andere, dst=d)] assert [(e.key, e.name) for e in o.edits] == [(s, "Quelle_1")] # Rundlauf: angewandt, neu gezeichnet, wieder gelesen -> dasselbe edited = _analyse(layout, o.edits, o.connection_edits) m2 = _markers(edited) again, _ = read_tros(m2, raw, elements, CSV, tmp_path, write=False, arrows=_arrows(edited, m2), base=_analyse(layout, o.edits)) assert [(e.key, e.name) for e in again] == [(s, "Quelle_1")] # --------------------------------------------------------------------------- # Pruefung # --------------------------------------------------------------------------- def _anlage_dateien(layout, tmp_path, bindings=None): doc = TroDocument.from_analysis(_analyse(layout), CSV) tro_file = tmp_path / "x_tro.json" tro_file.write_text(doc.to_json(), encoding="utf-8") anlage = AnlageDocument(layout=doc, layout_file=tro_file.name, bindings=bindings or []) anlage_file = tmp_path / "x_anlage.json" anlage_file.write_text(anlage.model_dump_json(by_alias=True, indent=2), encoding="utf-8") return tro_file, anlage_file, doc def test_pruefung_frische_anlage_ist_bereit(layout, tmp_path): tro_file, anlage_file, _ = _anlage_dateien(layout, tmp_path) os.utime(tro_file, (1_000_000, 1_000_000)) p = validate(anlage_file, cfg_dir=tmp_path) assert p.anzahl_fehler == 0, [str(b) for b in p.befunde] assert p.bereit() def test_pruefung_findet_fehler(layout, tmp_path): tro = TroDocument.from_analysis(_analyse(layout), CSV).tros[0] bindings = [Binding(tro="TRO99", slot="separator1", role="sensor", bmk="BG1"), Binding(tro=tro.tro_id, slot="separator1", role="sensor", bmk="BG2", status="widerspruch", note="BMK nicht im TIA-Import"), Binding(tro=tro.tro_id, slot="separator1", role="stopper", status="offen")] tro_file, anlage_file, _ = _anlage_dateien(layout, tmp_path, bindings) # TRO-JSON juenger als die Anlagen-JSON -> veraltet os.utime(anlage_file, (1_000_000, 1_000_000)) (tmp_path / "x_jam_names.ini").write_text("[jam]\nTRO99.1 = 0042_1\n", encoding="utf-8") p = validate(anlage_file, cfg_dir=tmp_path) texte = [b.text for b in p.befunde if b.stufe == FEHLER] assert any("TRO99 steht nicht im Layout" in t for t in texte) assert any("BMK nicht im TIA-Import" in t for t in texte) assert any("aelter als TRO-JSON" in t for t in texte) assert any("stopper 1x" in b.text for b in p.befunde) assert not p.bereit() # die Staubereichsdatei heisst nach der CSV, nicht nach der JSON assert not any("TRO99.1" in b.text for b in p.befunde) def test_pruefung_unbekannte_verbindung(layout, tmp_path): doc = TroDocument.from_analysis(_analyse(layout), CSV) doc.connections[0].dst = "TRO99" f = tmp_path / "x_tro.json" f.write_text(doc.to_json(), encoding="utf-8") p = validate(f, cfg_dir=tmp_path) assert any("TRO99 unbekannt" in b.text for b in p.befunde if b.stufe == FEHLER) # --------------------------------------------------------------------------- # Zeichnung aus der JSON # --------------------------------------------------------------------------- def test_zeichnung_zeigt_die_json(layout): raw = _analyse(layout) s = raw.tros[0].tro_id doc = TroDocument.from_analysis(_analyse(layout, [TroEdit(key=s, name="Neu_1")]), CSV) a = analysis_from_document(doc) assert [t.tro_id for t in a.tros] == [t.tro_id for t in doc.tros] assert set(a.edges) == {(c.src, c.dst) for c in doc.connections} assert a.edit_key("Neu_1") == s assert [b.tro_id for b in a.boundaries] == [b.tro_id for b in doc.boundaries]