Skip to content

build_tree truncates path for classes with >99 siblings (SNOMEDCT Jump To bug) #270

@jvendetti

Description

@jvendetti

Summary

The build_tree method in concept_tree.rb produces a truncated/incorrect tree for ontologies where nodes have more than 99 direct children (e.g., SNOMEDCT). This is the root cause of ncbo/bioportal_web_ui#459, where using "Jump to" search results in the selected class missing from the tree and sibling nodes losing their subtree indicators.

Root Cause

In lib/ontologies_linked_data/concerns/concepts/concept_tree.rb, the tree method calls load_children(path, threshold: 99), which uses partially_load_children to load up to 99 children per node. For large ontologies like SNOMEDCT, nodes such as "Clinical finding" (404684003) can have well over 99 direct children.

When build_tree iterates through the loaded children looking for the next path node and doesn't find it (because it wasn't among the first 99 loaded), two bugs are triggered:

Bug 1: Double removal from path

# concept_tree.rb — build_tree method
if !path.empty? && next_tree_node.nil?
  tree_node.children << path.shift    # removes one element from path
end
tree_node = next_tree_node            # nil
path.delete_at(0)                     # removes ANOTHER element from path

Both path.shift and path.delete_at(0) execute, removing two elements from the path instead of one. This causes an entire level of the hierarchy to be skipped.

Bug 2: Early loop termination

Since next_tree_node is nil, tree_node becomes nil and the while loop exits. The remaining path nodes are never processed, producing a truncated tree.

Bug 3: Missing hasChildren for sibling nodes

load_has_children is called inside build_tree only for levels that are processed. When the loop exits early, sibling nodes at deeper levels never get hasChildren computed. In the serialized API response, these nodes lack the hasChildren flag, so the web UI renders them without expand/collapse icons.

Steps to Reproduce

  1. Navigate to the SNOMEDCT Classes tab on BioPortal
  2. Use the "Jump to" search box to search for "Schizophrenia" (58214004)
  3. Select it from the results

Expected: The class tree shows the path from root to Schizophrenia, with correct subtree indicators on all sibling nodes.

Actual: Schizophrenia is missing from the tree. Many sibling nodes that should have subtrees (e.g., Adjustment disorder, Axis I diagnosis under Mental disorder) appear as leaf nodes.

Suggested Fix

  1. Ensure path nodes are always present in their parent's children list. After load_children(path, threshold: 99), check that each path node exists in its parent's children. If not, add it. This guarantees build_tree will always find the matching child.

  2. Fix the double-removal bug. When next_tree_node is nil, path.delete_at(0) should not execute because path.shift already consumed an element. Wrap path.delete_at(0) in a condition:

    path.delete_at(0) unless next_tree_node.nil?

    Or restructure the loop so that path.delete_at(0) only runs when the match was found.

  3. Consider removing or raising the threshold for path nodes. The path is typically short (5–10 nodes), so ensuring all children are loaded along the path is feasible and avoids the problem entirely.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions