From 1b37b1d22e388072f1b9e48abea9a0b4dfef9b5a Mon Sep 17 00:00:00 2001 From: "s.steuer" Date: Sun, 5 Nov 2023 23:25:10 +0100 Subject: [PATCH] =?UTF-8?q?In=20Klasse=20umgewandelt.=20-s=20entfernt=20->?= =?UTF-8?q?=20Sivas-Teilenummer=20wird=20nun=20wie=20bei=20Treeview-Darste?= =?UTF-8?q?llung=20=C3=BCber=20-n=20angegeben.=20Errorhandling=20hinzugef?= =?UTF-8?q?=C3=BCgt,=20wenn=20json-Datei=20oder=20ruldesigner-csv=20noch?= =?UTF-8?q?=20nicht=20exisiteren.=20Es=20wird=20nun=20automatisch=20versuc?= =?UTF-8?q?ht=20die=20csv-/json-Datei=20zu=20beschaffen/generieren.=20Bug?= =?UTF-8?q?=20bei=20Treeviewdarstellung=20von=20bearbeiteten=20Halbzeugen?= =?UTF-8?q?=20behoben=20-=20Bisher=20wurde=20in=20diesem=20Fall=20ein=20Fe?= =?UTF-8?q?hler=20ausgegeben,=20weil=20das=20Programm=20im=20Normalfall=20?= =?UTF-8?q?den=20Praefix=20f=C3=BCr=20die=20naechste=20Nummer=20festlegt.?= =?UTF-8?q?=20Bei=20bearbeiteten=20Halbzeugen=20exisitert=20allerdings=20k?= =?UTF-8?q?eine=20darauffolgende=20Nummer.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/compare_lists.py | 360 +++++++++++++++++++++---------------------- 1 file changed, 174 insertions(+), 186 deletions(-) diff --git a/lib/compare_lists.py b/lib/compare_lists.py index 7cf452e..7b7ff09 100644 --- a/lib/compare_lists.py +++ b/lib/compare_lists.py @@ -1,233 +1,221 @@ +import update_database import argparse import os -import pandas as pd import json +import pandas as pd """ -Dieses Programm : -- holt sich die CSV-Datei, in der alle exisitierenden RuleDesigner-Teilenummern enthalten sind -- holt sich die json-Datei, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer gewünschten Teilenummer migriert werden müssen -- exportiert alle Nummern aus der JSON-Datei in eine Excel Datei. Die Excel zeigt, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen. +Dieses Programm kann: +- eine Excel-Datei exportieren, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer angegebenen Nummer migriert werden müssen. + Sie gibt dabei Auskunft darüber, welche Teilenummern bereits migriert wurden und welche noch migriert werden müssen. +- die oben beschriebene Darstellung in einer Excel-Datei auch als Treeview auf der Kommandozeile darstellen """ -def create_sorter_string(sivasData, sivasId): +class Compare: - # Funktion setzt rekursiv einen String zusammen, der anzeigt, wo sich eine Teilenummer in der Hierarchie der obersten, zu migrierenden Baugruppe befindet. - # Beispiel: - # Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut. - # Die Funktion gibt dann folgenden Sortier-String zurück: - # 345678-234567-123456 + data_dir = os.environ.get('CREMIG_DATA') + out_dir = os.environ.get('CREMIG_WORK') + ruledesigner_csv_filename = os.environ.get('RD_DATABASE_NAME') + ruledesigner_csv_path = os.path.join(data_dir, ruledesigner_csv_filename) - parent = sivasData[sivasId]['Parent'] - if parent not in sivasData: - return f"{parent}-{sivasId}" - recurval = create_sorter_string(sivasData, parent) - return f"{recurval}-{sivasId}" + def __init__(self, main_sivas_id): + self.main_sivas_id = main_sivas_id + self.sivas_json_path = os.path.join(Compare.data_dir, main_sivas_id + ".json") + self.sivas_ids = self.read_sivas_ids() + self.ruledesigner_ids = self.read_ruledesigner_ids() + def read_sivas_ids(self): -def create_excel_export(existingIds, sivasData, sivasId, outPath): + # Metohde liest die JSON-Datei ein, in der alle Sivas-Teilenummern aufgelistet sind, die zusammen mit der Hauptnummer migriert werden müssen - # Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung - # Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug - # Teilenummer: Sivas-Teilenummer - # Bezeichnung: SIVAS-Bezeichnung der Teilenummer - # Bestandsabfrage: IMPORTIEREN oder VERFÜGBAR -> "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert - # Restliche Spalten bleiben leer, da sie erst in Excel befüllt werden + try: + with open(self.sivas_json_path, encoding='utf-8') as json_file: + return json.load(json_file) - sortierung = [] - teilenummer = [] - bezeichnung = [] - migrationsstatus = [] - teilebeurteilung = [] - zusatzbemerkung = [] + except FileNotFoundError: + # JSON-Datei wurde noch nicht generiert + update_database.get_sivas_dbase(self.main_sivas_id, Compare.data_dir) + return self.read_sivas_ids() - for item in sivasData: - sortierung.append(create_sorter_string(sivasData, item)) - teilenummer.append(item) - bezeichnung.append(sivasData[item]['Bezeichnung']) - existing = "VERFÜGBAR" if existingIds[item] is True else False - migrationsstatus.append(existing) - teilebeurteilung.append('') - zusatzbemerkung.append('') + def read_ruledesigner_ids(self): - data = { - "Sortierung": sortierung, - "Teilenummer": teilenummer, - "Bezeichnung": bezeichnung, - "Migrationsstatus": migrationsstatus, - "Teile Beurteilung": teilebeurteilung, - "Zusatzbemerkung": zusatzbemerkung - } + # Methode liest die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind - excel_filename = os.path.join(outPath, sivasId + ".xlsx") - df = pd.DataFrame.from_dict(data) - df.to_excel(excel_filename, sheet_name="Ergebnis", index_label="Sortierung") + try: + df = pd.read_csv(Compare.ruledesigner_csv_path, sep=';', converters={"Teilenummer":str}) + return tuple(df["Teilenummer"]) + except FileNotFoundError: + # CSV-Datei wurde noch nicht ins %CREMIG_DATA% kopiert + update_database.get_rd_dbase(Compare.data_dir) + return self.read_ruledesigner_ids() -def read_sivas_nums(jsonPath): - with open(jsonPath, encoding='utf-8') as json_file: - return json.load(json_file) + def check_migration_status(self): + # Methode prüft, ob die Sivas-Nummern bereits migriert wurden -def read_ruledesigner_nums(rdDataBasePath): - df = pd.read_csv(rdDataBasePath, sep=';', converters={"Teilenummer":str}) - return tuple(df["Teilenummer"]) + migrated = {} + for id in self.sivas_ids: + migrated[id] = True if id in self.ruledesigner_ids else False + return migrated + def create_sorting_string(self, id): + + # Methode setzt rekursiv einen String zusammen, der anzeigt, wo sich eine Teilenummer in der Hierarchie der obersten Baugruppe befindet. + # Beispiel: + # Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut. + # Die Methode gibt dann folgenden String zurück: 345678-234567-123456 -def compareFiles(rdDataBasePath, jsonPath, sivasId, outPath): + parent = self.sivas_ids[id]['Parent'] + if parent not in self.sivas_ids: + return f"{parent}-{id}" + recur_val = self.create_sorting_string(parent) + return f"{recur_val}-{id}" - # Liest: - # - die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind - # - die JSON-Datei ein, in der alle Sivas-Teilenummern, die i.V.m einer Teilenummer ebenfalls migriert werden müssen - ruledesigner_ids = read_ruledesigner_nums(rdDataBasePath) - sivas_ids = read_sivas_nums(jsonPath) + def create_excel_export(self): - # Prüft, ob die Sivas-Nummern bereits migriert wurden - existingIds = {} - for sivas_id in sivas_ids: - existingIds[sivas_id] = True if sivas_id in ruledesigner_ids else False + # Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung + # Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug + # Teilenummer: Sivas-Teilenummer + # Bezeichnung: SIVAS-Bezeichnung der Teilenummer + # Migrationsstatus: "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert + # Restliche Spalten bleiben leer, weil sie in Excel befüllt werden - # Erstellt das work-Verzeichnis, falls dieses noch nicht exisitert - if not os.path.isdir(outPath): - print(f"Fehlendes work-Verzeichnis unter '{outPath}' erstellt") - os.mkdir(outPath) + sorting = [] + prt_num = [] + description = [] + migration_status = [] + evaluation = [] + comment = [] - # Schreibt eine Excel-Datei mit den korrekten Spalten raus - create_excel_export(existingIds, sivas_ids, sivasId, outPath) + migrated = self.check_migration_status() + for id in self.sivas_ids: + sorting.append(self.create_sorting_string(id)) + prt_num.append(id) + description.append(self.sivas_ids[id]['Bezeichnung']) + migration_status.append("VERFÜGBAR" if migrated[id] is True else False) + evaluation.append('') + comment.append('') -def compare_treeview(json_path, ruledesigner_path): + data = { + "Sortierung": sorting, + "Teilenummer": prt_num, + "Bezeichnung": description, + "Migrationsstatus": migration_status, + "Beurteilung": evaluation, + "Bemerkung": comment + } + + filename = os.path.join(Compare.out_dir, self.main_sivas_id + ".xlsx") + df = pd.DataFrame.from_dict(data) + df.to_excel(filename, sheet_name="Ergebnis", index_label="Sortierung") + print(f"Excel-Datei erfolgreich in '{Compare.out_dir}' geschrieben...") + + def parent_children_mapping(self): + # Methode erstellt dict mit allen Parents (Keys) der json-Datei und weist den Parents deren Kinder als Werte zu. + parent_children = {} + for id in self.sivas_ids: + parent = self.sivas_ids[id]["Parent"] + if parent not in parent_children: + parent_children[parent] = [] + parent_children[parent].append(id) + return parent_children + + def create_treeview(self): - ruledesigner_nums = read_ruledesigner_nums(ruledesigner_path) - sivas_nums = read_sivas_nums(json_path) + symbols = { + "pipe": "│ ", + "empty": " ", + "elbow": "└──", + "t": "├──" + } - # Verbinder-Symbole - symbols = { - "pipe": "│ ", - "empty": " ", - "elbow": "└──", - "t": "├──" - } - - # ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus. - # Codes, siehe https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 - # BG = Hintergrundfarbe - colors = { - "BG_RED": "\033[41m", - "BG_GREEN": "\033[42m", - "RESET": "\033[0m" - } - - prefix = "" - - # Erstellt dict mit allen Parents (Keys) und weist den Parents deren Kinder als Werte zu. - # Wird später benötigt, um zu bestimmen, ob eine Teilenummer das letzte Kind eines Parents ist d.h. wenn ein Parent z.B. 4 Kinder - # besitzt, ob es sich um das letzte, also 4. Kind handelt. In diesem Fall darf unter der Nummer kein Pipe-Symbol erscheinen - parents_children = {} - for sivas_num in sivas_nums: - parent = sivas_nums[sivas_num]["Parent"] - if parent not in parents_children: - parents_children[parent] = [] - parents_children[parent].append(sivas_num) - - max_tree_depth = max(i["Position"].count(".") for i in sivas_nums.values()) - # Längste Sivas-Teilenummer (weil evtl. >9, wenn mit Underline) - max_sivas_num_len = max([len(i) for i in sivas_nums.keys()]) - max_description_len = max([len(i["Bezeichnung"]) for i in sivas_nums.values()]) - - - # Erstellung des Treeviews mit Spalte "Bezeichnung" - for i, sivas_num in enumerate(sivas_nums): - - # Für Verarbeitung der ANSI-Escape-Sequenzen, - # siehe https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows + # ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus. + # os.system wird für die Verarbeitung der Sequenzen benötigt os.system("color") + colors = { + "RED": "\033[41m", + "GREEN": "\033[42m", + "RESET": "\033[0m" + } - parent = sivas_nums[sivas_num]["Parent"] - description = sivas_nums[sivas_num]["Bezeichnung"] - position = sivas_nums[sivas_num]["Position"] + prefix = "" + + parent_children = self.parent_children_mapping() + + max_tree_depth = max(id_value["Position"].count(".") for id_value in self.sivas_ids.values()) + max_sivas_num_len = max([len(id) for id in self.sivas_ids.keys()]) + max_description_len = max([len(id_value["Bezeichnung"]) for id_value in self.sivas_ids.values()]) + + for i, id in enumerate(self.sivas_ids): + + parent = self.sivas_ids[id]["Parent"] + description = self.sivas_ids[id]["Bezeichnung"] + current_key_level = self.sivas_ids[id]["Position"].count(".") - # Wird später für Zusammensetzung des Präfix benötigt - if (i + 1) != len(sivas_nums): - next_key = list(sivas_nums)[i + 1] - next_key_pos = sivas_nums[next_key]["Position"] + # Aktueller Verbinder: └── , wenn letzter Knotenpunkt, sonst ├── + connector = symbols['elbow'] if id == parent_children[parent][-1] else symbols['t'] - # Ermittelt das Level des aktuellen und nächsten Keys bezogen auf die Nummern-Hierarchie - current_key_level = position.count(".") - next_key_level = next_key_pos.count(".") + # grün = Teilenummer migriert, rot = Teilenummer noch nicht migriert + out_color = f"{colors['GREEN']}" if id in self.ruledesigner_ids else f"{colors['RED']}" - # └── als Verbinder, wenn letzter Knotenpunkt, sonst ├── - connector_symbol = symbols['elbow'] if sivas_num == parents_children[parent][-1] else symbols['t'] + if i == 0: + # Bei oberster Teilenummer soll kein Verbinder dargestellt werden. + print(f"{out_color}{parent}{colors['RESET']}") - - # Farbliche Terminal-Darstellung abhängig vom Migrationsstatus: grün = migriert, rot = noch nicht migriert - output_color = f"{colors['BG_GREEN']}" if sivas_num in ruledesigner_nums else f"{colors['BG_RED']}" - - # Bei oberster Teilenummer soll kein Verbinder dargestellt werden. - if i == 0: - print(f"{output_color}{parent}{colors['RESET']}") - - tree_str = f"{prefix}{connector_symbol}{output_color}{sivas_num}{colors['RESET']}" - len_tree_str = len(tree_str) - len(output_color) - len(colors['RESET']) + tree_str = f"{prefix}{connector}{out_color}{id}{colors['RESET']}" + len_tree_str = len(tree_str) - len(out_color + colors['RESET']) - # Anzahl der Leerzeichen zwischen dem Tree und der Bezeichnungsspalte - pad_tree = ((max_tree_depth * len(symbols['pipe']) + len(symbols['t']) + max_sivas_num_len) - len_tree_str + 2) * " " - # Anzahl der Leerzeichen zwischen dem Tree und der Kommentarspalte - pad_description = (max_description_len - len(description) + 2) * " " + pad_tree = (max_tree_depth * len(symbols['pipe']) + len(symbols['t']) + max_sivas_num_len - len_tree_str + 2) * " " + pad_description = (max_description_len - len(description) + 2) * " " - print(f"{tree_str}{pad_tree}{description}{pad_description}") + print(f"{tree_str}{pad_tree}{description}{pad_description}") - # Nachfolgend wird der Präfix vor der Nummer für den NÄCHSTEN Durchlauf zusammengesetzt - # Der Präfix-Teil setzt sich fallabhängig aus Pipe-Symbolen (| ) und/oder Leerraum ( ) zusammen. - - # Prüft, ob die aktuelle Nummer das letzte Kind des Parents ist - if sivas_num == parents_children[parent][-1]: - - # Prüft, ob das aktuelle Teil auch ein Parent ist. - # Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum sein - if sivas_num in parents_children: - prefix += symbols["empty"] - - # Baumende auf der aktuellen Ebene ist erreicht. Die nächste Teilenummer (wenn nicht das Gesamtende des Baums erreicht ist) - # liegt unter der aktuellen Ebene. Der bisherige Präfix-String muss folglich gekürzt werden. Die Anzahl der Zeichen, - # die gekürzt werden müssen ergibt sich durch die Anzahl der Ebenen, die zwischen der aktuellen Teilenummer und der darauffolgenden Teilenummer - # liegen und der Länge des Pipe-Symbols, bzw. eines anderen Symbols (alle haben eine Länge von 3) + if (i + 1) != len(self.sivas_ids): + next_key = list(self.sivas_ids)[i + 1] + next_key_level = self.sivas_ids[next_key]["Position"].count(".") else: - prefix_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"]) - prefix = prefix[:prefix_trim_len] - else: - # Prüft erneut, ob das aktuelle Teil auch ein Parent ist. - # Wenn ja, muss dieses Mal unter der nächsten Nummer ein Pipe-Symbol kommen - if sivas_num in parents_children: - prefix += symbols["pipe"] + # Zu migrierende Nummer ist ein bearbeitetes Halbzeug. Programm muss hier beendet werden, da ansonsten später die + # nächste Teilenummer der json benötigt wird. Diese exisitert aber nicht, weil die json nur eine Teilenummer enthält + break + if id == parent_children[parent][-1]: + # Prüft, ob die aktuelle Nummer das letzte Kind des Parents ist + + if id in parent_children: + # Prüft, ob das aktuelle Teil selbst ein Parent ist. + # Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum stehen + prefix += symbols["empty"] + + else: + # Ende der aktuellen Gruppe an Teilenummern, die sich auf einer gemeinsamen Ebene befinden ist erreicht. + # Die nächste Teilenummer liegt eine oder mehrere Ebenen höher als die aktuelle Teilenummer (außer das Ende des gesamten Baumes ist erreicht). + # Der bisherige Präfix-String wird deshalb gekürzt. Die Anzahl der zu kürzenden Zeichen ergibt sich durch die Anzahl der Ebenen, die zwischen + # der aktuellen Teilenummer und der darauffolgenden Teilenummer liegen und der Länge des Pipe-Symbols, bzw. eines anderen Symbols (alle Symbole + # haben die gleiche Länge) + prefix_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"]) + prefix = prefix[:prefix_trim_len] + else: + if id in parent_children: + # Prüft erneut, ob das aktuelle Teil auch ein Parent ist. + # Wenn ja, muss dieses Mal unter der nächsten Nummer ein Pipe-Symbol kommen + prefix += symbols["pipe"] if __name__ == '__main__': - parser = argparse.ArgumentParser(description='fetches data from sivas or ruledesigner fusion', prog='update_database') - parser.add_argument('-n', '--number', action='store', help='SIVAS-Number') - parser.add_argument('-s', '--sivas', action='store', help='fetch data of this id from sivas fo local database folder') - parser.add_argument('-d', '--database', action='store', default="RD_Bestandspool.csv", help='local filename of the latest fetched RuleDesigner Database') - parser.add_argument('-t', '--tree', action='store_true', help='displays partnumber hierarchy for given part number in flag -s as treeview. green = migrated, red = not yet migrated') + parser = argparse.ArgumentParser(description='Das Programm gibt Auskunft über den aktuellen Migrationsstand', prog='compare_lists') + parser.add_argument('-n', '--number', action='store', required=True, help='Eingabe der SIVAS-Teilenummer, für die der Migrationsstatus angezeigt werden soll') + parser.add_argument('-t', '--tree', action='store_true', help='Erzeugt Baumdarstellung aller Teilenummern. grün = Teilenummer vorhanden, rot = Migration ausstehend') + parser.add_argument('-e', '--excel', action='store_true', help='Erzeugt Excel-Datei in der alle Teilenummern, die migriert werden müssen, aufgelistet sind') args = parser.parse_args() - # fetching the names of all existing cad files from the local file - dataDir = os.environ.get('CREMIG_DATA') - rdDataBasePath = os.path.join(dataDir, args.database) - # get the path to ask for further json files - exePath = os.environ.get('SIVAS_DATABASE_QUERY') - # in case the infos for this cad file are already there - sivasId = str(args.sivas) - jsonPath = os.path.join(dataDir, sivasId + ".json") - # there the results are written - outPath = os.environ.get('CREMIG_WORK') - - sivas_id = str(args.number) - json_path = os.path.join(dataDir, sivas_id + ".json") - - if args.sivas: - compareFiles(rdDataBasePath, jsonPath, sivasId, outPath) - - if args.tree: - compare_treeview(json_path, rdDataBasePath) +if args.excel: + excel_comparison = Compare(args.number) + excel_comparison.create_excel_export() +elif args.tree: + treeview_comparison = Compare(args.number) + treeview_comparison.create_treeview() +else: + print("Bitte entweder '-e' für Excel-Export oder '-t' für Treeview-Ausgabe angeben.") \ No newline at end of file