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