From 30b47a0fd191ff9cff36ddbf52e363b9ffca005e Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 25 Sep 2026 11:09:43 +0200 Subject: [PATCH] Kanten zwischen Gateways umgehen die Ziel-Raute (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - orthogonal_points(): Liegt bei Gateway -> Gateway der Stub des Starts schon jenseits des Ziel-Anschlusspunkts (z. B. Südpunkt unten, Ziel höher mit Nordpunkt), führt die Kante seitlich an der Ziel-Raute vorbei. Gleiches für den waagrechten Spiegelfall (Ost -> West). - _ausweichachse(): Die Ausweichachse liegt im freien Gang direkt vor der Ziel-Spalte (AUSWEICHABSTAND 85), nicht in der Mitte zwischen Quelle und Ziel – dort stand im Referenzmodell die Aufgabe S43. - Referenzmodell neu erzeugt: fünf Kanten korrigiert (vorher liefen alle fünf durch ein Gateway, gemeldet war nur S12->S13), dabei verschwindet auch die Kreuzung Flow_13/S40. Syntaxbeispiele unverändert. - xfail für referenz_gewannenfestlegung entfernt; drei neue Tests in test_md_to_bpmn.py. - Fallstricke: Ausweichachse; Strukturtests melden nur die erste Kreuzung. - Tests: python -m pytest -q -> 92 bestanden. Co-Authored-By: Claude Opus 5.5 --- .../referenz_gewannenfestlegung.bpmn | 15 ++++--- .agents/skills/sn-bpmn/md_to_bpmn.py | 29 +++++++++++- .agents/skills/sn-bpmn/test_md_to_bpmn.py | 45 +++++++++++++++++++ docs/status/STATUS.md | 4 ++ docs/wissen/fallstricke.md | 13 ++++-- tests/test_referenzmodelle.py | 4 +- 6 files changed, 97 insertions(+), 13 deletions(-) diff --git a/.agents/skills/sn-bpmn/beispiele/referenz_gewannenfestlegung.bpmn b/.agents/skills/sn-bpmn/beispiele/referenz_gewannenfestlegung.bpmn index d42aa0f..4fc972d 100644 --- a/.agents/skills/sn-bpmn/beispiele/referenz_gewannenfestlegung.bpmn +++ b/.agents/skills/sn-bpmn/beispiele/referenz_gewannenfestlegung.bpmn @@ -486,7 +486,8 @@ - + + @@ -541,7 +542,8 @@ - + + @@ -597,7 +599,8 @@ - + + @@ -641,7 +644,8 @@ - + + @@ -658,7 +662,8 @@ - + + diff --git a/.agents/skills/sn-bpmn/md_to_bpmn.py b/.agents/skills/sn-bpmn/md_to_bpmn.py index c333190..d70bf92 100644 --- a/.agents/skills/sn-bpmn/md_to_bpmn.py +++ b/.agents/skills/sn-bpmn/md_to_bpmn.py @@ -580,6 +580,21 @@ def _orthogonal_middle(ox, oy, src_side, tx, ty, tgt_side): return [(ox, oy), (tx, oy), (tx, ty)] +# Abstand der Ausweichachse von der Mitte der Ziel-Spalte bzw. -Zeile: halbe +# Breite der größten Elemente (Aufgabe 100x80) plus halber kleinster Gang +# zwischen zwei Spalten/Zeilen (170 - 100 = 70). Der Gang direkt vor dem Ziel +# ist damit in beiden Layouts frei; die Mitte zwischen Quelle und Ziel nicht, +# dort kann eine Spalte mit einer Aufgabe liegen. +AUSWEICHABSTAND = 85 + + +def _ausweichachse(quelle, ziel): + """Koordinate im freien Gang vor der Ziel-Spalte (bzw. -Zeile), auf der Seite + der Quelle: dort läuft eine Kante an der Ziel-Raute vorbei, ohne ein anderes + Element zu schneiden.""" + return ziel + AUSWEICHABSTAND if quelle > ziel else ziel - AUSWEICHABSTAND + + def orthogonal_points(ox, oy, src_side, tx, ty, tgt_side, src_is_gateway=False, tgt_is_gateway=False): """Baut einen rechtwinkligen Kantenverlauf zwischen zwei Anschlusspunkten, der @@ -627,7 +642,19 @@ def orthogonal_points(ox, oy, src_side, tx, ty, tgt_side, else: # Beide Enden sind Rauten: von der Stub-Achse des Starts direkt auf # die Stub-Achse des Ziels schwenken, jede nur einmal verlassen. - if src_axis == 'v': + # Liegt der Stub des Starts schon jenseits des Ziel-Anschlusspunkts + # (z. B. Ankunft am Nordpunkt eines höher liegenden Ziels), liefe das + # letzte gerade Stück durch die Ziel-Raute - dann seitlich daran + # vorbei über eine freie Ausweichachse (Issue #8). + if src_axis == 'v' and tgt_axis == 'v' and ( + (tgt_side == 'N' and sy > ty) or (tgt_side == 'S' and sy < ty)): + mx = _ausweichachse(sx, ex) + points = [(ox, oy), (sx, sy), (mx, sy), (mx, ey), (ex, ey), (tx, ty)] + elif src_axis == 'h' and tgt_axis == 'h' and ( + (tgt_side == 'W' and sx > tx) or (tgt_side == 'E' and sx < tx)): + my = _ausweichachse(sy, ey) + points = [(ox, oy), (sx, sy), (sx, my), (ex, my), (ex, ey), (tx, ty)] + elif src_axis == 'v': points = [(ox, oy), (sx, sy), (ex, sy), (ex, ey), (tx, ty)] else: points = [(ox, oy), (sx, sy), (sx, ey), (ex, ey), (tx, ty)] diff --git a/.agents/skills/sn-bpmn/test_md_to_bpmn.py b/.agents/skills/sn-bpmn/test_md_to_bpmn.py index 1a84897..5b45015 100644 --- a/.agents/skills/sn-bpmn/test_md_to_bpmn.py +++ b/.agents/skills/sn-bpmn/test_md_to_bpmn.py @@ -353,6 +353,51 @@ def test_kante_fuehrt_nicht_durchs_innere_eines_gateways(): assert _kante_kreuzt_gateway(defs, m.NS) is None +def _streckenzug_kreuzt_raute(punkte, raute): + """True, wenn ein Stück des Streckenzugs durchs Innere der Raute (x, y, w, h) läuft.""" + for (x1, y1), (x2, y2) in zip(punkte, punkte[1:]): + for s in range(41): + t = s / 40 + if _liegt_in_rautenflaeche(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t, *raute): + return True + return False + + +def test_kante_zwischen_gateways_zum_nordpunkt_eines_hoeheren_ziels_umgeht_die_raute(): + """Prüft, dass eine Kante vom Südpunkt eines Gateways zum Nordpunkt eines höher liegenden Gateways + seitlich an der Ziel-Raute vorbei nach oben führt statt senkrecht durch sie hindurch (Issue #8).""" + quelle, ziel = (2200, 145, 50, 50), (2390, 60, 50, 50) # Lage wie S12 -> S13 im Referenzmodell + punkte = m.orthogonal_points(2225, 195, 'S', 2415, 60, 'N', src_is_gateway=True, tgt_is_gateway=True) + assert punkte[0] == (2225, 195) and punkte[-1] == (2415, 60) + assert not _streckenzug_kreuzt_raute(punkte, ziel) + assert not _streckenzug_kreuzt_raute(punkte, quelle) + assert all(a[0] == b[0] or a[1] == b[1] for a, b in zip(punkte, punkte[1:])), "nicht rechtwinklig" + + +def test_ausweichweg_zwischen_gateways_meidet_die_spalte_dazwischen(): + """Prüft, dass der seitliche Umweg um die Ziel-Raute im freien Gang direkt vor der Ziel-Spalte liegt und + nicht mitten zwischen Quelle und Ziel, wo in einer Spalte dazwischen eine Aufgabe stehen kann.""" + aufgabe, ziel = (2960, 215, 100, 80), (3150, 230, 50, 50) # Lage wie S43 und S44 im Referenzmodell + punkte = m.orthogonal_points(2795, 280, 'S', 3175, 230, 'N', src_is_gateway=True, tgt_is_gateway=True) + assert not _streckenzug_kreuzt_raute(punkte, ziel) + x, y, w, h = aufgabe + for (x1, y1), (x2, y2) in zip(punkte, punkte[1:]): + for s in range(41): + px, py = x1 + (x2 - x1) * s / 40, y1 + (y2 - y1) * s / 40 + assert not (x < px < x + w and y < py < y + h), f"Kante läuft bei ({px:g}, {py:g}) durch die Aufgabe" + + +def test_kante_zwischen_gateways_zum_westpunkt_eines_linken_ziels_umgeht_die_raute(): + """Prüft den waagrechten Spiegelfall: vom Ostpunkt eines Gateways zum Westpunkt eines weiter links + liegenden Gateways führt die Kante ober- oder unterhalb an der Ziel-Raute vorbei.""" + quelle, ziel = (400, 300, 50, 50), (200, 100, 50, 50) + punkte = m.orthogonal_points(450, 325, 'E', 200, 125, 'W', src_is_gateway=True, tgt_is_gateway=True) + assert punkte[0] == (450, 325) and punkte[-1] == (200, 125) + assert not _streckenzug_kreuzt_raute(punkte, ziel) + assert not _streckenzug_kreuzt_raute(punkte, quelle) + assert all(a[0] == b[0] or a[1] == b[1] for a, b in zip(punkte, punkte[1:])), "nicht rechtwinklig" + + def test_ausgabe_ist_wohlgeformtes_xml(tmp_path): """Prüft, dass die erzeugte .bpmn-Datei sich als valides XML parsen laesst.""" src = tmp_path / 'prozess.md' diff --git a/docs/status/STATUS.md b/docs/status/STATUS.md index e383ab9..bfbed13 100644 --- a/docs/status/STATUS.md +++ b/docs/status/STATUS.md @@ -31,6 +31,10 @@ Skills sind übernommen, alles QGIS- und Plugin-Spezifische ist entfernt. Kern i ## Erledigt +- 25.09.2026 **Keine Kanten mehr durch Gateways im Referenzmodell** (#8): Gateway → Gateway mit Ziel hinter + dem Start-Stub weicht im Gang vor der Ziel-Spalte aus; fünf Kanten der Gewannenfestlegung korrigiert, `xfail` + entfernt. Offen: Rücksprungkanten und Nachrichtenflüsse durch Aufgaben, Beschriftungsüberlappungen. + - 25.09.2026 **Prozessbibliothek als eigenes Repository** (#9, ADR 0004): `prozesse/` ist aus Projektwissen entfernt, die Modelle liegen im Klon `Prozessbibliothek/` (`workspace.toml`). `pruefe_prozesse.py` und `einlesen.py` suchen dort; Regeln in `docs/leitfaden/08_prozessbibliothek.md`. diff --git a/docs/wissen/fallstricke.md b/docs/wissen/fallstricke.md index 4adea17..c274181 100644 --- a/docs/wissen/fallstricke.md +++ b/docs/wissen/fallstricke.md @@ -5,10 +5,15 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik ## BPMN und PICTURE (Skill sn-bpmn, md_to_bpmn.py) -- **Kanten durch Gateways sind noch nicht ganz behoben.** Im Referenzmodell Gewannenfestlegung läuft die Kante - S12→S13 (Gateway → Gateway, Ziel oberhalb der Quelle, Ankunft am Nordpunkt) senkrecht durch die Raute von S13. - Der Fix vom 21.09.2026 deckt diese Lage nicht ab. Erfasst als Issue #8 und als `xfail` in - `tests/test_referenzmodelle.py`. (23.09.2026) +- **Gateway → Gateway mit Ziel „hinter“ dem Start-Stub braucht einen seitlichen Umweg.** Liegt der Stub des + Start-Gateways schon jenseits des Ziel-Anschlusspunkts (z. B. Südpunkt unten, Ziel höher mit Nordpunkt), läuft + das letzte gerade Stück durch die Ziel-Raute. `orthogonal_points()` weicht dann über `_ausweichachse()` aus. + Die Achse liegt **im Gang direkt vor der Ziel-Spalte** (Abstand 85 zur Mitte), **nicht in der Mitte zwischen + Quelle und Ziel**: Liegen die beiden zwei Spalten auseinander, steht dort eine Aufgabe (im Referenzmodell S43). + (#8, 25.09.2026) +- **Die Strukturtests melden nur die erste Kreuzung.** `_kante_kreuzt_gateway()` bricht beim ersten Fund ab. Im + Referenzmodell stand deshalb nur S12→S13 im Issue, tatsächlich liefen fünf Kanten durch Gateways. Nach einem + Layout-Fix alle Kanten gegen alle Elemente prüfen, nicht nur den gemeldeten Fall. (#8, 25.09.2026) - **Zusammenführende Gateways brauchen eine Beschriftung mit Zuständigkeit**, sonst landen sie in der Lane „Nicht zugeordnet“ (`split_lane()` braucht Text nach dem Doppelpunkt). In BPMN bleiben Zusammenführungen üblicherweise unbeschriftet – ein Fall für eine spätere Konverteränderung. (23.09.2026, Referenzmodell) diff --git a/tests/test_referenzmodelle.py b/tests/test_referenzmodelle.py index d4a5cd0..f49fd02 100644 --- a/tests/test_referenzmodelle.py +++ b/tests/test_referenzmodelle.py @@ -21,9 +21,7 @@ GATEWAYS = ("exclusiveGateway", "inclusiveGateway", "parallelGateway", "eventBas # Bekannte, noch nicht behobene Konverterfehler je Referenzmodell. strict=True: Sobald der Fehler behoben ist, # schlägt der Test als XPASS fehl – dann den Eintrag hier entfernen. Nie verwenden, um neue Fehler zu verstecken. -BEKANNTE_KANTEN_DURCH_GATEWAY = { - "referenz_gewannenfestlegung": "Issue #8: Kante S12→S13 (Ja) läuft senkrecht durch die Raute von S13", -} +BEKANNTE_KANTEN_DURCH_GATEWAY = {} def mit_bekannten_fehlern(bekannt):