Fehlerhandling in eigene Klasse ausgelagert

This commit is contained in:
2025-12-16 16:27:23 +01:00
parent 45a56287b8
commit b89525556b
14 changed files with 905 additions and 135 deletions
+113 -39
View File
@@ -11,8 +11,9 @@ import math
from collections import defaultdict
import configparser
import updateconfignames as uc
from pathlib import Path
from error_collector import ErrorCollector, write_json_file
@dataclass
@@ -20,6 +21,7 @@ class Point:
x: float
y: float
@dataclass
class Polyline:
id: str
@@ -34,12 +36,12 @@ class Polyline:
return ret
@dataclass
# Fehlgeschlagene Anbindung von einem Sensor / Dist zu einem Rack
# Fehlgeschlagene Anbindung von einem Sensor / Dist zu einem Rack
class Error_Connection:
name: str
coords: Point
@dataclass
@dataclass
# Felgeschlagene Verbindung von einem Dist zu Sensor(en) aus beliebigem Grund
class Error_Routing:
unterverteiler: str
@@ -59,13 +61,8 @@ class RackGeometry:
@dataclass
class Polylines:
"""Enthält alle Kabel-Polylinien und Rack-Geometrien (ohne Fehlerbehandlung)."""
kabel: List[Polyline]
errors_routing: List[Error_Routing]
errors_sensors: List[Error_Connection]
errors_dists: List[Error_Connection]
errors_dists_not_in_layout: List[str]
errors_sensors_not_in_layout: List[str]
errors_missing_attributes: Dict[str, str]
rack_geometry: Dict[str, RackGeometry] = field(default_factory=dict)
@@ -336,22 +333,69 @@ def _calculate_text_placement(pt1, pt2, text_type='sensor', item_index=0, total_
def model_from_json(json_file):
"""
Lädt die Polylinien- und Geometriedaten aus einer JSON-Datei und gibt ein Polylines-Objekt zurück.
Lädt die Polylinien- und Geometriedaten aus einer JSON-Datei und gibt Polylines-Objekt und ErrorCollector zurück.
Args:
json_file (str): Pfad zur JSON-Datei.
Returns:
Polylines: Das geladene Polylinien-Objekt.
tuple: (Polylines-Objekt, ErrorCollector-Objekt)
"""
with open(json_file, encoding='utf-8') as fh:
data = json.load(fh)
# ErrorCollector für Fehler und Warnungen initialisieren
error_collector = ErrorCollector()
# Fehler-Daten aus JSON extrahieren und in ErrorCollector speichern
error_fields = {
'errors_routing': [],
'errors_sensors': [],
'errors_dists': [],
'errors_dists_not_in_layout': [],
'errors_sensors_not_in_layout': [],
'errors_missing_attributes': {},
'errors_tunnels': []
}
# Fehler aus data extrahieren
for field in error_fields.keys():
if field in data:
error_fields[field] = data.pop(field)
# Fehler zum ErrorCollector hinzufügen
errors_dict = {}
warnings_dict = {}
# Kritische Fehler
if error_fields['errors_routing']:
errors_dict['routing'] = error_fields['errors_routing']
if error_fields['errors_sensors']:
errors_dict['sensors_connection'] = error_fields['errors_sensors']
if error_fields['errors_dists']:
errors_dict['dists_connection'] = error_fields['errors_dists']
if error_fields['errors_dists_not_in_layout']:
errors_dict['dists_not_in_layout'] = error_fields['errors_dists_not_in_layout']
if error_fields['errors_sensors_not_in_layout']:
errors_dict['sensors_not_in_layout'] = error_fields['errors_sensors_not_in_layout']
if error_fields['errors_tunnels']:
errors_dict['tunnels'] = error_fields['errors_tunnels']
# Warnungen
if error_fields['errors_missing_attributes']:
warnings_dict['missing_attributes'] = error_fields['errors_missing_attributes']
if errors_dict:
error_collector.add_errors(errors_dict)
if warnings_dict:
error_collector.add_warnings(warnings_dict)
# Polylines-Objekt aus den verbleibenden Daten erstellen
plines = from_dict(
data_class=Polylines,
data=data
)
return plines
)
return plines, error_collector
def parse_sensors_from_json(positions_json):
"""
@@ -414,7 +458,7 @@ def mark_missings(all_artnrs):
if artnr not in bezeichner_cfg["Sivasnummern"]:
bezeichner_cfg["Missing"][artnr] = ""
def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, with_bom=True):
def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, error_collector: ErrorCollector = None, with_bom=True):
"""
Erstellt Excel-Reports (Kabelübersicht, Fehlerlisten, Stückliste) aus den Polylinien- und Zuordnungsdaten.
@@ -422,6 +466,7 @@ def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, wit
plines (Polylines): Die Polylinien- und Geometriedaten.
sens2cable (dict): Mapping von Sensor-IDs zu Kabel-Artikelnummern.
outpath (str): Pfad zur Ausgabedatei.
error_collector (ErrorCollector, optional): ErrorCollector mit Fehlern und Warnungen.
with_bom (bool, optional): Ob zusätzlich eine Stückliste (BOM) erzeugt werden soll. Default: True.
"""
# 1. Daten aggregieren und für die Reports vorbereiten
@@ -429,11 +474,11 @@ def write_excel_from_json(plines: Polylines, sens2cable: dict, outpath: str, wit
# 2. Haupt-Excel-Datei (Kabellängen und Fehler) erstellen
wb_main = Workbook()
_create_cable_list_sheet(wb_main.active, plines, sens2cable, bezeichner_cfg)
_create_cable_summary_sheet(wb_main.create_sheet(), processed_data)
_create_rack_lengths_sheet(wb_main.create_sheet(), plines)
_create_error_sheets(wb_main, plines)
_create_error_sheets(wb_main, error_collector)
wb_main.save(outpath)
print("Cable-Summary exported to Excel-file")
@@ -582,51 +627,74 @@ def _create_rack_lengths_sheet(ws, plines):
else:
ws.append([rackname, rack_geom.length])
def _create_error_sheets(wb, plines):
def _create_error_sheets(wb, error_collector: ErrorCollector = None):
"""
Erstellt die Arbeitsblätter für alle aufgetretenen Fehler (Equipment-Connection, Routing, Attribute).
Args:
wb (Workbook): Das Excel-Workbook.
plines (Polylines): Die Polylinien- und Geometriedaten.
error_collector (ErrorCollector, optional): ErrorCollector mit Fehlern und Warnungen.
"""
if not error_collector:
return
errors = error_collector.errors
warnings = error_collector.warnings
# Sheet: ERR-Equipment-Connection
if plines.errors_sensors or plines.errors_dists:
errors_sensors = errors.get('sensors_connection', [])
errors_dists = errors.get('dists_connection', [])
if errors_sensors or errors_dists:
ws = wb.create_sheet("ERR-Equipment-Connection")
ws.append(["Type", "ID", "x", "y"])
ws.column_dimensions['A'].width = 20
for error in plines.errors_sensors:
ws.append(["Sensor / Actuator", error.name, int(error.coords.x), int(error.coords.y)])
for error in plines.errors_dists:
ws.append(["Subistributor", error.name, int(error.coords.x), int(error.coords.y)])
for error in errors_sensors:
ws.append(["Sensor / Actuator", error['name'], int(error['coords']['x']), int(error['coords']['y'])])
for error in errors_dists:
ws.append(["Subdistributor", error['name'], int(error['coords']['x']), int(error['coords']['y'])])
# Sheet: ERR-Routing
if plines.errors_routing:
errors_routing = errors.get('routing', [])
errors_dists_not_in_layout = errors.get('dists_not_in_layout', [])
if errors_routing:
ws = wb.create_sheet("ERR-Routing")
ws.append(["Subdistributor", "Sensor / Actuator", "Details"])
ws.column_dimensions['A'].width = 20
ws.column_dimensions['B'].width = 20
ws.column_dimensions['C'].width = 50
nicht_angebunden = {e.name for e in plines.errors_sensors + plines.errors_dists}
for error in plines.errors_routing:
uv, uv_nicht_angebunden = error.unterverteiler, error.unterverteiler in nicht_angebunden
if uv in plines.errors_dists_not_in_layout:
nicht_angebunden = {e['name'] for e in errors_sensors + errors_dists}
for error in errors_routing:
uv = error['unterverteiler']
uv_nicht_angebunden = uv in nicht_angebunden
if uv in errors_dists_not_in_layout:
ws.append([uv, "-", "Distributor not found in given layout."])
continue
for sensor in error.sensoren:
for sensor in error['sensoren']:
sensor_nicht_angebunden = sensor in nicht_angebunden
if sensor_nicht_angebunden and uv_nicht_angebunden: grund = "Subdistributor and sensor / actuator not connected to racks"
elif sensor_nicht_angebunden: grund = "Sensor / actuator not connected to racks"
elif uv_nicht_angebunden: grund = "Subdistributor not connected to racks"
else: grund = "Failed routing (not caused by missing connection)"
if sensor_nicht_angebunden and uv_nicht_angebunden:
grund = "Subdistributor and sensor / actuator not connected to racks"
elif sensor_nicht_angebunden:
grund = "Sensor / actuator not connected to racks"
elif uv_nicht_angebunden:
grund = "Subdistributor not connected to racks"
else:
grund = "Failed routing (not caused by missing connection)"
ws.append([uv, sensor, grund])
# Sheet: ERR-Attributes
if plines.errors_missing_attributes:
missing_attributes = warnings.get('missing_attributes', {})
if missing_attributes:
ws = wb.create_sheet("ERR-Attributes")
ws.append(["ID", "Error Detail"])
ws.column_dimensions['B'].width = 35
for sname, err_msg in plines.errors_missing_attributes.items():
for sname, err_msg in missing_attributes.items():
ws.append([sname, err_msg])
def _create_bom_workbook(outpath, processed_data, bezeichner_cfg):
@@ -873,8 +941,8 @@ if __name__ == '__main__':
print(f"file {json_file} does not exist")
parser.print_help()
exit()
plines = model_from_json(json_path)
plines, error_collector = model_from_json(json_path)
# Allgemeine Config laden
config = configparser.ConfigParser(allow_no_value=True, delimiters=("="))
@@ -931,4 +999,10 @@ if __name__ == '__main__':
# 5. Excel schreiben
excel_path = os.path.join(work_dir, args.excel)
write_excel_from_json(plines, sens2cable, excel_path)
write_excel_from_json(plines, sens2cable, excel_path, error_collector)
# 6. Optionale Fehlerdatei schreiben
if error_collector.has_errors():
basename = Path(args.excel).stem
error_filename = f"{basename}_errors.json"
error_collector.write_errorfile(Path(work_dir), error_filename)
+81
View File
@@ -0,0 +1,81 @@
"""
ErrorCollector - Gemeinsame Klasse zum Sammeln von Fehlern und Warnungen
Diese Klasse wird von getpositions.py und drawdxf.py verwendet, um Fehler
und Warnungen während der Verarbeitung zu sammeln und zu verwalten.
"""
import json
from pathlib import Path
class ErrorCollector:
"""Sammelt alle Fehler und Warnungen während der Verarbeitung."""
def __init__(self):
self.errors = {}
self.warnings = {}
def add_errors(self, error_dict):
"""Fügt Fehler aus einem Dictionary hinzu."""
for key, value in error_dict.items():
if key in self.errors:
if isinstance(self.errors[key], list) and isinstance(value, list):
self.errors[key].extend(value)
else:
self.errors[key] = [self.errors[key], value]
else:
self.errors[key] = value
def add_warnings(self, warning_dict):
"""Fügt Warnungen aus einem Dictionary hinzu."""
for key, value in warning_dict.items():
if key in self.warnings:
if isinstance(self.warnings[key], list) and isinstance(value, list):
self.warnings[key].extend(value)
else:
self.warnings[key] = [self.warnings[key], value]
else:
self.warnings[key] = value
def has_errors_or_warnings(self):
"""Prüft ob Fehler oder Warnungen vorhanden sind."""
return bool(self.errors) or bool(self.warnings)
def has_errors(self):
"""Prüft ob Fehler vorhanden sind."""
return bool(self.errors)
def get_all_issues(self):
"""Gibt alle Fehler und Warnungen als Dictionary zurück."""
result = {}
if self.errors:
result["errors"] = self.errors
if self.warnings:
result["warnings"] = self.warnings
return result
def write_errorfile(self, work_dir: Path, filename: str):
"""Schreibt Fehlerdatei nur wenn Fehler oder Warnungen vorhanden sind."""
if self.has_errors():
print("Fehler gefunden. Schreibe Error-Datei.")
all_issues = self.get_all_issues()
write_json_file(all_issues, work_dir, filename)
else:
print("Keine Fehler oder Warnungen gefunden. Keine Error-Datei geschrieben.")
def write_json_file(data: dict, out_dir: Path, filename: str) -> None:
"""
Schreibt Daten in eine JSON-Datei.
Args:
data: Dictionary mit zu schreibenden Daten
out_dir: Ausgabeverzeichnis
filename: Dateiname für die JSON-Datei
"""
print("writing data to a json file ...")
outfile = out_dir / filename if isinstance(out_dir, Path) else Path(out_dir) / filename
with open(outfile, 'w', encoding='utf-8') as fh:
json.dump(data, fh, indent=2, ensure_ascii=False, default=str)
print("done")
+2 -56
View File
@@ -11,6 +11,8 @@ from ezdxf.addons import iterdxf
from shapely.geometry import Point
from ezdxf.lldxf.const import DXFStructureError
from error_collector import ErrorCollector, write_json_file
"""
Dieses Programm:
@@ -22,62 +24,6 @@ Dieses Programm:
"""
class ErrorCollector:
"""Sammelt alle Fehler und Warnungen während der Verarbeitung."""
def __init__(self):
self.errors = {}
self.warnings = {}
def add_errors(self, error_dict):
"""Fügt Fehler aus einem Dictionary hinzu."""
for key, value in error_dict.items():
if key in self.errors:
if isinstance(self.errors[key], list) and isinstance(value, list):
self.errors[key].extend(value)
else:
self.errors[key] = [self.errors[key], value]
else:
self.errors[key] = value
def add_warnings(self, warning_dict):
"""Fügt Warnungen aus einem Dictionary hinzu."""
for key, value in warning_dict.items():
if key in self.warnings:
if isinstance(self.warnings[key], list) and isinstance(value, list):
self.warnings[key].extend(value)
else:
self.warnings[key] = [self.warnings[key], value]
else:
self.warnings[key] = value
def has_errors_or_warnings(self):
"""Prüft ob Fehler oder Warnungen vorhanden sind."""
return bool(self.errors) or bool(self.warnings)
def has_errors(self):
"""Prüft ob Fehlervorhanden sind."""
return bool(self.errors)
def get_all_issues(self):
"""Gibt alle Fehler und Warnungen als Dictionary zurück."""
result = {}
if self.errors:
result["errors"] = self.errors
if self.warnings:
result["warnings"] = self.warnings
return result
def write_errorfile(self, work_dir: Path, filename: str):
"""Schreibt Fehlerdatei nur wenn Fehler oder Warnungen vorhanden sind."""
if self.has_errors():
print("Fehler gefunden. Schreibe Error-Datei.")
all_issues = self.get_all_issues()
write_results(to_json(all_issues), work_dir, filename)
else:
print("Keine Fehler oder Warnungen gefunden. Keine Error-Datei geschrieben.")
def write_results(jsn_results: str, out_dir: Path, filename: str) -> None:
"""Write results to a JSON file."""
print("writing results to a json file ...")