Refactor indicator management to a data-driven approach

- Introduced dynamic generation of parameter fields and callback handling for indicators, enhancing modularity and maintainability.
- Updated `config_utils.py` with new utility functions to load indicator templates and generate dynamic outputs and states for parameter fields.
- Refactored `indicators.py` to utilize these utilities, streamlining the callback logic and improving user experience by reducing hardcoded elements.
- Modified `indicator_modal.py` to create parameter fields dynamically based on JSON templates, eliminating the need for manual updates when adding new indicators.
- Added documentation outlining the new data-driven architecture for indicators, improving clarity and guidance for future development.

These changes significantly enhance the flexibility and scalability of the indicator system, aligning with project goals for maintainability and performance.
This commit is contained in:
Vasily.onl
2025-06-11 19:09:52 +08:00
parent 89b071230e
commit 3e0e89b826
8 changed files with 406 additions and 249 deletions

View File

@@ -6,6 +6,7 @@ import json
import os
import logging
from typing import List, Dict, Any, Optional
from dash import Output, Input, State
logger = logging.getLogger(__name__)
@@ -164,4 +165,174 @@ def generate_parameter_fields_config(indicator_type: str) -> Optional[Dict[str,
fields_config[param_name] = field_config
return fields_config
return fields_config
def get_parameter_field_outputs() -> List[Output]:
"""Generate dynamic Output components for parameter field visibility callbacks.
Returns:
List[Output]: List of Output components for all parameter containers
"""
templates = load_indicator_templates()
outputs = [Output('indicator-parameters-message', 'style')]
for indicator_type in templates.keys():
outputs.append(Output(f'{indicator_type}-parameters', 'style'))
return outputs
def get_parameter_field_states() -> List[State]:
"""Generate dynamic State components for parameter input fields.
Returns:
List[State]: List of State components for all parameter input fields
"""
templates = load_indicator_templates()
states = []
for indicator_type, template in templates.items():
config = generate_parameter_fields_config(indicator_type)
if config:
for field_config in config.values():
states.append(State(field_config['input_id'], 'value'))
return states
def get_parameter_field_edit_outputs() -> List[Output]:
"""Generate dynamic Output components for parameter fields in edit mode.
Returns:
List[Output]: List of Output components for setting parameter values
"""
templates = load_indicator_templates()
outputs = []
for indicator_type, template in templates.items():
config = generate_parameter_fields_config(indicator_type)
if config:
for field_config in config.values():
outputs.append(Output(field_config['input_id'], 'value'))
return outputs
def get_parameter_field_reset_outputs() -> List[Output]:
"""Generate dynamic Output components for resetting parameter fields.
Returns:
List[Output]: List of Output components for resetting parameter values
"""
templates = load_indicator_templates()
outputs = []
for indicator_type, template in templates.items():
config = generate_parameter_fields_config(indicator_type)
if config:
for field_config in config.values():
outputs.append(Output(field_config['input_id'], 'value', allow_duplicate=True))
return outputs
def collect_parameter_values(indicator_type: str, all_parameter_values: Dict[str, Any]) -> Dict[str, Any]:
"""Collect parameter values for a specific indicator type from callback arguments.
Args:
indicator_type (str): The indicator type
all_parameter_values (Dict[str, Any]): All parameter values from callback
Returns:
Dict[str, Any]: Parameters specific to the indicator type
"""
config = generate_parameter_fields_config(indicator_type)
if not config:
return {}
parameters = {}
defaults = get_indicator_default_parameters(indicator_type)
for param_name, field_config in config.items():
field_id = field_config['input_id']
value = all_parameter_values.get(field_id)
default_value = defaults.get(param_name, field_config.get('default'))
# Use provided value or fall back to default
parameters[param_name] = value if value is not None else default_value
return parameters
def set_parameter_values(indicator_type: str, parameters: Dict[str, Any]) -> List[Any]:
"""Generate parameter values for setting in edit mode.
Args:
indicator_type (str): The indicator type
parameters (Dict[str, Any]): Parameter values to set
Returns:
List[Any]: Values in the order expected by the callback outputs
"""
templates = load_indicator_templates()
values = []
for current_type, template in templates.items():
config = generate_parameter_fields_config(current_type)
if config:
for param_name, field_config in config.items():
if current_type == indicator_type:
# Set the actual parameter value for the matching indicator type
value = parameters.get(param_name)
else:
# Set None for other indicator types
value = None
values.append(value)
return values
def reset_parameter_values() -> List[Any]:
"""Generate default parameter values for resetting the form.
Returns:
List[Any]: Default values in the order expected by reset callback outputs
"""
templates = load_indicator_templates()
values = []
for indicator_type, template in templates.items():
config = generate_parameter_fields_config(indicator_type)
if config:
defaults = get_indicator_default_parameters(indicator_type)
for param_name, field_config in config.items():
default_value = defaults.get(param_name, field_config.get('default'))
values.append(default_value)
return values
def get_parameter_visibility_styles(selected_indicator_type: str) -> List[Dict[str, str]]:
"""Generate visibility styles for parameter containers.
Args:
selected_indicator_type (str): The currently selected indicator type
Returns:
List[Dict[str, str]]: Visibility styles for each parameter container
"""
templates = load_indicator_templates()
hidden_style = {'display': 'none'}
visible_style = {'display': 'block'}
# First style is for the message
message_style = {'display': 'block'} if not selected_indicator_type else {'display': 'none'}
styles = [message_style]
# Then styles for each indicator type container
for indicator_type in templates.keys():
style = visible_style if indicator_type == selected_indicator_type else hidden_style
styles.append(style)
return styles