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
+149 -161
View File
@@ -1,107 +1,129 @@
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')
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)
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):
# Metohde liest die JSON-Datei ein, in der alle Sivas-Teilenummern aufgelistet sind, die zusammen mit der Hauptnummer migriert werden müssen
try:
with open(self.sivas_json_path, encoding='utf-8') as json_file:
return json.load(json_file)
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()
def read_ruledesigner_ids(self):
# Methode liest die CSV-Datei ein, in der alle RuleDesigner-Nummern aufgelistet sind
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 check_migration_status(self):
# Methode prüft, ob die Sivas-Nummern bereits migriert wurden
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: # Beispiel:
# Die Teilenummer "123456" ist das Halbzeug der Teilenummer "234567". "234567" ist wiederum in der Baugruppe "345678" eingebaut. # 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: # Die Methode gibt dann folgenden String zurück: 345678-234567-123456
# 345678-234567-123456
parent = sivasData[sivasId]['Parent'] parent = self.sivas_ids[id]['Parent']
if parent not in sivasData: if parent not in self.sivas_ids:
return f"{parent}-{sivasId}" return f"{parent}-{id}"
recurval = create_sorter_string(sivasData, parent) recur_val = self.create_sorting_string(parent)
return f"{recurval}-{sivasId}" return f"{recur_val}-{id}"
def create_excel_export(self):
def create_excel_export(existingIds, sivasData, sivasId, outPath):
# Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung # Spalten in Excel-Datei: Sortierung, Teilenummer, Bezeichnung, Bestandsabfrage, Teilebeurteilung, Zusatzbemerkung
# Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug # Sortierung: Minus-separierter "Pfad" dieser Teilenummer im Assembly / im Halbzeug
# Teilenummer: Sivas-Teilenummer # Teilenummer: Sivas-Teilenummer
# Bezeichnung: SIVAS-Bezeichnung der Teilenummer # Bezeichnung: SIVAS-Bezeichnung der Teilenummer
# Bestandsabfrage: IMPORTIEREN oder VERFÜGBAR -> "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert # Migrationsstatus: "IMPORTIEREN" = Teilenummer muss noch migriert werden, "VERFÜGBAR" = Teilenummer wurde bereits migriert
# Restliche Spalten bleiben leer, da sie erst in Excel befüllt werden # Restliche Spalten bleiben leer, weil sie in Excel befüllt werden
sortierung = [] sorting = []
teilenummer = [] prt_num = []
bezeichnung = [] description = []
migrationsstatus = [] migration_status = []
teilebeurteilung = [] evaluation = []
zusatzbemerkung = [] comment = []
for item in sivasData: migrated = self.check_migration_status()
sortierung.append(create_sorter_string(sivasData, item))
teilenummer.append(item) for id in self.sivas_ids:
bezeichnung.append(sivasData[item]['Bezeichnung']) sorting.append(self.create_sorting_string(id))
existing = "VERFÜGBAR" if existingIds[item] is True else False prt_num.append(id)
migrationsstatus.append(existing) description.append(self.sivas_ids[id]['Bezeichnung'])
teilebeurteilung.append('') migration_status.append("VERFÜGBAR" if migrated[id] is True else False)
zusatzbemerkung.append('') evaluation.append('')
comment.append('')
data = { data = {
"Sortierung": sortierung, "Sortierung": sorting,
"Teilenummer": teilenummer, "Teilenummer": prt_num,
"Bezeichnung": bezeichnung, "Bezeichnung": description,
"Migrationsstatus": migrationsstatus, "Migrationsstatus": migration_status,
"Teile Beurteilung": teilebeurteilung, "Beurteilung": evaluation,
"Zusatzbemerkung": zusatzbemerkung "Bemerkung": comment
} }
excel_filename = os.path.join(outPath, sivasId + ".xlsx") filename = os.path.join(Compare.out_dir, self.main_sivas_id + ".xlsx")
df = pd.DataFrame.from_dict(data) df = pd.DataFrame.from_dict(data)
df.to_excel(excel_filename, sheet_name="Ergebnis", index_label="Sortierung") 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 read_sivas_nums(jsonPath): def create_treeview(self):
with open(jsonPath, encoding='utf-8') as json_file:
return json.load(json_file)
def read_ruledesigner_nums(rdDataBasePath):
df = pd.read_csv(rdDataBasePath, sep=';', converters={"Teilenummer":str})
return tuple(df["Teilenummer"])
def compareFiles(rdDataBasePath, jsonPath, sivasId, outPath):
# 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)
# 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
# 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)
# Schreibt eine Excel-Datei mit den korrekten Spalten raus
create_excel_export(existingIds, sivas_ids, sivasId, outPath)
def compare_treeview(json_path, ruledesigner_path):
ruledesigner_nums = read_ruledesigner_nums(ruledesigner_path)
sivas_nums = read_sivas_nums(json_path)
# Verbinder-Symbole
symbols = { symbols = {
"pipe": "", "pipe": "",
"empty": " ", "empty": " ",
@@ -110,124 +132,90 @@ def compare_treeview(json_path, ruledesigner_path):
} }
# ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus. # ANSI-Escape-Sequenzen für Einfärbung des Terminals abhängig vom Migrationsstatus.
# Codes, siehe https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 # os.system wird für die Verarbeitung der Sequenzen benötigt
# BG = Hintergrundfarbe os.system("color")
colors = { colors = {
"BG_RED": "\033[41m", "RED": "\033[41m",
"BG_GREEN": "\033[42m", "GREEN": "\033[42m",
"RESET": "\033[0m" "RESET": "\033[0m"
} }
prefix = "" prefix = ""
# Erstellt dict mit allen Parents (Keys) und weist den Parents deren Kinder als Werte zu. parent_children = self.parent_children_mapping()
# 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()) max_tree_depth = max(id_value["Position"].count(".") for id_value in self.sivas_ids.values())
# Längste Sivas-Teilenummer (weil evtl. >9, wenn mit Underline) max_sivas_num_len = max([len(id) for id in self.sivas_ids.keys()])
max_sivas_num_len = max([len(i) for i in sivas_nums.keys()]) max_description_len = max([len(id_value["Bezeichnung"]) for id_value in self.sivas_ids.values()])
max_description_len = max([len(i["Bezeichnung"]) for i in sivas_nums.values()])
for i, id in enumerate(self.sivas_ids):
# Erstellung des Treeviews mit Spalte "Bezeichnung" parent = self.sivas_ids[id]["Parent"]
for i, sivas_num in enumerate(sivas_nums): description = self.sivas_ids[id]["Bezeichnung"]
current_key_level = self.sivas_ids[id]["Position"].count(".")
# Für Verarbeitung der ANSI-Escape-Sequenzen, # Aktueller Verbinder: └── , wenn letzter Knotenpunkt, sonst ├──
# siehe https://stackoverflow.com/questions/12492810/python-how-can-i-make-the-ansi-escape-codes-to-work-also-in-windows connector = symbols['elbow'] if id == parent_children[parent][-1] else symbols['t']
os.system("color")
parent = sivas_nums[sivas_num]["Parent"] # grün = Teilenummer migriert, rot = Teilenummer noch nicht migriert
description = sivas_nums[sivas_num]["Bezeichnung"] out_color = f"{colors['GREEN']}" if id in self.ruledesigner_ids else f"{colors['RED']}"
position = sivas_nums[sivas_num]["Position"]
# 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"]
# 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(".")
# └── als Verbinder, wenn letzter Knotenpunkt, sonst ├──
connector_symbol = symbols['elbow'] if sivas_num == parents_children[parent][-1] else symbols['t']
# 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: if i == 0:
print(f"{output_color}{parent}{colors['RESET']}") # Bei oberster Teilenummer soll kein Verbinder dargestellt werden.
print(f"{out_color}{parent}{colors['RESET']}")
tree_str = f"{prefix}{connector_symbol}{output_color}{sivas_num}{colors['RESET']}" tree_str = f"{prefix}{connector}{out_color}{id}{colors['RESET']}"
len_tree_str = len(tree_str) - len(output_color) - len(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) * " "
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_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(".")
else:
# 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 # 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. if id in parent_children:
# Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum sein # Prüft, ob das aktuelle Teil selbst ein Parent ist.
if sivas_num in parents_children: # Wenn ja, muss unter dem connector-Symbol der aktuellen Nummer (also im nächsten Durchlauf) Leerraum stehen
prefix += symbols["empty"] 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:
# 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_trim_len = (next_key_level - current_key_level) * len(symbols["pipe"])
prefix = prefix[:prefix_trim_len] prefix = prefix[:prefix_trim_len]
else: else:
if id in parent_children:
# Prüft erneut, ob das aktuelle Teil auch ein Parent ist. # 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 # Wenn ja, muss dieses Mal unter der nächsten Nummer ein Pipe-Symbol kommen
if sivas_num in parents_children:
prefix += symbols["pipe"] 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)