forked from Daniel/Plugin_SN_Plan41
Button Fachdaten laden hinzugefügt und angebunden (pipeline datagrabber-prüfer-datenlader-datenschreiber)
This commit is contained in:
137
modules/listenauswerter.py
Normal file
137
modules/listenauswerter.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#sn_plan41/modules/listenauswerter.py
|
||||
from typing import Any, Dict, List, Mapping, Optional, Tuple
|
||||
from collections.abc import Mapping as _Mapping
|
||||
# Prüfer-Typen (werden als Instanzen erwartet)
|
||||
from sn_basis.modules.Pruefmanager import Pruefmanager # type: ignore
|
||||
from sn_basis.modules.pruef_ergebnis import pruef_ergebnis
|
||||
from sn_basis.modules.stilpruefer import Stilpruefer # type: ignore
|
||||
|
||||
class Listenauswerter:
|
||||
"""
|
||||
Validiert Zeilen aus einem DataDict, das vom DataGrabber stammt.
|
||||
Erwartet wird die Struktur::
|
||||
|
||||
{"rows": [ {attr}, ... ]}
|
||||
|
||||
Die Linkprüfung entfällt vollständig, da der DataGrabber nur gültige
|
||||
Links liefert. Diese Methode prüft ausschließlich die Konsistenz der
|
||||
Zeilen mit dem erwarteten Datenschema und führt optional eine
|
||||
Stilprüfung durch.
|
||||
"""
|
||||
def __init__(self, pruefmanager, stil_pruefer):
|
||||
""" Parameters
|
||||
----------
|
||||
pruefmanager: Instanz des Pruefmanagers, der pruef_ergebnis verarbeitet.
|
||||
stil_pruefer: Instanz des Stilpruefers, der Stildateien prüft.
|
||||
"""
|
||||
self.pruefmanager = pruefmanager
|
||||
self.stil_pruefer = stil_pruefer
|
||||
|
||||
def validate_rows(
|
||||
self,
|
||||
data_dict: Dict[str, List[Mapping[str, Any]]]
|
||||
) -> Tuple[Dict[str, List[Mapping[str, Any]]], List[Any]]:
|
||||
"""
|
||||
Validiert die Zeilen aus ``data_dict`` anhand des erwarteten Schemas.
|
||||
|
||||
Erwartete Felder pro Zeile
|
||||
--------------------------
|
||||
Pflichtfelder:
|
||||
- ``ident``: eindeutige Kennung
|
||||
- ``Link``: bereits geprüfter Link (vom DataGrabber garantiert gültig)
|
||||
- ``Provider``: Datenquelle (wird in Großbuchstaben normalisiert)
|
||||
|
||||
Optionale Felder:
|
||||
- ``Inhalt``: thematische Beschreibung
|
||||
- ``Stildatei``: Pfad zur Stildatei (falls vorhanden)
|
||||
|
||||
Verhalten
|
||||
---------
|
||||
- Zeilen, denen Pflichtfelder fehlen oder deren Werte leer sind,
|
||||
werden verworfen.
|
||||
- ``Provider`` wird in Großbuchstaben normalisiert.
|
||||
- Wenn ``Stildatei`` vorhanden ist, wird sie durch
|
||||
``self.stil_pruefer.pruefe(...)`` geprüft.
|
||||
- Bei OK bleibt der Wert erhalten.
|
||||
- Bei nicht OK wird ``Stildatei`` auf ``None`` gesetzt und das
|
||||
verarbeitete Prüfergebnis gesammelt.
|
||||
- Alle Prüfergebnisse werden durch ``self.pruefmanager.verarbeite(...)``
|
||||
geleitet und in der Rückgabe gesammelt.
|
||||
|
||||
Rückgabe
|
||||
--------
|
||||
Tuple[Dict[str, List[Mapping[str, Any]]]], List[Any]]
|
||||
- ``valid_data_dict``: enthält nur Zeilen, die dem Schema entsprechen
|
||||
- ``processed_results``: Liste der verarbeiteten Prüfergebnisse
|
||||
|
||||
Hinweise
|
||||
--------
|
||||
- Diese Methode führt **keine Linkprüfung** durch.
|
||||
- Die Verantwortung für die Linkvalidität liegt vollständig beim DataGrabber.
|
||||
- Die Methode verändert die Zeilen nur minimal (Provider‑Normalisierung,
|
||||
Stildatei ggf. auf ``None``).
|
||||
"""
|
||||
|
||||
processed_results: List[Any] = []
|
||||
valid_rows: List[Mapping[str, Any]] = []
|
||||
|
||||
# Grundstruktur prüfen
|
||||
if not isinstance(data_dict, dict):
|
||||
return {"rows": []}, processed_results
|
||||
|
||||
rows = data_dict.get("rows", [])
|
||||
if not isinstance(rows, (list, tuple)):
|
||||
return {"rows": []}, processed_results
|
||||
|
||||
for raw in rows:
|
||||
# Sicherstellen, dass raw ein Mapping ist
|
||||
if not isinstance(raw, _Mapping):
|
||||
continue
|
||||
|
||||
ident = raw.get("ident")
|
||||
inhalt = raw.get("Inhalt")
|
||||
link = raw.get("Link")
|
||||
stildatei = raw.get("Stildatei")
|
||||
provider = raw.get("Provider")
|
||||
|
||||
# Pflichtfelder prüfen
|
||||
if not ident or not link or not provider:
|
||||
# Fehler dokumentieren
|
||||
pe = pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung="Pflichtfelder fehlen oder sind leer",
|
||||
aktion="pflichtfelder_fehlen",
|
||||
kontext=raw,
|
||||
)
|
||||
processed_results.append(self.pruefmanager.verarbeite(pe))
|
||||
continue
|
||||
|
||||
# Provider normalisieren
|
||||
provider_norm = str(provider).upper()
|
||||
|
||||
# Stildatei prüfen (falls vorhanden)
|
||||
if stildatei:
|
||||
pe_stil = self.stil_pruefer.pruefe(stildatei)
|
||||
processed_stil = self.pruefmanager.verarbeite(pe_stil)
|
||||
|
||||
if not getattr(processed_stil, "ok", False):
|
||||
processed_results.append(processed_stil)
|
||||
stildatei_value: Optional[str] = None
|
||||
else:
|
||||
stildatei_value = stildatei
|
||||
else:
|
||||
stildatei_value = None
|
||||
|
||||
# Validierte Zeile zusammenbauen
|
||||
validated_row = {
|
||||
"ident": ident,
|
||||
"Inhalt": inhalt,
|
||||
"Link": link,
|
||||
"Stildatei": stildatei_value,
|
||||
"Provider": provider_norm,
|
||||
}
|
||||
|
||||
valid_rows.append(validated_row)
|
||||
|
||||
result_dict = {"rows": valid_rows}
|
||||
return result_dict, processed_results
|
||||
@@ -155,99 +155,4 @@ class TabALogic:
|
||||
layer_type = get_layer_type(layer)
|
||||
return layer_type == "vector"
|
||||
|
||||
# -------------------------------
|
||||
# Validierung und Filterung von data_dict
|
||||
# -------------------------------
|
||||
|
||||
def validate_and_filter_rows(self, data_dict: DataDict) -> Tuple[DataDict, List[Any]]:
|
||||
"""
|
||||
Validiert und filtert die Zeilen aus `data_dict`.
|
||||
|
||||
Erwartete Struktur von `data_dict`: {'rows': [ {attr}, ... ]}.
|
||||
|
||||
Für jede Zeile werden die folgenden Attribute gelesen:
|
||||
ident = attr['ident'] (Pflicht)
|
||||
thema = attr['Inhalt'] (optional)
|
||||
url = attr['Link'] (Pflicht)
|
||||
stildatei = attr['Stildatei'] (optional)
|
||||
provider = attr['Provider'] (Pflicht, wird uppercased)
|
||||
|
||||
Verhalten
|
||||
- Pflichtfelder (ident, Link, Provider) müssen vorhanden und nicht-leer sein,
|
||||
sonst wird die Zeile verworfen.
|
||||
- Wenn Link nicht leer ist, wird self.link_pruefer.pruefe(url) aufgerufen.
|
||||
- Ist das Ergebnis ok: Zeile wird behalten.
|
||||
- Ist das Ergebnis nicht ok: Zeile wird verworfen; das verarbeitete
|
||||
pruef_ergebnis wird gesammelt.
|
||||
- Wenn Stildatei nicht leer ist, wird self.stil_pruefer.pruefe(stildatei) aufgerufen.
|
||||
- Ist das Ergebnis ok: der Wert bleibt erhalten.
|
||||
- Ist das Ergebnis nicht ok: das Feld `Stildatei` wird in der zurückgegebenen
|
||||
Zeile auf None gesetzt; das verarbeitete pruef_ergebnis wird gesammelt.
|
||||
- Alle pruef_ergebnis-Objekte werden an self.pruefmanager.verarbeite(...) übergeben.
|
||||
Die verarbeiteten Ergebnisse werden in der Rückgabe-Liste gesammelt.
|
||||
|
||||
Rückgabe
|
||||
- (valid_data_dict, processed_results)
|
||||
valid_data_dict: {'rows': [valid_row1, valid_row2, ...]}
|
||||
processed_results: Liste der vom Pruefmanager verarbeiteten pruef_ergebnis-Objekte
|
||||
"""
|
||||
processed_results: List[Any] = []
|
||||
valid_rows: List[Row] = []
|
||||
|
||||
# Grundstruktur prüfen
|
||||
if not isinstance(data_dict, dict):
|
||||
return {"rows": []}, processed_results
|
||||
|
||||
rows = data_dict.get("rows", [])
|
||||
if not isinstance(rows, (list, tuple)):
|
||||
return {"rows": []}, processed_results
|
||||
|
||||
for raw in rows:
|
||||
# Sicherstellen, dass raw ein Mapping ist
|
||||
if not isinstance(raw, _Mapping):
|
||||
continue
|
||||
|
||||
ident = raw.get("ident")
|
||||
inhalt = raw.get("Inhalt")
|
||||
link = raw.get("Link")
|
||||
stildatei = raw.get("Stildatei")
|
||||
provider = raw.get("Provider")
|
||||
|
||||
# Pflichtfelder prüfen
|
||||
if not ident or not link or not provider:
|
||||
continue
|
||||
|
||||
# Provider normalisieren
|
||||
provider_norm = str(provider).upper()
|
||||
|
||||
# Link prüfen
|
||||
pe_link = self.link_pruefer.pruefe(link)
|
||||
processed_link = self.pruefmanager.verarbeite(pe_link)
|
||||
if not getattr(processed_link, "ok", False):
|
||||
processed_results.append(processed_link)
|
||||
continue # Zeile verwerfen
|
||||
|
||||
# Stil prüfen (falls vorhanden)
|
||||
if stildatei:
|
||||
pe_stil = self.stil_pruefer.pruefe(stildatei)
|
||||
processed_stil = self.pruefmanager.verarbeite(pe_stil)
|
||||
if not getattr(processed_stil, "ok", False):
|
||||
processed_results.append(processed_stil)
|
||||
stildatei_value: Optional[str] = None
|
||||
else:
|
||||
stildatei_value = stildatei
|
||||
else:
|
||||
stildatei_value = None
|
||||
|
||||
# Validierte Zeile zusammenbauen
|
||||
validated_row: Row = {
|
||||
"ident": ident,
|
||||
"Inhalt": inhalt,
|
||||
"Link": link,
|
||||
"Stildatei": stildatei_value,
|
||||
"Provider": provider_norm,
|
||||
}
|
||||
valid_rows.append(validated_row)
|
||||
|
||||
result_dict: DataDict = {"rows": valid_rows}
|
||||
return result_dict, processed_results
|
||||
|
||||
158
ui/tab_a_ui.py
158
ui/tab_a_ui.py
@@ -16,7 +16,7 @@ from sn_basis.functions.qt_wrapper import (
|
||||
ArrowRight,
|
||||
SizePolicyPreferred,
|
||||
SizePolicyMaximum,
|
||||
ComboBox,
|
||||
QComboBox,
|
||||
)
|
||||
from sn_basis.functions.qgisui_wrapper import QgsFileWidget, QgsMapLayerComboBox
|
||||
from sn_basis.functions.qgiscore_wrapper import QgsProject, QgsMapLayerProxyModel
|
||||
@@ -30,6 +30,8 @@ from sn_basis.modules.Pruefmanager import Pruefmanager
|
||||
from sn_basis.modules.DataGrabber import DataGrabber
|
||||
from sn_basis.modules.linkpruefer import Linkpruefer
|
||||
from sn_basis.modules.stilpruefer import Stilpruefer
|
||||
from sn_basis.modules.Datenschreiber import Datenschreiber
|
||||
|
||||
# Raumfilter-Optionen
|
||||
RAUMFILTER_VAR = "Raumfilter"
|
||||
RAUMFILTER_OPTIONS = ("Verfahrensgebiet", "Pufferlayer", "ohne")
|
||||
@@ -62,6 +64,8 @@ class TabA(QWidget):
|
||||
self.pruefmanager = Pruefmanager(ui_modus="qgis")
|
||||
|
||||
# DataGrabber-Instanz (synchroner Aufruf; Prüfungen übernimmt Pruefmanager/Pruefer)
|
||||
# Hinweis: DataGrabber erwartet ggf. Prüfer-Objekte; hier werden sie nicht übergeben,
|
||||
# da TabALogic / Pruefmanager diese zur Laufzeit bereitstellen können.
|
||||
self.data_grabber = DataGrabber(pruefmanager=self.pruefmanager)
|
||||
|
||||
# Platzhalter, die vom Plugin oder Nutzer gesetzt werden können
|
||||
@@ -72,7 +76,7 @@ class TabA(QWidget):
|
||||
self.lokale_linkliste: Optional[str] = None
|
||||
|
||||
# UI-Widget-Referenz für Raumfilter
|
||||
self._raumfilter_combo: Optional[ComboBox] = None
|
||||
self._raumfilter_combo: Optional[QComboBox] = None
|
||||
|
||||
if build_ui:
|
||||
self._build_ui()
|
||||
@@ -164,7 +168,7 @@ class TabA(QWidget):
|
||||
|
||||
# Raumfilter-Label + ComboBox (unterhalb der Layer-Auswahl)
|
||||
main_layout.addWidget(QLabel("Raumfilter"))
|
||||
self._raumfilter_combo = ComboBox(self)
|
||||
self._raumfilter_combo = QComboBox(self)
|
||||
# Fülle Optionen (Wrapper stellt addItems bereit)
|
||||
try:
|
||||
self._raumfilter_combo.addItems(list(RAUMFILTER_OPTIONS))
|
||||
@@ -211,8 +215,14 @@ class TabA(QWidget):
|
||||
|
||||
main_layout.addWidget(self._raumfilter_combo)
|
||||
|
||||
# Aktion: Fachdaten laden
|
||||
self.btn_load = QPushButton("Fachdaten laden")
|
||||
# Neuer Button direkt unterhalb der Raumfilter-Combo: "Fachdaten laden"
|
||||
self.btn_pipeline = QPushButton("Fachdaten laden")
|
||||
self.btn_pipeline.setToolTip("Starte Pipeline: Linkliste → DataGrabber → Datenschreiber → Log")
|
||||
self.btn_pipeline.clicked.connect(self._on_run_pipeline)
|
||||
main_layout.addWidget(self.btn_pipeline)
|
||||
|
||||
# (Optional) bestehender Button weiter unten für alternative Platzierung
|
||||
self.btn_load = QPushButton("Fachdaten laden (alt)")
|
||||
self.btn_load.clicked.connect(self._on_load_fachdaten)
|
||||
main_layout.addWidget(self.btn_load)
|
||||
|
||||
@@ -307,11 +317,8 @@ class TabA(QWidget):
|
||||
|
||||
def _on_load_fachdaten(self) -> None:
|
||||
"""
|
||||
Platzhalter-Handler für 'Fachdaten laden'.
|
||||
|
||||
Keine Prüfungen oder Exception-Handling hier. Die fachliche Prüfung
|
||||
und Fehlerbehandlung erfolgen zur Laufzeit durch den Pruefmanager und
|
||||
die Prüfer, die vom DataGrabber verwendet werden.
|
||||
Bestehender, kompakter Handler für 'Fachdaten laden'.
|
||||
Führt Dateiprüfung und DataGrabber.run aus (wie zuvor).
|
||||
"""
|
||||
pfad = self.file_widget.filePath()
|
||||
|
||||
@@ -327,13 +334,130 @@ class TabA(QWidget):
|
||||
except Exception:
|
||||
zielpfad = ergebnis.kontext
|
||||
|
||||
self.data_grabber.run(
|
||||
attributes_list=self._attributes_list,
|
||||
pufferlayer=self._pufferlayer,
|
||||
zielpfad=zielpfad,
|
||||
temporaer=(ergebnis.aktion == "temporaer_erzeugen"),
|
||||
temporaer_erlaubt=True,
|
||||
)
|
||||
# DataGrabber.run wird wie bisher aufgerufen; Signatur kann variieren.
|
||||
# Wir übergeben die bekannten Parameter; DataGrabber ist verantwortlich,
|
||||
# die Linkliste intern zu verwenden (z. B. aus TabALogic oder über Argumente).
|
||||
try:
|
||||
self.data_grabber.run(
|
||||
attributes_list=self._attributes_list,
|
||||
pufferlayer=self._pufferlayer,
|
||||
zielpfad=zielpfad,
|
||||
temporaer=(ergebnis.aktion == "temporaer_erzeugen"),
|
||||
temporaer_erlaubt=True,
|
||||
)
|
||||
except Exception:
|
||||
# Fehler werden vom Pruefmanager / DataGrabber protokolliert
|
||||
pass
|
||||
|
||||
def _on_run_pipeline(self) -> None:
|
||||
"""
|
||||
Neuer, vollständiger Pipeline-Handler, der:
|
||||
- Dateiprüfung (Verfahrens-DB)
|
||||
- DataGrabber-Ausführung (mit Linkliste)
|
||||
- Datenschreiber (schreiben, laden)
|
||||
- Logschreiber (Log-Datei)
|
||||
ausführt und Ergebnisse über den Pruefmanager protokolliert.
|
||||
"""
|
||||
# 1) Verfahrens-DB prüfen / ermitteln
|
||||
pfad = self.file_widget.filePath()
|
||||
pruefer = Dateipruefer(pfad=pfad, temporaer_erlaubt=True)
|
||||
ergebnis = pruefer.pruefe()
|
||||
ergebnis = self.pruefmanager.verarbeite(ergebnis)
|
||||
|
||||
zielpfad = None
|
||||
if ergebnis.kontext is not None:
|
||||
try:
|
||||
zielpfad = str(ergebnis.kontext)
|
||||
except Exception:
|
||||
zielpfad = ergebnis.kontext
|
||||
|
||||
if not zielpfad:
|
||||
# Falls kein Zielpfad ermittelt werden konnte, protokollieren und abbrechen
|
||||
pe_err = pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung="Kein gültiger Speicherort für Verfahrens-DB ermittelt; Pipeline abgebrochen.",
|
||||
aktion="kein_dateipfad",
|
||||
kontext={},
|
||||
)
|
||||
self.pruefmanager.verarbeite(pe_err)
|
||||
return
|
||||
|
||||
# 2) DataGrabber ausführen
|
||||
# Erwartung: DataGrabber.run gibt (daten_dict, processed_results) zurück.
|
||||
# Falls die konkrete Implementierung anders ist, passt dieser Aufruf entsprechend an.
|
||||
try:
|
||||
run_result = self.data_grabber.run(
|
||||
attributes_list=self._attributes_list,
|
||||
pufferlayer=self._pufferlayer,
|
||||
zielpfad=zielpfad,
|
||||
temporaer=(ergebnis.aktion == "temporaer_erzeugen"),
|
||||
temporaer_erlaubt=True,
|
||||
)
|
||||
except Exception as exc:
|
||||
pe_err = pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung=f"DataGrabber-Fehler: {exc}",
|
||||
aktion="datenabruf",
|
||||
kontext={},
|
||||
)
|
||||
self.pruefmanager.verarbeite(pe_err)
|
||||
return
|
||||
|
||||
# Normalisiere Rückgabe: unterstütze sowohl None, einzelnes dict oder Tuple
|
||||
daten_dict = {}
|
||||
processed_results = []
|
||||
if isinstance(run_result, tuple) and len(run_result) >= 2:
|
||||
daten_dict, processed_results = run_result[0], run_result[1]
|
||||
elif isinstance(run_result, dict) and "daten" in run_result:
|
||||
daten_dict = run_result
|
||||
# processed_results bleiben leer oder werden vom DataGrabber intern protokolliert
|
||||
else:
|
||||
# Wenn run() nichts zurückgibt, versuchen wir, auf DataGrabber intern gespeicherte Ergebnisse zuzugreifen
|
||||
daten_dict = getattr(self.data_grabber, "last_daten_dict", {}) or {}
|
||||
processed_results = getattr(self.data_grabber, "last_processed_results", []) or []
|
||||
|
||||
# 3) Datenschreiber: Daten in GPKG schreiben
|
||||
try:
|
||||
ds = Datenschreiber(pruefmanager=self.pruefmanager, gpkg_path=zielpfad)
|
||||
layer_infos = ds.schreibe_Daten(daten_dict=daten_dict, processed_results=processed_results, speicherort=zielpfad)
|
||||
except Exception as exc:
|
||||
pe_err = pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung=f"Fehler beim Schreiben der Daten: {exc}",
|
||||
aktion="save_exception",
|
||||
kontext={},
|
||||
)
|
||||
self.pruefmanager.verarbeite(pe_err)
|
||||
return
|
||||
|
||||
# 4) Layer laden und Stile anwenden
|
||||
try:
|
||||
ds.lade_Layer(layer_infos)
|
||||
except Exception as exc:
|
||||
pe_warn = pruef_ergebnis(
|
||||
ok=True,
|
||||
meldung=f"Fehler beim Laden der Layer: {exc}",
|
||||
aktion="layer_nicht_gefunden",
|
||||
kontext={},
|
||||
)
|
||||
self.pruefmanager.verarbeite(pe_warn)
|
||||
|
||||
# 5) Log schreiben
|
||||
try:
|
||||
log_path = ds.schreibe_log(processed_results=processed_results, speicherort=zielpfad)
|
||||
# Optional: zeige Erfolgsmeldung
|
||||
try:
|
||||
QMessageBox.information(self, "Pipeline abgeschlossen", f"Pipeline erfolgreich abgeschlossen.\nLog: {log_path}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
pe_warn = pruef_ergebnis(
|
||||
ok=True,
|
||||
meldung=f"Log konnte nicht geschrieben werden: {exc}",
|
||||
aktion="standarddatei_vorschlagen",
|
||||
kontext={},
|
||||
)
|
||||
self.pruefmanager.verarbeite(pe_warn)
|
||||
|
||||
# ---------------------------------------------------------
|
||||
# Raumfilter Callback
|
||||
|
||||
Reference in New Issue
Block a user