Projektwissen der AG Prozesse aus AG_QGIS/Projektwissen abgeleitet
Übernommen ohne Historie aus AG_QGIS/Projektwissen (main @ decd2a0), Skill sn-bpmn vom Branch BPMN-Skill @ 6efcac4 (ADR 0001). - Skripte hallo.py, gitea.py, setup_workspace.py auf die Organisation AG_Prozesse umgestellt; das Workspace-Repo zählt über [workspace] repo als Ticket-Repo, auch ohne weitere [[repos]]. - pruefe_umlaute.py prüft jetzt die .py-Dateien des Workspace selbst. - Branchmodell main plus Arbeitsbranches (ADR 0002). - AGENTS.md, README, Leitfäden, Skills, Vorlagen, Fallstricke und Glossar auf die Prozessmodellierung zugeschnitten; QGIS- und Plugin-Spezifisches entfernt. - Tests: python -m pytest -q .agents/skills -> 32 bestanden. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: sn-aufgabe-bearbeiten
|
||||
description: Standardablauf für eine Aufgabe im Projektwissen der AG Prozesse (Konverter md_to_bpmn.py, Skills, Skripte, Doku, Prozessmodelle) von Gitea-Issue über Branch, Umsetzung und Tests bis zum vorbereiteten Pull Request mit Übergabenotiz. Verwenden bei "bearbeite Issue", "setze um", "Bugfix", "neue Funktion".
|
||||
---
|
||||
|
||||
# Aufgabe bearbeiten
|
||||
|
||||
Für das reine Modellieren eines Prozesses gilt der Skill `sn-bpmn`. Dieser Ablauf gilt, wenn etwas im Repository
|
||||
geändert und per Pull Request zurückgeführt wird.
|
||||
|
||||
## 0. Stand holen (nicht überspringen)
|
||||
```bash
|
||||
python scripts/hallo.py
|
||||
```
|
||||
Holt den Serverstand und aktualisiert `main` per Fast-Forward, wo das gefahrlos geht.
|
||||
Ohne diesen Schritt wird womöglich etwas behoben, das längst behoben ist.
|
||||
|
||||
## 1. Auftrag verstehen (nicht überspringen)
|
||||
- Issue **mit Kommentaren** lesen – Skill `sn-ticket`, `python scripts/gitea.py ticket Projektwissen <nr>`.
|
||||
Label `Diskussion` = noch kein Konsens: Annahmen im PR kennzeichnen.
|
||||
- Ziel, Abnahmekriterien und Grenzen in drei Sätzen zurückspiegeln, bevor etwas geändert wird.
|
||||
Fehlt ein Abnahmekriterium, ist das die erste Rückfrage – nicht der erste Commit.
|
||||
|
||||
## 2. Kontext laden – gezielt
|
||||
- `docs/status/STATUS.md` (wer arbeitet woran), bei Überraschungen `docs/wissen/fallstricke.md`.
|
||||
- Am Konverter: `grep -n "def \|class " .agents/skills/sn-bpmn/md_to_bpmn.py` für die Struktur, dann nur die
|
||||
betroffenen Abschnitte lesen. Die Datei hat über 1000 Zeilen.
|
||||
- Fachliche Regeln: `.agents/skills/sn-bpmn/Modellierregeln.md`, `Elementkatalog.md`, `Anleitung.md`.
|
||||
|
||||
## 3. Branch
|
||||
Immer von `main`. Vollständig: `docs/leitfaden/05_git_arbeitsweise.md`.
|
||||
|
||||
```bash
|
||||
git switch main && git pull --ff-only
|
||||
git switch -c feature/<issue-nr>-<kurzname> # oder bugfix/… ; klein, ASCII, mit Nummer
|
||||
```
|
||||
|
||||
## 4. Umsetzen
|
||||
- Kleine Schritte, jeder für sich prüfbar. Nach jedem Schritt `python -m pytest -q .agents/skills`.
|
||||
- Regeln aus `AGENTS.md`: nur Standardbibliothek, kein Umbau nebenbei, Stil der Datei beibehalten, Deutsch mit
|
||||
echten Umlauten.
|
||||
- **Neues Verhalten des Konverters bekommt einen Test** in `.agents/skills/sn-bpmn/test_md_to_bpmn.py` – pytest,
|
||||
ohne Netzwerk, ein Test je Verhalten. Bei einem Bugfix zuerst den Test schreiben, der den Fehler zeigt.
|
||||
- **Jeder neue oder geänderte Test bekommt einen deutschen Docstring**, erste Zeile `"""Prüft, dass …"""`.
|
||||
- Ändert sich die Markdown-Syntax oder ein Element: `Anleitung.md` bzw. `Elementkatalog.md` mitziehen.
|
||||
|
||||
## 5. Nachweis
|
||||
- `python -m pytest -q .agents/skills` → alles grün.
|
||||
- `python scripts/pruefe_umlaute.py` → keine Funde.
|
||||
- Ändert sich die erzeugte BPMN-Datei: ein Beispiel erzeugen und in PICTURE importieren. Kannst du das nicht
|
||||
selbst, im PR als offenen Punkt eintragen. **Nicht als erledigt ausgeben.**
|
||||
|
||||
## 6. Abschluss
|
||||
- Commit-Nachrichten deutsch, Präsens, erste Zeile ≤ 72 Zeichen, Issue-Nummer am Ende.
|
||||
- Pull-Request-Text nach `docs/vorlagen/pull_request.md` vorbereiten; Übergabenotiz mit Skill `sn-uebergabe`.
|
||||
- **Hier ist Schluss.** Push, Pull Request, Merge und alles auf Gitea macht der Mensch.
|
||||
@@ -0,0 +1,229 @@
|
||||
# Markdown → BPMN 2.0 für PICTURE
|
||||
|
||||
`md_to_bpmn.py` wandelt eine einfache textuelle Prozessbeschreibung (`.md`)
|
||||
in eine standardkonforme BPMN-2.0-XML-Datei um, die sich über den
|
||||
BPMN-Import in PICTURE laden lässt. BPMN 2.0 ist ein offener, seit 2011
|
||||
stabiler OMG-Standard – das Skript muss dafür kein PICTURE-internes
|
||||
Schema "lernen" und erzeugt keine PICTURE-spezifischen UUIDs.
|
||||
|
||||
## Empfohlener Arbeitsablauf bei unstrukturiertem Ausgangstext
|
||||
|
||||
1. Rohtext (Fließtext, Stichpunkte, Sitzungsnotizen o. ä.) bereitstellen.
|
||||
2. Daraus wird eine strukturierte Arbeitsschritt-`.md` in der unten
|
||||
beschriebenen Syntax abgeleitet und zur Prüfung vorgelegt.
|
||||
3. Fachlich prüfen und in der `.md` direkt anpassen (Reihenfolge,
|
||||
Verzweigungen, Verantwortliche, Formulierungen) – dabei gegen
|
||||
`Modellierregeln.md` abgleichen.
|
||||
4. Aus der freigegebenen `.md` die BPMN-2.0-Datei erzeugen und in PICTURE
|
||||
importieren.
|
||||
|
||||
Schritt 2 erfordert Interpretation von Fließtext und ist kein rein
|
||||
mechanischer Schritt – die Qualität hängt von der Klarheit der Quelle und
|
||||
der fachlichen Prüfung in Schritt 3 ab. Die fachlichen BPMN-Modellierregeln
|
||||
stehen in `Modellierregeln.md`.
|
||||
|
||||
## Aufruf
|
||||
|
||||
```bash
|
||||
python3 md_to_bpmn.py <prozess.md> <ausgabe.bpmn>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
python3 md_to_bpmn.py beispiel_prozess.md beispiel_prozess.bpmn
|
||||
```
|
||||
|
||||
Die erzeugte `.bpmn`-Datei kann anschließend über den BPMN-Import-Dialog
|
||||
in PICTURE geladen werden.
|
||||
|
||||
### Diagramm vorab prüfen, ohne PICTURE
|
||||
|
||||
Um zu prüfen, ob `md_to_bpmn.py` selbst korrekt gerechnet hat (Struktur,
|
||||
Anschlusspunkte, Layout) – unabhängig davon, was ein Import-Werkzeug daraus
|
||||
macht – eignet sich ein originalgetreuer BPMN-Viewer:
|
||||
|
||||
- **`demo.bpmn.io`** – im Browser, kein Install nötig: `.bpmn`-Datei per
|
||||
Drag & Drop reinziehen.
|
||||
- **Camunda Modeler** – kostenlose Desktop-App, gleiche Rendering-Engine
|
||||
wie bpmn.io, auch offline.
|
||||
|
||||
Beide übernehmen importierte Anschlusspunkte originalgetreu, statt sie neu
|
||||
zu berechnen.
|
||||
|
||||
## Markdown-Syntax
|
||||
|
||||
### Kopfzeile (optional, vor dem ersten Schritt)
|
||||
|
||||
```
|
||||
Prozess: <Name des Prozesses>
|
||||
```
|
||||
|
||||
### Schritte
|
||||
|
||||
Jeder Schritt ist eine Zeile der Form:
|
||||
|
||||
```
|
||||
<ID> <Typ>: <Beschriftung>
|
||||
```
|
||||
|
||||
- **ID**: ein kurzes, eindeutiges Kürzel ohne Leerzeichen (z. B. `S1`, `3.3`, `A`)
|
||||
- **Typ** (Groß-/Kleinschreibung egal):
|
||||
|
||||
| Schreibweise im .md | Erzeugtes BPMN-Element |
|
||||
|--------------------------------------|-------------------------------------|
|
||||
| `Start` | Start-Ereignis |
|
||||
| `Ende` / `End` | End-Ereignis |
|
||||
| `Zwischenereignis` / `Intermediate` | Zwischen-Ereignis (Catch) |
|
||||
| `Task` / `Aufgabe` | Aufgabe (Task) |
|
||||
| `Manuelle Aufgabe` | Manuelle Aufgabe |
|
||||
| `User Aufgabe` | User-Aufgabe |
|
||||
| `Service Aufgabe` | Service-Aufgabe |
|
||||
| `Script Aufgabe` | Script-Aufgabe |
|
||||
| `Geschäftsregel Aufgabe` | Geschäftsregel-Aufgabe |
|
||||
| `Sendeaufgabe` | Aufgabe (sendend) |
|
||||
| `Empfangsaufgabe` | Aufgabe (empfangend) |
|
||||
| `Teilprozess` / `Subprocess` | Geschlossener Teilprozess |
|
||||
| `Aufruf` | Aufruf-Element (Call Activity) |
|
||||
| `Gateway XOR` | Exklusives Gateway (XOR) |
|
||||
| `Gateway OR` / `Gateway ODER` | Inklusives Gateway (OR) |
|
||||
| `Gateway AND` / `Gateway UND` | Paralleles Gateway (AND) |
|
||||
| `Gateway Ereignisbasiert` | Ereignisbasiertes Gateway |
|
||||
| `Hinweis` / `Note` | Textuelle Anmerkung (unverbunden) |
|
||||
| `Datenobjekt` | Datenobjekt (unverbunden) |
|
||||
| `Datenspeicher` | Datenspeicher (unverbunden) |
|
||||
| `Gruppe` | Gruppe (unverbunden) |
|
||||
|
||||
Vollständige Liste inkl. Bedeutung: `Elementkatalog.md`.
|
||||
|
||||
### Typisierte Ereignisse
|
||||
|
||||
Start-, Zwischen- und Endereignisse können ein zweites Wort für die Art des
|
||||
Ereignisses bekommen: `<Start|Zwischenereignis|Ende> <Kind>: <Beschriftung>`.
|
||||
Mögliche `<Kind>`-Wörter: `Nachricht`, `Zeit`, `Eskalation`, `Bedingung`,
|
||||
`Link`, `Fehler`, `Signal`, `Terminierung`. Details und Einschränkungen (z. B.
|
||||
`Terminierung` nur als Endereignis, `Link` nur als Zwischenereignis) stehen in
|
||||
`Elementkatalog.md` und `Modellierregeln.md`.
|
||||
|
||||
```
|
||||
S1 Start Nachricht: Antrag geht ein
|
||||
S9 Zwischenereignis Zeit: 14 Tage Frist abwarten
|
||||
S17 Ende Terminierung: Verfahren beendet
|
||||
```
|
||||
|
||||
Ein Zwischenereignis ist standardmäßig empfangend (Catch); mit einem
|
||||
zusätzlichen dritten Wort `sendend` wird daraus ein auslösendes (Throw-)
|
||||
Ereignis, z. B. `S2 Zwischenereignis Nachricht sendend: Status melden`.
|
||||
|
||||
### Geschlossene Pools und Nachrichtenflüsse
|
||||
|
||||
Externe Beteiligte (Bürger, andere Behörden, …), mit denen nur Nachrichten
|
||||
ausgetauscht werden, vor dem ersten Schritt deklarieren:
|
||||
|
||||
```
|
||||
Extern: Bürger
|
||||
Extern: Landratsamt
|
||||
```
|
||||
|
||||
Unter einem Schritt erzeugt `~> <Name>` einen ausgehenden, `<~ <Name>` einen
|
||||
eingehenden Nachrichtenfluss zu einem so deklarierten Beteiligten:
|
||||
|
||||
```
|
||||
S8 Sendeaufgabe: Bescheid versenden
|
||||
~> Bürger
|
||||
-> S9
|
||||
```
|
||||
|
||||
Nachrichtenflüsse sind nur zwischen Pools erlaubt (siehe `Modellierregeln.md`)
|
||||
– das Ziel muss immer ein per `Extern:` deklarierter Name sein, nie eine
|
||||
Schritt-ID im selben Pool.
|
||||
|
||||
### Verzweigungen / Sprünge
|
||||
|
||||
Unter einem Schritt können beliebig viele Pfeil-Zeilen stehen:
|
||||
|
||||
```
|
||||
S3 Gateway XOR: Antrag vollständig?
|
||||
Ja -> S4
|
||||
Nein -> S3a
|
||||
```
|
||||
|
||||
- `<Bezeichnung> -> <Ziel-ID>` – beschriftete Kante (z. B. für Ja/Nein-Zweige)
|
||||
- `-> <Ziel-ID>` – unbeschriftete Kante (z. B. für parallele AND-Zweige oder
|
||||
einfache Weiterleitung)
|
||||
|
||||
**Wichtig:** Wenn ein Schritt **keine** Pfeil-Zeile hat, wird automatisch
|
||||
eine Kante zum **nächsten Schritt in der Datei** erzeugt. Ihr müsst also nur
|
||||
dort explizite Pfeile setzen, wo der Ablauf von der reinen Lesereihenfolge
|
||||
abweicht (Verzweigung, Sprung zurück, Zusammenführung).
|
||||
|
||||
Rücksprünge (Schleifen, z. B. „Nachforderung" zurück zur Prüfung) werden
|
||||
automatisch erkannt und am Rand des Diagramms herumgeführt.
|
||||
|
||||
### Verantwortliche / Swimlanes
|
||||
|
||||
Schreibt ihr am Anfang der Beschriftung eines Schritts einen kurzen Namen
|
||||
gefolgt von einem Doppelpunkt, wird dieser als **Verantwortlicher** erkannt
|
||||
und daraus automatisch eine Swimlane gemacht:
|
||||
|
||||
```
|
||||
V1 Task: Flurneuordnung: Gewannenkonzept festlegen
|
||||
O3 Task: ÖbVI: Waldgrenzen verhandeln
|
||||
O9 Task: LRA: Aufmessung im Ortstermin
|
||||
```
|
||||
|
||||
Aus den drei unterschiedlichen Verantwortlichen entstehen drei Lanes in
|
||||
einem Pool; Schritte ohne Verantwortlichen-Angabe landen gesammelt in einer
|
||||
Lane „Nicht zugeordnet". Ohne jede Verantwortlichen-Angabe entsteht wie
|
||||
bisher ein Diagramm ohne Lanes.
|
||||
|
||||
Das ist eine Heuristik: Ein Doppelpunkt weit vorn in einer kurzen
|
||||
Beschriftung (max. 30 Zeichen, max. 4 Wörter, kein „?") wird als
|
||||
Verantwortlicher gedeutet. Enthält eine Beschriftung an dieser Stelle
|
||||
zufällig einen Doppelpunkt, der keine Zuständigkeit meint, entsprechend
|
||||
umformulieren.
|
||||
|
||||
### Vollständige Beispiele
|
||||
|
||||
- Ohne Lanes: `beispiel_prozess.md` / `beispiel_prozess.bpmn`
|
||||
- Mit Lanes: `gewannenvermessung_lanes.md` / `gewannenvermessung_lanes.bpmn`
|
||||
|
||||
## Einschränkungen der aktuellen Version
|
||||
|
||||
- Es wird pro `.md`-Datei **ein** Diagramm (ein Prozess) erzeugt, keine
|
||||
mehrstufigen Prozessnetze mit verknüpften Teilprozess-Diagrammen.
|
||||
- `Teilprozess` (geschlossener Teilprozess) und `Aufruf` (Aufruf-Element)
|
||||
werden als Bausteine angelegt, aber **ohne** Inhalt bzw. **ohne**
|
||||
Verknüpfung zu einem Ziel-Prozess – Details bzw. Verknüpfung lassen sich
|
||||
in PICTURE nach dem Import nachtragen. Der **aufgeklappte** Teilprozess
|
||||
(Inhalt inline im selben Diagramm) wird noch nicht unterstützt.
|
||||
- Wiederholungs-Marker (Schleife, Multi-Instanz sequenziell/parallel) werden
|
||||
noch nicht unterstützt; bei Bedarf als `Hinweis` an der betroffenen
|
||||
Aktivität dokumentieren.
|
||||
- Assoziationen (gepunktete Linien) von Datenobjekten/-speichern zu
|
||||
Aktivitäten werden nicht automatisch gezeichnet; Datenobjekt/-speicher
|
||||
landen unverbunden im Diagramm und werden nach dem Import in PICTURE bei
|
||||
Bedarf manuell mit den betroffenen Aktivitäten verbunden.
|
||||
- Das automatische Layout ist ein einfacher Schichten-Algorithmus
|
||||
(ähnlich den meisten BPMN-Auto-Layouts) – bei komplexen Prozessen lohnt
|
||||
sich nach dem Import ein manuelles Nachjustieren einzelner Boxen und
|
||||
Beschriftungen. Das gilt besonders für geschlossene Pools (externe
|
||||
Beteiligte): Sie werden schlicht unterhalb des Hauptpools gestapelt.
|
||||
- Jede Kante bekommt in der erzeugten `.bpmn`-Datei einen eigenen
|
||||
Anschlusspunkt (Modellierregel 4, in `Modellierregeln.md`) statt eines
|
||||
gemeinsamen Mittelpunkts. Bei Task/Ereignis/Datenobjekt usw. werden dazu
|
||||
mehrere Punkte über die tatsächliche Kontur verteilt (Rechteckkante bzw.
|
||||
Kreisbogen). **Ein Gateway bekommt dagegen nur einen der vier festen
|
||||
PICTURE-Eckpunkte (0°/90°/180°/270°) je Kante**, nie einen Punkt
|
||||
dazwischen auf der Rautenkante – mehr als vier Kanten an einem Gateway
|
||||
(eingehend plus ausgehend) können sich damit nicht mehr eindeutig
|
||||
darstellen lassen, siehe Modellierregel 4. Wirkt das Ergebnis in einem
|
||||
Anzeige-Werkzeug trotzdem so, als würden Verzweigungen an einem Punkt
|
||||
zusammenlaufen, zuerst in einem originalgetreuen BPMN-Viewer (z. B.
|
||||
`demo.bpmn.io`, siehe unten) prüfen: Ein Viewer, der importierte
|
||||
Anschlusspunkte respektiert statt sie neu zu berechnen, zeigt die
|
||||
verteilten Punkte korrekt an.
|
||||
- Der BPMN-Import in PICTURE übernimmt die reine Ablaufstruktur
|
||||
zuverlässig; PICTURE-/FIM-spezifische Zusatzfelder (Fallzahl,
|
||||
Bearbeitungszeit, RACI usw.) sind kein Teil des BPMN-Standards und
|
||||
müssten nach dem Import ergänzt werden.
|
||||
@@ -0,0 +1,99 @@
|
||||
# PICTURE-Elementkatalog (BPMN 2.0)
|
||||
|
||||
Quelle: Informationsportal der PICTURE-Prozessplattform, Abschnitt „BPMN 2.0"
|
||||
(Stand der Übernahme: 2026-09-21). PICTURE deckt die OMG-Konformitätsklassen
|
||||
„Deskriptives BPMN" und „Analytisches BPMN" ab – Symbole/Icons sind hier
|
||||
bewusst weggelassen, da für den Skill nur Name, Bedeutung und DSL-Zuordnung
|
||||
relevant sind. Bei Bedarf original im PICTURE-Portal nachschlagen.
|
||||
|
||||
Spalte „`md_to_bpmn.py`" zeigt das DSL-Schlüsselwort aus `Anleitung.md`.
|
||||
**„–“ bedeutet: von `md_to_bpmn.py` aktuell nicht erzeugbar** – im Entwurf als
|
||||
Freitext/Hinweis dokumentieren und nach dem Import manuell in PICTURE ergänzen.
|
||||
|
||||
## Aktivitäten
|
||||
|
||||
| Name | Beschreibung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Aufgabe | Arbeitsschritt, der nicht weiter verfeinert wird. | `Task` / `Aufgabe` |
|
||||
| Manuelle Aufgabe | Ausführung durch einen Menschen ohne IT-Unterstützung. | `Manuelle Aufgabe` |
|
||||
| User-Aufgabe | Ausführung durch einen Menschen mit Software-Unterstützung. | `User Aufgabe` |
|
||||
| Service-Aufgabe | Automatische Ausführung durch einen Dienst/Service. | `Service Aufgabe` |
|
||||
| Script-Aufgabe | Automatische Ausführung durch eine Process Engine per Script. | `Script Aufgabe` |
|
||||
| Geschäftsregel-Aufgabe | Automatische Ausführung nach Entscheidungs-/Berechnungsregeln. | `Geschäftsregel Aufgabe` |
|
||||
| Aufgabe (sendend) | Versand einer Nachricht an einen externen Beteiligten. | `Sendeaufgabe` |
|
||||
| Aufgabe (empfangend) | Empfang einer Nachricht von einem externen Beteiligten. | `Empfangsaufgabe` |
|
||||
| Geschlossener Teilprozess | Gekapselte Aktivitätsmenge, Details in separatem Diagramm ausgelagert. | `Teilprozess` |
|
||||
| Aufgeklappter Teilprozess | Gekapselte, aber inline dargestellte Aktivitätsmenge (keine Auslagerung). | – (noch nicht unterstützt, siehe Einschränkungen in `Anleitung.md`) |
|
||||
| Aufruf-Element (Call Activity) | Verknüpfung mit einem separaten, eigenständigen Prozess. | `Aufruf` |
|
||||
|
||||
## Wiederholungen
|
||||
|
||||
| Name | Beschreibung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Schleife | Aktivität wiederholt sich, solange eine Bedingung zutrifft. | – (noch nicht unterstützt) |
|
||||
| Multi-Instanz (sequenziell) | Aktivität läuft für eine bekannte Anzahl Objekte nacheinander. | – (noch nicht unterstützt) |
|
||||
| Multi-Instanz (parallel) | Wie sequenziell, aber alle Ausführungen parallel. | – (noch nicht unterstützt) |
|
||||
|
||||
## Ereignisse
|
||||
|
||||
Start-, Zwischen- und Endereignisse können optional typisiert werden. In der
|
||||
DSL: `<Start|Zwischenereignis|Ende> <Kind> [sendend|empfangend]: <Label>`.
|
||||
|
||||
| Ereignis-Kind | Bedeutung | `md_to_bpmn.py`-Wort |
|
||||
|---|---|---|
|
||||
| (untypisiert) | Allgemeines Ereignis, meist Start/Ende. | – (kein Zusatzwort) |
|
||||
| Nachricht | Empfang/Versand einer Nachricht (Dokument, Information). | `Nachricht` |
|
||||
| Zeit | Periodisches, zeitpunkt- oder zeitspannenbezogenes Ereignis. | `Zeit` |
|
||||
| Eskalation | Meldung an den nächsthöheren Verantwortlichen. | `Eskalation` |
|
||||
| Bedingung | Reaktion auf veränderte (Geschäfts-)Bedingungen. | `Bedingung` |
|
||||
| Link | Fortsetzung eines aufgetrennten Sequenzflusses; nur als Zwischenereignis, paarweise sendend/empfangend. Von Analysefunktionen nicht unterstützt. | `Link` (nur Zwischenereignis) |
|
||||
| Fehler | Auslösen/Behandeln definierter Fehler. | `Fehler` |
|
||||
| Signal | Signal an (potenziell) mehrere Prozesse gleichzeitig. | `Signal` |
|
||||
| Terminierung | Sofortige Beendigung der Prozessinstanz. | `Terminierung` (nur Endereignis) |
|
||||
|
||||
Start-Ereignisse sind immer eintretend/empfangend, Endereignisse immer
|
||||
auslösend/sendend (Skript weist eine widersprüchliche Angabe ab).
|
||||
Zwischenereignisse sind standardmäßig empfangend (Catch); mit `sendend`
|
||||
wird daraus ein Throw-Ereignis.
|
||||
|
||||
## Kontrollfluss-Operatoren (Gateways)
|
||||
|
||||
| Name | Bedeutung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Parallele Verzweigung (AND-Split) | Alle nachfolgenden Pfade laufen gleichzeitig. | `Gateway AND` |
|
||||
| Exklusive datenbasierte Verzweigung (XOR) | Genau ein Pfad wird abhängig von den Daten gewählt. | `Gateway XOR` |
|
||||
| Inklusive datenbasierte Verzweigung (OR) | Ein oder mehrere Pfade werden gewählt. | `Gateway OR` |
|
||||
| Exklusive ereignisbasierte Verzweigung | Es folgt der Pfad, dessen Ereignis/empfangende Aufgabe zuerst eintritt; alle anderen Pfade verfallen für diese Instanz. Darf **nicht** spiegelbildlich als Zusammenführung genutzt werden. | `Gateway Ereignisbasiert` |
|
||||
| Entweder-Oder-Zusammenführung (XOR-Merge) | Ankommende Pfade laufen unabhängig voneinander einfach weiter; Nachfolger wird ggf. mehrfach durchlaufen. | `Gateway XOR` als Zusammenführung |
|
||||
| Verschmelzung (AND-Merge) | Wartet auf **alle** eingehenden Pfade, bevor es weitergeht. | `Gateway AND` als Zusammenführung |
|
||||
| Oder-Verschmelzung (OR-Merge) | Wartet auf alle **aktiven** eingehenden Pfade. | `Gateway OR` als Zusammenführung |
|
||||
|
||||
`md_to_bpmn.py` erzeugt für Split und Merge denselben Gateway-Typ (XOR/OR/AND
|
||||
sind in BPMN dieselben Elemente, nur mit unterschiedlicher Kanten-Anzahl) –
|
||||
siehe `Modellierregeln.md` für die Regel, welcher Merge-Typ zu welchem
|
||||
Split-Typ passt, und für PICTUREs Grenze von vier Anschlusspunkten je
|
||||
Gateway (Modellierregel 4).
|
||||
|
||||
## Sequenz- und Nachrichtenflüsse
|
||||
|
||||
| Name | Beschreibung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Sequenzfluss | Reihenfolge innerhalb eines Pools. | `->` |
|
||||
| Nachrichtenfluss | Nur zwischen Pools, nie innerhalb desselben Pools. | `~>` (ausgehend) / `<~` (eingehend) zu einem per `Extern:` deklarierten Beteiligten |
|
||||
|
||||
## Artefakte
|
||||
|
||||
| Name | Beschreibung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Datenobjekt / Dokument | Information, die im Prozess erstellt/genutzt/weitergegeben wird. Sparsam einsetzen, nur bei besonders wichtigem Sachverhalt. | `Datenobjekt` |
|
||||
| Datenspeicher | Dauerhafte, von der Prozessinstanz unabhängige Ablage (Datenbank, Aktenschrank). | `Datenspeicher` |
|
||||
| Textuelle Anmerkung | Kommentar zu einem beliebigen Modellelement. | `Hinweis` |
|
||||
| Gruppe | Rein visuelle Klammer ohne Einfluss auf Ablauf/Verantwortlichkeiten. | `Gruppe` |
|
||||
|
||||
## Pools & Schwimmbahnen
|
||||
|
||||
| Name | Beschreibung | `md_to_bpmn.py` |
|
||||
|---|---|---|
|
||||
| Pool | Grenze eines Prozesses/Teilprozesses. | entsteht automatisch, sobald Lanes oder `Extern:`-Beteiligte vorkommen |
|
||||
| Schwimmbahn | Unterteilung eines Pools nach Organisationseinheit/Rolle/System. | automatisch aus `Verantwortlicher: Aufgabe`-Präfix |
|
||||
| Geschlossener Pool | Externe/r Beteiligte/r bzw. externer Prozess ohne dargestellten Inhalt, tauscht nur Nachrichten aus. | `Extern: <Name>` + `~>`/`<~` |
|
||||
@@ -0,0 +1,73 @@
|
||||
# BPMN-Modellierregeln
|
||||
|
||||
Fachliche Regeln, an denen sich ein Entwurf (`entwuerfe/<name>.md`) und die
|
||||
daraus erzeugte BPMN-Datei messen lassen müssen – zusätzlich zur reinen
|
||||
Markdown-Syntax aus `Anleitung.md`. Beim Erzeugen und beim Prüfen eines
|
||||
Entwurfs (Schritt 2 in `SKILL.md`) gegen diese Liste abgleichen und
|
||||
Verstöße im Annahmen-/Hinweis-Abschnitt benennen. Welche Elemente es
|
||||
überhaupt gibt und wofür sie stehen, steht in `Elementkatalog.md`.
|
||||
|
||||
1. **Verzweigungen laufen passend zusammen.** Verzweigte Pfade müssen in
|
||||
einem Verzweigungselement (Gateway) zusammenlaufen, das zur Art der
|
||||
Verzweigung passt, bevor der Pfad in den nächsten Arbeitsschritt nach
|
||||
der Verzweigung mündet. Pfade nicht einfach ohne zusammenführendes
|
||||
Gateway in denselben Folgeschritt münden lassen. Welcher Gateway-Typ
|
||||
zusammenführt, hängt vom Split ab – und die drei Typen unterscheiden
|
||||
sich fachlich, nicht nur formal:
|
||||
- **XOR-Split** (genau ein Pfad wird genommen) → **XOR-Merge**
|
||||
(Entweder-Oder-Zusammenführung): die ankommenden Pfade laufen
|
||||
unabhängig voneinander einfach weiter, es wird auf nichts gewartet.
|
||||
- **AND-Split** (alle Pfade parallel) → **AND-Merge** (Verschmelzung):
|
||||
wartet, bis **alle** eingehenden Pfade angekommen sind, bevor es
|
||||
weitergeht. Ein AND-Split ohne passenden AND-Merge am Ende der
|
||||
parallelen Pfade führt zu mehrfach durchlaufenen Folgeschritten.
|
||||
- **OR-Split** (ein oder mehrere Pfade) → **OR-Merge**
|
||||
(Oder-Verschmelzung): wartet auf alle **aktiven** eingehenden Pfade,
|
||||
nicht zwingend auf alle theoretisch möglichen.
|
||||
- **Ereignisbasierte Verzweigung** ist ein Sonderfall: Sie darf nicht
|
||||
spiegelbildlich als Zusammenführung genutzt werden, also nie mehr
|
||||
als eine eingehende Kante haben (`md_to_bpmn.py` lehnt das ab).
|
||||
|
||||
2. **Nachrichtenfluss nur zwischen Pools.** Ein Nachrichtenfluss verbindet
|
||||
ausschließlich Elemente unterschiedlicher Pools (z. B. den eigenen
|
||||
Prozess mit einem externen Beteiligten). Innerhalb desselben Pools
|
||||
werden Informationsübergaben über den normalen Sequenzfluss
|
||||
modelliert, nie über Nachrichtenfluss.
|
||||
|
||||
3. **Datenobjekte, Datenspeicher und Gruppen sind keine Ablaufelemente.**
|
||||
Sie hängen nicht am Sequenzfluss (kein `->` zu/von ihnen) und werden
|
||||
sparsam eingesetzt – nur wenn der Sachverhalt (z. B. ein zentrales
|
||||
Dokument oder eine geteilte Ablage) für das Verständnis des Prozesses
|
||||
wirklich wichtig ist, nicht für jede beiläufig erwähnte Unterlage.
|
||||
|
||||
4. **Jede Kante bekommt einen eigenen Anschlusspunkt.** Jeder Eingang und
|
||||
jeder Ausgang an einem Element muss einen eigenen Verbindungspunkt am
|
||||
Element nutzen, nicht denselben Punkt wie eine andere Kante. Laufen
|
||||
mehrere Kanten sichtbar in einem einzigen Punkt zusammen, sieht das
|
||||
Diagramm so aus, als wären mehrere fachlich getrennte Verzweigungen
|
||||
eine einzige Linie – das verschleiert genau die Verzweigung, die
|
||||
dargestellt werden soll. `md_to_bpmn.py` setzt das automatisch um, aber
|
||||
je nach Elementtyp unterschiedlich:
|
||||
- **Bei Task/Ereignis/Datenobjekt usw.** werden mehrere Punkte entlang
|
||||
der tatsächlichen Kontur verteilt (Rechteckkante bzw. Kreisbogen).
|
||||
- **Bei einem Gateway hat PICTURE nur vier feste Anschlusspunkte** – je
|
||||
einen bei 0°/90°/180°/270° (Ost/Süd/West/Nord), die Kante verläuft
|
||||
dort immer gerade. `md_to_bpmn.py` weist deshalb jeder Kante an einem
|
||||
Gateway einen dieser vier Eckpunkte zu, nicht einen Punkt auf der
|
||||
Rautenkante dazwischen. **Daraus folgt eine harte Obergrenze: ein
|
||||
Gateway kann höchstens vier Kanten insgesamt (eingehend plus
|
||||
ausgehend zusammengerechnet) eindeutig darstellen.** Ein XOR-Split
|
||||
mit einer eingehenden Kante hat also nur für maximal drei ausgehende
|
||||
Zweige Platz; bei vier oder mehr Zweigen muss mindestens eine Kante
|
||||
sich einen Punkt mit einer anderen teilen – das ist eine Grenze von
|
||||
PICTURE selbst, kein Darstellungsfehler von `md_to_bpmn.py`. Bei
|
||||
einer Verzweigung mit vielen Zweigen und einer eingehenden Kante
|
||||
lohnt sich daher, die Entscheidung in mehrere, weniger stark
|
||||
verzweigte Gateways aufzuteilen, wenn jeder Zweig einzeln sichtbar
|
||||
bleiben soll.
|
||||
|
||||
Wirkt ein Diagramm trotzdem so, als würden Verzweigungen an einem
|
||||
Punkt zusammenlaufen, zuerst in einem originalgetreuen BPMN-Viewer
|
||||
(z. B. `demo.bpmn.io`) prüfen, ob es an der erzeugten Datei liegt oder
|
||||
tatsächlich an der Vier-Punkte-Grenze eines Gateways mit mehr als vier
|
||||
Kanten.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: sn-bpmn
|
||||
description: Wandelt eine Prozessbeschreibung (Rohtext, Stichpunkte oder bereits strukturierte Arbeitsschritt-.md) in eine BPMN-2.0-Datei für den Import in PICTURE um. Aufrufen, wenn ein Verwaltungsprozess als Ablaufdiagramm für PICTURE modelliert werden soll.
|
||||
---
|
||||
|
||||
# BPMN-Prozess-Konverter
|
||||
|
||||
Begleitet den Weg von einer (oft unstrukturierten) Prozessbeschreibung zu
|
||||
einer importierbaren BPMN-2.0-Datei für PICTURE, mit `md_to_bpmn.py` in
|
||||
diesem Verzeichnis.
|
||||
|
||||
Lies `Anleitung.md` in diesem Verzeichnis für die vollständige
|
||||
Markdown-Syntax (Schritt-Typen, Verzweigungen, Rücksprung-Erkennung,
|
||||
Verantwortliche/Swimlanes), `Elementkatalog.md` für die vollständige
|
||||
Liste der PICTURE-BPMN-Elemente und `Modellierregeln.md` für die
|
||||
fachlichen BPMN-Modellierregeln, bevor du eine Arbeitsschritt-.md
|
||||
schreibst oder bearbeitest.
|
||||
|
||||
## Ablauf
|
||||
|
||||
1. **Quelle ermitteln.** Frage nach der Rohtext-, Word- oder PDF-Quelle,
|
||||
falls keine angegeben oder angehängt wurde.
|
||||
|
||||
2. **Entwurf erzeugen.** Lies die Quelle und leite daraus eine
|
||||
strukturierte Arbeitsschritt-`.md` nach der Syntax aus `Anleitung.md`
|
||||
ab. Triff nachvollziehbare fachliche Annahmen (Schrittgrenzen,
|
||||
Verzweigungen, Verantwortliche wo erkennbar) und schreibe das Ergebnis
|
||||
nach `entwuerfe/<name>.md` (Verzeichnis bei Bedarf anlegen). Gleiche den
|
||||
Entwurf gegen `Modellierregeln.md` ab und behebe Verstöße direkt im
|
||||
Entwurf. Fasse kurz zusammen, welche Annahmen getroffen wurden, und
|
||||
weise auf Stellen hin, die fachlich geprüft werden sollten.
|
||||
|
||||
3. **Auf Freigabe warten.** Nicht eigenständig weiterbearbeiten. Der
|
||||
Nutzer passt `entwuerfe/<name>.md` selbst an (Reihenfolge,
|
||||
Verzweigungen, Verantwortliche, Formulierungen) und meldet sich, wenn
|
||||
sie passt.
|
||||
|
||||
4. **BPMN erzeugen.** Erst nach ausdrücklicher Freigabe (z. B. "ist
|
||||
angepasst", "passt so", "wandle um"):
|
||||
```
|
||||
python .agents/skills/sn-bpmn/md_to_bpmn.py entwuerfe/<name>.md <name>.bpmn
|
||||
```
|
||||
Ergebnis kurz melden (Anzahl Schritte/Kanten/Lanes, Pfad der Datei).
|
||||
|
||||
Wenn der Nutzer direkt eine bereits fertige Arbeitsschritt-`.md` angibt
|
||||
und ausdrücklich nur die Umwandlung möchte, Schritt 2-3 überspringen
|
||||
und direkt umwandeln.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,379 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests für md_to_bpmn.py - reines Python/pytest, ohne Netzwerk."""
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
|
||||
import md_to_bpmn as m
|
||||
|
||||
|
||||
def test_bestehende_syntax_bleibt_kompatibel():
|
||||
"""Prüft, dass Start/Task/Gateway-XOR/Ende mit Verzweigung wie bisher geparst werden."""
|
||||
text = """
|
||||
S1 Start: Beginn
|
||||
-> S2
|
||||
S2 Task: Antrag pruefen
|
||||
S3 Gateway XOR: Vollständig?
|
||||
Ja -> S4
|
||||
Nein -> S2
|
||||
S4 Ende: Fertig
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
assert externals == []
|
||||
assert steps['S1'].type == 'StartEvent'
|
||||
assert steps['S3'].type == 'XorGateway'
|
||||
assert ('Ja', 'S4') in steps['S3'].edges
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
assert defs.find(f".//{{{m.NS['bpmn']}}}exclusiveGateway") is not None
|
||||
|
||||
|
||||
def test_lane_erkennung_bleibt_unveraendert():
|
||||
"""Prüft, dass 'Verantwortlicher: Rest' weiterhin als Swimlane erkannt wird."""
|
||||
text = """
|
||||
S1 Start: Beginn
|
||||
-> V1
|
||||
V1 Task: Flurneuordnung: Gewannenkonzept festlegen
|
||||
O3 Task: ÖbVI: Waldgrenzen verhandeln
|
||||
S_end Ende: Fertig
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
lanes = {s.lane for s in steps.values() if s.lane}
|
||||
assert lanes == {'Flurneuordnung', 'ÖbVI'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('typewort, erwarteter_typ', [
|
||||
('Manuelle Aufgabe', 'ManualTask'),
|
||||
('User Aufgabe', 'UserTask'),
|
||||
('Service Aufgabe', 'ServiceTask'),
|
||||
('Script Aufgabe', 'ScriptTask'),
|
||||
('Geschäftsregel Aufgabe', 'BusinessRuleTask'),
|
||||
('Sendeaufgabe', 'SendTask'),
|
||||
('Empfangsaufgabe', 'ReceiveTask'),
|
||||
('Teilprozess', 'SubProcess'),
|
||||
('Aufruf', 'CallActivity'),
|
||||
('Gateway Ereignisbasiert', 'EventBasedGateway'),
|
||||
('Datenobjekt', 'DataObject'),
|
||||
('Datenspeicher', 'DataStore'),
|
||||
('Gruppe', 'Group'),
|
||||
])
|
||||
def test_neue_elementtypen_werden_aufgeloest(typewort, erwarteter_typ):
|
||||
"""Prüft, dass alle neuen Aufgaben-/Gateway-/Artefakttypen korrekt zugeordnet werden."""
|
||||
typ, kind, direction = m.resolve_type(typewort, 'Testzeile')
|
||||
assert typ == erwarteter_typ
|
||||
|
||||
|
||||
def test_typisiertes_startereignis_erzeugt_eventdefinition():
|
||||
"""Prüft, dass ein typisiertes Startereignis das passende BPMN-eventDefinition-Kind erhaelt."""
|
||||
text = """
|
||||
S1 Start Nachricht: Antrag eingegangen
|
||||
-> S2
|
||||
S2 Ende: Fertig
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
assert defs.find(f".//{{{m.NS['bpmn']}}}messageEventDefinition") is not None
|
||||
|
||||
|
||||
def test_zwischenereignis_sendend_wird_throw_event():
|
||||
"""Prüft, dass 'sendend' bei einem Zwischenereignis ein intermediateThrowEvent erzeugt."""
|
||||
text = """
|
||||
S1 Start: Beginn
|
||||
-> S2
|
||||
S2 Zwischenereignis Nachricht sendend: Status melden
|
||||
-> S3
|
||||
S3 Ende: Fertig
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
assert m.bpmn_tag_for(steps['S2']) == 'intermediateThrowEvent'
|
||||
|
||||
|
||||
def test_terminierung_nur_als_endereignis_erlaubt():
|
||||
"""Prüft, dass 'Terminierung' bei einem Startereignis einen Fehler ausloest."""
|
||||
text = """
|
||||
S1 Start Terminierung: X
|
||||
-> S2
|
||||
S2 Ende: Y
|
||||
"""
|
||||
with pytest.raises(ValueError, match='Terminierung'):
|
||||
m.parse_markdown(text)
|
||||
|
||||
|
||||
def test_link_nur_als_zwischenereignis_erlaubt():
|
||||
"""Prüft, dass 'Link' bei einem Endereignis einen Fehler ausloest."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 Ende Link: Y
|
||||
"""
|
||||
with pytest.raises(ValueError, match='Link'):
|
||||
m.parse_markdown(text)
|
||||
|
||||
|
||||
def test_ereignisbasiertes_gateway_verbietet_zusammenfuehrung():
|
||||
"""Prüft, dass ein ereignisbasiertes Gateway mit mehreren eingehenden Kanten abgelehnt wird."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
-> S3
|
||||
S2 Task: A
|
||||
-> S4
|
||||
S3 Task: B
|
||||
-> S4
|
||||
S4 Gateway Ereignisbasiert: C
|
||||
-> S5
|
||||
S5 Ende: Y
|
||||
"""
|
||||
with pytest.raises(ValueError, match='Zusammenführung'):
|
||||
m.parse_markdown(text)
|
||||
|
||||
|
||||
def test_datenobjekt_bekommt_keinen_sequenzfluss():
|
||||
"""Prüft, dass ein Sequenzfluss zu einem Datenobjekt abgelehnt wird."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> D1
|
||||
D1 Datenobjekt: Akte
|
||||
S2 Ende: Y
|
||||
"""
|
||||
with pytest.raises(ValueError, match='keine Ablaufelemente'):
|
||||
m.parse_markdown(text)
|
||||
|
||||
|
||||
def test_datenobjekt_wird_nicht_automatisch_verkettet():
|
||||
"""Prüft, dass die automatische Verkettung über unverbundene Artefakte hinwegspringt."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
D1 Datenobjekt: Akte
|
||||
S2 Ende: Y
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
assert steps['S1'].edges == [(None, 'S2')]
|
||||
assert steps['D1'].edges == []
|
||||
|
||||
|
||||
def test_datenobjekt_und_gruppe_erzeugen_erwartete_elemente():
|
||||
"""Prüft, dass Datenobjekt und Gruppe als eigenstaendige, unverbundene Artefakte gerendert werden."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 Ende: Y
|
||||
D1 Datenobjekt: Akte
|
||||
G1 Gruppe: Fachliche Pruefung
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
assert defs.find(f".//{{{m.NS['bpmn']}}}dataObjectReference") is not None
|
||||
assert defs.find(f".//{{{m.NS['bpmn']}}}group") is not None
|
||||
assert defs.find(f".//{{{m.NS['bpmn']}}}categoryValue") is not None
|
||||
|
||||
|
||||
def test_externer_beteiligter_erzeugt_messageflow():
|
||||
"""Prüft, dass ein deklarierter externer Beteiligter mit Nachrichtenfluss korrekt verknuepft wird."""
|
||||
text = """
|
||||
Extern: Bürger
|
||||
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 SendTask: Bescheid senden
|
||||
~> Bürger
|
||||
-> S3
|
||||
S3 Ende: Y
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
assert externals == ['Bürger']
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
msg_flow = defs.find(f".//{{{m.NS['bpmn']}}}messageFlow")
|
||||
assert msg_flow is not None
|
||||
participants = defs.findall(f".//{{{m.NS['bpmn']}}}participant")
|
||||
assert any(p.get('name') == 'Bürger' and p.get('processRef') is None for p in participants)
|
||||
|
||||
|
||||
def test_unbekannter_externer_beteiligter_wird_abgelehnt():
|
||||
"""Prüft, dass ein Nachrichtenfluss zu einem nicht deklarierten externen Beteiligten scheitert."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
~> Unbekannt
|
||||
S2 Ende: Y
|
||||
"""
|
||||
with pytest.raises(ValueError, match='unbekannter externer Beteiligter'):
|
||||
m.parse_markdown(text)
|
||||
|
||||
|
||||
def test_verteilte_anschlusspunkte_liegen_auf_der_rautenkontur():
|
||||
"""Prüft, dass verteilte Anschlusspunkte an einem Gateway auf der Diamant-Kontur liegen, nicht auf der Bounding-Box."""
|
||||
x, y, w, h = 590, 2290, 50, 50
|
||||
cx, cy = x + w / 2, y + h / 2
|
||||
for idx in range(4):
|
||||
px, py = m.spread_point(x, y, w, h, 'S', idx, 4, 'diamond')
|
||||
# Manhattan-Abstand zum Mittelpunkt, skaliert auf die Diamant-Halbachsen, muss 1 ergeben.
|
||||
assert abs(abs(px - cx) / (w / 2) + abs(py - cy) / (h / 2) - 1) < 1e-9
|
||||
|
||||
|
||||
def test_verteilte_anschlusspunkte_liegen_auf_dem_ereigniskreis():
|
||||
"""Prüft, dass verteilte Anschlusspunkte an einem Ereignis auf dem Kreisradius liegen, nicht auf der Bounding-Box."""
|
||||
x, y, w, h = 100, 100, 36, 36
|
||||
cx, cy = x + w / 2, y + h / 2
|
||||
r = w / 2
|
||||
for idx in range(3):
|
||||
px, py = m.spread_point(x, y, w, h, 'S', idx, 3, 'circle')
|
||||
dist = ((px - cx) ** 2 + (py - cy) ** 2) ** 0.5
|
||||
assert abs(dist - r) < 1e-9
|
||||
|
||||
|
||||
def test_gateway_kanten_bekommen_eigene_kardinalpunkte():
|
||||
"""Prüft, dass bis zu vier Kanten an einem Gateway je einen eigenen Eckpunkt (N/O/S/W) bekommen, wie PICTURE es rendert."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 Gateway XOR: Auswahl
|
||||
A -> S3
|
||||
B -> S4
|
||||
C -> S5
|
||||
S3 Ende: A
|
||||
S4 Ende: B
|
||||
S5 Ende: C
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
ns = m.NS
|
||||
gw = next(el for el in defs.iter(f"{{{ns['bpmn']}}}exclusiveGateway") if el.get('name') == 'Auswahl')
|
||||
shape = defs.find(f".//{{{ns['bpmndi']}}}BPMNShape[@bpmnElement='{gw.get('id')}']")
|
||||
x, y, w, h = (float(shape[0].get(a)) for a in ('x', 'y', 'width', 'height'))
|
||||
vertices = {(x + w / 2, y), (x + w / 2, y + h), (x, y + h / 2), (x + w, y + h / 2)}
|
||||
|
||||
out_ids = {o.text for o in gw.findall(f"{{{ns['bpmn']}}}outgoing")}
|
||||
in_ids = {i.text for i in gw.findall(f"{{{ns['bpmn']}}}incoming")}
|
||||
used = set()
|
||||
for fid in out_ids | in_ids:
|
||||
edge = defs.find(f".//{{{ns['bpmndi']}}}BPMNEdge[@bpmnElement='{fid}']")
|
||||
wps = edge.findall(f"{{{ns['di']}}}waypoint")
|
||||
wp = wps[0] if fid in out_ids else wps[-1]
|
||||
point = (float(wp.get('x')), float(wp.get('y')))
|
||||
assert point in vertices
|
||||
used.add(point)
|
||||
assert len(used) == 4
|
||||
|
||||
|
||||
def test_gateway_mit_mehr_als_vier_kanten_bleibt_gueltiges_xml():
|
||||
"""Prüft, dass ein Gateway mit mehr als vier anliegenden Kanten (über PICTUREs Grenze hinaus) trotzdem gültiges XML ohne Absturz erzeugt."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 Gateway XOR: Fuenf-Wege
|
||||
A -> S3
|
||||
B -> S4
|
||||
C -> S5
|
||||
D -> S6
|
||||
E -> S7
|
||||
S3 Ende: A
|
||||
S4 Ende: B
|
||||
S5 Ende: C
|
||||
S6 Ende: D
|
||||
S7 Ende: E
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
ET.tostring(defs) # loest bei kaputter Struktur eine Exception aus
|
||||
|
||||
|
||||
def test_mehrere_kanten_an_derselben_seite_bekommen_eigenen_anschlusspunkt():
|
||||
"""Prüft, dass mehrere ausgehende Kanten an einem Gateway nicht im selben Punkt starten."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S2
|
||||
S2 Gateway XOR: Auswahl
|
||||
A -> S3
|
||||
B -> S4
|
||||
C -> S5
|
||||
S3 Ende: A
|
||||
S4 Ende: B
|
||||
S5 Ende: C
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
starts = []
|
||||
for edge in defs.iter(f"{{{m.NS['bpmndi']}}}BPMNEdge"):
|
||||
wp = edge.find(f"{{{m.NS['di']}}}waypoint")
|
||||
starts.append((wp.get('x'), wp.get('y')))
|
||||
# 3 der 4 Kanten gehen vom selben Gateway aus - deren Startpunkte muessen sich unterscheiden.
|
||||
assert len(set(starts)) == len(starts)
|
||||
|
||||
|
||||
def _liegt_in_rautenflaeche(px, py, x, y, w, h, sicherheitsabstand=0.5):
|
||||
"""Manhattan-Abstand zum Mittelpunkt, skaliert auf die Diamant-Halbachsen: < 1 heisst innerhalb."""
|
||||
cx, cy = x + w / 2, y + h / 2
|
||||
wert = abs(px - cx) / (w / 2) + abs(py - cy) / (h / 2)
|
||||
return wert < 1 - (sicherheitsabstand / max(w, h))
|
||||
|
||||
|
||||
def _kante_kreuzt_gateway(defs, ns):
|
||||
"""Liefert (Kanten-Id, Gateway-Id) für die erste Kante, deren Streckenzug durchs Innere
|
||||
eines Gateways (statt nur an dessen Kontur entlang) laeuft - oder None, wenn keine kreuzt."""
|
||||
shapes = {}
|
||||
for shape in defs.iter(f"{{{ns['bpmndi']}}}BPMNShape"):
|
||||
b = shape.find(f"{{{ns['dc']}}}Bounds")
|
||||
if b is not None:
|
||||
shapes[shape.get('bpmnElement')] = tuple(float(b.get(a)) for a in ('x', 'y', 'width', 'height'))
|
||||
|
||||
gateway_ids = set()
|
||||
for tag in ('exclusiveGateway', 'inclusiveGateway', 'parallelGateway', 'eventBasedGateway'):
|
||||
gateway_ids.update(el.get('id') for el in defs.iter(f"{{{ns['bpmn']}}}{tag}"))
|
||||
|
||||
for edge in defs.iter(f"{{{ns['bpmndi']}}}BPMNEdge"):
|
||||
pts = [(float(wp.get('x')), float(wp.get('y'))) for wp in edge.findall(f"{{{ns['di']}}}waypoint")]
|
||||
for gid in gateway_ids:
|
||||
if gid not in shapes:
|
||||
continue
|
||||
x, y, w, h = shapes[gid]
|
||||
for (x1, y1), (x2, y2) in zip(pts, pts[1:]):
|
||||
for s in range(41):
|
||||
t = s / 40
|
||||
if _liegt_in_rautenflaeche(x1 + (x2 - x1) * t, y1 + (y2 - y1) * t, x, y, w, h):
|
||||
return edge.get('bpmnElement'), gid
|
||||
return None
|
||||
|
||||
|
||||
def test_kante_fuehrt_nicht_durchs_innere_eines_gateways():
|
||||
"""Prüft, dass eine Kante von/zu einem Gateway, dessen Ziel/Quelle nicht auf der Achse des
|
||||
zugewiesenen Anschlusspunkts liegt, aussen herum und nicht durchs Innere der Raute fuehrt."""
|
||||
text = """
|
||||
S1 Start: X
|
||||
-> S5
|
||||
S5 Gateway XOR: Sofort?
|
||||
Ja -> S6
|
||||
Nein -> S6m
|
||||
S6 Task: Leitungsplaene einholen
|
||||
-> S6m
|
||||
S6m Gateway XOR: Zusammenfuehrung
|
||||
-> S7
|
||||
S7 Ende: Fertig
|
||||
"""
|
||||
meta, steps, order, externals = m.parse_markdown(text)
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
assert _kante_kreuzt_gateway(defs, m.NS) is None
|
||||
|
||||
|
||||
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'
|
||||
src.write_text("""
|
||||
Extern: Bürger
|
||||
|
||||
S1 Start Nachricht: Antrag geht ein
|
||||
-> S2
|
||||
S2 Gateway XOR: Vollständig?
|
||||
Ja -> S3
|
||||
Nein -> S2
|
||||
S3 SendTask: Bescheid senden
|
||||
~> Bürger
|
||||
-> S4
|
||||
S4 Ende Terminierung: Fertig
|
||||
""", encoding='utf-8')
|
||||
out = tmp_path / 'prozess.bpmn'
|
||||
|
||||
meta, steps, order, externals = m.parse_markdown(src.read_text(encoding='utf-8'))
|
||||
defs = m.build_bpmn(meta, steps, order, externals)
|
||||
out.write_bytes(m.prettify(defs))
|
||||
|
||||
parsed = ET.parse(out)
|
||||
assert parsed.getroot().tag == f"{{{m.NS['bpmn']}}}definitions"
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
name: sn-ticket
|
||||
description: Gitea-Tickets lesen, durchsuchen und beantworten - offene Issues auflisten, ein Issue mit allen Kommentaren holen, Issue- und Kommentartexte als Entwurf vorbereiten, den ein Mensch absendet. Verwenden bei "welche Tickets gibt es", "lies Issue", "was steht in dem Ticket", "leg ein Issue an", "kommentiere das Ticket".
|
||||
---
|
||||
|
||||
# Tickets auf Gitea
|
||||
|
||||
Alle Repositories der Organisation `AG_Prozesse` liegen auf `entwicklung.flurneuordnung-sachsen.de`. Die API ist
|
||||
**lesend ohne Anmeldung** erreichbar; dafür gibt es `scripts/gitea.py` (nur Standardbibliothek, kein Token).
|
||||
|
||||
> **Der Agent verändert auf dem Server nichts.** Kein Anlegen, kein Kommentieren, kein Schließen, kein Zuweisen.
|
||||
> Der Agent bereitet den Text vor, der Mensch fügt ihn ein und sendet ab. Grund: Jede Aktion trägt den Namen
|
||||
> einer Person und deren Verantwortung. Siehe `docs/leitfaden/05_git_arbeitsweise.md` Abschnitt 6.
|
||||
|
||||
## Lesen
|
||||
|
||||
```bash
|
||||
python scripts/gitea.py tickets # alle offenen Issues der Organisation
|
||||
python scripts/gitea.py tickets Projektwissen # nur ein Repo (Kurzname genügt)
|
||||
python scripts/gitea.py tickets --label Bug # nach Label filtern
|
||||
python scripts/gitea.py ticket Projektwissen 12 # ein Issue mit allen Kommentaren
|
||||
python scripts/gitea.py suche "Swimlane" # Volltext über alle Repos
|
||||
python scripts/gitea.py pulls # offene Pull Requests
|
||||
```
|
||||
|
||||
Bekannt sind das Workspace-Repo (`[workspace] repo` in `workspace.toml`, derzeit `Projektwissen`) und alle
|
||||
`[[repos]]`-Einträge; eindeutige Kurznamen (`wissen`) werden aufgelöst.
|
||||
|
||||
**Immer `ticket` statt `tickets` verwenden, bevor mit der Arbeit begonnen wird.** Die Kommentare enthalten
|
||||
regelmäßig die eigentliche Anforderung — die Titelzeile führt in die Irre.
|
||||
|
||||
## Ein Ticket verstehen, bevor Code angefasst wird
|
||||
|
||||
1. Issue **mit Kommentaren** lesen (`ticket`), nicht nur den Titel.
|
||||
2. Label `Diskussion` bedeutet: es gibt noch keinen Konsens. Dann nicht umsetzen, sondern die offene Frage
|
||||
benennen. Wird trotzdem umgesetzt, sind die Annahmen im Pull Request zu kennzeichnen.
|
||||
3. **Ziel, Abnahmekriterien und Grenzen in drei Sätzen zurückspiegeln**, bevor die erste Datei geöffnet wird.
|
||||
Steht im Ticket kein Abnahmekriterium, ist das die erste Rückfrage — nicht der erste Commit.
|
||||
4. Fehlt fachlicher Kontext: `docs/wissen/glossar.md`, bei BPMN-Fragen
|
||||
`.agents/skills/sn-bpmn/Modellierregeln.md`.
|
||||
|
||||
Danach weiter mit dem Skill `sn-aufgabe-bearbeiten`.
|
||||
|
||||
## Entwurf vorbereiten
|
||||
|
||||
```bash
|
||||
python scripts/gitea.py entwurf issue Projektwissen # neues Issue
|
||||
python scripts/gitea.py entwurf kommentar Projektwissen 12 # Kommentar zu Issue 12
|
||||
```
|
||||
|
||||
Das legt eine Datei unter `entwuerfe/` an (nicht versioniert) und nennt die Gitea-URL zum Absenden. Vorgehen:
|
||||
|
||||
1. Entwurf anlegen lassen.
|
||||
2. Text ausfüllen — bei Issues nach `docs/vorlagen/issue.md`: Ziel, Betroffen, fachlicher Hintergrund,
|
||||
Abnahmekriterien, Nicht Teil dieses Issues.
|
||||
3. Dem Menschen den Pfad der Datei und die Ziel-URL nennen.
|
||||
4. **Dort aufhören.** Nicht versuchen, den Text per `curl`, API-Token oder auf anderem Weg abzusenden.
|
||||
|
||||
Ein guter Issue-Entwurf ist so geschrieben, dass ein anderer Agent ihn ohne Rückfragen umsetzen kann: ein
|
||||
beobachtbares Ziel, benannte Dateien, klare Abgrenzung.
|
||||
|
||||
## Wenn Gitea nicht erreichbar ist
|
||||
|
||||
Das Skript meldet das im Klartext. Mögliche Ursachen: kein Netz, Proxy nicht gesetzt (`HTTPS_PROXY`), Server
|
||||
nicht verfügbar. Dann **nicht raten**, was im Ticket steht — mit der Arbeit warten oder den Menschen um den
|
||||
Ticketinhalt bitten.
|
||||
|
||||
## Grenzen
|
||||
|
||||
- Keine Zugangsdaten, keine personenbezogenen Daten, keine echten Verfahrensdaten und keine internen
|
||||
Originalunterlagen in Ticket-Texten.
|
||||
- Keine Ticket-Inhalte in Zusammenfassungen erfinden oder ergänzen. Was nicht im Ticket steht, ist eine offene
|
||||
Frage, keine Annahme.
|
||||
- Labels, Zuweisungen, Meilensteine und Status ändert der Mensch im Browser.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: sn-uebergabe
|
||||
description: Aufgabe sauber abschließen oder pausieren – Übergabenotiz nach Vorlage schreiben, STATUS.md und Wissensbasis (fallstricke, glossar, entscheidungen, sn-bpmn-Doku) nachziehen, damit die nächste Person oder der nächste Agent nicht neu erkunden muss. Verwenden am Ende jeder Aufgabe, vor dem PR, bei Sitzungsende oder "Übergabe".
|
||||
---
|
||||
|
||||
# Übergabe schreiben und Wissen sichern
|
||||
|
||||
## 1. Übergabenotiz (immer)
|
||||
Vorlage `docs/vorlagen/uebergabe.md` ausfüllen; sie gehört in die PR-Beschreibung (bei Pausen: als Kommentar im Issue).
|
||||
Kurz und konkret: Stand, geänderte Dateien mit Grund, Nachweis (Testzeile, PICTURE-Import), Annahmen, nächste Schritte,
|
||||
und die zwei bis drei Dateien, mit denen es weitergeht.
|
||||
|
||||
## 2. Wissensbasis prüfen – nur was **nicht aus dem Code ablesbar** ist
|
||||
| Frage | Wenn ja → |
|
||||
|---|---|
|
||||
| Hat etwas überraschend Zeit gekostet (PICTURE-Import, Layoutfehler, Werkzeugfalle)? | `docs/wissen/fallstricke.md`, passende Rubrik, oben, mit Datum und Quelle |
|
||||
| Hat sich die Markdown-Syntax, ein Element oder eine Modellierregel geändert? | `.agents/skills/sn-bpmn/Anleitung.md`, `Elementkatalog.md` bzw. `Modellierregeln.md` |
|
||||
| Fachbegriff musste erklärt werden? | `docs/wissen/glossar.md` |
|
||||
| Hat das Team etwas entschieden? | neue Datei `docs/entscheidungen/NNNN-<thema>.md` nach Vorlage im dortigen README, Tabelle im README ergänzen |
|
||||
| Nichts davon? | In der Übergabenotiz „Wissensbasis: nichts Neues“ |
|
||||
|
||||
## 3. `docs/status/STATUS.md`
|
||||
- Zeile in „Wer arbeitet woran“ aktualisieren; bei Merge Eintrag unter „Erledigt“; erledigte Baustellen streichen.
|
||||
- Datum im Kopf aktualisieren.
|
||||
|
||||
## 4. Werkzeug-Gedächtnis
|
||||
Notizen aus der Claude-Auto-Memory, die andere brauchen, jetzt in die Dateien oben
|
||||
übertragen. Lokale Pfade und persönliche Vorlieben bleiben lokal (`CLAUDE.local.md`, gitignored).
|
||||
|
||||
## 5. Vor dem Beenden
|
||||
- Zwischenstand committet (auch WIP-Commit auf dem Feature-Branch ist besser als ungesicherte Änderungen).
|
||||
- Offene Fragen im Issue notiert, nicht nur im Chat.
|
||||
Reference in New Issue
Block a user