initial commit
This commit is contained in:
+41
@@ -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
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
@echo on
|
||||||
|
python src\main.py %*
|
||||||
|
pause
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"merge_groups": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Python-Abhaengigkeiten
|
||||||
|
# Installation: pip install -r requirements.txt
|
||||||
|
|
||||||
|
pandas>=2.0.0
|
||||||
|
openpyxl>=3.1.0
|
||||||
+175
@@ -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()
|
||||||
Reference in New Issue
Block a user