Files
CreoMigrate/lib/compare_lists.py
T

221 lines
10 KiB
Python

import update_database
import argparse
import os
import json
import pandas as pd
"""
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
"""
class Compare:
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:
# 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
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}"
def create_excel_export(self):
# 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
sorting = []
prt_num = []
description = []
migration_status = []
evaluation = []
comment = []
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('')
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):
symbols = {
"pipe": "",
"empty": " ",
"elbow": "└──",
"t": "├──"
}
# 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"
}
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(".")
# Aktueller Verbinder: └── , wenn letzter Knotenpunkt, sonst ├──
connector = symbols['elbow'] if id == parent_children[parent][-1] else symbols['t']
# grün = Teilenummer migriert, rot = Teilenummer noch nicht migriert
out_color = f"{colors['GREEN']}" if id in self.ruledesigner_ids else f"{colors['RED']}"
if i == 0:
# 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']}"
len_tree_str = len(tree_str) - len(out_color + colors['RESET'])
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}")
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:
# 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='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()
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.")