In Klasse umgewandelt. -s entfernt -> Sivas-Teilenummer wird nun wie bei Treeview-Darstellung über -n angegeben. Errorhandling hinzugefügt, wenn json-Datei oder ruldesigner-csv noch nicht exisiteren. Es wird nun automatisch versucht die csv-/json-Datei zu beschaffen/generieren. Bug bei Treeviewdarstellung von bearbeiteten Halbzeugen behoben - Bisher wurde in diesem Fall ein Fehler ausgegeben, weil das Programm im Normalfall den Praefix für die naechste Nummer festlegt. Bei bearbeiteten Halbzeugen exisitert allerdings keine darauffolgende Nummer.

This commit is contained in:
2023-11-05 23:25:10 +01:00
parent b1fe51dafa
commit 1b37b1d22e
+174 -186
View File
@@ -1,233 +1,221 @@
import update_database
import argparse import argparse
import os import os
import pandas as pd
import json import json
import pandas as pd
""" """
Dieses Programm : Dieses Programm kann:
- holt sich die CSV-Datei, in der alle exisitierenden RuleDesigner-Teilenummern enthalten sind - eine Excel-Datei exportieren, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer angegebenen Nummer migriert werden müssen.
- holt sich die json-Datei, in der alle Teilenummern aufgelistet sind, die in Verbindung mit einer gewünschten Teilenummer migriert werden müssen Sie gibt dabei Auskunft darüber, welche Teilenummern bereits migriert wurden und welche noch 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. - 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. data_dir = os.environ.get('CREMIG_DATA')
# Beispiel: out_dir = os.environ.get('CREMIG_WORK')
# Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut. ruledesigner_csv_filename = os.environ.get('RD_DATABASE_NAME')
# Die Funktion gibt dann folgenden Sortier-String zurück: ruledesigner_csv_path = os.path.join(data_dir, ruledesigner_csv_filename)
# 345678-234567-123456
parent = sivasData[sivasId]['Parent'] def __init__(self, main_sivas_id):
if parent not in sivasData: self.main_sivas_id = main_sivas_id
return f"{parent}-{sivasId}" self.sivas_json_path = os.path.join(Compare.data_dir, main_sivas_id + ".json")
recurval = create_sorter_string(sivasData, parent) self.sivas_ids = self.read_sivas_ids()
return f"{recurval}-{sivasId}" 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 try:
# Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug with open(self.sivas_json_path, encoding='utf-8') as json_file:
# Teilenummer: Sivas-Teilenummer return json.load(json_file)
# 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
sortierung = [] except FileNotFoundError:
teilenummer = [] # JSON-Datei wurde noch nicht generiert
bezeichnung = [] update_database.get_sivas_dbase(self.main_sivas_id, Compare.data_dir)
migrationsstatus = [] return self.read_sivas_ids()
teilebeurteilung = []
zusatzbemerkung = []
for item in sivasData: def read_ruledesigner_ids(self):
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('')
data = { # Methode liest die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind
"Sortierung": sortierung,
"Teilenummer": teilenummer,
"Bezeichnung": bezeichnung,
"Migrationsstatus": migrationsstatus,
"Teile Beurteilung": teilebeurteilung,
"Zusatzbemerkung": zusatzbemerkung
}
excel_filename = os.path.join(outPath, sivasId + ".xlsx") try:
df = pd.DataFrame.from_dict(data) df = pd.read_csv(Compare.ruledesigner_csv_path, sep=';', converters={"Teilenummer":str})
df.to_excel(excel_filename, sheet_name="Ergebnis", index_label="Sortierung") 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): def check_migration_status(self):
with open(jsonPath, encoding='utf-8') as json_file:
return json.load(json_file)
# Methode prüft, ob die Sivas-Nummern bereits migriert wurden
def read_ruledesigner_nums(rdDataBasePath): migrated = {}
df = pd.read_csv(rdDataBasePath, sep=';', converters={"Teilenummer":str}) for id in self.sivas_ids:
return tuple(df["Teilenummer"]) 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: def create_excel_export(self):
# - 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)
# Prüft, ob die Sivas-Nummern bereits migriert wurden # Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
existingIds = {} # Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug
for sivas_id in sivas_ids: # Teilenummer: Sivas-Teilenummer
existingIds[sivas_id] = True if sivas_id in ruledesigner_ids else False # 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 sorting = []
if not os.path.isdir(outPath): prt_num = []
print(f"Fehlendes work-Verzeichnis unter '{outPath}' erstellt") description = []
os.mkdir(outPath) migration_status = []
evaluation = []
comment = []
# Schreibt eine Excel-Datei mit den korrekten Spalten raus migrated = self.check_migration_status()
create_excel_export(existingIds, sivas_ids, sivasId, outPath)
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) symbols = {
sivas_nums = read_sivas_nums(json_path) "pipe": "",
"empty": " ",
"elbow": "└──",
"t": "├──"
}
# Verbinder-Symbole # ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus.
symbols = { # os.system wird für die Verarbeitung der Sequenzen benötigt
"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
os.system("color") os.system("color")
colors = {
"RED": "\033[41m",
"GREEN": "\033[42m",
"RESET": "\033[0m"
}
parent = sivas_nums[sivas_num]["Parent"] prefix = ""
description = sivas_nums[sivas_num]["Bezeichnung"]
position = sivas_nums[sivas_num]["Position"] 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 # Aktueller Verbinder: └── , wenn letzter Knotenpunkt, sonst ├──
if (i + 1) != len(sivas_nums): connector = symbols['elbow'] if id == parent_children[parent][-1] else symbols['t']
next_key = list(sivas_nums)[i + 1]
next_key_pos = sivas_nums[next_key]["Position"]
# Ermittelt das Level des aktuellen und nächsten Keys bezogen auf die Nummern-Hierarchie # grün = Teilenummer migriert, rot = Teilenummer noch nicht migriert
current_key_level = position.count(".") out_color = f"{colors['GREEN']}" if id in self.ruledesigner_ids else f"{colors['RED']}"
next_key_level = next_key_pos.count(".")
# └── als Verbinder, wenn letzter Knotenpunkt, sonst ├── if i == 0:
connector_symbol = symbols['elbow'] if sivas_num == parents_children[parent][-1] else symbols['t'] # Bei oberster Teilenummer soll kein Verbinder dargestellt werden.
print(f"{out_color}{parent}{colors['RESET']}")
tree_str = f"{prefix}{connector}{out_color}{id}{colors['RESET']}"
# Farbliche Terminal-Darstellung abhängig vom Migrationsstatus: grün = migriert, rot = noch nicht migriert len_tree_str = len(tree_str) - len(out_color + colors['RESET'])
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'])
# 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) * " "
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) * " "
# Anzahl der Leerzeichen zwischen dem Tree und der Kommentarspalte
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 if (i + 1) != len(self.sivas_ids):
# Der Präfix-Teil setzt sich fallabhängig aus Pipe-Symbolen (| ) und/oder Leerraum ( ) zusammen. next_key = list(self.sivas_ids)[i + 1]
next_key_level = self.sivas_ids[next_key]["Position"].count(".")
# 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)
else: else:
prefix_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"]) # Zu migrierende Nummer ist ein bearbeitetes Halbzeug. Programm muss hier beendet werden, da ansonsten später die
prefix = prefix[:prefix_trim_len] # nächste Teilenummer der json benötigt wird. Diese exisitert aber nicht, weil die json nur eine Teilenummer enthält
else: break
# 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"]
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__': if __name__ == '__main__':
parser = argparse.ArgumentParser(description='fetches data from sivas or ruledesigner fusion', prog='update_database') parser = argparse.ArgumentParser(description='Das Programm gibt Auskunft über den aktuellen Migrationsstand', prog='compare_lists')
parser.add_argument('-n', '--number', action='store', help='SIVAS-Number') 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('-s', '--sivas', action='store', help='fetch data of this id from sivas fo local database folder') parser.add_argument('-t', '--tree', action='store_true', help='Erzeugt Baumdarstellung aller Teilenummern. grün = Teilenummer vorhanden, rot = Migration ausstehend')
parser.add_argument('-d', '--database', action='store', default="RD_Bestandspool.csv", help='local filename of the latest fetched RuleDesigner Database') parser.add_argument('-e', '--excel', action='store_true', help='Erzeugt Excel-Datei in der alle Teilenummern, die migriert werden müssen, aufgelistet sind')
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')
args = parser.parse_args() args = parser.parse_args()
# fetching the names of all existing cad files from the local file if args.excel:
dataDir = os.environ.get('CREMIG_DATA') excel_comparison = Compare(args.number)
rdDataBasePath = os.path.join(dataDir, args.database) excel_comparison.create_excel_export()
# get the path to ask for further json files elif args.tree:
exePath = os.environ.get('SIVAS_DATABASE_QUERY') treeview_comparison = Compare(args.number)
# in case the infos for this cad file are already there treeview_comparison.create_treeview()
sivasId = str(args.sivas) else:
jsonPath = os.path.join(dataDir, sivasId + ".json") print("Bitte entweder '-e' für Excel-Export oder '-t' für Treeview-Ausgabe angeben.")
# 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)