diff --git a/doc/Klassenhierarchie.md b/doc/Klassenhierarchie.md new file mode 100644 index 0000000..d0cf7ac --- /dev/null +++ b/doc/Klassenhierarchie.md @@ -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) (= _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).