Vertrag für ein Fachplugin (Muster: `Plugin_SN_Widmung/main.py`):
- Paketname beginnt mit `sn_` (nur dann ruft `sn_basis` das `initGui()` nach).
-`initGui()` holt `plugins.get("sn_basis")`; ohne `basis.ui` nichts tun (QGIS lädt Plugins in nicht garantierter Reihenfolge; `sn_basis` ruft später nach).
- Dock-Objektname `sn_dock_<pluginname klein>`; nur ein `sn_dock_*` ist gleichzeitig sichtbar.
-`unload()` muss Dock entfernen und Action abmelden, sonst bleiben Reste in Menü/Toolbar.
## 2. DockWidget und Tabs
- Fachplugin-Dock erbt von `sn_basis.ui.base_dockwidget.BaseDockWidget`; Klassenattribut `tabs = [TabKlasse, ...]`.
- Tab-Klasse: `QWidget`-Unterklasse mit Klassenattribut `tab_title`; Konstruktor `Tab(parent)`.
Optional `set_services(pruefmanager=..., data_grabber=...)` (wird von Fachplugin-Docks aufgerufen) und `cleanup()` (wird beim Schließen des Docks aufgerufen – Signale trennen!).
- Gemeinsame Tabs aus `sn_basis`: `ui.tabs.print_tab.PrintTab` (Druck), `ui.tabs.settings_tab.SettingsTab` (Projekteigenschaften).
-`dock.action` = Referenz auf die Toolbar-Action; `BaseDockWidget.closeEvent` setzt sie zurück.
| `sn_abdeckungslayer` ⚠ | PrintTab/PrintLogic | Druck (Atlas) | Atlas-Abdeckungslayer. **Schlüssel enthält bereits `sn_` → tatsächliche Variable `sn_sn_abdeckungslayer`**; funktioniert, ist aber inkonsistent |
| `verfahrensgebietslayer` ⚠ | `print_logic.py` | `print_logic.py` | **Zweite Schreibweise neben `verfahrensgebiet_layer`**– vermutlich Altlast, beim Refactoring zusammenführen |
Benutzer-/Maschinendaten außerhalb des Projekts: `QSettings` in `sn_plan41` (`mail`, `api_key` der VLN-API).
## 4. Prüf- und Entscheidungsmodell
-`pruef_ergebnis(ok: bool, meldung, aktion: PruefAktion, kontext)` ist das einheitliche Rückgabeobjekt aller Prüfer (`Dateipruefer`, `Layerpruefer`, `Linkpruefer`, `Stilpruefer`, `Datenbankpruefer`) und vieler Fachfunktionen.
-`PruefAktion` ist ein `Literal` mit Codes wie `datei_nicht_gefunden`, `layer_leer`, `datei_existiert_ueberschreiben`, `verfahrensgebiet_ergaenzen`. Neue Codes dort ergänzen, nicht als freie Strings verwenden.
-`Pruefmanager(ui_modus="qgis")`: `verarbeite(ergebnis)` entscheidet je Aktion (Dialog, Meldung, Standardwert). Mit `ui_modus != "qgis"` (Tests) werden Standardantworten ohne Dialog geliefert.
- Fachplugins erhalten **eine**`Pruefmanager`- und **eine**`DataGrabber`-Instanz aus `main.py` (Konstruktor) und reichen sie an Tabs weiter.
## 5. Datenpipeline Plan41 (Fachdaten aus Linkliste)
- Das zentrale Repository `Linkliste` (`linkliste.csv`: `id;gruppe;ebene;inhalt;url;provider;stil`) ist **nicht** angebunden → zwei Wahrheiten, siehe `fallstricke.md`.
- Die einzige Rückwärtsabhängigkeit `sn_basis → sn_plan41` ist der Lazy-Import von `Listenauswerter` in `DataGrabber._process_excel_source` (seit PR #57 abgesichert, damit `sn_verfahrensgebiet` ohne `sn_plan41` lädt).
## 6. Verfahrensgebiet-Abläufe (Fachplugin)
- Zentrale Kennung des Verfahrens: Projektvariable `verfahrensnummer` (VKZ) und/oder `landkreis_kennzahl`.
- ALKIS-Quellen: vereinfachtes ALKIS (`.../public_alkis/vereinf/wfs`) für Gemarkungen/BROR, ALKIS-SF (`.../public_alkis/sf/wfs`) für Flurstücke und `PunktortTA`.
- Layer-Namenskonventionen (String-Konstanten in den Modulen): `BROR_LAYER_NAME`, `FLST_LAYER_NAME`, `JOINED_LAYER_NAME`, „Verfahrensgebiet“, „Neuordnungsgebiet (NoBIS)“, „Neuvermessungsgebiet (NoBIS)“. Andere Module finden Layer **über den Namen**– Umbenennungen sind Schnittstellenänderungen.
- Ablaufdokumentation mit Diagrammen: `Plugin_SN_Verfahrensgebiet/docs/`.
## 7. VLN-Karten-API (Plan41)
- Basis-URL `https://api.flurneuordnung-sachsen.de/v2`; Login per E-Mail/Passwort, danach Header `X-API-Key`.
- Laden: Verfahren (VKZ) → Plan41-GeoJSON → Memory-Layer je Geometriefamilie (`vln_layer_manager`). Upload: alle Teillayer → eine FeatureCollection, `PUT` ersetzt den Serverbestand der VKZ vollständig.
- Arbeits-CRS EPSG:25833.
## 8. Mock-Modus (Tests ohne QGIS)
- Jeder Wrapper versucht den Import von `qgis`/`PyQt6`/`PyQt5`; schlägt das fehl, definiert er `_Mock*`-Klassen. Kennzeichen: `qgiscore_wrapper.QGIS_AVAILABLE`, `qt_wrapper.QT_VERSION == 0`.
- Tests importieren Module über den Paketnamen (`from sn_basis...`), deshalb müssen die Repos unter ihrem Paketnamen erreichbar sein → `scripts/run_tests.py` erledigt das über ein temporäres Verzeichnis.
- Tests, die echtes Layer- oder Netzwerkverhalten brauchen, laufen nur in QGIS (`tests/test_qgis.bat`, Windows).
## 9. Release-Vertrag
```mermaid
flowchart LR
T["Tag v26.9.1-u auf Plugin-Repo"] --> W1["Plugin-Workflow release.yaml"]
C --> M["metadata.template → metadata.txt:<br/>version, experimental, repository-URL"]
M --> Z["rsync ohne .git .gitea .gitignore __pycache__ → ZIP_FOLDER.zip"]
Z --> R["Gitea-Release + Asset"]
R --> X["plugins-<kanal>.xml aktualisieren"]
```
- Tagformat `v<JJ.M.N>-u` (unstable), `-t` (testing), ohne Suffix (stable). Version = Jahr.Monat.Laufnummer.
- **Gebaut wird der Kopf des Kanal-Branches, nicht der getaggte Commit.** Tag also nur auf dem aktuellen Kopf von `unstable` bzw. `testing` setzen.
- Paketname im ZIP kommt aus der Gitea-Repo-Variable `ZIP_FOLDER` (z. B. `sn_basis`).
-`metadata.txt` im Repo ist nur ein Abbild des letzten Builds; gepflegt wird `.gitea/templates/metadata.template`.
- Release-Body = Block über dem ersten `---` in `changelog.txt`.
- Neue Workspace-Dateien (`AGENTS.md`, `CLAUDE.md`, `.agents/`, `.claude/`, `docs/` der Repos) landen derzeit im ZIP → Excludes im zentralen Workflow ergänzen (offener Punkt in `STATUS.md`).
## 10. Linkliste-Repository
`linkliste.csv` (Semikolon, Spalten `id;gruppe;ebene;inhalt;url;provider;stil`) → Gitea Action → `linkliste.xml/json/yaml` auf `main`. Die Plan41-Laufzeit nutzt stattdessen die mitgelieferte `assets/Linkliste.xlsx` mit abweichenden Spaltennamen.