# -*- coding: utf-8 -*- """Tests fuer die Handkorrekturen aus BricsCAD (Dialog TRO_EDIT). Weg: Marker der Zeichnung (Attribute KEY/ID/TYPE/SLOTS) -> tro_annotate.read_tros -> Abschnitte edit/edit_add in tro_overrides.ini (tro_overrides.save_edits) -> tro_flow.apply_edits beim naechsten Lauf. Fixture: Testanlage test_system (TRO01 3Sep2Swi, TRO02 2Sep1Swi, TRO03 2Sep2Swi, TRO04-06 1Sep1Swi an den Separatoren 0012/0011/0010). """ from __future__ import annotations from pathlib import Path import pytest from material_flow import build_graph, read_elements from model import TroDocument, derive_slots from tro_annotate import emit_lisp, format_slots, parse_slots, read_tros, tro_slots from tro_flow import analyse_tros from tro_overrides import PartRef, TroEdit, load_overrides, save_edits 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): elements, graph = layout return analyse_tros(graph, elements, edits=edits) def _by_sep(analysis, sep: str): return next(t for t in analysis.tros if sep in t.separators) def _markers(analysis) -> list[dict[str, str]]: """Marker, wie tro_annotate.place_markers sie in die Zeichnung schreibt.""" return [{"ID": t.tro_id, "KEY": analysis.edit_key(t.tro_id), "TYPE": t.type_name, "SLOTS": format_slots(*tro_slots(t)), "_BLOCK": f"TRO_SYM_{t.type_name}"} for t in analysis.tros] # --------------------------------------------------------------------------- # INI: lesen und schreiben # --------------------------------------------------------------------------- def test_partref_hin_und_zurueck(): ref = PartRef.parse("0010 @ 1234.5 -567.8") assert (ref.teile_id, ref.x, ref.y) == ("0010", 1234.5, -567.8) assert PartRef.parse(ref.render()) == ref assert PartRef.parse("-") == PartRef() assert PartRef.parse("0010").x is None with pytest.raises(ValueError): PartRef.parse("0010 @ 1") def test_load_overrides_liest_edit_abschnitte(tmp_path): (tmp_path / "tro_overrides.ini").write_text( f"[{CSV}.edit.TRO04]\ntype = 2Sep1Swi\nname = Weiche_K8\n" f"separator1 = 0012 @ 1 2\nswitch1 = -\nreason = Begehung\n\n" f"[{CSV}.edit_add.TRO99]\ntype = 1Sep\nseparator1 = 0011\n\n" f"[{CSV}.edit.TRO05]\ndelete = true\n\n" f"[{CSV}.edit.TRO06]\nweiche = 0023\n\n" f"[{CSV}.edit_add.TRO98]\nseparator1 = 0011\n", encoding="utf-8") overrides = load_overrides(CSV, tmp_path) edits = {e.key: e for e in overrides.edits} assert set(edits) == {"TRO04", "TRO99", "TRO05"} assert edits["TRO04"].name == "Weiche_K8" assert edits["TRO04"].slots == {"separator1": PartRef("0012", 1.0, 2.0), "switch1": PartRef()} assert edits["TRO04"].reason == "Begehung" assert edits["TRO99"].add and edits["TRO99"].lines is None assert edits["TRO05"].delete # unbekannter Schluessel und neuer TRO ohne Typ: gemeldet, nicht angewandt assert len(overrides.warnings) == 2 # material_flow --connect sieht die Handkorrekturen nicht assert not overrides def test_save_edits_ersetzt_nur_die_eigenen_abschnitte(tmp_path): ini = tmp_path / "tro_overrides.ini" ini.write_text( "; Kopf\n\n" f"[{CSV}.tro.TRO03]\ntype = 1Sep\n\n" f"[{CSV}.edit.TRO01]\ntype = 1Sep\n\n" "; Kommentar zum naechsten Abschnitt\n" f"[andere.csv.edit.TRO01]\ntype = 1Sep\n", encoding="utf-8") save_edits(CSV, [TroEdit(key="TRO02", name="Neu"), TroEdit(key="TRO07", add=True, type_name="1Sep", slots={"separator1": PartRef("0010", 5, 6)})], tmp_path) text = ini.read_text(encoding="utf-8") assert f"[{CSV}.edit.TRO01]" not in text assert f"[{CSV}.tro.TRO03]" in text and "[andere.csv.edit.TRO01]" in text assert "; Kommentar zum naechsten Abschnitt\n[andere.csv.edit.TRO01]" in text assert text.index("[andere.csv") < text.index(f"[{CSV}.edit.TRO02]") assert "separator1 = 0010 @ 5.0 6.0" in text assert {e.key for e in load_overrides(CSV, tmp_path).edits} == {"TRO02", "TRO07"} # erneut schreiben ersetzt den Block, leer entfernt ihn samt Kopfzeile save_edits(CSV, [TroEdit(key="TRO02", name="Neu")], tmp_path) assert ini.read_text(encoding="utf-8").count("Handkorrekturen aus BricsCAD") == 1 save_edits(CSV, [], tmp_path) text = ini.read_text(encoding="utf-8") assert "Handkorrekturen" not in text and f"{CSV}.edit." not in text assert text.startswith("; Kopf\n") # --------------------------------------------------------------------------- # tro_flow.apply_edits # --------------------------------------------------------------------------- def test_typ_und_bauteile_aendern_verdraengt_den_nachbarn(layout): raw = _analyse(layout) key = _by_sep(raw, "0012").tro_id verdraengt = _by_sep(raw, "0011").tro_id edit = TroEdit(key=key, type_name="2Sep1Swi", slots={ "separator1": PartRef("0012"), "separator2": PartRef("0011"), "switch1": PartRef("0024")}) analysis = _analyse(layout, [edit]) tro = analysis.by_id(key) assert (tro.type_name, tro.separators, tro.switches) == ("2Sep1Swi", ["0012", "0011"], ["0024"]) assert tro.confidence == "bestaetigt" assert analysis.by_id(verdraengt) is None assert any(f"{verdraengt} hat keinen Separator mehr" in f for f in analysis.findings) # 2Sep1Swi hat dazu zwei Scanner-Slots, hier leer assert [s.layout_id for s in derive_slots(tro)] == ["0012", "0011", "0024", "", ""] def test_umbenennen_loeschen_und_neu_anlegen(layout): raw = _analyse(layout) a, b = _by_sep(raw, "0012").tro_id, _by_sep(raw, "0010").tro_id edits = [ TroEdit(key=a, name="Weiche_K8"), TroEdit(key="TRO99", add=True, type_name="1Sep", slots={"separator1": PartRef("0010")}), TroEdit(key=b, delete=True), TroEdit(key=_by_sep(raw, "0011").tro_id, name="SYS1"), ] analysis = _analyse(layout, edits) assert analysis.by_id("Weiche_K8") is not None and analysis.by_id(a) is None assert analysis.renamed == {"Weiche_K8": a} assert analysis.edit_key("Weiche_K8") == a neu = analysis.by_id("TRO99") assert neu.separators == ["0010"] and neu.position is not None assert analysis.added == ["TRO99"] assert analysis.by_id(b) is None # das Loeschen laeuft vor dem Anlegen: 0010 wird nicht "verdraengt" assert not any("verschoben" in f or "keinen Separator" in f for f in analysis.findings) assert any("Umbenennung nicht moeglich" in f for f in analysis.findings) # die korrigierten TROs laufen durch das Modell der TRO-JSON TroDocument.from_json(TroDocument.from_analysis(analysis, CSV).to_json()) def test_teileid_aus_altem_export_wird_ueber_die_lage_gefunden(layout): elements, _ = layout sep = next(e for e in elements if e.teile_id == "0012") raw = _analyse(layout) key = _by_sep(raw, "0012").tro_id edit = TroEdit(key=key, slots={"separator1": PartRef("9999", sep.x, sep.y), "switch1": PartRef("0024")}) analysis = _analyse(layout, [edit]) assert analysis.by_id(key).separators == ["0012"] assert any("9999 steht im Export nicht mehr dort" in f for f in analysis.findings) # --------------------------------------------------------------------------- # tro_annotate.read_tros # --------------------------------------------------------------------------- def test_slots_attribut_hin_und_zurueck(): slots, lines = {"separator1": "0010", "switch1": ""}, ["0066", "0069"] assert format_slots(slots, lines) == "separator1=0010;switch1=;lines=0066,0069" assert parse_slots(format_slots(slots, lines)) == (slots, lines) assert parse_slots("separator1=0010") == ({"separator1": "0010"}, None) def test_unveraenderte_zeichnung_ergibt_keine_korrektur(layout, tmp_path): elements, _ = layout raw = _analyse(layout) edits, meldungen = read_tros(_markers(raw), raw, elements, CSV, tmp_path) assert edits == [] assert not (tmp_path / "tro_overrides.ini").read_text(encoding="utf-8").strip() def test_dialogaenderungen_werden_zu_korrekturen(layout, tmp_path): elements, _ = layout raw = _analyse(layout) markers = _markers(raw) by_key = {m["KEY"]: m for m in markers} a = _by_sep(raw, "0012").tro_id b = _by_sep(raw, "0010").tro_id by_key[a].update(ID="Weiche_K8", TYPE="2Sep1Swi", SLOTS="separator1=0012;separator2=;switch1=;scanner1=0010") markers.remove(by_key[b]) markers.append({"ID": "TRO99", "KEY": "", "TYPE": "1Sep", "SLOTS": "separator1=0010", "_BLOCK": "TRO_SYM_1Sep"}) edits, meldungen = read_tros(markers, raw, elements, CSV, tmp_path) by = {(e.section_kind, e.key): e for e in edits} assert set(by) == {("edit", a), ("edit_add", "TRO99"), ("edit", b)} assert by[("edit", a)].name == "Weiche_K8" assert by[("edit", a)].type_name == "2Sep1Swi" assert by[("edit", a)].slots["separator2"] == PartRef() assert by[("edit", a)].slots["switch1"] == PartRef() assert any("scanner1 = 0010 ist ein Separator" in m for m in meldungen) assert by[("edit", b)].delete # Lage aus dem CSV hinter der TeileId assert by[("edit_add", "TRO99")].slots["separator1"].x is not None assert any("separator2 ist leer" in m for m in meldungen) # Rundlauf: angewandt, neu beschriftet, wieder gelesen -> dieselben Korrekturen edited = _analyse(layout, load_overrides(CSV, tmp_path).edits) again, _ = read_tros(_markers(edited), raw, elements, CSV, tmp_path, write=False) assert {(e.section_kind, e.key) for e in again} == set(by) def test_kopierter_marker_wird_neuer_tro(layout, tmp_path): elements, _ = layout raw = _analyse(layout) markers = _markers(raw) kopie = dict(markers[0], ID="TRO77") edits, meldungen = read_tros(markers + [kopie], raw, elements, CSV, tmp_path, write=False) assert [(e.section_kind, e.key) for e in edits] == [("edit_add", "TRO77")] assert any("doppelt" in m for m in meldungen) # --------------------------------------------------------------------------- # Slot-Listen fuer den Dialog # --------------------------------------------------------------------------- def test_emit_lisp_schreibt_slots_je_typ(tmp_path): text = emit_lisp(tmp_path).read_text(encoding="utf-8") assert ('("2Sep1Swi" 0 "separator1" "separator2" "switch1" "scanner1" "scanner2")' in text) assert '("PinStore_Auto" 19 "separator1" "scanner1")' in text assert '("switch" "Ausschleus" "Weiche")' in text