Klassenhierarchie dazu gebaut

This commit is contained in:
Michael Stangl
2026-10-10 09:24:49 +02:00
parent 97a0db6707
commit ba58c98851
+146
View File
@@ -0,0 +1,146 @@
# 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).