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
+29 -15
View File
@@ -28,15 +28,19 @@ activates `.venv` if present and runs `lib/<name>.py` with all arguments passed
through. Every tool accepts `--help`.
```
CSV ─► material_flow ─► tro_flow ─► tro_annotate ─► [edit in BricsCAD] ─► tro_extract ─► scl_skeleton
(+ DXF) (DXF only) (JSON → SCL)
CSV ─► material_flow ─► tro_flow --json ─► <name>_tro.json ─► scl_skeleton ─► <name>_FB_Main.scl
│ (model.py) (validates)
└─► tro_annotate (+ DXF) ─► <name>_annotated.dxf viewer only
parked: tro_extract (DXF → JSON), see 2.4
```
Shared conventions:
- Input names (`--file`, `--dxf`) are a bare filename resolved against
`%SKEL_DATA%`, or a full path. `tro_extract`/`scl_skeleton` look up their
input in `%SKEL_RESULTS%` instead (it is the output of the previous step).
`%SKEL_DATA%`, or a full path. `scl_skeleton` (and the parked `tro_extract`)
look up their input in `%SKEL_RESULTS%` instead (it is the output of the
previous step).
- All output goes to `%SKEL_RESULTS%`.
- `--tosvg` needs Graphviz `dot` (`neato` with `--use-cords`) on `PATH`, or
`GRAPHVIZ_DOT` pointing at `dot`.
@@ -78,16 +82,20 @@ Input: CSV export. Details: [`Python_Scripts.md` §4](Python_Scripts.md#4-tro_fl
| `--true-scale` | — | off | Only with `--use-cords`: draw to scale (default shortens empty stretches > 1500 mm). |
| `--doc` | — | off | Write Markdown report `<name>_tro_doc.md`. |
| `--connect` | — | off | Write TRO topology as `<name>_tro_connect.ini`. |
| `--json` | — | off | Write the TRO-JSON `<name>_tro.json` (model `lib/model.py`) — input for `scl_skeleton`. |
Exit codes: `0` ok · `1` input/CLI error · `2` SVG rendering failed ·
`3` TRO validation errors (`<name>_tro_errors.md` written).
```bat
bin\tro_flow.bat --file mubea.csv --tosvg --use-cords --doc
bin\tro_flow.bat --file mubea.csv --json
```
### 2.3 `tro_annotate` — burn the TROs into a copy of the drawing
Viewer only: shows the same TROs as `tro_flow` in the drawing for a visual
check; the drawing is not read back as data.
Input: CSV export + original BricsCAD DXF. The original is never modified.
Details: [`Python_Scripts.md` §5](Python_Scripts.md#5-tro_annotatepy--burn-the-tros-into-a-copy-of-the-cad-drawing).
@@ -117,6 +125,10 @@ bin\tro_annotate.bat --emit-lisp
### 2.4 `tro_extract` — read the TROs back out of the drawing into JSON
**Parked** — kept, but not part of the main path. Its JSON (schema
`sps_skel-tro-extract-v1`) is rejected by `scl_skeleton`; use
`tro_flow --json` instead.
Input: the (possibly hand-edited) annotated DXF only, never the CSV.
Details: [`Python_Scripts.md` §5a](Python_Scripts.md#5a-tro_extractpy--read-the-tros-back-out-of-the-drawing-into-json).
@@ -133,8 +145,9 @@ bin\tro_extract.bat --dxf mubea_annotated.dxf
### 2.5 `scl_skeleton` — generate the `FB_Main` SCL skeleton
Input: TRO-JSON from `tro_extract`. Output is a skeleton with
`TODO(E-Planung)` gaps, not a compilable program.
Input: TRO-JSON from `tro_flow --json`, validated against `lib/model.py` — a
wrong or renamed key stops the run (exit `1`) instead of producing SCL. Output
is a skeleton with `TODO(E-Planung)` gaps, not a compilable program.
Details: [`Python_Scripts.md` §5b](Python_Scripts.md#5b-scl_skeletonpy--generate-the-fb_main-skeleton-as-scl).
| Switch | Argument | Default | Effect |
@@ -143,11 +156,11 @@ Details: [`Python_Scripts.md` §5b](Python_Scripts.md#5b-scl_skeletonpy--generat
| `--out` | `NAME` | `<source>_FB_Main.scl` | Output SCL filename. |
| `--start` | `TRO` | — | TRO where the REGION order begins. Only needed while the flow is a closed loop without a loading/unloading station (`SYSn`). |
Exit codes: `0` ok · `1` input/CLI error.
Exit codes: `0` ok · `1` input/CLI error or JSON does not match the model.
```bat
bin\scl_skeleton.bat --json mubea_annotated_tro.json
bin\scl_skeleton.bat --json mubea_annotated_tro.json --start TRO01
bin\scl_skeleton.bat --json mubea_tro.json
bin\scl_skeleton.bat --json mubea_tro.json --start TRO01
```
## 3. End-to-end example
@@ -155,18 +168,19 @@ bin\scl_skeleton.bat --json mubea_annotated_tro.json --start TRO01
```bat
bin\activate_venv.bat
bin\material_flow.bat --file mubea.csv --tosvg --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
REM optional, for a visual check in BricsCAD:
bin\tro_annotate.bat --file mubea.csv --dxf Mubea.dxf --flow --legend
REM ... optionally review / edit results\mubea_annotated.dxf in BricsCAD ...
bin\tro_extract.bat --dxf mubea_annotated.dxf
bin\scl_skeleton.bat --json mubea_annotated_tro.json
```
Tests: `python -m pytest tests/` (with `.venv` active).
## 4. Documentation tooling (not part of the pipeline)
| Command | Purpose |
|---|---|
| `py doc/TRO_Katalog/tro_graphs/gen_tro_graphs.py` | Regenerates the per-type SVGs, the edge legend and the UH01–UH05 topology graphs for `TRO_Katalog.md` from `doc/TRO_Katalog/connect.ini`. No switches, no `bin/` wrapper. Needs Graphviz `dot`. |
The libraries `lib/tro_catalog.py`, `lib/dxf_registration.py` and
`lib/tro_overrides.py` have no command line of their own.
The libraries `lib/tro_catalog.py`, `lib/model.py`, `lib/dxf_registration.py`
and `lib/tro_overrides.py` have no command line of their own.
+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)