model.py: gemeinsames TRO-Modell; Hauptweg CSV -> tro_flow --json -> scl_skeleton
- lib/model.py (neu, pydantic): TroBase -> DerivedTro, Connection, OpenPoint, Analysis (aus tro_flow verschoben), TroDocument = TRO-JSON (Schema sps_skel-tro-v2). items/fbType werden immer ueber den Typ aus tro_catalog gelesen, nie gespeichert; abweichende Werte in einer JSON sind ein Fehler. JSON-Schluessel wie bisher (id, type, fbType ...), extra="forbid". - tro_flow: Tro/Analysis aus model.py; neuer Schalter --json schreibt <name>_tro.json (TroDocument) - scl_skeleton: liest die JSON ueber TroDocument statt per Dict-Zugriff, Bauteile ueber TroItem-Konstanten; ungueltige JSON -> Exit 1 ohne SCL, alte tro_extract-JSON (v1) mit Hinweis auf tro_flow --json - tro_annotate: nur noch Ansicht; derselbe analyse_tros-Aufruf wie tro_flow (inkl. sepliste-Gegenprobe), damit Zeichnung und JSON uebereinstimmen - tro_extract: geparkt (unveraendert) - tests/test_model.py + conftest.py, pytest in requirements.txt aktiviert - Doku: Python_Scripts.md (§6b model.py), Commandlines.md, CLAUDE.md, Aufrufkommentare in bin/ (scl_skeleton.bat zeigte tro_flow-Aufrufe) Geprueft: alle tro_flow/tro_annotate-Ausgaben (8 CSVs) bis auf Zeitstempel identisch zum vorherigen Stand; scl_skeleton alt/neu auf derselben JSON byte-identisch (export, mubea, HundM05); 10 Tests gruen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
90cafec99c
commit
97a0db6707
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests fuer lib/model.py - die TRO-JSON zwischen tro_flow und scl_skeleton."""
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from model import SCHEMA, Analysis, Connection, DerivedTro, TroDocument
|
||||
from scl_skeleton import render
|
||||
|
||||
|
||||
def _tro(tro_id: str, type_name: str, **extra) -> DerivedTro:
|
||||
data = dict(
|
||||
tro_id=tro_id,
|
||||
type_name=type_name,
|
||||
label=f"Label {tro_id}",
|
||||
hosts=["0001"],
|
||||
separators=["0040"],
|
||||
scanners=[],
|
||||
reason="Test",
|
||||
confidence="mittel",
|
||||
)
|
||||
data.update(extra)
|
||||
return DerivedTro(**data)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def document() -> TroDocument:
|
||||
"""Kleine Anlage: SYS1 -> TRO01 (Weiche) -> TRO02 / TRO03 -> SYS2."""
|
||||
analysis = Analysis(
|
||||
tros=[
|
||||
_tro("TRO01", "1Sep1Swi", predecessors=["SYS1"], successors=["TRO02", "TRO03"],
|
||||
position=(1030.0, 4700.0, 1500.0)),
|
||||
_tro("TRO02", "1Sep", separators=["0041"], predecessors=["TRO01"],
|
||||
successors=["SYS2"]),
|
||||
_tro("TRO03", "Vario", separators=["0042"], predecessors=["TRO01"]),
|
||||
],
|
||||
boundaries=[
|
||||
_tro("SYS1", "Materialeingang", separators=[], successors=["TRO01"]),
|
||||
_tro("SYS2", "Materialausgang", separators=[], predecessors=["TRO02"]),
|
||||
],
|
||||
edges=[("SYS1", "TRO01"), ("TRO01", "TRO02"), ("TRO01", "TRO03"), ("TRO02", "SYS2")],
|
||||
edge_kinds={("TRO01", "TRO02"): "weiche"},
|
||||
findings=["ein Befund"],
|
||||
)
|
||||
return TroDocument.from_analysis(analysis, source="test.csv", note="Test")
|
||||
|
||||
|
||||
def test_round_trip_is_lossless(document):
|
||||
text = document.to_json()
|
||||
again = TroDocument.from_json(text)
|
||||
assert again.to_json() == text
|
||||
assert again.by_id("TRO01").successors == ["TRO02", "TRO03"]
|
||||
|
||||
|
||||
def test_json_uses_tro_json_keys(document):
|
||||
data = json.loads(document.to_json())
|
||||
assert data["schema"] == SCHEMA
|
||||
first = data["tros"][0]
|
||||
assert first["id"] == "TRO01"
|
||||
assert first["type"] == "1Sep1Swi"
|
||||
assert first["fbType"] == "FB_ILS_MTRO_1Sep1Swi"
|
||||
assert first["items"] == {"Separator": 1, "Switch": 1}
|
||||
assert first["position"] == {"x": 1030.0, "y": 4700.0, "z": 1500.0}
|
||||
assert data["connections"][1] == {"from": "TRO01", "to": "TRO02", "kind": "weiche"}
|
||||
assert "openPoints" in data
|
||||
|
||||
|
||||
def test_items_and_fb_block_follow_the_type():
|
||||
tro = _tro("TRO01", "1Sep")
|
||||
assert tro.items == {"Separator": 1}
|
||||
tro.type_name = "1Sep2Swi"
|
||||
assert tro.items == {"Separator": 1, "Switch": 2, "Scanner": 1}
|
||||
assert tro.fb_block == "FB_ILS_MTRO_1Sep2Swi"
|
||||
|
||||
|
||||
def test_unknown_type_has_no_catalog_data():
|
||||
station = _tro("SYS1", "Materialeingang")
|
||||
assert station.definition() is None
|
||||
assert station.items == {}
|
||||
assert station.fb_block == ""
|
||||
|
||||
|
||||
def test_catalog_fields_cannot_be_assigned():
|
||||
tro = _tro("TRO01", "1Sep")
|
||||
with pytest.raises((AttributeError, ValidationError)):
|
||||
tro.fb_block = "FB_Anders"
|
||||
|
||||
|
||||
def test_unknown_key_is_rejected(document):
|
||||
data = json.loads(document.to_json())
|
||||
data["tros"][0]["separatorz"] = data["tros"][0].pop("separators")
|
||||
with pytest.raises(ValidationError, match="separatorz"):
|
||||
TroDocument.model_validate(data)
|
||||
|
||||
|
||||
def test_items_not_matching_catalog_are_rejected(document):
|
||||
data = json.loads(document.to_json())
|
||||
data["tros"][1]["items"] = {"Separator": 2}
|
||||
with pytest.raises(ValidationError, match="passt nicht zum Typ"):
|
||||
TroDocument.model_validate(data)
|
||||
|
||||
|
||||
def test_fb_type_not_matching_catalog_is_rejected(document):
|
||||
data = json.loads(document.to_json())
|
||||
data["tros"][1]["fbType"] = "FB_ILS_MTRO_Vario"
|
||||
with pytest.raises(ValidationError, match="fbType"):
|
||||
TroDocument.model_validate(data)
|
||||
|
||||
|
||||
def test_connection_accepts_alias_and_field_name():
|
||||
assert Connection(**{"from": "A", "to": "B"}) == Connection(src="A", dst="B")
|
||||
|
||||
|
||||
def test_scl_skeleton_renders_from_document(document):
|
||||
scl = render(document, "test_tro.json")
|
||||
assert 'TRO01 : "FB_ILS_MTRO_1Sep1Swi";' in scl
|
||||
assert 'TRO03 : "FB_ILS_MTRO_Vario";' in scl
|
||||
# Weiche: FC_Direction und eine Zielliste je Abgang
|
||||
assert '"FC_Direction"' in scl
|
||||
assert "// ueber TRO02: SYS2, TRO02" in scl
|
||||
# Vario-TRO bekommt den Vario-Parameterblock
|
||||
assert "#TRO03.stInVario.Settings" in scl
|
||||
# Systemgrenzen sind keine Instanzen
|
||||
assert "SYS1 :" not in scl
|
||||
assert "Anfang bei TRO01" in scl
|
||||
Reference in New Issue
Block a user