7.1 KiB
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) (= <csv>_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
OpenPointexistiert zweimal mit fast identischen Feldern (name, x, y, z, expect, belongs_to/belongsTo, note+describe_position()): einmal inmodel.py(Pydantic, JSON-AliasbelongsTo), einmal intro_overrides.py(Dataclass, kein Alias). Das ist duplizierte Definition, kein Vererbungsfall —tro_overrides.pybaut seineOpenPoint-Liste und wandelt sie vermutlich später inmodel.OpenPointum (oder sollte das tun; laut Roadmap im Projekt-CLAUDE.mdist "tro_overrides.pyauf die gemeinsamenmodel.Connection/model.OpenPoint-Klassen umstellen" ohnehin schon als offener Punkt vermerkt). Kandidat:tro_overrides.pyimportiert künftigmodel.OpenPointstatt eine eigene Kopie zu pflegen.- Zwei parallele Graph-Hierarchien in
material_flow.py(Node/Edge/Graphvs.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 generischesGraphBase[NodeT, EdgeT]) wäre möglich, aber angesichts der wenigen Stellen, die beide Typen anfassen, vermutlich mehr Abstraktion als Nutzen. TroItemundTroSymbolsind reine Konstanten-Namespaces (keine Instanzen, keine Methoden außerTroItem.all()), kein Pydantic-Modell wie der Rest vontro_catalog.py. Das ist bewusst so (siehe Docstring: "damit Tippfehler auffallen") und funktioniert wie einEnum-Ersatz — einStrEnumwäre eine nahe liegende, idiomatischere Alternative, falls ein Refactoring ansteht.- Die einzige echte Vererbung (
DerivedTro(TroBase)) ist sauber:TroBaseist bereits so geschnitten, dass es für Systemgrenzen (boundaries) wiederverwendet wird,DerivedTroergä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).