diff --git a/backend/app/api/endpoints/kb_accelerator.py b/backend/app/api/endpoints/kb_accelerator.py index 52274640..ba65c1d2 100644 --- a/backend/app/api/endpoints/kb_accelerator.py +++ b/backend/app/api/endpoints/kb_accelerator.py @@ -667,26 +667,60 @@ def _build_troubleshooting_tree(nodes: list[KBImportNode]) -> dict: # question/decision type — recursively build children options = content.get("options", []) - children = [] + + # Count how many options point to buildable (not-yet-placed) targets + buildable_targets = [] for opt in options: next_id = opt.get("next_node_id") + if next_id and next_id in original_id_map and next_id not in placed: + buildable_targets.append(next_id) + + # Decision nodes MUST have at least 2 branches to pass validation. + # If fewer than 2 buildable targets, demote to action node. + if len(buildable_targets) < 2: + demoted: dict = { + "id": node_id, + "type": "action", + "title": question_text, + "description": content.get("description", ""), + } + if buildable_targets: + demoted["next_node_id"] = buildable_targets[0] + elif options: + # All targets already placed; reference first option's target + first_next = options[0].get("next_node_id") + if first_next: + demoted["next_node_id"] = first_next + return demoted + + # Build children for decision node + children = [] + built_options = [] + for opt in options: + next_id = opt.get("next_node_id") + opt_id = opt.get("id", f"opt-{node_id}-{len(built_options)}") if next_id and next_id in original_id_map: child_node = _build_node(original_id_map[next_id]) if child_node is not None: children.append(child_node) - # If the child is an action with a next_node_id, also - # build that target as a sibling (the tree editor - # expects reachable nodes nested under the decision) _collect_action_chain(child_node, children) + built_options.append({ + "id": opt_id, + "label": opt.get("label", ""), + "next_node_id": next_id, + }) + else: + built_options.append({ + "id": opt_id, + "label": opt.get("label", ""), + "next_node_id": next_id or "", + }) return { "id": node_id, "type": "decision", "question": question_text, - "options": [ - {"label": opt.get("label", ""), "next_node_id": opt.get("next_node_id", "")} - for opt in options - ], + "options": built_options, "children": children, }