Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9f65893f0 | ||
|
|
dcaca4c883 | ||
|
|
dbd5fca40b | ||
|
|
ecb1e49d8d | ||
|
|
ad6c7ea3e1 | ||
|
|
1bb425e419 |
@@ -1,4 +1,8 @@
|
||||
---
|
||||
Version 26.04.1-unstable:
|
||||
---
|
||||
Version 26.3.3-unstable:
|
||||
---
|
||||
Version 26.3.2-unstable:
|
||||
---
|
||||
Version 26.3.1-testing:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[general]
|
||||
version=26.3.2-unstable
|
||||
version=26.04.1-unstable
|
||||
name=LNO Sachsen | Plan41
|
||||
description=Plugin zum Erzeugen der Pläne nach §38 und §41
|
||||
author=Daniel Helbig, Michael Otto
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from typing import Any, cast
|
||||
|
||||
from sn_plan41.ui.tab_a_logic import TabALogic
|
||||
|
||||
|
||||
class _DummyPruefmanager:
|
||||
def __init__(self) -> None:
|
||||
self.hinweise: list[tuple[str, str]] = []
|
||||
|
||||
def verarbeite(self, ergebnis):
|
||||
return ergebnis
|
||||
|
||||
def zeige_hinweis(self, titel: str, meldung: str) -> None:
|
||||
self.hinweise.append((titel, meldung))
|
||||
|
||||
|
||||
class _DummyDateiErgebnis:
|
||||
def __init__(self, ok: bool = True, kontext: str = "C:/tmp/v_db.gpkg") -> None:
|
||||
self.ok = ok
|
||||
self.kontext = kontext
|
||||
|
||||
|
||||
class _DummyProgress:
|
||||
def __init__(self) -> None:
|
||||
self.closed = False
|
||||
|
||||
def set_total(self, _value: int) -> None:
|
||||
pass
|
||||
|
||||
def set_value(self, _value: int) -> None:
|
||||
pass
|
||||
|
||||
def set_label(self, _value: str) -> None:
|
||||
pass
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class _DummyProject:
|
||||
def mapLayer(self, _layer_id: str):
|
||||
return None
|
||||
|
||||
|
||||
class _DummyGrabber:
|
||||
pass
|
||||
|
||||
|
||||
class TestTabAPreconditions(unittest.TestCase):
|
||||
|
||||
def _make_logic(self) -> tuple[TabALogic, _DummyPruefmanager]:
|
||||
pruefmanager = _DummyPruefmanager()
|
||||
logic = TabALogic(
|
||||
pruefmanager=cast(Any, pruefmanager),
|
||||
link_pruefer=cast(Any, object()),
|
||||
stil_pruefer=cast(Any, object()),
|
||||
)
|
||||
return logic, pruefmanager
|
||||
|
||||
def test_get_verfahrensgebiet_layer_shows_hint_when_layer_id_missing(self):
|
||||
logic, pruefmanager = self._make_logic()
|
||||
|
||||
with patch.object(logic, "load_verfahrensgebiet_layer_id", return_value=None):
|
||||
layer = logic._get_verfahrensgebiet_layer()
|
||||
|
||||
self.assertIsNone(layer)
|
||||
self.assertEqual(len(pruefmanager.hinweise), 1)
|
||||
self.assertIn("Verfahrensgebiet", pruefmanager.hinweise[0][0])
|
||||
|
||||
@patch("sn_plan41.ui.tab_a_logic.QgsProject.instance", return_value=_DummyProject())
|
||||
def test_get_verfahrensgebiet_layer_shows_hint_when_layer_not_in_project(self, _mock_project_instance):
|
||||
logic, pruefmanager = self._make_logic()
|
||||
|
||||
with patch.object(logic, "load_verfahrensgebiet_layer_id", return_value="layer_404"):
|
||||
layer = logic._get_verfahrensgebiet_layer()
|
||||
|
||||
self.assertIsNone(layer)
|
||||
self.assertEqual(len(pruefmanager.hinweise), 1)
|
||||
self.assertIn("Verfahrensgebiet", pruefmanager.hinweise[0][0])
|
||||
|
||||
@patch("sn_plan41.ui.tab_a_logic.Dateipruefer")
|
||||
def test_pipeline_aborts_and_shows_hint_when_verfahrensgebiet_missing(
|
||||
self,
|
||||
mock_dateipruefer,
|
||||
):
|
||||
logic, pruefmanager = self._make_logic()
|
||||
logic.data_grabber = cast(Any, _DummyGrabber())
|
||||
|
||||
mock_dateipruefer.return_value.pruefe.return_value = _DummyDateiErgebnis(ok=True, kontext="C:/tmp/v_db.gpkg")
|
||||
|
||||
progress = _DummyProgress()
|
||||
|
||||
with patch("sn_plan41.ui.tab_a_logic.create_progress_dialog", return_value=progress):
|
||||
with patch.object(logic, "_resolve_linkliste", return_value="C:/tmp/linkliste.xlsx"):
|
||||
with patch.object(logic, "load_verfahrensgebiet_layer_id", return_value=None):
|
||||
result = logic._on_run_pipeline(
|
||||
source="C:/tmp/v_db.gpkg",
|
||||
linkliste=None,
|
||||
raumfilter="Verfahrensgebiet",
|
||||
)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertEqual(len(pruefmanager.hinweise), 1)
|
||||
self.assertIn("Verfahrensgebiet", pruefmanager.hinweise[0][0])
|
||||
self.assertTrue(progress.closed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+221
-47
@@ -12,6 +12,7 @@ import os
|
||||
import datetime
|
||||
import json
|
||||
import tempfile
|
||||
import html
|
||||
|
||||
from sn_basis.functions.qgiscore_wrapper import (
|
||||
QgsVectorFileWriter,
|
||||
@@ -63,9 +64,88 @@ class TabALogic:
|
||||
self.link_pruefer = link_pruefer
|
||||
self.stil_pruefer = stil_pruefer
|
||||
self.data_grabber: Optional[DataGrabber] = None
|
||||
|
||||
class _SilentPruefmanagerProxy:
|
||||
"""Proxy, der Prüfergebnisse ohne UI-Meldungen verarbeitet."""
|
||||
|
||||
def __init__(self, outer: "TabALogic", fallback: Optional[Any]) -> None:
|
||||
self._outer = outer
|
||||
self._fallback = fallback
|
||||
self.results: List[Any] = []
|
||||
|
||||
def verarbeite(self, ergebnis: Any) -> Any:
|
||||
self.results.append(ergebnis)
|
||||
ok = bool(getattr(ergebnis, "ok", False))
|
||||
if not ok:
|
||||
aktion = getattr(ergebnis, "aktion", "unbekannt")
|
||||
meldung = getattr(ergebnis, "meldung", "")
|
||||
self._outer._log(f"[SILENT-PRUEFMANAGER] {aktion}: {meldung}")
|
||||
return ergebnis
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if self._fallback is not None:
|
||||
return getattr(self._fallback, name)
|
||||
raise AttributeError(name)
|
||||
|
||||
def _log(self, msg: str) -> None:
|
||||
print(f"[TabALogic] {msg}")
|
||||
|
||||
def _create_silent_pruefmanager(self) -> "TabALogic._SilentPruefmanagerProxy":
|
||||
return TabALogic._SilentPruefmanagerProxy(self, self.pruefmanager)
|
||||
|
||||
def _extract_datenabruf_error_maps(self, datenabruf_results: List[Any]) -> Dict[str, Dict[str, str]]:
|
||||
by_ident: Dict[str, str] = {}
|
||||
by_thema: Dict[str, str] = {}
|
||||
|
||||
for result in datenabruf_results or []:
|
||||
if not result:
|
||||
continue
|
||||
context = getattr(result, "kontext", None)
|
||||
if not isinstance(context, dict):
|
||||
continue
|
||||
|
||||
if getattr(result, "aktion", None) == "url_nicht_erreichbar":
|
||||
ident = str(context.get("ident") or "").strip()
|
||||
thema = str(context.get("thema") or "").strip()
|
||||
reason = str(context.get("error") or getattr(result, "meldung", "") or "unbekannter Fehler").strip()
|
||||
if ident and reason:
|
||||
by_ident[ident] = reason
|
||||
if thema and reason:
|
||||
by_thema[thema] = reason
|
||||
|
||||
if getattr(result, "aktion", None) == "datenabruf":
|
||||
fehler_map = context.get("fehler")
|
||||
if isinstance(fehler_map, dict):
|
||||
for thema_key, value in fehler_map.items():
|
||||
thema = str(thema_key or "").strip()
|
||||
reason = str(value or "").strip()
|
||||
if thema and reason and thema not in by_thema:
|
||||
by_thema[thema] = reason
|
||||
|
||||
return {"by_ident": by_ident, "by_thema": by_thema}
|
||||
|
||||
def _zeige_verfahrensgebiet_hinweis(self) -> None:
|
||||
if not self.pruefmanager:
|
||||
return
|
||||
show_hint = getattr(self.pruefmanager, "zeige_hinweis", None)
|
||||
if callable(show_hint):
|
||||
show_hint(
|
||||
"Verfahrensgebiet fehlt",
|
||||
"Kein gueltiger Verfahrensgebiet-Layer vorhanden. "
|
||||
"Bitte zuerst in sn_verfahrensgebiet den Layer ueber 'Aus ALKIS laden' laden "
|
||||
"oder im Dropdown einen gueltigen Layer auswaehlen.",
|
||||
)
|
||||
|
||||
def _close_progress_dialog(self, progress: Optional[Any]) -> None:
|
||||
if progress is None:
|
||||
return
|
||||
close = getattr(progress, "close", None)
|
||||
if callable(close):
|
||||
try:
|
||||
close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# -------------------------------
|
||||
# Verfahrens-Datenbank (Pfad-Management)
|
||||
# -------------------------------
|
||||
@@ -211,6 +291,7 @@ class TabALogic:
|
||||
linkliste_final = self._resolve_linkliste(linkliste)
|
||||
if linkliste_final is None:
|
||||
self._log("Linkliste kann nicht aufgelöst werden")
|
||||
self._close_progress_dialog(progress)
|
||||
return None
|
||||
else:
|
||||
self._log(f"Linkliste final: '{linkliste_final}'")
|
||||
@@ -218,6 +299,7 @@ class TabALogic:
|
||||
raumfilter_layer = self._resolve_raumfilter(raumfilter, final_pfad)
|
||||
if raumfilter in ("Verfahrensgebiet", "Pufferlayer") and raumfilter_layer is None:
|
||||
self._log(f"Raumfilter '{raumfilter}' nicht verfügbar")
|
||||
self._close_progress_dialog(progress)
|
||||
return None
|
||||
|
||||
# 4) Lade-Status initialisieren (funktioniert ab Bestätigung überschreiben/anhängen)
|
||||
@@ -230,7 +312,16 @@ class TabALogic:
|
||||
progress.set_label("Prüfe Eingaben...")
|
||||
|
||||
# 5) Daten aus Linkliste laden und prüfen
|
||||
source_dict, grabber_summary = self.data_grabber.run(linkliste_final)
|
||||
silent_pruefmanager = self._create_silent_pruefmanager()
|
||||
local_data_grabber = DataGrabber(
|
||||
silent_pruefmanager,
|
||||
datei_pruefer_cls=self.data_grabber._datei_pruefer_cls,
|
||||
link_pruefer=self.data_grabber.link_pruefer,
|
||||
layer_pruefer=self.data_grabber.layer_pruefer,
|
||||
stil_pruefer=self.data_grabber.stil_pruefer,
|
||||
excel_importer_cls=self.data_grabber._excel_importer_cls,
|
||||
)
|
||||
source_dict, grabber_summary = local_data_grabber.run(linkliste_final)
|
||||
self._log(f"DataGrabber: {grabber_summary.meldung} [{grabber_summary.aktion}]")
|
||||
|
||||
# DEBUG: detaillierter Status
|
||||
@@ -243,6 +334,7 @@ class TabALogic:
|
||||
if not source_dict.get("rows"):
|
||||
self._log("Keine validen Linkliste-Einträge für Datenabruf")
|
||||
print("[TabALogic] ... STOP: rows:", len(source_dict.get("rows", [])))
|
||||
self._close_progress_dialog(progress)
|
||||
return None
|
||||
|
||||
total_rows = len(source_dict.get("rows", []))
|
||||
@@ -260,7 +352,8 @@ class TabALogic:
|
||||
self._log("Warnung: DataGrabber meldet fehlerhafte Zeilen, fahre mit Validierungsdaten fort")
|
||||
|
||||
# 5) Datenabruf (aus validierten Zeilen)
|
||||
datenabruf = Datenabruf(self.pruefmanager)
|
||||
silent_pruefmanager = self._create_silent_pruefmanager()
|
||||
datenabruf = Datenabruf(silent_pruefmanager)
|
||||
result_dict, datenabruf_results = datenabruf.datenabruf(
|
||||
result_dict=source_dict,
|
||||
raumfilter=raumfilter,
|
||||
@@ -290,7 +383,7 @@ class TabALogic:
|
||||
progress.set_label("Fachdaten laden abgeschlossen. Bitte OK klicken, um den Dialog zu schließen.")
|
||||
|
||||
# 7) Log-Datei schreiben
|
||||
self._write_markdown_log(final_pfad, source_dict, pipeline_context, load_summary)
|
||||
self._write_html_log(final_pfad, source_dict, pipeline_context, load_summary)
|
||||
|
||||
print("=" * 60 + "\n")
|
||||
return pipeline_context
|
||||
@@ -304,7 +397,15 @@ class TabALogic:
|
||||
aborted = False
|
||||
if not rows:
|
||||
self._log("Keine Dienste zum Laden")
|
||||
return
|
||||
return {
|
||||
"row_count": 0,
|
||||
"loaded_count": 0,
|
||||
"skipped_outside": 0,
|
||||
"aborted": False,
|
||||
"row_stats": [],
|
||||
"layer_call_status": {},
|
||||
"raumfilter_name": pipeline_context.get("raumfilter_name", "unbekannt"),
|
||||
}
|
||||
|
||||
final_pfad = pipeline_context.get("source") or ""
|
||||
use_datenschreiber = bool(final_pfad)
|
||||
@@ -334,7 +435,12 @@ class TabALogic:
|
||||
raumfilter_extent = raumfilter_layer.extent()
|
||||
|
||||
temp_layers: List[Any] = []
|
||||
layer_loader = LayerLoader(self.pruefmanager, stil_pruefer=self.stil_pruefer, layer_pruefer=self.link_pruefer)
|
||||
silent_pruefmanager = self._create_silent_pruefmanager()
|
||||
layer_loader = LayerLoader(silent_pruefmanager, stil_pruefer=self.stil_pruefer, layer_pruefer=self.link_pruefer)
|
||||
|
||||
error_maps = self._extract_datenabruf_error_maps(pipeline_context.get("datenabruf_results", []))
|
||||
error_by_ident = error_maps.get("by_ident", {})
|
||||
error_by_thema = error_maps.get("by_thema", {})
|
||||
|
||||
# Statistiken für Log: Raumfilter-Info pro Dienst
|
||||
row_stats: List[Dict[str, Any]] = []
|
||||
@@ -351,6 +457,7 @@ class TabALogic:
|
||||
daten_map = (pipeline_context.get("result_dict") or {}).get("daten", {})
|
||||
fetched_features = daten_map.get(ident, []) if isinstance(daten_map, dict) else []
|
||||
fetched_count = len(fetched_features) if isinstance(fetched_features, list) else None
|
||||
base_reason = error_by_ident.get(ident) or error_by_thema.get(thema)
|
||||
|
||||
if progress is not None:
|
||||
progress.set_label(f"Lade Dienst {idx}/{total}: {thema}")
|
||||
@@ -379,6 +486,8 @@ class TabALogic:
|
||||
if not layer:
|
||||
layer_call_status[thema] = "layer_nicht_ladbar"
|
||||
self._log(f"[DEBUG] Layeraufruf fehlgeschlagen: thema='{thema}'")
|
||||
reason = base_reason or "Layer konnte nicht geladen werden (Dienst nicht erreichbar oder Link fehlerhaft)"
|
||||
self._log(f"[DEBUG] Dienst nicht geladen: {thema} | Grund: {reason}")
|
||||
row_stats.append({
|
||||
"dienst": thema,
|
||||
"provider": provider,
|
||||
@@ -388,7 +497,7 @@ class TabALogic:
|
||||
"total_features": None,
|
||||
"filtered_features": None,
|
||||
"status": "layer_nicht_ladbar",
|
||||
"raumfilter": raumfilter_name,
|
||||
"reason": reason,
|
||||
})
|
||||
continue
|
||||
|
||||
@@ -442,6 +551,7 @@ class TabALogic:
|
||||
layer_call_status[thema] = "raumfilter_ausserhalb"
|
||||
self._log(f"Dienst {thema} ist außerhalb des Raumfilters")
|
||||
skipped_outside += 1
|
||||
reason = "Keine Objekte innerhalb des Raumfilters"
|
||||
row_stats.append({
|
||||
"dienst": thema,
|
||||
"provider": provider,
|
||||
@@ -451,7 +561,7 @@ class TabALogic:
|
||||
"total_features": total_features,
|
||||
"filtered_features": 0,
|
||||
"status": "außerhalb",
|
||||
"raumfilter": raumfilter_name,
|
||||
"reason": reason,
|
||||
})
|
||||
continue
|
||||
|
||||
@@ -467,7 +577,7 @@ class TabALogic:
|
||||
"total_features": total_features,
|
||||
"filtered_features": filtered_features,
|
||||
"status": "geladen",
|
||||
"raumfilter": raumfilter_name,
|
||||
"reason": "geladen",
|
||||
})
|
||||
layer_call_status[thema] = "geladen"
|
||||
self._log(f"[DEBUG] Dienst geladen: thema='{thema}', provider='{provider}', filtered_features={filtered_features}")
|
||||
@@ -665,75 +775,118 @@ class TabALogic:
|
||||
self._log(f"[DEBUG] Fehler beim Erzeugen lokaler Featureschicht für {thema}: {exc}")
|
||||
return None
|
||||
|
||||
def _write_markdown_log(
|
||||
def _write_html_log(
|
||||
self,
|
||||
final_pfad: str,
|
||||
source_dict: DataDict,
|
||||
pipeline_context: Dict[str, Any],
|
||||
load_summary: Dict[str, Any],
|
||||
) -> None:
|
||||
"""Schreibt den Pipeline-Log (Markdown)."""
|
||||
"""Schreibt den Pipeline-Log als HTML-Datei."""
|
||||
lines = [
|
||||
"# Plan41 Fachdaten-Ladevorgang",
|
||||
"",
|
||||
f"**Datum**: {datetime.datetime.now().isoformat()}",
|
||||
f"**Verfahrens-DB**: {final_pfad or 'temporär'}",
|
||||
f"**Linkliste**: {pipeline_context.get('linkliste')}",
|
||||
"",
|
||||
"## Zusammenfassung",
|
||||
"",
|
||||
f"- **Zeilen gesamt**: {load_summary.get('row_count', 0)}",
|
||||
f"- **Geladene Dienste**: {load_summary.get('loaded_count', 0)}",
|
||||
f"- **Außerhalb Raumfilter**: {load_summary.get('skipped_outside', 0)}",
|
||||
f"- **Abgebrochen**: {load_summary.get('aborted', False)}",
|
||||
f"- **Raumfilter**: {load_summary.get('raumfilter_name', 'unbekannt')}",
|
||||
f"- **Raumfilter-Typ**: {pipeline_context.get('raumfilter_name', 'unbekannt')}",
|
||||
"",
|
||||
"## Dienstliste",
|
||||
"",
|
||||
"| Dienst | Provider | Linkadresse | Aufrufstatus | Ergebnisstatus |",
|
||||
"|---|---|---|---|---|",
|
||||
"<!doctype html>",
|
||||
"<html lang='de'>",
|
||||
"<head>",
|
||||
" <meta charset='utf-8'>",
|
||||
" <title>Plan41 Fachdaten-Ladevorgang</title>",
|
||||
" <style>",
|
||||
" body { font-family: Arial, sans-serif; margin: 20px; color: #222; }",
|
||||
" h1, h2 { margin-bottom: 8px; }",
|
||||
" .meta p { margin: 4px 0; }",
|
||||
" table { border-collapse: collapse; width: 100%; margin: 12px 0 24px; }",
|
||||
" th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; vertical-align: top; }",
|
||||
" th { background: #f2f2f2; }",
|
||||
" .ok { color: #1b5e20; font-weight: 600; }",
|
||||
" .warn { color: #8d6e00; font-weight: 600; }",
|
||||
" .err { color: #b71c1c; font-weight: 600; }",
|
||||
" </style>",
|
||||
"</head>",
|
||||
"<body>",
|
||||
"<h1>Plan41 Fachdaten-Ladevorgang</h1>",
|
||||
"<div class='meta'>",
|
||||
f" <p><strong>Datum:</strong> {datetime.datetime.now().isoformat()}</p>",
|
||||
f" <p><strong>Verfahrens-DB:</strong> {final_pfad or 'temporär'}</p>",
|
||||
f" <p><strong>Linkliste:</strong> {pipeline_context.get('linkliste')}</p>",
|
||||
f" <p><strong>Raumfilter:</strong> {load_summary.get('raumfilter_name', 'unbekannt')}</p>",
|
||||
"</div>",
|
||||
"<h2>Zusammenfassung</h2>",
|
||||
"<ul>",
|
||||
f" <li>Zeilen gesamt: {load_summary.get('row_count', 0)}</li>",
|
||||
f" <li>Geladene Dienste: {load_summary.get('loaded_count', 0)}</li>",
|
||||
f" <li>Außerhalb Raumfilter: {load_summary.get('skipped_outside', 0)}</li>",
|
||||
f" <li>Abgebrochen: {load_summary.get('aborted', False)}</li>",
|
||||
"</ul>",
|
||||
"<h2>Dienstliste</h2>",
|
||||
"<table>",
|
||||
"<thead><tr><th>Dienst</th><th>Provider</th><th>Linkadresse</th><th>Aufrufstatus</th></tr></thead>",
|
||||
"<tbody>",
|
||||
]
|
||||
|
||||
status_by_dienst = {
|
||||
str(stat.get("dienst", "")): str(stat.get("status", "n/a"))
|
||||
for stat in load_summary.get("row_stats", [])
|
||||
}
|
||||
aufrufstatus_by_dienst = {
|
||||
str(key): str(value)
|
||||
for key, value in (load_summary.get("layer_call_status", {}) or {}).items()
|
||||
}
|
||||
aufrufstatus_labels = {
|
||||
"layeraufruf_start": "Layer-Aufruf gestartet",
|
||||
"layeraufruf_ok": "Layer-Aufruf erfolgreich",
|
||||
"geladen": "Geladen",
|
||||
"layer_nicht_ladbar": "Nicht ladbar",
|
||||
"raumfilter_ausserhalb": "Außerhalb Raumfilter",
|
||||
"abbruch_vor_layeraufruf": "Abbruch vor Layer-Aufruf",
|
||||
"abbruch_nach_layeraufruf": "Abbruch nach Layer-Aufruf",
|
||||
"abbruch_nach_raumfilter": "Abbruch nach Raumfilter",
|
||||
"nicht_aufgerufen": "Nicht aufgerufen",
|
||||
}
|
||||
status_labels = {
|
||||
"geladen": "Geladen",
|
||||
"außerhalb": "Außerhalb Raumfilter",
|
||||
"layer_nicht_ladbar": "Nicht ladbar",
|
||||
}
|
||||
|
||||
for row in source_dict.get('rows', []):
|
||||
dienst = row.get('Inhalt') or row.get('ident') or ''
|
||||
provider = row.get('Provider') or ''
|
||||
link = row.get('Link') or ''
|
||||
aufrufstatus = aufrufstatus_by_dienst.get(str(dienst), "nicht_aufgerufen")
|
||||
ergebnisstatus = status_by_dienst.get(str(dienst), "n/a")
|
||||
lines.append(f"| {dienst} | {provider} | {link} | {aufrufstatus} | {ergebnisstatus} |")
|
||||
aufrufstatus_text = aufrufstatus_labels.get(str(aufrufstatus), str(aufrufstatus))
|
||||
lines.append(
|
||||
f"<tr><td>{html.escape(str(dienst))}</td><td>{html.escape(str(provider))}</td><td>{html.escape(str(link))}</td><td>{html.escape(str(aufrufstatus_text))}</td></tr>"
|
||||
)
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"## Raumfilter-Statistik",
|
||||
"",
|
||||
"| Dienst | Provider | Linkadresse | Datenabruf-Objekte | Gesamt-Objekte | Gefilterte Objekte | Raumfilter | Status |",
|
||||
"|---|---|---|---|---|---|---|---|",
|
||||
"</tbody>",
|
||||
"</table>",
|
||||
"<h2>Raumfilter-Statistik</h2>",
|
||||
"<table>",
|
||||
"<thead><tr><th>Dienst</th><th>Datenabruf-Objekte</th><th>Gesamt-Objekte</th><th>Gefilterte Objekte</th><th>Status</th></tr></thead>",
|
||||
"<tbody>",
|
||||
])
|
||||
|
||||
for stat in load_summary.get('row_stats', []):
|
||||
status = str(stat.get('status', 'n/a'))
|
||||
status_label = status_labels.get(status, status)
|
||||
reason = str(stat.get('reason', '') or '')
|
||||
status_text = status_label if not reason else f"{status_label}: {reason}"
|
||||
lines.append(
|
||||
f"| {stat.get('dienst', '')} | {stat.get('provider', '')} | {stat.get('link', '')} | {stat.get('datenabruf_features', 'n/a')} | {stat.get('total_features', 'n/a')} | {stat.get('filtered_features', 'n/a')} | {stat.get('raumfilter', '')} | {stat.get('status', 'n/a')} |"
|
||||
f"<tr><td>{html.escape(str(stat.get('dienst', '')))}</td><td>{html.escape(str(stat.get('datenabruf_features', 'n/a')))}</td><td>{html.escape(str(stat.get('total_features', 'n/a')))}</td><td>{html.escape(str(stat.get('filtered_features', 'n/a')))}</td><td>{html.escape(status_text)}</td></tr>"
|
||||
)
|
||||
|
||||
markdown = "\n".join(lines)
|
||||
lines.extend([
|
||||
"</tbody>",
|
||||
"</table>",
|
||||
"</body>",
|
||||
"</html>",
|
||||
])
|
||||
|
||||
html_text = "\n".join(lines)
|
||||
|
||||
if final_pfad:
|
||||
log_dir = os.path.dirname(final_pfad)
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_file = os.path.join(log_dir, "plan41_lade_log.md")
|
||||
log_file = os.path.join(log_dir, "plan41_lade_log.html")
|
||||
try:
|
||||
with open(log_file, "w", encoding="utf-8") as fh:
|
||||
fh.write(markdown)
|
||||
fh.write(html_text)
|
||||
|
||||
self.pruefmanager.verarbeite(
|
||||
pruef_ergebnis(
|
||||
@@ -748,15 +901,33 @@ class TabALogic:
|
||||
self.pruefmanager.verarbeite(
|
||||
pruef_ergebnis(
|
||||
ok=False,
|
||||
meldung=f"Fehler beim Schreiben des Logle (md): {exc}",
|
||||
meldung=f"Fehler beim Schreiben des Logs (html): {exc}",
|
||||
aktion="log_schreiben_fehlgeschlagen",
|
||||
kontext={"error": str(exc)},
|
||||
)
|
||||
)
|
||||
warning("Lade-Log", f"Konnte Datei nicht schreiben: {exc}", duration=10)
|
||||
else:
|
||||
# temporärer Modus: nur anzeigen
|
||||
info("Lade-Log (temporär)", markdown, duration=20)
|
||||
try:
|
||||
tmp_dir = os.path.join(tempfile.gettempdir(), "sn_plan41")
|
||||
os.makedirs(tmp_dir, exist_ok=True)
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
log_file = os.path.join(tmp_dir, f"plan41_lade_log_{timestamp}.html")
|
||||
|
||||
with open(log_file, "w", encoding="utf-8") as fh:
|
||||
fh.write(html_text)
|
||||
|
||||
self._log(f"Lade-Log (temporär) gespeichert: {log_file}")
|
||||
|
||||
if hasattr(os, "startfile"):
|
||||
try:
|
||||
os.startfile(log_file)
|
||||
except Exception as exc:
|
||||
self._log(f"Konnte temporäre Log-Datei nicht öffnen: {exc}")
|
||||
|
||||
info("Lade-Log (temporär)", f"Lade-Protokoll gespeichert: {log_file}", duration=20)
|
||||
except Exception as exc:
|
||||
warning("Lade-Log", f"Konnte temporäre Log-Datei nicht schreiben: {exc}", duration=10)
|
||||
|
||||
|
||||
def _clone_layer_with_extent(self, layer: QgsVectorLayer, extent, thema: str) -> QgsVectorLayer | None:
|
||||
@@ -869,15 +1040,18 @@ class TabALogic:
|
||||
|
||||
if not layer_id:
|
||||
self._log("❌ Keine Layer-ID gespeichert")
|
||||
self._zeige_verfahrensgebiet_hinweis()
|
||||
return None
|
||||
|
||||
layer = QgsProject.instance().mapLayer(layer_id)
|
||||
if not layer:
|
||||
self._log("❌ Layer-ID existiert nicht im Projekt")
|
||||
self._zeige_verfahrensgebiet_hinweis()
|
||||
return None
|
||||
|
||||
if not self.is_valid_verfahrensgebiet_layer(layer):
|
||||
self._log("❌ Layer ist kein gültiger Vektorlayer")
|
||||
self._zeige_verfahrensgebiet_hinweis()
|
||||
return None
|
||||
|
||||
self._log(f"Verfahrensgebiet-Layer OK: '{layer.name()}'")
|
||||
|
||||
Reference in New Issue
Block a user