Files
chfoit-ipa/template/03_Zeitplan/export.py
T
2026-09-18 16:36:52 +02:00

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