steer-core 0.1.7__tar.gz → 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {steer_core-0.1.7 → steer_core-0.1.9}/PKG-INFO +1 -1
  2. {steer_core-0.1.7 → steer_core-0.1.9}/setup.py +1 -0
  3. steer_core-0.1.9/steer_core/Apps/Components/RangeSliderComponents.py +500 -0
  4. steer_core-0.1.9/steer_core/Apps/Components/SliderComponents.py +657 -0
  5. steer_core-0.1.9/steer_core/Apps/Utils/SliderControls.py +677 -0
  6. steer_core-0.1.9/steer_core/Data/database.db +0 -0
  7. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/DataManager.py +5 -3
  8. steer_core-0.1.9/steer_core/Decorators/__init__.py +0 -0
  9. steer_core-0.1.9/steer_core/Mixins/__init__.py +0 -0
  10. steer_core-0.1.9/steer_core/__init__.py +1 -0
  11. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core.egg-info/PKG-INFO +1 -1
  12. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core.egg-info/SOURCES.txt +7 -0
  13. steer_core-0.1.9/test/test_compound_components_clean.py +0 -0
  14. steer_core-0.1.7/steer_core/__init__.py +0 -1
  15. {steer_core-0.1.7 → steer_core-0.1.9}/README.md +0 -0
  16. {steer_core-0.1.7 → steer_core-0.1.9}/setup.cfg +0 -0
  17. /steer_core-0.1.7/steer_core/Apps/Performance/__init__.py → /steer_core-0.1.9/steer_core/Apps/Components/CompoundComponents.py +0 -0
  18. {steer_core-0.1.7/steer_core/Apps → steer_core-0.1.9/steer_core/Apps/Components}/__init__.py +0 -0
  19. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Apps/ContextManagers.py +0 -0
  20. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Apps/Performance/CallbackTimer.py +0 -0
  21. {steer_core-0.1.7/steer_core/Constants → steer_core-0.1.9/steer_core/Apps/Performance}/__init__.py +0 -0
  22. {steer_core-0.1.7/steer_core/ContextManagers → steer_core-0.1.9/steer_core/Apps/Utils}/__init__.py +0 -0
  23. {steer_core-0.1.7/steer_core/Data → steer_core-0.1.9/steer_core/Apps}/__init__.py +0 -0
  24. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Constants/Units.py +0 -0
  25. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Constants/Universal.py +0 -0
  26. {steer_core-0.1.7/steer_core/Decorators → steer_core-0.1.9/steer_core/Constants}/__init__.py +0 -0
  27. {steer_core-0.1.7/steer_core/Mixins → steer_core-0.1.9/steer_core/ContextManagers}/__init__.py +0 -0
  28. /steer_core-0.1.7/test/test_compound_components_clean.py → /steer_core-0.1.9/steer_core/Data/__init__.py +0 -0
  29. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Decorators/Coordinates.py +0 -0
  30. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Decorators/Electrochemical.py +0 -0
  31. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Decorators/General.py +0 -0
  32. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Decorators/Objects.py +0 -0
  33. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Mixins/Colors.py +0 -0
  34. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Mixins/Coordinates.py +0 -0
  35. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Mixins/Data.py +0 -0
  36. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Mixins/Serializer.py +0 -0
  37. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core/Mixins/Validators.py +0 -0
  38. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core.egg-info/dependency_links.txt +0 -0
  39. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core.egg-info/requires.txt +0 -0
  40. {steer_core-0.1.7 → steer_core-0.1.9}/steer_core.egg-info/top_level.txt +0 -0
  41. {steer_core-0.1.7 → steer_core-0.1.9}/test/test_compound_components.py +0 -0
  42. {steer_core-0.1.7 → steer_core-0.1.9}/test/test_slider_controls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steer-core
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Modelling energy storage from cell to site - STEER OpenCell Design
5
5
  Home-page: https://github.com/nicholas9182/steer-core/
6
6
  Author: Nicholas Siemons
@@ -22,6 +22,7 @@ setup(
22
22
  "scipy==1.15.3",
23
23
  "plotly==6.2.0",
24
24
  ],
