""" Soft Label / Probability Distribution Layout Generates a set of constrained sliders where annotators distribute probability mass across labels. All sliders are constrained to sum to a fixed total (default 100). Research basis: - Fornaciari et al. (2021) "Beyond Black & White: Leveraging Annotator Disagreement via Soft-Label Multi-Task Learning" ACL - Plank et al. (2014) "Linguistically Debatable or Just Plain Wrong?" ACL """ 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, generate_tooltip_html, ) logger = logging.getLogger(__name__) # Defaults DEFAULT_TOTAL = 100 DEFAULT_MIN_PER_LABEL = 0 def generate_soft_label_layout(annotation_scheme): """ Generate HTML for a soft label / probability distribution interface. Args: annotation_scheme (dict): Configuration including: - name: Schema identifier - description: Display description - labels: List of label names (strings or dicts with 'name') - total: Sum constraint (default 100) - min_per_label: Minimum per label (default 0) - show_distribution_chart: Show bar chart (default true) Returns: tuple: (html_string, key_bindings) """ return safe_generate_layout(annotation_scheme, _generate_soft_label_layout_internal) def _generate_soft_label_layout_internal(annotation_scheme): schema_name = annotation_scheme["name"] safe_schema = escape_html_content(schema_name) description = annotation_scheme["description"] labels = annotation_scheme.get("labels", []) total = annotation_scheme.get("total", DEFAULT_TOTAL) min_per_label = annotation_scheme.get("min_per_label", DEFAULT_MIN_PER_LABEL) show_chart = annotation_scheme.get("show_distribution_chart", True) if not labels: raise ValueError(f"soft_label schema '{schema_name}' requires 'labels'") layout_attrs = generate_layout_attributes(annotation_scheme) validation = generate_validation_attribute(annotation_scheme) # Normalize labels label_names = [] for lbl in labels: if isinstance(lbl, str): label_names.append(lbl) elif isinstance(lbl, dict) and "name" in lbl: label_names.append(lbl["name"]) else: raise ValueError(f"Invalid label format: {lbl}") # Calculate initial equal distribution initial_value = total // len(label_names) remainder = total - (initial_value * len(label_names)) html = f"""
""" # Inline JS for sum constraint html += f""" """ key_bindings = [] logger.info(f"Generated soft_label layout for {schema_name} with {len(label_names)} labels") return html, key_bindings