Fehlerhandling in eigene Klasse ausgelagert
This commit is contained in:
+113
-39
@@ -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)
|
||||
Reference in New Issue
Block a user