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 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.")