Files
sps_skel/tests/test_anlage_workflow.py
T
s.ayadi 1ac17b95e2 validate_anlage, stabiler TRO-Schluessel, Verbindungen aus BricsCAD, Zeichnung aus der Anlagen-JSON
- validate_anlage.py (+ bin-Wrapper): Pruefung vor der SCL fuer beide Wege
  (Struktur, Zuordnungen, Aktualitaet der JSON, Korrekturen ohne Ziel);
  scl_skeleton ruft sie auf (Warnung, --strict bricht ab)
- DerivedTro.key: stabiler Schluessel (ID der ID-Datei); hw_overrides.ini,
  <csv>_jam_names.ini und tro_overrides.ini greifen ueber ID oder Schluessel,
  eine Umbenennung in TRO_EDIT laesst sie bestehen; JAM_AREA traegt KEY
- --read-tros liest die CONNECTION_ARROW-Bloecke mit -> [<csv>.edit_connections];
  tro_flow.apply_connection_edits wendet sie nach link_tros an
- tro_annotate --anlage zeichnet Marker/Verbindungen aus der Anlagen-JSON
- INI-Korrekturen auch mit BOM lesbar (utf-8-sig)
- Ablauf CSV/DXF+TIA -> Anlagen-JSON -> (BricsCAD) -> SCL in doc/Python_Scripts.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 14:32:10 +01:00

233 lines
10 KiB
Python

# -*- 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]