ErrorCollector standarisiert. Warnings werden von getpositions zur routing zu draw durchgereicht.

This commit is contained in:
2025-12-18 16:34:04 +01:00
parent a731889561
commit 9b9d3a8f7c
6 changed files with 235 additions and 28 deletions
+21 -1
View File
@@ -385,6 +385,12 @@ def model_from_json(json_file):
if error_fields['errors_missing_attributes']: if error_fields['errors_missing_attributes']:
warnings_dict['missing_attributes'] = error_fields['errors_missing_attributes'] warnings_dict['missing_attributes'] = error_fields['errors_missing_attributes']
# Warnungen aus getpositions und routing übernehmen
if 'warnings' in data:
warnings_from_upstream = data.pop('warnings')
if isinstance(warnings_from_upstream, dict):
warnings_dict.update(warnings_from_upstream)
if errors_dict: if errors_dict:
error_collector.add_errors(errors_dict) error_collector.add_errors(errors_dict)
if warnings_dict: if warnings_dict:
@@ -697,6 +703,20 @@ def _create_error_sheets(wb, error_collector: ErrorCollector = None):
for sname, err_msg in missing_attributes.items(): for sname, err_msg in missing_attributes.items():
ws.append([sname, err_msg]) ws.append([sname, err_msg])
# Sheet: WARNINGS (für allgemeine Warnungen wie z-Koordinaten-Abweichungen)
general_warnings = {k: v for k, v in warnings.items() if k != 'missing_attributes'}
if general_warnings:
ws = wb.create_sheet("WARNINGS")
ws.append(["Warning Type", "Details"])
ws.column_dimensions['A'].width = 30
ws.column_dimensions['B'].width = 80
for warning_type, warning_msg in general_warnings.items():
# Formatiere den Warning Type lesbarer
readable_type = warning_type.replace('_', ' ').title()
ws.append([readable_type, str(warning_msg)])
def _create_bom_workbook(outpath, processed_data, bezeichner_cfg): def _create_bom_workbook(outpath, processed_data, bezeichner_cfg):
""" """
Erstellt eine separate Excel-Arbeitsmappe für die Stückliste (BOM) und speichert sie ab. Erstellt eine separate Excel-Arbeitsmappe für die Stückliste (BOM) und speichert sie ab.
@@ -927,7 +947,7 @@ if __name__ == '__main__':
parser.add_argument('-d', '--dxf', action='store', help='this dxf drawing will be copied and the new layer with the cables will be added. Original file must be added with --origin', metavar='myfile.dxf') parser.add_argument('-d', '--dxf', action='store', help='this dxf drawing will be copied and the new layer with the cables will be added. Original file must be added with --origin', metavar='myfile.dxf')
parser.add_argument('-n', '--new', action='store', help='create a new dxf file only with cables in it. Name is basename and a timestamp') parser.add_argument('-n', '--new', action='store', help='create a new dxf file only with cables in it. Name is basename and a timestamp')
parser.add_argument('-x', '--excel', action='store', help='create a xlsx file with cables data', metavar='allCables.xls') parser.add_argument('-x', '--excel', action='store', help='create a xlsx file with cables data', metavar='allCables.xls')
parser.add_argument('-o', '--origin', action='store', help='name of original .dxf file used by -d and -a', metavar='original.dxf') parser.add_argument('-o', '--origin', action='store', help='name of original .dxf file used by -d', metavar='original.dxf')
parser.add_argument('-l', '--local', action='store_true', help='using only local data for naming of article numbers. If not set: fetching names from SIVAS.') parser.add_argument('-l', '--local', action='store_true', help='using only local data for naming of article numbers. If not set: fetching names from SIVAS.')
args = parser.parse_args() args = parser.parse_args()
+1 -1
View File
@@ -62,7 +62,7 @@ class ErrorCollector:
all_issues = self.get_all_issues() all_issues = self.get_all_issues()
write_json_file(all_issues, work_dir, filename) write_json_file(all_issues, work_dir, filename)
else: else:
print("Keine Fehler oder Warnungen gefunden. Keine Error-Datei geschrieben.") print("Keine Fehler gefunden. Keine Error-Datei geschrieben.")
def write_json_file(data: dict, out_dir: Path, filename: str) -> None: def write_json_file(data: dict, out_dir: Path, filename: str) -> None:
+44
View File
@@ -762,6 +762,47 @@ def check_file_in_work(work_dir: Path, filename: Path) -> tuple[Path, bool]:
mypath = filename mypath = filename
return mypath, fexists return mypath, fexists
def check_rack_z_coordinates(res_racks: dict, error_collector, config) -> None:
"""
Prüft die z-Koordinaten aller Racks und gibt eine Warnung aus,
wenn die Differenz zwischen min und max größer als der konfigurierte Schwellwert ist.
Args:
res_racks: Dictionary mit Rack-Daten (key: rack_name, value: list of coordinates)
error_collector: ErrorCollector Instanz zum Hinzufügen von Warnungen
config: ConfigParser Objekt mit allgemein.cfg
"""
if not res_racks:
return
# Lese Schwellwert aus Config (Standard: 2000.0 mm)
max_height_diff = config.getfloat("Racks", "MaximalTotalHeightDifferences", fallback=2000.0)
all_z_coords = []
# Sammle alle z-Koordinaten aus allen Racks
for rack_name, coordinates in res_racks.items():
if isinstance(coordinates, list):
for coord in coordinates:
if isinstance(coord, (list, tuple)) and len(coord) >= 3:
all_z_coords.append(coord[2])
elif isinstance(coord, dict) and 'z' in coord:
all_z_coords.append(coord['z'])
if not all_z_coords:
return
# Berechne Min und Max
min_z = min(all_z_coords)
max_z = max(all_z_coords)
diff = max_z - min_z
# Prüfe, ob Differenz > Schwellwert
if diff > max_height_diff:
warning_msg = f"The z-coordinates of the racks differ strong between each other from {min_z:.1f} to {max_z:.1f}"
error_collector.add_warnings({"rack_z_coordinate_deviation": warning_msg})
#print(f"WARNING: {warning_msg}")
def check_existance(res_mappings: dict, res_dist: dict, res_pos: dict, res_tunnel: dict) -> dict: def check_existance(res_mappings: dict, res_dist: dict, res_pos: dict, res_tunnel: dict) -> dict:
ret = dict() ret = dict()
@@ -963,6 +1004,9 @@ if __name__ == '__main__':
output_results['racks'] = res_rac output_results['racks'] = res_rac
# Prüfe z-Koordinaten der Racks auf starke Abweichungen
check_rack_z_coordinates(res_rac, error_collector, config)
if args.console: if args.console:
print(to_json(res_rac)) print(to_json(res_rac))
+97 -3
View File
@@ -383,19 +383,33 @@ class ExcelConverter:
else: else:
raise ValueError("Unbekannter Export-Typ") raise ValueError("Unbekannter Export-Typ")
def export_excel(self, data, export_type, export_path): def export_excel(self, data, export_type, export_path, getpos_errors=None, getpos_warnings=None):
""" """
Wrapper, der aufgerufen wird und in sich die Aufbereitung der Daten und das Abspeichern als Excel abruft. Wrapper, der aufgerufen wird und in sich die Aufbereitung der Daten und das Abspeichern als Excel abruft.
Args:
data: Die zu exportierenden Daten.
export_type (str): Art des Exports (EA, TIA, WSCAD).
export_path (str): Pfad zur Ausgabedatei.
getpos_errors (dict, optional): Fehler-Dictionary aus getpositions.py.
getpos_warnings (dict, optional): Warnungen-Dictionary aus getpositions.py.
""" """
# Daten vorbereiten # Daten vorbereiten
dataframes = self._prep_data(data, export_type) dataframes = self._prep_data(data, export_type)
# Excel Datei schreiben und speichern # Excel Datei schreiben und speichern
self._write_excel(dataframes, export_type, export_path) self._write_excel(dataframes, export_type, export_path, getpos_errors, getpos_warnings)
def _write_excel(self, dataframes, export_type, export_path): def _write_excel(self, dataframes, export_type, export_path, getpos_errors=None, getpos_warnings=None):
""" """
Erzeugung der Excel-Datei im gewünschten Muster Erzeugung der Excel-Datei im gewünschten Muster
Args:
dataframes: Die vorbereiteten DataFrames für den Export.
export_type (str): Art des Exports (EA, TIA, WSCAD).
export_path (str): Pfad zur Ausgabedatei.
getpos_errors (dict, optional): Fehler-Dictionary aus getpositions.py.
getpos_warnings (dict, optional): Warnungen-Dictionary aus getpositions.py.
""" """
if export_type == "EA": if export_type == "EA":
# Format für Simpel-Ausgabe # Format für Simpel-Ausgabe
@@ -413,6 +427,9 @@ class ExcelConverter:
# Duplikate und Errors # Duplikate und Errors
self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler) self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler)
# Fehler und Warnungen aus getpositions.py
self._write_getpositions_errors(writer, getpos_errors, getpos_warnings)
elif export_type == "TIA": elif export_type == "TIA":
df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags = dataframes df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_consts, df_tags = dataframes
@@ -426,6 +443,9 @@ class ExcelConverter:
# Duplikate und Errors # Duplikate und Errors
self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler) self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler)
# Fehler und Warnungen aus getpositions.py
self._write_getpositions_errors(writer, getpos_errors, getpos_warnings)
elif export_type in ["WSCAD", "WSCAD mit Bezug"]: elif export_type in ["WSCAD", "WSCAD mit Bezug"]:
df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = dataframes df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler, df_eingaenge, df_ausgaenge = dataframes
@@ -470,6 +490,9 @@ class ExcelConverter:
# Duplikate und Errors # Duplikate und Errors
self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler) self._write_duplicates_errors(writer, df_gleiche_eingaenge, df_gleiche_ausgaenge, df_fehler)
# Fehler und Warnungen aus getpositions.py
self._write_getpositions_errors(writer, getpos_errors, getpos_warnings)
def _append_df(self, target_df, source_df, include_bezug=False): def _append_df(self, target_df, source_df, include_bezug=False):
""" """
Hilfsfunktion, die Dataframes für WSCAD Export aneinander reiht und entweder Bezug inkludiert oder nicht Hilfsfunktion, die Dataframes für WSCAD Export aneinander reiht und entweder Bezug inkludiert oder nicht
@@ -515,6 +538,77 @@ class ExcelConverter:
if not df_fehler.empty: if not df_fehler.empty:
df_fehler.to_excel(writer, sheet_name='Fehler', index=False) df_fehler.to_excel(writer, sheet_name='Fehler', index=False)
def _write_getpositions_errors(self, writer, errors_dict=None, warnings_dict=None):
"""
Schreibt Fehler und Warnungen aus getpositions.py in separate Excel-Sheets.
Diese Methode erstellt ähnliche Sheets wie in drawdxf.py für den getexdraw-Workflow.
Args:
writer (pd.ExcelWriter): Der Excel-Writer für die Ausgabedatei.
errors_dict (dict, optional): Dictionary mit Fehlern aus getpositions.
warnings_dict (dict, optional): Dictionary mit Warnungen aus getpositions.
"""
if not errors_dict and not warnings_dict:
return
# Sheet: ERR-Equipment-Connection (nur für ioconverter relevant)
if errors_dict:
errors_sensors = errors_dict.get('sensors_connection', [])
errors_dists = errors_dict.get('dists_connection', [])
if errors_sensors or errors_dists:
data = []
for error in errors_sensors:
data.append({
"Type": "Sensor / Actuator",
"ID": error['name'],
"x": int(error['coords']['x']),
"y": int(error['coords']['y'])
})
for error in errors_dists:
data.append({
"Type": "Subdistributor",
"ID": error['name'],
"x": int(error['coords']['x']),
"y": int(error['coords']['y'])
})
df = pd.DataFrame(data)
df.to_excel(writer, sheet_name='ERR-Equipment-Connection', index=False)
# Sheet: Duplicate IDs
errors_duplicates = errors_dict.get('duplicate_ids', {})
if errors_duplicates:
data = []
for id_name, positions in errors_duplicates.items():
for pos in positions:
data.append({
"ID": id_name,
"x": int(pos[0]),
"y": int(pos[1])
})
df = pd.DataFrame(data)
df.to_excel(writer, sheet_name='ERR-Duplicate-IDs', index=False)
# Sheet: Tunnel Errors
errors_tunnels = errors_dict.get('overdefined_tunnel', [])
if errors_tunnels:
df = pd.DataFrame({"Tunnel ID": errors_tunnels})
df.to_excel(writer, sheet_name='ERR-Tunnels', index=False)
# Sheet: Warnungen (fehlende Attribute)
if warnings_dict:
missing_attributes = warnings_dict.get('missing_attributes', {})
if missing_attributes:
data = []
for id_name, error_msg in missing_attributes.items():
data.append({
"ID": id_name,
"Error Detail": error_msg
})
df = pd.DataFrame(data)
df.to_excel(writer, sheet_name='WARN-Attributes', index=False)
def call_portal_to_excel(self, textlines, konvertierungsart): def call_portal_to_excel(self, textlines, konvertierungsart):
ec = ExcelConverter() ec = ExcelConverter()
+52 -9
View File
@@ -73,8 +73,47 @@ def check_file_in_work(work_dir:Path, filename:Path):
mypath = filename mypath = filename
return (mypath, fexists) return (mypath, fexists)
# Helper zum Laden der Error-JSON
def load_error_json(json_path: Path, work_dir: Path):
"""
Lädt die Fehler-JSON-Datei, falls vorhanden.
Args:
json_path (Path): Pfad zur Haupt-JSON-Datei.
work_dir (Path): Arbeitsverzeichnis.
Returns:
tuple: (errors_dict, warnings_dict) oder (None, None) wenn keine Fehler vorhanden.
"""
# Konstruiere Error-JSON-Dateinamen basierend auf der Positions-JSON
basename = json_path.stem
if basename.endswith("_positionsconv"):
basename = basename.replace("_positionsconv", "")
elif basename.endswith("_positionsdraw"):
basename = basename.replace("_positionsdraw", "")
error_json_name = f"{basename}_errors.json"
error_json_path = work_dir / error_json_name
# Prüfen ob Error-JSON existiert
if not error_json_path.exists():
# Auch im gleichen Verzeichnis wie die Positions-JSON prüfen
error_json_path = json_path.parent / error_json_name
if not error_json_path.exists():
return None, None
# Error-JSON laden
try:
error_data = load_json(error_json_path)
errors_dict = error_data.get("errors", None)
warnings_dict = error_data.get("warnings", None)
return errors_dict, warnings_dict
except Exception as e:
print(f"Warnung: Fehler beim Laden der Error-JSON: {e}")
return None, None
# Helper für Export # Helper für Export
def do_export(conv_obj:ExcelConverter, mode_str, base_input, output_dir, lines): def do_export(conv_obj:ExcelConverter, mode_str, base_input, output_dir, lines, getpos_errors=None, getpos_warnings=None):
# Für WSCAD Dateinamen anpassen (Export-Typ bleibt "WSCAD" oder "WSCAD mit Bezug") # Für WSCAD Dateinamen anpassen (Export-Typ bleibt "WSCAD" oder "WSCAD mit Bezug")
if mode_str == "WSCAD": if mode_str == "WSCAD":
if args.no_bezug: if args.no_bezug:
@@ -90,19 +129,19 @@ def do_export(conv_obj:ExcelConverter, mode_str, base_input, output_dir, lines):
export_path = os.path.join(output_dir, filename) export_path = os.path.join(output_dir, filename)
try: try:
conv_obj.export_excel(lines, export_type, export_path) conv_obj.export_excel(lines, export_type, export_path, getpos_errors, getpos_warnings)
print(f"Export '{export_type}' erfolgreich: {export_path}") print(f"Export '{export_type}' erfolgreich: {export_path}")
except Exception as e: except Exception as e:
print(f"Fehler bei Export '{export_type}': {e}") print(f"Fehler bei Export '{export_type}': {e}")
def do_exports(converter, export_mode, output_dir, base_input, lines): def do_exports(converter, export_mode, output_dir, base_input, lines, getpos_errors=None, getpos_warnings=None):
if export_mode: if export_mode:
do_export(converter, export_mode, base_input, output_dir, lines) do_export(converter, export_mode, base_input, output_dir, lines, getpos_errors, getpos_warnings)
else: else:
# Kein Modus → alle drei exportieren # Kein Modus → alle drei exportieren
do_export(converter, "EA", base_input, output_dir, lines) do_export(converter, "EA", base_input, output_dir, lines, getpos_errors, getpos_warnings)
do_export(converter, "TIA", base_input, output_dir, lines) do_export(converter, "TIA", base_input, output_dir, lines, getpos_errors, getpos_warnings)
do_export(converter, "WSCAD", base_input, output_dir, lines) do_export(converter, "WSCAD", base_input, output_dir, lines, getpos_errors, getpos_warnings)
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Excel-Konverter für BMK-Daten") parser = argparse.ArgumentParser(description="Excel-Konverter für BMK-Daten")
@@ -176,12 +215,16 @@ if __name__ == "__main__":
rawdata = load_json(json_path) rawdata = load_json(json_path)
(dsensors, lsensors) = prepare_data(rawdata) (dsensors, lsensors) = prepare_data(rawdata)
# Fehler-JSON laden (falls vorhanden)
getpos_errors, getpos_warnings = load_error_json(json_path, work_dir)
if len(dsensors) == 0: if len(dsensors) == 0:
do_exports(converter, export_mode, output_dir, basename_out, lsensors) do_exports(converter, export_mode, output_dir, basename_out, lsensors, getpos_errors, getpos_warnings)
else: else:
for sps_nr, lines in dsensors.items(): for sps_nr, lines in dsensors.items():
name = f"{basename_out}-{sps_nr}" name = f"{basename_out}-{sps_nr}"
do_exports(converter, export_mode, output_dir, name, lines) do_exports(converter, export_mode, output_dir, name, lines, getpos_errors, getpos_warnings)
else: else:
parser.print_help() parser.print_help()
+8 -2
View File
@@ -161,8 +161,12 @@ def prepare_data(rawdata:dict):
if "missing_attributes" in rawdata["not_found"]: if "missing_attributes" in rawdata["not_found"]:
errors_attributes = rawdata["not_found"]["missing_attributes"] errors_attributes = rawdata["not_found"]["missing_attributes"]
# Warnungen aus getpositions übernehmen
warnings = dict()
if "warnings" in rawdata:
warnings = rawdata["warnings"]
return (dracks, dsensors, dsubdists, mapping, dtunnels, dtunlength, errors_dists, errors_sensors, errors_attributes) return (dracks, dsensors, dsubdists, mapping, dtunnels, dtunlength, errors_dists, errors_sensors, errors_attributes, warnings)
if __name__ == "__main__": if __name__ == "__main__":
@@ -188,7 +192,7 @@ if __name__ == "__main__":
# Einlesen und Vorbereiten der Daten # Einlesen und Vorbereiten der Daten
rawdata = load_json(sensors_path) rawdata = load_json(sensors_path)
(racks, sensors, subdists, mapping, tunnels, tunlength, errors_dists, errors_sensors, errors_attributes) = prepare_data(rawdata) (racks, sensors, subdists, mapping, tunnels, tunlength, errors_dists, errors_sensors, errors_attributes, warnings) = prepare_data(rawdata)
config = configparser.ConfigParser(allow_no_value=True, delimiters=("=")) config = configparser.ConfigParser(allow_no_value=True, delimiters=("="))
config.optionxform = lambda optionstr: optionstr # preserve case for letters config.optionxform = lambda optionstr: optionstr # preserve case for letters
@@ -212,6 +216,8 @@ if __name__ == "__main__":
cable_paths["errors_dists_not_in_layout"] = errors_dists cable_paths["errors_dists_not_in_layout"] = errors_dists
cable_paths["errors_sensors_not_in_layout"] = errors_sensors cable_paths["errors_sensors_not_in_layout"] = errors_sensors
cable_paths["errors_missing_attributes"] = errors_attributes cable_paths["errors_missing_attributes"] = errors_attributes
# Warnungen aus getpositions übertragen
cable_paths["warnings"] = warnings
write_results(to_json(cable_paths), work_dir, f"{basename}.json") write_results(to_json(cable_paths), work_dir, f"{basename}.json")