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)