"""Helper module to convert sessions into tree structures.""" import uuid from typing import Any def convert_session_to_tree( session_path: list[str], tree_snapshot: dict[str, Any], custom_steps: list[dict[str, Any]], decisions: list[dict[str, Any]] ) -> dict[str, Any]: """Convert a session's path and custom steps into a linear tree structure. Creates a linear decision tree that represents the path taken through the original tree, including any custom steps inserted during the session. Args: session_path: List of node IDs representing the path taken tree_snapshot: Original tree structure (for node details) custom_steps: Custom steps inserted during session decisions: Decision records with answers and notes Returns: Tree structure dict representing the linear path """ if not session_path: # Return minimal valid tree if no path taken return { "id": str(uuid.uuid4()), "type": "solution", "title": "Session had no recorded path", "children": [] } # Build a map of custom steps by their ID custom_steps_map = {} for step in custom_steps: if "id" in step: custom_steps_map[step["id"]] = step # Build a map of decisions by node_id for quick lookup decisions_map = {} for decision in decisions: if "node_id" in decision: decisions_map[decision["node_id"]] = decision # Build the linear tree structure root_node = None current_node = None for i, node_id in enumerate(session_path): # Check if this is a custom step if node_id in custom_steps_map: step = custom_steps_map[node_id] new_node = _create_node_from_custom_step(step, node_id) else: # Find node in original tree original_node = _find_node_in_tree(tree_snapshot, node_id) if original_node: new_node = _create_node_from_original(original_node, decisions_map.get(node_id)) else: # Node not found, create a placeholder new_node = { "id": node_id, "type": "action", "title": f"Step from original tree (node {node_id})", "children": [] } # Add notes from decision if available decision = decisions_map.get(node_id) if decision and decision.get("notes"): new_node["notes"] = decision["notes"] # Build the chain if root_node is None: root_node = new_node current_node = root_node else: current_node["children"] = [new_node] current_node = new_node return root_node def _find_node_in_tree(tree: dict[str, Any], node_id: str) -> dict[str, Any] | None: """Recursively find a node in the tree structure by ID. Args: tree: Tree structure dict node_id: Node ID to find Returns: Node dict if found, None otherwise """ if tree.get("id") == node_id: return tree for child in tree.get("children", []): result = _find_node_in_tree(child, node_id) if result: return result return None def _create_node_from_original( original_node: dict[str, Any], decision: dict[str, Any] | None = None ) -> dict[str, Any]: """Create a new node based on an original tree node. Args: original_node: Original node from tree decision: Decision record for this node (optional) Returns: New node dict for the linear tree """ node_type = original_node.get("type", "action") new_node = { "id": str(uuid.uuid4()), # Generate new ID for the saved tree "type": node_type, "children": [] } # Copy relevant content based on node type if node_type == "decision": new_node["question"] = original_node.get("question", "") if decision and decision.get("answer"): new_node["question"] += f"\n\nAnswer: {decision['answer']}" elif node_type == "action": new_node["title"] = original_node.get("title", original_node.get("action", "")) if decision and decision.get("action_performed"): new_node["title"] = decision["action_performed"] if decision and decision.get("command_output"): output = decision["command_output"].strip() if output: new_node["title"] += f"\n\nCommand Output:\n{output}" elif node_type == "solution": new_node["title"] = original_node.get("title", original_node.get("solution", "")) return new_node def _create_node_from_custom_step( custom_step: dict[str, Any], step_id: str ) -> dict[str, Any]: """Create a node from a custom step. Args: custom_step: Custom step dict step_id: ID of the custom step Returns: Node dict for the linear tree """ step_type = custom_step.get("type", "action") content = custom_step.get("content", "") new_node = { "id": str(uuid.uuid4()), "type": step_type, "children": [] } # Map content to appropriate field based on type if step_type == "decision": new_node["question"] = content elif step_type == "action": new_node["title"] = content elif step_type == "solution": new_node["title"] = content # Add notes if present if custom_step.get("notes"): if step_type == "decision": new_node["question"] += f"\n\nNotes: {custom_step['notes']}" elif step_type == "action": new_node["title"] += f"\n\nNotes: {custom_step['notes']}" elif step_type == "solution": new_node["title"] += f"\n\nNotes: {custom_step['notes']}" return new_node def generate_tree_name_from_session( original_tree_name: str, ticket_number: str | None = None, client_name: str | None = None ) -> str: """Generate a descriptive name for the saved tree. Args: original_tree_name: Name of the original tree ticket_number: Optional ticket number client_name: Optional client name Returns: Generated tree name """ parts = [original_tree_name, "Session"] if ticket_number: parts.append(f"(Ticket {ticket_number})") if client_name: parts.append(f"- {client_name}") return " ".join(parts)