commit 573ccd7c61632eac3102f187d4f72cc5e4729456 Author: Simon Hensch Date: Wed May 6 14:35:12 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bfcf01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +*.pyc.* + +# Virtual Environments +.venv/ +venv/ +env/ +ENV/ + +# Distribution / Packaging +build/ +dist/ +*.egg-info/ + +# Logs +*.log + +# OS / Editor +.DS_Store +Thumbs.db +*.swp +*.swo + +# IDEs +.vscode/ +.idea/ + +# Projekt-spezifisch + +# Input-Dateien (Rohdaten, nicht versionieren) +input/* + +# Output-Dateien (generierte Ergebnisse) +output/* + +# Optional: config ignorieren, falls sensitiv +# config/config.json \ No newline at end of file diff --git a/Vorlage/Vorlage.xlsx b/Vorlage/Vorlage.xlsx new file mode 100644 index 0000000..d856414 Binary files /dev/null and b/Vorlage/Vorlage.xlsx differ diff --git a/Vorlage/Vorlage_clean.xlsx b/Vorlage/Vorlage_clean.xlsx new file mode 100644 index 0000000..03ff4fe Binary files /dev/null and b/Vorlage/Vorlage_clean.xlsx differ diff --git a/auswertung.bat b/auswertung.bat new file mode 100644 index 0000000..2a3e613 --- /dev/null +++ b/auswertung.bat @@ -0,0 +1,3 @@ +@echo on +python src\main.py %* +pause \ No newline at end of file diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..b71a326 --- /dev/null +++ b/config/config.json @@ -0,0 +1,5 @@ +{ + "merge_groups": [ + + ] +} \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..d7f38de --- /dev/null +++ b/readme.md @@ -0,0 +1,146 @@ +# Auswertung Automationskalkulation + +Dieses Repository enthält ein Python-Skript zur automatisierten Auswertung von Excel-Dateien. Die Verarbeitung erfolgt entweder über einen Input-Ordner oder durch Übergabe einzelner Dateien per Kommandozeile. + +--- + +## Projektstruktur + +```text + +. +├── src/ +│ └── main.py +├── input/ +│ └── Excel-Dateien zur Verarbeitung +├── output/ +│ └── erzeugte Ergebnisdateien +├── config/ +│ └── config.json +├── run.bat +└── README.md + +``` + +--- + +## Voraussetzungen + +- Python 3.9 oder höher +- Abhängigkeiten: + - pandas + - openpyxl + +Installation der Abhängigkeiten: + +```text + +pip install pandas openpyxl + +``` + +--- + +## Nutzung + +### Variante 1: Verarbeitung über Input-Ordner + +1. Excel-Dateien in das Verzeichnis `input/` legen +2. `run.bat` ausführen + +Alle Dateien im Input-Ordner werden verarbeitet. + +--- + +### Variante 2: Verarbeitung einzelner Dateien + +Excel-Dateien auf `run.bat` ziehen. + +Nur die übergebenen Dateien werden verarbeitet. + +--- + +## Ausgabe + +Die Ergebnisdateien werden im Verzeichnis `output/` gespeichert. + +Der Dateiname hat folgendes Format: + +```text + +{inputname}*auswertung*{timestamp}.xlsx + +``` + +Inhalt der Ausgabe: + +- aggregierte Stunden pro Sivas:AG +- Detailaufstellungen je Eintrag +- zusätzliche Werte aus der ersten Datei + +--- + +## Konfiguration + +Die Datei `config/config.json` enthält Konfigurationsparameter. + +Beispiel: + +```json +{ + "merge_groups": [ + ["25", "26"], + ["47/101", "50/111"] + ] +} +``` + +Die definierten Sivas-Arbeitsgänge werden bei der Zusammenführung von Schlüsseln berücksichtigt. Dabei werden alle Zeilen mit gleichen Werten der Spalte D ("Sivas:AG") zusammengeführt. + +Beispiel: + +- ["25", "26"]: "Montage Elektrisch/Inbetriebnahmetest Hardware durch Montage" (Sivas AG 25) und "Maschinentest/Inbetriebnahmeunterstützung durch Montage" (Sivas AG 26) +- ["47/101", "50/111"]: "SPS-Inbetriebsetzung" (Sivas AG 47/101) und "PC-Inbetriebsetzung" (Sivas AG 50/111) + +--- + +## Verarbeitungslogik + +Das Skript verarbeitet definierte Zellbereiche aus Excel-Dateien: + +- Zeilen 28 bis 56 +- Spalte D: Schlüssel (Sivas:AG) +- Spalte F: Stunden +- Spalte A: Beschreibung + +Die Stundenwerte werden gerundet und pro Schlüssel aggregiert. + +--- + +## Fehlerquellen + +Keine Ausgabe oder leere Verarbeitung kann folgende Ursachen haben: + +- Input-Verzeichnis ist leer +- keine gültigen Excel-Dateien vorhanden +- temporäre Excel-Dateien (`~$...`) werden ignoriert +- falsches Arbeitsverzeichnis beim Start + +--- + +## Ausführungsvoraussetzung im Skript + +Die Datei `main.py` muss folgenden Einstiegspunkt enthalten: + +```python +if __name__ == "__main__": + main() +``` + +--- + +## Hinweis + +Das Skript ist für die Batch-Verarbeitung von Dateien vorgesehen und verfügt über keine grafische Oberfläche. + +--- diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..824578a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# Python-Abhaengigkeiten +# Installation: pip install -r requirements.txt + +pandas>=2.0.0 +openpyxl>=3.1.0 \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..632b3e7 --- /dev/null +++ b/src/main.py @@ -0,0 +1,175 @@ +import pandas as pd +import os +import sys +import json +import math +from datetime import datetime +from openpyxl.styles import Alignment +from openpyxl.utils import get_column_letter + + +def load_config(): + with open(os.path.join("config", "config.json"), "r", encoding="utf-8") as f: + return json.load(f) + +def normalize_key(value, merge_groups): + value = str(value) + for group in merge_groups: + if value in group: + return ", ".join(merge_groups) + return value + +def safe_hours(value, key_raw): + try: + val = float(value) + return math.ceil(val), None + except: + return 0, f"Sivas:AG_{key_raw}" + +def process_files(input_paths): + config = load_config() + merge_groups = config.get("merge_groups", []) + + result = {} + + for input_path in input_paths: + df = pd.read_excel(input_path, header=None) + df_slice = df.iloc[28:56] + + for _, row in df_slice.iterrows(): + key_raw = row[3] # Spalte D + hours_raw = row[5] # Spalte F + desc = row[0] # Spalte A + + # 🔹 NEU: komplett leere Zeilen ignorieren + if pd.isna(key_raw) and pd.isna(hours_raw): + continue + + key_raw = str(key_raw) + + hours, error_key = safe_hours(hours_raw, key_raw) + + if error_key: + key = error_key + else: + key = normalize_key(key_raw, merge_groups) + + if key not in result: + result[key] = { + "hours": 0, + "details": [], + "descriptions": [] + } + + result[key]["hours"] += hours + + result[key]["descriptions"].append(str(desc)) + + if error_key: + result[key]["details"].append(f"FEHLER: {hours_raw} ({desc})") + else: + result[key]["details"].append(f"{hours}h {desc}") + + # Ergebnis bauen + output_rows = [] + total_sum = 0 + + for key, data in result.items(): + total_sum += data["hours"] + + # 🔹 NEU: SUMME in Details + details_text = "\n".join(data["details"]) + details_text += f"\n{data['hours']}h SUMME" + + descriptions_text = "\n".join(data["descriptions"]) + + output_rows.append({ + "Sivas:AG": key, + "Summe Stunden": data["hours"], + "Details": details_text, + "Beschreibungen": descriptions_text + }) + + output_df = pd.DataFrame(output_rows) + + # Zusatzwerte nur aus erster Datei (sonst Chaos) + df_first = pd.read_excel(input_paths[0], header=None) + extra_values = df_first.iloc[23:27, 4] + + extra_df = pd.DataFrame({ + "Wert": extra_values.values + }) + + now = datetime.now().strftime("%Y%m%d_%H%M%S") + + if len(input_paths) == 1: + base_name = os.path.splitext(os.path.basename(input_paths[0]))[0] + else: + base_name = "multi_file" + + output_path = os.path.join("output", f"{base_name}_auswertung_{now}.xlsx") + + with pd.ExcelWriter(output_path, engine="openpyxl") as writer: + output_df.to_excel(writer, sheet_name="Arbeitsgänge", index=False) + extra_df.to_excel(writer, sheet_name="Material", index=False) + + #workbook = writer.book + worksheet = writer.sheets["Arbeitsgänge"] + + wrap_alignment = Alignment(wrap_text=True, vertical="top") + + # Spalten C und D (0-basiert: C=2, D=3) + target_cols = [2, 3] + + for row in worksheet.iter_rows(min_row=2): # Header überspringen + for col_idx in target_cols: + cell = row[col_idx] + cell.alignment = wrap_alignment + + worksheet.column_dimensions["A"].width = 25 + worksheet.column_dimensions["B"].width = 15 + worksheet.column_dimensions["C"].width = 50 + worksheet.column_dimensions["D"].width = 50 + + worksheet.freeze_panes = "A2" + + worksheet.auto_filter.ref = worksheet.dimensions + + print("\n=== Kontrollübersicht ===") + print(f"Gesamtsumme Stunden: {total_sum}") + + print("\nDetails pro Sivas-AG:") + for key, data in result.items(): + print(f"{key}: {data['hours']}h") + + print(f"\nDatei gespeichert unter: {output_path}") + +def main(): + print("CWD:", os.getcwd()) + input_paths = sys.argv[1:] + + # 🔹 Wenn keine Dateien per Drag & Drop kommen → input Ordner nutzen + if not input_paths: + input_dir = os.path.join(os.getcwd(), "input") + print("Working dir:", os.getcwd()) + print("Input dir:", input_dir) + if not os.path.exists(input_dir): + print("Input-Ordner existiert nicht.") + return + + input_paths = [ + os.path.join(input_dir, f) + for f in os.listdir(input_dir) + if f.lower().endswith(".xlsx") or f.lower().endswith(".xls") + ] + + valid_paths = [p for p in input_paths if os.path.exists(p)] + + if not valid_paths: + print("Keine gültigen Dateien gefunden.") + return + + process_files(valid_paths) + +if __name__ == "__main__": + main() \ No newline at end of file