# Klassenhierarchie Bestandsaufnahme aller Python-Klassen in `lib/` (Stand 2026-10-09). Kurzes Ergebnis vorweg: es gibt praktisch **keine Vererbung** in diesem Code (eine Ausnahme: `DerivedTro(TroBase)`, plus die Standard-Exception `RegistrationError(RuntimeError)`). Alles andere sind flache, eigenständige Klassen — entweder `@dataclass` (veränderlicher Laufzeit-/Analysezustand) oder Pydantic `BaseModel` (validierte Daten, die auch als JSON ein-/ausgehen). Eine klassische "ist-ein"-Hierarchie zu suchen wäre hier der falsche Blickwinkel; interessanter ist, **wer wen enthält/referenziert** und in welchem Modul-Layer eine Klasse sitzt (siehe Layer-Kommentar oben in `model.py`). ## Modul-Layer (wer darf wen importieren) ``` tro_catalog.py (Typ-Ebene: was ein TRO-Typ ist) | v model.py (Instanz-Ebene: ein konkreter TRO, die TRO-JSON) | v tro_flow.py (Ableitung: CSV-Graph -> Analysis -> TroDocument) | +----------------+ v v scl_skeleton.py tro_annotate.py (Ausgabe: SCL-Skeleton / annotierte Zeichnung) material_flow.py dxf_registration.py tro_overrides.py (eigenständig, ohne Abhängigkeit auf obige) ``` `tro_flow.py`, `scl_skeleton.py`, `tro_annotate.py`, `material_flow.py` selbst definieren keine eigenen Klassen mehr (nur Funktionen) — `material_flow.py` ist die Ausnahme: seine Graph-Dataclasses sind CSV-Rohmodell, nicht TRO-Modell. ## Vererbung (ASCII) ``` RuntimeError (stdlib) BaseModel (pydantic, stdlib: pydantic) | | +-- RegistrationError +-- TroBase (dxf_registration.py) | +-- DerivedTro (model.py) ``` Das ist die gesamte `class X(Y)`-Liste im Projekt — alle anderen Klassen unten haben `object`/`BaseModel` direkt als einzige Basis und erscheinen hier nur noch als Gruppen nach Datei. ## Klassen nach Datei, mit Komposition ("enthält") ``` lib/tro_catalog.py [Typ-Katalog, keine Instanzdaten] ├── TroItem (Konstanten-Klasse: Bauteilarten wie SEPARATOR, SWITCH) ├── TroSymbol (Konstanten-Klasse: Zeichensymbole wie CIRCLE, TRIANGLE) ├── TroStyle(BaseModel) Farbe/Darstellung einer Typgruppe └── TroDefinition(BaseModel) └─ style: TroStyle (1 TroDefinition hat genau 1 TroStyle) lib/model.py [Instanz-Ebene / TRO-JSON] ├── TroBase(BaseModel) │ └─ definition() --> TroDefinition (gelesen aus tro_catalog, nicht gespeichert) │ └── DerivedTro(TroBase) [einzige echte Vererbung im Projekt] ├── Connection(BaseModel) (from/to/kind zwischen zwei TRO-IDs) ├── OpenPoint(BaseModel) (gemeldete, noch nicht modellierte Stelle) ├── Analysis(BaseModel) │ ├─ tros: list[DerivedTro] │ ├─ isolated: list[DerivedTro] │ └─ boundaries: list[DerivedTro] └── TroDocument(BaseModel) (= _tro.json) ├─ tros: list[DerivedTro] ├─ boundaries: list[DerivedTro] ├─ connections: list[Connection] └─ open_points: list[OpenPoint] lib/material_flow.py [CSV-Rohmodell -> Graph, @dataclass] ├── Element (1 CSV-Zeile) ├── Node │ └─ element: Element ├── Edge ├── Graph │ ├─ nodes: dict[str, Node] │ ├─ edges: list[Edge] │ └─ unassigned: list[Element] ├── TrackNode │ └─ element: Element | None ├── TrackEdge └── TrackGraph ├─ nodes: dict[str, TrackNode] └─ edges: list[TrackEdge] lib/tro_overrides.py [manuelle Korrekturen aus cfg/tro_overrides.ini, @dataclass] ├── TroOverride (Typkorrektur an einem abgeleiteten TRO) ├── SplitSpec (TRO aus einem bestehenden heraustrennen) ├── ConnectionSpec (bestätigte Verbindung zwischen zwei TROs) ├── MergeSpec (mehrere TROs zu einem zusammenfassen) ├── OpenPoint (eigene Kopie, NICHT dieselbe Klasse wie model.OpenPoint!) └── Overrides (Sammelcontainer für eine CSV-Datei) ├─ tros: dict[str, TroOverride] ├─ splits: list[SplitSpec] ├─ merges: list[MergeSpec] ├─ connections: list[ConnectionSpec] └─ open_points: list[OpenPoint] (tro_overrides.OpenPoint, s.o.) lib/dxf_registration.py [CSV<->DXF Koordinatentransform] ├── RegistrationError(RuntimeError) └── Transform(BaseModel) ``` ## Auffälligkeiten für ein mögliches Refactoring 1. **`OpenPoint` existiert zweimal** mit fast identischen Feldern (`name, x, y, z, expect, belongs_to/belongsTo, note` + `describe_position()`): einmal in `model.py` (Pydantic, JSON-Alias `belongsTo`), einmal in `tro_overrides.py` (Dataclass, kein Alias). Das ist duplizierte Definition, kein Vererbungsfall — `tro_overrides.py` baut seine `OpenPoint`-Liste und wandelt sie vermutlich später in `model.OpenPoint` um (oder sollte das tun; laut Roadmap im Projekt-`CLAUDE.md` ist "`tro_overrides.py` auf die gemeinsamen `model.Connection`/`model.OpenPoint`-Klassen umstellen" ohnehin schon als offener Punkt vermerkt). Kandidat: `tro_overrides.py` importiert künftig `model.OpenPoint` statt eine eigene Kopie zu pflegen. 2. **Zwei parallele Graph-Hierarchien** in `material_flow.py` (`Node/Edge/Graph` vs. `TrackNode/TrackEdge/TrackGraph`) teilen sich keine Basisklasse, obwohl Struktur und Feldnamen sich stark ähneln (`node_id, kind, ...` / `src, dst, ...`). Eine gemeinsame leichte Basis (z. B. ein generisches `GraphBase[NodeT, EdgeT]`) wäre möglich, aber angesichts der wenigen Stellen, die beide Typen anfassen, vermutlich mehr Abstraktion als Nutzen. 3. **`TroItem` und `TroSymbol` sind reine Konstanten-Namespaces** (keine Instanzen, keine Methoden außer `TroItem.all()`), kein Pydantic-Modell wie der Rest von `tro_catalog.py`. Das ist bewusst so (siehe Docstring: "damit Tippfehler auffallen") und funktioniert wie ein `Enum`-Ersatz — ein `StrEnum` wäre eine nahe liegende, idiomatischere Alternative, falls ein Refactoring ansteht. 4. Die einzige echte Vererbung (`DerivedTro(TroBase)`) ist sauber: `TroBase` ist bereits so geschnitten, dass es für Systemgrenzen (`boundaries`) wiederverwendet wird, `DerivedTro` ergänzt nur das, was die CSV-Ableitung zusätzlich weiß. Kein Handlungsbedarf. **Fazit:** Die Code-Basis nutzt Vererbung bewusst kaum — die Architektur trennt stattdessen über Module (Typ-Katalog vs. Instanz vs. Ableitung vs. Ausgabe, siehe `model.py`-Kopfkommentar) und über Komposition. Ein Refactoring in Richtung "mehr Vererbung" wäre hier eher kontraproduktiv; die einzig konkret lohnenden Punkte sind die doppelte `OpenPoint`-Definition (1) und optional `StrEnum` statt Konstanten-Klassen (3).