fix: KB tree builder demotes decisions with < 2 branches to actions
Decision nodes with fewer than 2 buildable child targets now get demoted to action nodes instead of creating invalid tree structures. Also adds id fields to option objects (required by tree editor). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user