Klassenhierarchie dazu gebaut
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user