tro_flow: --use-overrides, --from-connect und --geometry entfernt
TROs, Typen und Verbindungen kommen nur noch aus dem Layout. Fehler der Ableitung werden im Code behoben, nicht in einer Korrekturdatei. - tro_flow: Optionen --use-overrides/--no-overrides, --from-connect, --geometry, --geo-tol-mm entfernt; analyse_tros/derive_tros ohne overrides/resolve_host/count_exits-Parameter, Analysis.overrides entfernt, Override-Schritte 4b/4c/5b, Doku-Abschnitt "6. Manuelle Korrekturen" und die Override-Zeilen im Bericht entfernt; connect.ini ist reine Ausgabe - lib/tro_geometry.py geloescht (nur von --geometry genutzt), shapely aus requirements.txt - tro_overrides: apply_splits/_merges/_connections/_overrides und load_connect_ini entfernt - nur noch load_overrides fuer material_flow.py --connect - doc/Python_Scripts.md nachgezogen Gegenprobe: DOT (beide Layouts) und connect.ini fuer HundM05 und Mubea unveraendert (28 bzw. 26 Verbindungen), Doku nur ohne den leeren Abschnitt 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a5fab7e2dc
commit
e0defad011
+8
-278
@@ -10,10 +10,13 @@ eigener TRO erkannt wurde. Solche Erkenntnisse kommen aus der Begehung bzw.
|
||||
aus dem Review der erzeugten Diagramme.
|
||||
|
||||
Damit sie den naechsten Lauf ueberleben, stehen sie in einer INI-Datei
|
||||
(`%SKEL_CFG%/tro_overrides.ini`) statt in den erzeugten Dateien. `tro_flow.py`
|
||||
und `tro_annotate.py` lesen sie und wenden sie nach der Ableitung an; die Doku
|
||||
weist jede angewandte Korrektur getrennt aus, damit abgeleitet und behauptet
|
||||
unterscheidbar bleiben.
|
||||
(`%SKEL_CFG%/tro_overrides.ini`) statt in den erzeugten Dateien.
|
||||
|
||||
Seit 2026-10-05 nur noch von `material_flow.py --connect` gelesen (Abschnitt
|
||||
`[<Anlage>.manual]` der connect.ini). `tro_flow.py` und `tro_annotate.py`
|
||||
wenden keine Korrekturen mehr an: die Abschnitte hingen an laufenden
|
||||
TRO-Nummern und veralteten mit jedem Export - Fehler der Ableitung werden
|
||||
seitdem im Code behoben (lib/tro_flow.py), nicht in der INI.
|
||||
|
||||
Aufbau der INI (Sektionsnamen mit dem CSV-Dateinamen als Praefix, damit
|
||||
mehrere Anlagen in einer Datei stehen koennen):
|
||||
@@ -31,9 +34,7 @@ mehrere Anlagen in einer Datei stehen koennen):
|
||||
belongs_to = TRO10 TRO, der das Bauteil heute enthaelt
|
||||
note = ... Erlaeuterung
|
||||
|
||||
Offene Punkte aendern die TRO-Struktur ausdruecklich **nicht**. Sie werden nur
|
||||
in der Doku und in der annotierten Zeichnung markiert, damit sie in BricsCAD
|
||||
geprueft werden koennen, bevor daraus ein TRO wird.
|
||||
Offene Punkte aendern die TRO-Struktur ausdruecklich **nicht**.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -320,274 +321,3 @@ def load_overrides(csv_name: str, cfg_dir: Path | None = None) -> Overrides:
|
||||
|
||||
result.open_points.sort(key=lambda p: p.name)
|
||||
return result
|
||||
|
||||
|
||||
def apply_splits(tros, overrides: Overrides, make_tro) -> list:
|
||||
"""Neue TROs aus bestehenden herausloesen (vor der Nummernvergabe).
|
||||
|
||||
`make_tro(spec, hosts, label)` erzeugt das projektspezifische Tro-Objekt -
|
||||
so bleibt dieses Modul frei von einem Import aus tro_flow (das umgekehrt
|
||||
schon von hier importiert).
|
||||
"""
|
||||
for spec in overrides.splits:
|
||||
quellen = [t for t in tros if set(spec.separators) & set(t.separators)]
|
||||
if not quellen:
|
||||
overrides.warnings.append(
|
||||
f"Split '{spec.name}': keiner der Separatoren "
|
||||
f"{', '.join(spec.separators)} kommt in diesem Lauf vor - "
|
||||
f"nicht angelegt"
|
||||
)
|
||||
continue
|
||||
|
||||
hosts: list[str] = []
|
||||
for quelle in quellen:
|
||||
quelle.separators = [s for s in quelle.separators
|
||||
if s not in spec.separators]
|
||||
quelle.scanners = [s for s in quelle.scanners if s not in spec.scanners]
|
||||
hosts.extend(quelle.hosts)
|
||||
if not quelle.separators and quelle.type_name not in ("Vario",):
|
||||
overrides.warnings.append(
|
||||
f"Split '{spec.name}': {quelle.label} hat danach keinen "
|
||||
f"Separator mehr - bitte pruefen"
|
||||
)
|
||||
|
||||
ziel_hosts = [spec.host] if spec.host else sorted(set(hosts))[:1]
|
||||
label = spec.label or (
|
||||
f"Separator {', '.join(spec.separators)} "
|
||||
f"(manuell aus {quellen[0].label} herausgeloest)"
|
||||
)
|
||||
tros.append(make_tro(spec, ziel_hosts, label))
|
||||
overrides.applied.append(
|
||||
f"neu {spec.name}: {spec.type_name} auf {', '.join(ziel_hosts)} "
|
||||
f"mit Separator {', '.join(spec.separators)}, herausgeloest aus "
|
||||
f"{', '.join(sorted({q.label for q in quellen}))} "
|
||||
f"({spec.reason or 'manuelle Ergaenzung'})"
|
||||
)
|
||||
return tros
|
||||
|
||||
|
||||
def apply_merges(tros, overrides: Overrides) -> list:
|
||||
"""Mehrere TROs zu einem zusammenfassen (nach den Splits, vor der Nummer).
|
||||
|
||||
Der erste betroffene TRO bleibt bestehen und uebernimmt Bauteile, Hosts und
|
||||
Typ; die uebrigen werden aus der Liste entfernt.
|
||||
"""
|
||||
for spec in overrides.merges:
|
||||
betroffen = [t for t in tros if set(spec.separators) & set(t.separators)]
|
||||
if len(betroffen) < 2:
|
||||
gefunden = ", ".join(t.tro_id or t.label for t in betroffen) or "keiner"
|
||||
overrides.warnings.append(
|
||||
f"Merge '{spec.name}': nur {len(betroffen)} der Separatoren "
|
||||
f"{', '.join(spec.separators)} gefunden ({gefunden}) - "
|
||||
f"nicht zusammengefasst"
|
||||
)
|
||||
continue
|
||||
|
||||
ziel = betroffen[0]
|
||||
quellen = betroffen[1:]
|
||||
for quelle in quellen:
|
||||
ziel.separators = sorted(set(ziel.separators) | set(quelle.separators))
|
||||
ziel.scanners = sorted(set(ziel.scanners) | set(quelle.scanners))
|
||||
ziel.hosts = sorted(set(ziel.hosts) | set(quelle.hosts))
|
||||
tros.remove(quelle)
|
||||
|
||||
if spec.host:
|
||||
# Ein Host bestimmt, wo der zusammengefasste TRO im Fluss haengt;
|
||||
# die uebrigen Bahnen bleiben als weitere Hosts erhalten, damit die
|
||||
# Verknuepfung beide Seiten des Uebergangs sieht.
|
||||
ziel.hosts = [spec.host] + [h for h in ziel.hosts if h != spec.host]
|
||||
ziel.type_name = spec.type_name
|
||||
ziel.confidence = spec.confidence
|
||||
ziel.label = spec.label or ziel.label
|
||||
grund = spec.reason or "manuell zusammengefasst"
|
||||
ziel.reason = f"{ziel.reason}; manuell zusammengefasst ({grund})"
|
||||
overrides.applied.append(
|
||||
f"merge {spec.name}: {len(betroffen)} TROs -> ein {spec.type_name} "
|
||||
f"mit Separatoren {', '.join(ziel.separators)} ({grund})"
|
||||
)
|
||||
return tros
|
||||
|
||||
|
||||
def apply_connections(tros, overrides: Overrides):
|
||||
"""Gesetzte Topologie an die Stelle der abgeleiteten Kanten setzen.
|
||||
|
||||
Ersetzt vollstaendig, nicht ergaenzend: wenn die Verbindungen vor Ort
|
||||
aufgenommen wurden, sind die abgeleiteten damit erledigt - sonst blieben
|
||||
genau die Kanten stehen, die korrigiert werden sollten. Was dabei wegfaellt
|
||||
bzw. hinzukommt, steht anschliessend in overrides.applied.
|
||||
|
||||
Rueckgabe: (edges, edge_kinds) wie von link_tros.
|
||||
"""
|
||||
bekannt = {t.tro_id for t in tros}
|
||||
edges: list[tuple[str, str]] = []
|
||||
kinds: dict[tuple[str, str], str] = {}
|
||||
for spec in overrides.connections:
|
||||
for seite, wert in (("von", spec.src), ("nach", spec.dst)):
|
||||
if wert not in bekannt:
|
||||
overrides.warnings.append(
|
||||
f"Verbindung {spec.src} -> {spec.dst}: {seite} '{wert}' ist "
|
||||
f"kein TRO dieses Laufs - Verbindung ignoriert"
|
||||
)
|
||||
break
|
||||
else:
|
||||
paar = (spec.src, spec.dst)
|
||||
if paar in kinds:
|
||||
continue
|
||||
edges.append(paar)
|
||||
kinds[paar] = spec.kind or "gesetzt"
|
||||
|
||||
for tro in tros:
|
||||
tro.successors = sorted(d for s, d in edges if s == tro.tro_id)
|
||||
tro.predecessors = sorted(s for s, d in edges if d == tro.tro_id)
|
||||
tro.colocated = []
|
||||
|
||||
ohne = [t.tro_id for t in tros if not t.successors and not t.predecessors]
|
||||
if ohne:
|
||||
overrides.warnings.append(
|
||||
"ohne Verbindung in der gesetzten Topologie: " + ", ".join(sorted(ohne))
|
||||
)
|
||||
overrides.applied.append(
|
||||
f"Topologie gesetzt: {len(edges)} Verbindungen aus der Override-Datei "
|
||||
f"ersetzen die abgeleiteten"
|
||||
)
|
||||
return sorted(edges), kinds
|
||||
|
||||
|
||||
def apply_overrides(tros, overrides: Overrides) -> None:
|
||||
"""Typkorrekturen auf die abgeleitete TRO-Liste anwenden.
|
||||
|
||||
Wird aufgerufen, nachdem die TRO-IDs vergeben sind (die Korrekturen sind
|
||||
ueber die ID adressiert) und bevor FB-Baustein und Bauteilliste aus dem
|
||||
Katalog gezogen werden - der Typwechsel zieht beides mit.
|
||||
"""
|
||||
if not overrides.tros:
|
||||
return
|
||||
for section_key, override in sorted(overrides.tros.items()):
|
||||
treffer = [t for t in tros if override.matches(t)]
|
||||
if not treffer:
|
||||
wie = "Separator" if override.by_separator else "TRO-ID"
|
||||
overrides.warnings.append(
|
||||
f"[{section_key}]: kein TRO mit {wie} '{override.key}' in diesem "
|
||||
f"Lauf - Korrektur nicht angewandt"
|
||||
+ ("" if override.by_separator else
|
||||
" (Nummerierung geaendert? Besser ueber SEP<Nummer> adressieren)")
|
||||
)
|
||||
continue
|
||||
if len(treffer) > 1:
|
||||
overrides.warnings.append(
|
||||
f"[{section_key}]: trifft {len(treffer)} TROs "
|
||||
f"({', '.join(t.tro_id for t in treffer)}) - auf alle angewandt"
|
||||
)
|
||||
for tro in treffer:
|
||||
if not override.type_name or override.type_name == tro.type_name:
|
||||
continue
|
||||
vorher = tro.type_name
|
||||
tro.type_name = override.type_name
|
||||
tro.confidence = override.confidence
|
||||
grund = override.reason or "manuelle Korrektur"
|
||||
tro.reason = (f"{tro.reason}; manuell auf {override.type_name} "
|
||||
f"gesetzt ({grund})")
|
||||
overrides.applied.append(
|
||||
f"{tro.tro_id}: {vorher} -> {override.type_name} ({grund})"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# connect.ini (TRO-Ebene) zurueck einlesen - tro_flow.py --from-connect
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Vertrauensangabe fuer Typen, die aus einer (evtl. von Hand geaenderten)
|
||||
# connect.ini stammen - in der Doku von Layout und Begehung unterscheidbar.
|
||||
CONNECT_CONFIDENCE = "connect.ini"
|
||||
|
||||
|
||||
def _connect_pair(raw: str) -> tuple[str, str, str] | None:
|
||||
"""'<von> -> <nach> | <JamArea> | <kind>' -> (von, nach, kind); None wenn
|
||||
kein Pfeil. Die JamArea wird (noch) nicht ausgewertet."""
|
||||
pfeil, _, rest = str(raw).partition("|")
|
||||
src, sep, dst = (t.strip() for t in pfeil.partition("->"))
|
||||
if not sep or not src or not dst:
|
||||
return None
|
||||
felder = [f.strip() for f in rest.split("|")]
|
||||
kind = felder[1] if len(felder) > 1 else ""
|
||||
return src, dst, kind
|
||||
|
||||
|
||||
def load_connect_ini(path: Path, base: Overrides | None = None) -> Overrides:
|
||||
"""
|
||||
Eine von tro_flow.py --connect geschriebene connect.ini als Korrekturen lesen.
|
||||
|
||||
Die Datei wird dabei nicht als eigenes Modell behandelt, sondern auf die
|
||||
vorhandenen Korrekturarten abgebildet - so laeuft sie durch dieselbe
|
||||
Pipeline wie cfg/tro_overrides.ini:
|
||||
|
||||
[<Anlage>.nodes] TRO-ID = <Typ> | ... -> Typkorrektur je TRO-ID
|
||||
[<Anlage>.connections] cNNN = von -> nach | JamArea | kind
|
||||
[<Anlage>.externals] eNNN = SYSn -> TRO | ... bzw. TRO -> SYSn | ...
|
||||
-> zusammen die gesetzte Topologie, die die
|
||||
abgeleiteten Kanten vollstaendig ersetzt
|
||||
|
||||
[<Anlage>.tros] (Vorgaenger/Nachfolger je TRO) ist nur eine Uebersicht und
|
||||
wird ignoriert - massgeblich sind die Verbindungen.
|
||||
|
||||
`base` (die Korrekturen aus cfg/tro_overrides.ini) bleibt erhalten; Typen
|
||||
aus der connect.ini gewinnen, die Topologie der connect.ini ersetzt eine
|
||||
dort gesetzte.
|
||||
"""
|
||||
result = base if base is not None else Overrides()
|
||||
parser = configparser.ConfigParser(inline_comment_prefixes=(";",),
|
||||
interpolation=None)
|
||||
parser.optionxform = str # TRO-IDs nicht in Kleinschreibung wandeln
|
||||
# utf-8-sig: von Hand gespeicherte Dateien (Windows-Editoren, PowerShell)
|
||||
# tragen oft ein BOM, an dem configparser sonst in Zeile 1 scheitert.
|
||||
try:
|
||||
parser.read(path, encoding="utf-8-sig")
|
||||
except configparser.Error as exc:
|
||||
raise ValueError(f"{path.name} nicht lesbar: {exc}") from exc
|
||||
|
||||
connections: list[ConnectionSpec] = []
|
||||
for section in parser.sections():
|
||||
art = section.rsplit(".", 1)[-1].lower()
|
||||
values = parser[section]
|
||||
if art == "nodes":
|
||||
for tro_id, raw in values.items():
|
||||
type_name = str(raw).partition("|")[0].strip()
|
||||
# Systemgrenzen (SYSn) sind kein TRO, ihr "Typ" ist nur Anzeige
|
||||
if tro_id.upper().startswith("SYS") or not type_name:
|
||||
continue
|
||||
if get_tro(type_name) is None:
|
||||
result.warnings.append(
|
||||
f"{path.name} [{section}] {tro_id}: Typ '{type_name}' steht "
|
||||
f"nicht im TRO-Katalog - Typ bleibt wie abgeleitet"
|
||||
)
|
||||
continue
|
||||
result.tros[tro_id.upper()] = TroOverride(
|
||||
key=tro_id.upper(), type_name=type_name,
|
||||
reason=f"aus {path.name}", confidence=CONNECT_CONFIDENCE,
|
||||
)
|
||||
elif art in ("connections", "externals"):
|
||||
for key, raw in values.items():
|
||||
pair = _connect_pair(raw)
|
||||
if pair is None:
|
||||
result.warnings.append(
|
||||
f"{path.name} [{section}] {key}: '{raw}' ist keine "
|
||||
f"Verbindung '<von> -> <nach>'"
|
||||
)
|
||||
continue
|
||||
src, dst, kind = pair
|
||||
if "EXTERN" in (src.upper(), dst.upper()):
|
||||
result.warnings.append(
|
||||
f"{path.name} [{section}] {key}: EXTERN wird nicht "
|
||||
f"ausgewertet - Systemgrenze als SYSn angeben"
|
||||
)
|
||||
continue
|
||||
connections.append(ConnectionSpec(src=src, dst=dst, kind=kind))
|
||||
|
||||
if connections:
|
||||
result.connections = connections
|
||||
else:
|
||||
result.warnings.append(
|
||||
f"{path.name}: keine Verbindungen gefunden - Topologie bleibt wie abgeleitet"
|
||||
)
|
||||
result.applied.append(f"connect.ini eingelesen: {path}")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user