Files
sps_skel/doc/Klassenhierarchie.md
T

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

  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).