91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
import pandas as pd
|
|
import numpy as np
|
|
import datetime
|
|
|
|
# Load the file
|
|
file_path = 'Zeitplan.xlsx'
|
|
df = pd.read_excel(file_path, header=[1, 2, 3], index_col=0)
|
|
|
|
# 1. Tag Rows as 'should' (Plan) or 'is' (Actual)
|
|
# Assumption: Rows with a Task Name are 'should'. Empty rows below them are 'is'.
|
|
df['Type'] = np.where(df.index.notna(), 'should', 'is')
|
|
|
|
# 2. Propagate Task Names to the 'is' rows
|
|
# We forward-fill the index so the empty 'IST' row gets the name of the task above it
|
|
df.index = df.index.to_series().ffill()
|
|
|
|
# 3. Cleanup Garbage
|
|
# Remove the header row itself ('Tätigkeit') and any top-level empty rows
|
|
df = df.drop('Tätigkeit', errors='ignore')
|
|
df = df[df.index.notna()] # Drop rows that were empty at the very top before any task
|
|
|
|
# 4. Save Original Order
|
|
original_task_order = df.index.unique().tolist()
|
|
|
|
# 5. Separate Time Grid
|
|
# We take columns from index 4 onwards. We must exclude our new 'Type' column from the data grid.
|
|
# (The 'Type' column is at the very end now)
|
|
time_grid = df.iloc[:, 4:-1]
|
|
row_types = df['Type'] # Save the types to re-attach
|
|
|
|
# 6. Unpivot (Melt)
|
|
# We add 'Type' to the index temporarily so it sticks with the data during stacking
|
|
time_grid = time_grid.set_index(row_types, append=True)
|
|
df_long = time_grid.stack(level=[0, 1, 2], future_stack=True).reset_index()
|
|
|
|
# Rename columns (Index 0=Task, Index 1=Type, followed by the stacked levels)
|
|
df_long.columns = ['Task', 'Type', 'KW', 'Date', 'Halbtag', 'Hours']
|
|
|
|
# 7. Clean Data
|
|
df_long['Hours'] = pd.to_numeric(df_long['Hours'], errors='coerce').fillna(0)
|
|
|
|
# 8. Pivot to get 'should' and 'is' side-by-side
|
|
# We group by Date/Task and sum up the 'should' hours and 'is' hours separately
|
|
df_pivot = df_long.pivot_table(
|
|
index=['Date', 'KW', 'Task'],
|
|
columns='Type',
|
|
values='Hours',
|
|
aggfunc='sum'
|
|
).reset_index()
|
|
|
|
# Ensure we have both columns (fill with 0 if missing)
|
|
for col in ['should', 'is']:
|
|
if col not in df_pivot.columns:
|
|
df_pivot[col] = 0.0
|
|
df_pivot = df_pivot.fillna(0)
|
|
|
|
# 9. Apply Order & Sort
|
|
df_pivot['Task'] = pd.Categorical(df_pivot['Task'], categories=original_task_order, ordered=True)
|
|
df_pivot['Date'] = pd.to_datetime(df_pivot['Date'], errors='coerce')
|
|
|
|
# Remove the 'Total' row and any rows where Task might be 'Total' or NaN
|
|
df_final = df_pivot[
|
|
(df_pivot['Task'].notna()) &
|
|
(~df_pivot['Task'].astype(str).str.contains('Total|Summe|Tätigkeit', case=False))
|
|
].copy()
|
|
|
|
# Filter out rows where NOTHING happened (neither plan nor actual)
|
|
df_final = df_pivot[(df_pivot['should'] > 0) | (df_pivot['is'] > 0)].copy()
|
|
df_final = df_final.sort_values(by=['Date', 'Task'])
|
|
|
|
# --- YAML Output ---
|
|
print("### last updated at: " + datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") + " ###\n")
|
|
|
|
current_date = None
|
|
|
|
for _, row in df_final.iterrows():
|
|
# Format date
|
|
date_str = f"{row['Date'].day}-{row['Date'].month}-{row['Date'].year}"
|
|
|
|
if date_str != current_date:
|
|
current_date = date_str
|
|
print(f"{date_str}:")
|
|
|
|
# Convert Hours to Minutes
|
|
should_min = int(row['should'] * 60)
|
|
is_min = int(row['is'] * 60)
|
|
|
|
print(f" - name: {row['Task']}")
|
|
print(f" should: {should_min}")
|
|
print(f" is: {is_min}")
|