""" Hierarchical Multi-Label Selection Layout Generates an expandable/collapsible tree of checkboxes for hierarchical taxonomy labeling. Stores selections as a hidden input with comma-separated values. Research basis: - Silla & Freitas (2011) "A Survey of Hierarchical Classification Across Different Application Domains" Data Mining and Knowledge Discovery - Vens et al. (2008) "Decision Trees for Hierarchical Multi-label Classification" Machine Learning """ import json import logging from potato.ai.ai_help_wrapper import get_ai_wrapper from .identifier_utils import ( safe_generate_layout, generate_element_identifier, generate_validation_attribute, escape_html_content, generate_layout_attributes, ) logger = logging.getLogger(__name__) def generate_hierarchical_multiselect_layout(annotation_scheme): """ Generate HTML for a hierarchical multi-label selection interface. Args: annotation_scheme (dict): Configuration including: - name: Schema identifier - description: Display description - taxonomy: Nested dict/list defining the hierarchy - auto_select_children: Auto-select children when parent selected (default false) - auto_select_parent: Auto-select parent when all children selected (default false) - show_search: Show search/filter box (default false) - max_selections: Maximum number of selections (null = unlimited) Returns: tuple: (html_string, key_bindings) """ return safe_generate_layout(annotation_scheme, _generate_hierarchical_multiselect_layout_internal) def _build_tree_html(taxonomy, schema_name, prefix="", depth=0): """Recursively build tree HTML from taxonomy dict/list.""" html = "" safe_schema = escape_html_content(schema_name) if isinstance(taxonomy, dict): for key, children in taxonomy.items(): safe_key = escape_html_content(key) node_id = f"{prefix}{key}".replace(" ", "_") safe_node_id = escape_html_content(node_id) has_children = bool(children) toggle = '' if has_children else '' html += f"""
{toggle}
""" if has_children: html += f'" html += "
" elif isinstance(taxonomy, list): for item in taxonomy: safe_item = escape_html_content(str(item)) node_id = f"{prefix}{item}".replace(" ", "_") safe_node_id = escape_html_content(node_id) html += f"""
""" return html def _generate_hierarchical_multiselect_layout_internal(annotation_scheme): schema_name = annotation_scheme["name"] safe_schema = escape_html_content(schema_name) description = annotation_scheme["description"] taxonomy = annotation_scheme.get("taxonomy", {}) auto_select_children = annotation_scheme.get("auto_select_children", False) auto_select_parent = annotation_scheme.get("auto_select_parent", False) show_search = annotation_scheme.get("show_search", False) max_selections = annotation_scheme.get("max_selections", None) layout_attrs = generate_layout_attributes(annotation_scheme) validation = generate_validation_attribute(annotation_scheme) if not taxonomy: raise ValueError(f"hierarchical_multiselect schema '{schema_name}' requires 'taxonomy'") identifiers = generate_element_identifier(schema_name, "selected_labels", "hidden") html = f"""
{get_ai_wrapper()}
{escape_html_content(description)} """ if show_search: html += f""" """ html += f"""
""" html += _build_tree_html(taxonomy, schema_name) html += f"""
""" key_bindings = [] logger.info(f"Generated hierarchical_multiselect layout for {schema_name}") return html, key_bindings