diff --git a/.claude/skills/week_work_package_log/work_package_logger.py b/.claude/skills/week_work_package_log/work_package_logger.py index 468e841..7193dda 100644 --- a/.claude/skills/week_work_package_log/work_package_logger.py +++ b/.claude/skills/week_work_package_log/work_package_logger.py @@ -365,6 +365,8 @@ class WeekWorkPackageLogger: project_trees = {} # Group entries by project and work package + # Note: project_id here is the direct project associated with the time entry, + # which may not be the root project but a child project in the hierarchy project_entries = defaultdict(lambda: defaultdict(list)) for entry in time_entries: @@ -382,7 +384,7 @@ class WeekWorkPackageLogger: # Extract linked information user_name = self._get_linked_info(entry, 'user', 'title') - project_id = self._get_linked_id(entry, 'project') + project_id = self._get_linked_id(entry, 'project') # This is the actual project from time entry activity_name = self._get_linked_info(entry, 'activity', 'title') work_package_id = self._get_linked_id(entry, 'workPackage') @@ -397,6 +399,7 @@ class WeekWorkPackageLogger: 'hours': hours } + # Group by the direct project from time entry project_entries[project_id][work_package_id].append(entry_data) except Exception as e: @@ -405,34 +408,33 @@ class WeekWorkPackageLogger: # Build tree structure for each project for project_id, wp_entries in project_entries.items(): + # Get the full project hierarchy path from root to this project project_path = self._get_project_path(project_id) - # Build project hierarchy + # Build project hierarchy - create all projects in the path current_project_node = None - project_root = None for proj_id, proj_name in project_path: if proj_id not in project_trees: - node = TreeNode(proj_id, proj_name, 'project') - project_trees[proj_id] = node - if project_root is None: - project_root = node + project_trees[proj_id] = TreeNode(proj_id, proj_name, 'project') if current_project_node is None: + # This is the root project in the path current_project_node = project_trees[proj_id] - project_root = current_project_node else: + # Link parent to child in the project hierarchy if project_trees[proj_id] not in current_project_node.children: current_project_node.add_child(project_trees[proj_id]) current_project_node = project_trees[proj_id] - # If no project path found, create a simple node + # If no project path found (no hierarchy support), create a simple node if not project_path and project_id: if project_id not in project_trees: project_trees[project_id] = TreeNode(project_id, f"Project-{project_id}", 'project') current_project_node = project_trees[project_id] # Build work package hierarchy for this project + # current_project_node now points to the actual project where time was logged wp_nodes = {} for wp_id, entries in wp_entries.items(): @@ -446,6 +448,7 @@ class WeekWorkPackageLogger: for wp_id_in_path, wp_name in wp_path: if wp_id_in_path not in wp_nodes: + # Only attach entries to the actual work package (not parent work packages) node_entries = entries if wp_id_in_path == wp_id else [] node_hours = total_hours if wp_id_in_path == wp_id else 0 wp_nodes[wp_id_in_path] = TreeNode(wp_id_in_path, wp_name, 'work_package', node_hours, node_entries) @@ -457,7 +460,7 @@ class WeekWorkPackageLogger: current_wp_node.add_child(wp_nodes[wp_id_in_path]) current_wp_node = wp_nodes[wp_id_in_path] - # Attach work package hierarchy to project + # Attach work package hierarchy to the project where time was actually logged if wp_path and current_project_node: root_wp_id = wp_path[0][0] if root_wp_id in wp_nodes: @@ -471,19 +474,24 @@ class WeekWorkPackageLogger: """Display tree structure in the specified format""" # Find root nodes (nodes without parents in our tree structure) root_nodes = [] - all_nodes = set(trees.values()) + all_nodes = set() + # Collect all nodes from the trees (including nested children) + def collect_all_nodes(node): + all_nodes.add(node) + for child in node.children: + collect_all_nodes(child) + + for node in trees.values(): + collect_all_nodes(node) + + # Find nodes that don't have parents in the tree for node in all_nodes: - has_parent_in_tree = False - for other_node in all_nodes: - if node in other_node.children: - has_parent_in_tree = True - break - - if not has_parent_in_tree: + if node.parent is None or node.parent not in all_nodes: root_nodes.append(node) - # Sort root nodes by total hours (descending) + # Remove duplicates and sort root nodes by total hours (descending) + root_nodes = list(set(root_nodes)) root_nodes.sort(key=lambda x: x.get_total_hours(), reverse=True) print(f"📊 Work Package Time Logging Summary") @@ -524,8 +532,27 @@ class WeekWorkPackageLogger: def _generate_json_output(self, trees: Dict[str, TreeNode], start_date: str, end_date: str) -> Dict: """Generate JSON format output from tree structure""" - # Sort root nodes by total hours (descending) - root_nodes = sorted(trees.values(), key=lambda x: x.get_total_hours(), reverse=True) + # Find root nodes using the same logic as _display_tree + root_nodes = [] + all_nodes = set() + + # Collect all nodes from the trees (including nested children) + def collect_all_nodes(node): + all_nodes.add(node) + for child in node.children: + collect_all_nodes(child) + + for node in trees.values(): + collect_all_nodes(node) + + # Find nodes that don't have parents in the tree + for node in all_nodes: + if node.parent is None or node.parent not in all_nodes: + root_nodes.append(node) + + # Remove duplicates and sort by total hours (descending) + root_nodes = list(set(root_nodes)) + root_nodes.sort(key=lambda x: x.get_total_hours(), reverse=True) # Calculate total hours across all projects total_hours = sum(node.get_total_hours() for node in root_nodes)