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:
Samer Ayadi
2026-10-09 12:42:46 +02:00
co-authored by Claude Opus 5.5
parent 90cafec99c
commit 97a0db6707
14 changed files with 790 additions and 275 deletions
+92 -25
View File
@@ -1,13 +1,14 @@
# Python Scripts — CLI Tooling Overview
**As of:** 2026-09-17
**As of:** 2026-10-09
`lib/` currently holds nine Python modules (five of them runnable CLI tools,
four supporting libraries), plus one documentation-generator script that lives
`lib/` currently holds ten Python modules (five of them runnable CLI tools,
five supporting libraries), plus one documentation-generator script that lives
under `doc/TRO_Katalog/tro_graphs/`. Together the CLI tools turn a CSV export
of the mechanical layout (ILS 2.0) into a material-flow graph, a derived TRO
list/diagram, an annotated copy of the BricsCAD drawing, a TRO-JSON read back
out of that drawing, and an `FB_Main` SCL skeleton generated from that JSON.
list/diagram, a TRO-JSON (shared model `lib/model.py`), an `FB_Main` SCL
skeleton generated from that JSON, and an annotated copy of the BricsCAD
drawing for visual checking.
This document is the practical "what does each script do and which switches
does it take" reference. For the underlying domain model (TRO types, JSON
@@ -26,18 +27,25 @@ material_flow.py ──► <name>_material_flow.dot / .svg / .md
▼
tro_flow.py ──► <name>_tro_flow.dot / .svg, _tro_doc.md, _tro_errors.md
│ (derives TROs, using the type catalog in tro_catalog.py)
▼
tro_annotate.py ──► <name>_annotated.dxf, <name>_registration.json
│ (also needs the BricsCAD DXF; uses dxf_registration.py for the CSV↔DXF
│ coordinate offset and tro_catalog.py for marker shape/color per TRO type)
▼ (drawing may be hand-edited in BricsCAD before the next step)
tro_extract.py ──► <drawing>_tro.json
│ (reads only the drawing; resolves plant coords via the registration)
▼
scl_skeleton.py ──► <source>_FB_Main.scl
(one REGION per TRO; E-planning values left as TODO(E-Planung) gaps)
│
├── --json ──► <name>_tro.json (model.TroDocument — MAIN PATH)
│ │
│ ▼
│ scl_skeleton.py ──► <name>_FB_Main.scl
│ (validates the JSON against model.py; one REGION per TRO;
│ E-planning values left as TODO(E-Planung) gaps)
│
└── same derivation ──► tro_annotate.py ──► <name>_annotated.dxf (VIEWER ONLY)
(also needs the BricsCAD DXF; uses dxf_registration.py for the CSV↔DXF
coordinate offset and tro_catalog.py for marker shape/color per TRO type)
parked: tro_extract.py drawing ──► JSON (not extended; see §5a)
```
The drawing is a **view** for checking the result, not a data source: the
TRO-JSON is written by `tro_flow.py --json` straight from the CSV, and
`tro_annotate.py` derives exactly the same TROs to draw them.
- `material_flow.py` models the material flow of the **mechanical** objects only
(Gefällestrecke, Strecke, Kreisel).
- `tro_flow.py` is its sister program: it derives the **control objects**
@@ -51,6 +59,10 @@ Supporting libraries (no CLI of their own, imported by the tools above):
- **`lib/tro_catalog.py`** — the single source of truth for TRO types: short
name, Siemens FB block, sub-components and counts, color group, CAD marker
shape.
- **`lib/model.py`** — the shared pydantic model of TRO *instances* and of the
TRO-JSON (`TroBase`, `DerivedTro`, `Connection`, `OpenPoint`, `Analysis`,
`TroDocument`). Written by `tro_flow.py --json`, read and validated by
`scl_skeleton.py`. See §6b.
- **`lib/dxf_registration.py`** — derives, verifies and persists the rigid
(rotation + offset) transform between the CSV export's coordinate system and
the DXF drawing's.
@@ -77,7 +89,8 @@ Each CLI tool is invoked through its `bin/<name>.bat` (Windows) / `bin/<name>.sh
```bat
bin\material_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc --json
bin\scl_skeleton.bat --json mubea_tro.json
bin\tro_annotate.bat --file mubea.csv --dxf 500573_60_1.dxf --flow --fb --legend
```
@@ -281,6 +294,7 @@ colors all come from `lib/tro_catalog.py` — nothing is hard-coded here.
| `--doc` | — | off | Also write a Markdown report (`<name>_tro_doc.md`) with the TRO list, connections, findings and the full type catalog. |
| `--connect` | — | off | Also write the TRO topology as `<name>_tro_connect.ini` (nodes, connections, system boundaries, predecessors/successors per TRO). Pure output, never read back. |
| `--true-scale` | — | off | Only with `--use-cords`: draw to scale. By default empty stretches longer than 1500 mm between nodes are shortened (order and neighbourhood are kept). |
| `--json` | — | off | Also write the TRO-JSON `<name>_tro.json` (schema `sps_skel-tro-v2`, model `TroDocument` in `lib/model.py`): TROs, system boundaries (`SYSn`), connections with their kind, findings. Input for `scl_skeleton.py`. |
TROs, types and connections always come from the layout alone;
`cfg/tro_overrides.ini` is not read. Errors in the derivation are fixed in
@@ -288,16 +302,21 @@ TROs, types and connections always come from the layout alone;
**Output** (in `%SKEL_RESULTS%`): `<name>_tro_flow.dot` (always, with `pos`
attributes when `--use-cords` was used), `<name>_tro_flow.svg` (with
`--tosvg`), `<name>_tro_doc.md` (with `--doc`), `<name>_tro_errors.md` (only
if TROs are unconnected or of an unknown type).
`--tosvg`), `<name>_tro_doc.md` (with `--doc`), `<name>_tro.json` (with
`--json`), `<name>_tro_errors.md` (only if TROs are unconnected or of an
unknown type).
**Exit codes:** `0` ok · `1` input/CLI error · `2` SVG rendering failed
(Graphviz) · `3` TRO validation errors found (error file written).
## 5. `tro_annotate.py` — burn the TROs into a copy of the CAD drawing
**Viewer only.** The drawing shows the result for a visual check in BricsCAD;
it is no longer a data source for the SCL (that is `tro_flow.py --json`).
Takes the CSV export, the original BricsCAD drawing (DXF) and the TROs
derived the same way as `tro_flow.py`, and writes a **copy** of the drawing
derived with exactly the same call as `tro_flow.py` (including the separator
cross-check from `<name>_raw.json`), and writes a **copy** of the drawing
with:
- one colored marker block per TRO, each type with its own shape (circle,
@@ -353,6 +372,13 @@ could not be proven (or its residual error exceeds 600 mm) — resolve with
## 5a. `tro_extract.py` — read the TROs back out of the drawing into JSON
> **Parked since 2026-10-09.** The TRO-JSON for `scl_skeleton.py` now comes
> from `tro_flow.py --json`. `tro_extract.py` is kept but not extended; its
> output (schema `sps_skel-tro-extract-v1`) is **not** accepted by
> `scl_skeleton.py` any more, which says so and asks for `tro_flow --json`. If
> hand edits in the drawing are ever needed again, it should write the same
> `model.TroDocument` format.
The counter-direction to `tro_annotate.py`:
```
@@ -394,12 +420,18 @@ Last step of the chain:
```
material_flow.py objects -> material flow
tro_flow.py flow -> TROs
tro_annotate.py TROs -> markers in the drawing
tro_extract.py drawing -> JSON
tro_flow.py flow -> TROs -> <name>_tro.json (--json)
scl_skeleton.py JSON -> FB_Main.scl
```
The JSON is read through `model.TroDocument` and **validated**: an unknown or
renamed key, or `items`/`fbType` that do not match the TRO type in
`tro_catalog.py`, stops the run with exit code `1` and the validation error —
no SCL is written. A file with a different schema (e.g. the old
`tro_extract.py` output) gets a short message asking for `tro_flow --json`.
Part counts (`items`) and the FB type are taken from the catalog via the TRO
type.
Produces a **skeleton, not a running program**: one `REGION` per TRO with the
instance call and every parameter line in the right order, following the real
call sites in `doc/TRO_Katalog/scl_templates/*.scl`.
@@ -426,14 +458,16 @@ only auto-filled for a single-switch TRO; where several switches share the
exits, how they divide them is a planning decision, so it stays a gap rather
than a guess.
Switches: `--json NAME` (default `export_tro.json`), `--out NAME` (default
Switches: `--json NAME` (default `export_tro.json`, looked up in
`%SKEL_RESULTS%`; written by `tro_flow.py --json`), `--out NAME` (default
`<source>_FB_Main.scl`), `--start TRO`. The last one matters while the plant has
no loading/unloading station: the flow is then a closed loop with no entry, so
the order has to be broken somewhere arbitrary. The chosen start and the reason
are recorded in the file header.
**Stations (`SYSn`).** Loading/unloading stations from `tro_flow.py` appear in
the JSON as `SYSn` neighbours, not as TROs. They get no REGION and no instance.
the JSON under `boundaries` and as `SYSn` neighbours of the TROs, not in
`tros`. They get no REGION and no instance.
A TRO fed by a `SYSn` is the start of the order ("Materialeingang ueber SYSn"),
so `--start` is no longer needed once the export contains a station. In the
jam-area comments they are marked "(Station, kein TRO)", and an exit station
@@ -461,7 +495,40 @@ verified against `data/*.scl` where the source is vendored locally, and against
Everything is a Pydantic v2 model
(`TroDefinition`/`TroStyle`), so it validates on every assignment and comes
with `model_dump()`/`model_validate()` for the planned JSON layout model for
free. Consumed by `tro_flow.py` and `tro_annotate.py`; not run directly.
free. Consumed by `model.py`, `tro_flow.py`, `tro_annotate.py` and
`scl_skeleton.py`; not run directly.
## 6b. `model.py` — shared model of TRO instances and the TRO-JSON (library, no CLI)
`tro_catalog.py` says what a TRO **type** is; `model.py` says what one concrete
TRO in a plant, and the whole TRO-JSON, look like. It imports only the catalog.
```
tro_catalog.py type level TroDefinition (items, FB block, color, symbol)
▲ get_tro()
model.py instance level TroBase ─► DerivedTro, Connection, OpenPoint,
Analysis, TroDocument
▲
tro_flow.py (writes) · scl_skeleton.py (reads) · tro_annotate.py (draws)
```
| Class | What it is |
|---|---|
| `TroBase` | Core of every TRO: `id`, `type`, `confidence`, `separators`, `predecessors`, `successors`, `position` (`{x,y,z}` in JSON). `definition()`, `fb_block` (JSON `fbType`) and `items` are **read from the catalog** on every access, never stored — so they follow a type change and cannot go stale. |
| `DerivedTro` | `TroBase` + what `tro_flow.py` knows from the derivation: `label`, `hosts`, `scanners`, `reason`, `colocated`, `positionFrom`, `positionPoints`. Also used for the system boundaries `SYSn`. |
| `Connection` | Directed edge `from` → `to` with its `kind` (transfer / umlauf / weiche / unbestimmt / normal / bahn). |
| `OpenPoint` | A reported spot that is not a TRO yet (empty in the JSON for now; meant to replace the class of the same name in `tro_overrides.py`). |
| `Analysis` | Working result of `tro_flow.analyse_tros` (moved here from `tro_flow.py`). |
| `TroDocument` | The TRO-JSON: `schema` (`sps_skel-tro-v2`), `source`, `created`, `note`, `tros`, `boundaries`, `connections`, `openPoints`, `findings`. `from_analysis()`, `to_json()`, `from_json()`. |
All models validate on creation and assignment and reject unknown keys
(`extra="forbid"`). JSON keys keep the camelCase names of the TRO-JSON (`id`,
`type`, `fbType`, `openPoints` …); Python code uses the field names
(`tro_id`, `type_name`, `fb_block`, `open_points`) — both are accepted on input.
Tests: `tests/test_model.py` (round trip, catalog-backed fields, rejection of
wrong keys/items, SCL rendering from a document) — run with
`python -m pytest tests/`.
## 6a. `tro_overrides.py` — manual corrections to the derived TROs (library, no CLI)