25
+ package_data={"steer_core.Data": ["database.db"]},
25
26
  scripts=[],
26
27
  classifiers=[
27
28
  "Programming Language :: Python :: 3",
@@ -0,0 +1,500 @@
1
+ import dash as ds
2
+ import numpy as np
3
+ from typing import Union
4
+ from dash import Input, Output
5
+
6
+
7
+ class RangeSliderWithTextInput:
8
+ """
9
+ A custom Dash component that combines a range slider with two text inputs for synchronized range control.
10
+
11
+ This component creates a user interface element consisting of a range slider and two numeric input fields
12
+ that can be used together to set numeric ranges. The range slider provides visual feedback and easy
13
+ adjustment of both start and end values, while the text inputs allow for precise value entry. All
14
+ components are synchronized and share the same value constraints.
15
+
16
+ The component is designed for use in Dash applications where users need to input numeric ranges
17
+ within a specified domain, with the flexibility of both visual (range slider) and precise (text inputs)
18
+ control methods.
19
+
20
+ Attributes:
21
+ id_base (dict): Base identifier dictionary used to construct unique IDs for child components
22
+ min_val (float): Minimum allowed value for both slider and inputs
23
+ max_val (float): Maximum allowed value for both slider and inputs
24
+ default_val (list[float]): Default range values to display on initialization [start, end]
25
+ step (float): Step size for value increments/decrements
26
+ mark_interval (float): Interval between tick marks on the slider
27
+ property_name (str): Property identifier used in component ID construction
28
+ title (str): Display title for the component
29
+ with_slider_titles (bool): Whether to show the title above the slider
30
+ div_width (str): CSS width specification for the container div
31
+ slider_disable (bool): Whether the components should be disabled
32
+ message (str): Optional message displayed between title and slider
33
+ slider_id (dict): Computed ID for the range slider component
34
+ input_start_id (dict): Computed ID for the start value input component
35
+ input_end_id (dict): Computed ID for the end value input component
36
+
37
+ Example:
38
+ >>> range_component = RangeSliderWithTextInput(
39
+ ... id_base={'type': 'parameter', 'index': 0},
40
+ ... min_val=0.0,
41
+ ... max_val=100.0,
42
+ ... step=1.0,
43
+ ... mark_interval=10.0,
44
+ ... property_name='temperature_range',
45
+ ... title='Temperature Range (°C)',
46
+ ... default_val=[20.0, 30.0],
47
+ ... message='Select optimal temperature range' # Optional message
48
+ ... )
49
+ >>> layout_element = range_component() # Returns Dash HTML Div component
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ id_base: dict,
55
+ min_val: float,
56
+ max_val: float,
57
+ step: float,
58
+ mark_interval: float,
59
+ property_name: str,
60
+ title: str,
61
+ default_val: Union[list[float], None] = None,
62
+ with_slider_titles: bool = True,
63
+ slider_disable: bool = False,
64
+ div_width: str = 'calc(90%)',
65
+ message: str = None,
66
+ ):
67
+ """
68
+ Initialize the RangeSliderWithTextInput component.
69
+
70
+ Args:
71
+ id_base (dict): Base dictionary for generating component IDs. Should contain
72
+ identifying information that will be extended with component-specific
73
+ subtypes and properties.
74
+ min_val (float): Minimum value that can be selected on the slider or entered
75
+ in the text inputs.
76
+ max_val (float): Maximum value that can be selected on the slider or entered
77
+ in the text inputs.
78
+ step (float): The granularity of value changes. Determines the smallest
79
+ increment/decrement possible.
80
+ mark_interval (float): The spacing between tick marks displayed on the slider.
81
+ Should be a multiple of step for best visual alignment.
82
+ property_name (str): A string identifier for this specific property, used
83
+ in ID generation and callbacks.
84
+ title (str): Human-readable title displayed above the component.
85
+ default_val (list[float], optional): Initial range values to display [start, end].
86
+ If None, defaults to [min_val, max_val].
87
+ with_slider_titles (bool, optional): If True, displays the title above
88
+ the slider. If False, shows a non-breaking
89
+ space to maintain layout. Defaults to True.
90
+ slider_disable (bool, optional): If True, disables slider and input
91
+ interactions. Defaults to False.
92
+ div_width (str, optional): CSS width specification for the container div.
93
+ Defaults to 'calc(90%)'.
94
+ message (str, optional): Optional message to display between the title
95
+ and slider. If None, no message is displayed.
96
+ Defaults to None.
97
+
98
+ Raises:
99
+ ValueError: If min_val >= max_val, or if step <= 0, or if mark_interval <= 0,
100
+ or if default_val contains invalid range values.
101
+ TypeError: If default_val is provided but not a list of numeric values.
102
+ """
103
+
104
+ # Validate inputs
105
+ if min_val >= max_val:
106
+ raise ValueError(f"min_val ({min_val}) must be less than max_val ({max_val})")
107
+ if step <= 0:
108
+ raise ValueError(f"step ({step}) must be positive")
109
+ if mark_interval <= 0:
110
+ raise ValueError(f"mark_interval ({mark_interval}) must be positive")
111
+
112
+ # Validate and set default values
113
+ if default_val is None:
114
+ default_val = [min_val, max_val]
115
+ else:
116
+ if not isinstance(default_val, list) or len(default_val) != 2:
117
+ raise TypeError("default_val must be a list of two numeric values [start, end]")
118
+ try:
119
+ default_val = [float(default_val[0]), float(default_val[1])]
120
+ except (ValueError, TypeError):
121
+ raise TypeError("default_val must contain numeric values")
122
+ if default_val[0] > default_val[1]:
123
+ raise ValueError("default_val start value must be <= end value")
124
+
125
+ self.id_base = id_base
126
+ self.min_val = min_val
127
+ self.max_val = max_val
128
+ self.default_val = self._validate_and_clamp_range(default_val)
129
+ self.step = step
130
+ self.mark_interval = mark_interval
131
+ self.property_name = property_name
132
+ self.title = title
133
+ self.with_slider_titles = with_slider_titles
134
+ self.div_width = div_width
135
+ self.slider_disable = slider_disable
136
+ self.message = message
137
+
138
+ self.slider_id = self._make_id('rangeslider')
139
+ self.input_start_id = self._make_id('input_start')
140
+ self.input_end_id = self._make_id('input_end')
141
+
142
+ def _make_id(self, subtype: str):
143
+ """
144
+ Generate a unique ID dictionary for component sub-elements.
145
+
146
+ Combines the base ID with component-specific subtype and property information
147
+ to create unique identifiers for Dash callbacks and component references.
148
+
149
+ Args:
150
+ subtype (str): The specific component subtype (e.g., 'rangeslider', 'input_start', 'input_end').
151
+
152
+ Returns:
153
+ dict: Complete ID dictionary containing base ID information plus subtype
154
+ and property specifications.
155
+
156
+ Example:
157
+ >>> component._make_id('rangeslider')
158
+ {'type': 'parameter', 'index': 0, 'subtype': 'rangeslider', 'property': 'temperature_range'}
159
+ """
160
+ return {**self.id_base, 'subtype': subtype, 'property': self.property_name}
161
+
162
+ def _make_range_slider(self):
163
+ """
164
+ Create and configure the Dash range slider component.
165
+
166
+ Generates a dcc.RangeSlider with the specified range, step size, default values,
167
+ and tick marks. The range slider provides visual feedback for range selection
168
+ and is synchronized with the text input components.
169
+
170
+ Returns:
171
+ dash.dcc.RangeSlider: Configured range slider component with ID, value constraints,
172
+ tick marks, and styling options.
173
+
174
+ Note:
175
+ - Tick marks are generated at intervals specified by mark_interval
176
+ - updatemode is set to 'mouseup' to reduce callback frequency
177
+ - The slider can be disabled via the slider_disable attribute
178
+ """
179
+ return ds.dcc.RangeSlider(
180
+ id=self.slider_id,
181
+ min=self.min_val,
182
+ max=self.max_val,
183
+ value=self.default_val,
184
+ step=self.step,
185
+ disabled=self.slider_disable,
186
+ marks={int(i): "" for i in np.arange(self.min_val, self.max_val + self.mark_interval, self.mark_interval)},
187
+ updatemode='mouseup'
188
+ )
189
+
190
+ def _make_start_input(self):
191
+ """
192
+ Create and configure the Dash numeric input component for the start value.
193
+
194
+ Generates a dcc.Input with number type for precise start value entry.
195
+ The input is synchronized with the range slider and provides an alternative
196
+ method for users to specify exact start values.
197
+
198
+ Returns:
199
+ dash.dcc.Input: Configured numeric input component with ID, type,
200
+ value constraints, styling, and step specification.
201
+
202
+ Note:
203
+ - Input type is set to 'number' for numeric validation
204
+ - Width and margin styling provides visual alignment
205
+ - Step size matches the slider for consistent granularity
206
+ """
207
+ return ds.dcc.Input(
208
+ id=self.input_start_id,
209
+ type='number',
210
+ value=self.default_val[0],
211
+ step=self.step,
212
+ style={'width': '80px'},
213
+ disabled=self.slider_disable,
214
+ )
215
+
216
+ def _make_end_input(self):
217
+ """
218
+ Create and configure the Dash numeric input component for the end value.
219
+
220
+ Generates a dcc.Input with number type for precise end value entry.
221
+ The input is synchronized with the range slider and provides an alternative
222
+ method for users to specify exact end values.
223
+
224
+ Returns:
225
+ dash.dcc.Input: Configured numeric input component with ID, type,
226
+ value constraints, styling, and step specification.
227
+
228
+ Note:
229
+ - Input type is set to 'number' for numeric validation
230
+ - Width and margin styling provides visual alignment
231
+ - Step size matches the slider for consistent granularity
232
+ """
233
+ return ds.dcc.Input(
234
+ id=self.input_end_id,
235
+ type='number',
236
+ value=self.default_val[1],
237
+ step=self.step,
238
+ style={'margin-left': '10px', 'width': '80px'},
239
+ disabled=self.slider_disable,
240
+ )
241
+
242
+ def __call__(self):
243
+ """
244
+ Generate the complete component layout as a callable object.
245
+
246
+ Creates and returns a Dash HTML Div containing the title, optional message,
247
+ range slider, and input components arranged in a cohesive layout. This method allows
248
+ the class instance to be used as a callable that returns the complete
249
+ component structure.
250
+
251
+ Returns:
252
+ dash.html.Div: Complete component layout containing:
253
+ - Title paragraph (conditional based on with_slider_titles)
254
+ - Optional message paragraph (if message is provided)
255
+ - Range slider component in a styled container
256
+ - Start and end numeric input components with labels
257
+ - Spacing elements (line breaks)
258
+
259
+ Note:
260
+ - Title display is controlled by with_slider_titles attribute
261
+ - When title is hidden, a non-breaking space maintains layout
262
+ - Message is displayed only if provided during initialization
263
+ - Negative bottom margin on slider container reduces spacing
264
+ - Container width is controlled by div_width attribute
265
+ - Input labels provide clear indication of start/end values
266
+ """
267
+ slider_title = self.title if self.with_slider_titles else "\u00A0"
268
+
269
+ # Build the component list
270
+ components = [
271
+ ds.html.P(slider_title, style={'margin-left': '20px', 'margin-bottom': '0px'})
272
+ ]
273
+
274
+ # Add optional message if provided
275
+ if self.message:
276
+ components.append(
277
+ ds.html.P(self.message, style={
278
+ 'margin-left': '20px',
279
+ 'margin-bottom': '5px',
280
+ 'margin-top': '2px',
281
+ 'font-size': '0.9em',
282
+ 'color': '#666666',
283
+ 'font-style': 'italic'
284
+ })
285
+ )
286
+
287
+ # Add slider and input components
288
+ components.extend([
289
+ ds.html.Div([self._make_range_slider()], style={'margin-bottom': '-18px'}),
290
+ ds.html.Div([
291
+ ds.html.Span("Start:", style={'margin-left': '20px', 'margin-right': '5px'}),
292
+ self._make_start_input(),
293
+ ds.html.Span("End:", style={'margin-left': '15px', 'margin-right': '5px'}),
294
+ self._make_end_input(),
295
+ ], style={'display': 'flex', 'align-items': 'center'}),
296
+ ds.html.Br(), ds.html.Br()
297
+ ])
298
+
299
+ return ds.html.Div(components, style={'width': self.div_width, 'margin-left': '-20px'})
300
+
301
+ @property
302
+ def components(self):
303
+ """
304
+ Get a dictionary mapping component types to their IDs.
305
+
306
+ Provides easy access to the IDs of the range slider and input components
307
+ for use in Dash callbacks and component interactions.
308
+
309
+ Returns:
310
+ dict: Dictionary with component type keys mapping to their ID dictionaries.
311
+
312
+ Example:
313
+ >>> component.components
314
+ {
315
+ 'rangeslider': {'type': 'parameter', 'subtype': 'rangeslider', 'property': 'temp_range'},
316
+ 'input_start': {'type': 'parameter', 'subtype': 'input_start', 'property': 'temp_range'},
317
+ 'input_end': {'type': 'parameter', 'subtype': 'input_end', 'property': 'temp_range'}
318
+ }
319
+
320
+ Note:
321
+ This property is particularly useful for setting up Dash callbacks
322
+ that need to reference the specific component IDs.
323
+ """
324
+ return {
325
+ 'rangeslider': self.slider_id,
326
+ 'input_start': self.input_start_id,
327
+ 'input_end': self.input_end_id
328
+ }
329
+
330
+ def get_value_inputs(self):
331
+ """
332
+ Get Input objects for listening to component value changes.
333
+
334
+ Returns a list of Dash Input objects that can be used in callbacks to
335
+ listen for value changes from the range slider or input components.
336
+
337
+ Returns:
338
+ list: List containing [Input(slider_id, 'value'), Input(input_start_id, 'value'), Input(input_end_id, 'value')]
339
+
340
+ Example:
341
+ >>> @app.callback(
342
+ ... Output('some-output', 'children'),
343
+ ... component.get_value_inputs()
344
+ ... )
345
+ ... def update_display(slider_range, start_val, end_val):
346
+ ... return f"Range: {slider_range or [start_val, end_val]}"
347
+ """
348
+ return [
349
+ Input(self.slider_id, 'value'),
350
+ Input(self.input_start_id, 'value'),
351
+ Input(self.input_end_id, 'value')
352
+ ]
353
+
354
+ def get_value_outputs(self):
355
+ """
356
+ Get Output objects for updating component values.
357
+
358
+ Returns a list of Dash Output objects that can be used in callbacks to
359
+ update the values of the range slider and input components.
360
+
361
+ Returns:
362
+ list: List containing [Output(slider_id, 'value'), Output(input_start_id, 'value'), Output(input_end_id, 'value')]
363
+
364
+ Example:
365
+ >>> @app.callback(
366
+ ... component.get_value_outputs(),
367
+ ... [Input('some-input', 'value')]
368
+ ... )
369
+ ... def update_components(new_range):
370
+ ... return [new_range, new_range[0], new_range[1]]
371
+ """
372
+ return [
373
+ Output(self.slider_id, 'value'),
374
+ Output(self.input_start_id, 'value'),
375
+ Output(self.input_end_id, 'value')
376
+ ]
377
+
378
+ def get_pattern_matching_value_inputs(self, property_name='ALL'):
379
+ """
380
+ Get pattern-matching Input objects for listening to multiple component instances.
381
+
382
+ Returns Input objects using pattern-matching to listen to all components
383
+ with the specified property name, useful for multi-component callbacks.
384
+
385
+ Args:
386
+ property_name: The property to match. Use 'ALL' for all properties,
387
+ or specify a specific property name.
388
+
389
+ Returns:
390
+ list: List containing pattern-matching Input objects
391
+
392
+ Example:
393
+ >>> @app.callback(
394
+ ... Output('summary', 'children'),
395
+ ... component.get_pattern_matching_value_inputs('ALL')
396
+ ... )
397
+ ... def update_summary(slider_ranges, start_values, end_values):
398
+ ... return f"Total range sliders: {len(slider_ranges)}"
399
+ """
400
+ slider_pattern = {'type': 'parameter', 'subtype': 'rangeslider', 'property': property_name}
401
+ start_pattern = {'type': 'parameter', 'subtype': 'input_start', 'property': property_name}
402
+ end_pattern = {'type': 'parameter', 'subtype': 'input_end', 'property': property_name}
403
+
404
+ return [
405
+ Input(slider_pattern, 'value'),
406
+ Input(start_pattern, 'value'),
407
+ Input(end_pattern, 'value')
408
+ ]
409
+
410
+ def get_pattern_matching_value_outputs(self, property_name='ALL'):
411
+ """
412
+ Get pattern-matching Output objects for updating multiple component instances.
413
+
414
+ Returns Output objects using pattern-matching to update all components
415
+ with the specified property name.
416
+
417
+ Args:
418
+ property_name: The property to match. Use 'ALL' for all properties,
419
+ or specify a specific property name.
420
+
421
+ Returns:
422
+ list: List containing pattern-matching Output objects
423
+
424
+ Example:
425
+ >>> @app.callback(
426
+ ... component.get_pattern_matching_value_outputs('ALL'),
427
+ ... [Input('reset-button', 'n_clicks')]
428
+ ... )
429
+ ... def reset_all_components(n_clicks):
430
+ ... if n_clicks:
431
+ ... return [[[0, 100]] * len(ranges), [0] * len(starts), [100] * len(ends)]
432
+ ... return [dash.no_update] * 3
433
+ """
434
+ slider_pattern = {'type': 'parameter', 'subtype': 'rangeslider', 'property': property_name}
435
+ start_pattern = {'type': 'parameter', 'subtype': 'input_start', 'property': property_name}
436
+ end_pattern = {'type': 'parameter', 'subtype': 'input_end', 'property': property_name}
437
+
438
+ return [
439
+ Output(slider_pattern, 'value'),
440
+ Output(start_pattern, 'value'),
441
+ Output(end_pattern, 'value')
442
+ ]
443
+
444
+ def _validate_and_clamp_range(self, range_values):
445
+ """
446
+ Validate and clamp range values to the component's valid domain.
447
+
448
+ Ensures that range values are within the min_val to max_val domain,
449
+ maintains proper ordering (start <= end), and handles invalid values.
450
+
451
+ Args:
452
+ range_values (list): The range values to validate and clamp [start, end].
453
+
454
+ Returns:
455
+ list[float]: The clamped range values within the valid domain,
456
+ with proper ordering maintained.
457
+ """
458
+ if not isinstance(range_values, list) or len(range_values) != 2:
459
+ return [self.min_val, self.max_val]
460
+
461
+ try:
462
+ start, end = float(range_values[0]), float(range_values[1])
463
+
464
+ # Clamp to valid domain
465
+ start = max(self.min_val, min(self.max_val, start))
466
+ end = max(self.min_val, min(self.max_val, end))
467
+
468
+ # Ensure proper ordering
469
+ if start > end:
470
+ start, end = end, start
471
+
472
+ return [start, end]
473
+
474
+ except (ValueError, TypeError):
475
+ return [self.min_val, self.max_val]
476
+
477
+ def _validate_and_clamp_value(self, value):
478
+ """
479
+ Validate and clamp a single value to the component's valid range.
480
+
481
+ Ensures that any input value is within the min_val to max_val range
482
+ and handles None values appropriately.
483
+
484
+ Args:
485
+ value: The value to validate and clamp.
486
+
487
+ Returns:
488
+ float: The clamped value within the valid range, or min_val
489
+ if the input value is invalid.
490
+ """
491
+ if value is None:
492
+ return self.min_val
493
+
494
+ try:
495
+ numeric_value = float(value)
496
+ return max(self.min_val, min(self.max_val, numeric_value))
497
+ except (ValueError, TypeError):
498
+ return self.min_val
499
+
500
+