feat(value-sources): optional normalization for magnitude sources
Add an opt-out `normalize` flag to the four magnitude value sources (ha_entity, http, system_metrics, game_event). get_value() stays in [0,1] for every source (the normalized scalar-bus invariant), so all existing consumers — brightness sinks, gradient_map, template `name` bindings, and color-strip bindable floats — are unaffected. - normalize=False is a clamp-passthrough: skip the min/max rescale and clamp the raw reading into [0,1] (for sources already reporting a 0..1 fraction). The un-normalized magnitude stays available via get_raw_value() / template raw[name] / automations. - Add get_raw_value() + a raw channel to GameEventValueStream (it had none). game_event's flag is model/stream-level only (no CRUD schema). - Finite-safe clamp01() util; harden the composite-layer brightness multiply (latent negative-wrap / >=1 skip) with it. - Preview WebSocket: tolerate non-numeric raw, generalize raw-range. - Frontend: settings-toggle slider per HA/system_metrics/http editor with min/max grey-out; toggle hidden for fixed-mapping percent metrics. en/ru/zh locale keys. - Additive optional field (default True) — JSON round-trip, no migration. Tests: store create/update round-trip, clamp-passthrough, live normalize flip, game_event raw channel + build_stream forwarding, and finite-safe clamp01.
This commit is contained in:
@@ -7,7 +7,10 @@ from ledgrab.storage.value_source import (
|
||||
AnimatedValueSource,
|
||||
AudioValueSource,
|
||||
DaylightValueSource,
|
||||
HAEntityValueSource,
|
||||
HTTPValueSource,
|
||||
StaticValueSource,
|
||||
SystemMetricsValueSource,
|
||||
ValueSource,
|
||||
)
|
||||
from ledgrab.storage.value_source_store import ValueSourceStore
|
||||
@@ -238,6 +241,50 @@ class TestValueSourceStoreCRUD:
|
||||
assert updated.speed == 30.0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# normalize flag — full store create/update path (factory builder + applier)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestValueSourceNormalizeStoreRoundTrip:
|
||||
"""The ``normalize`` flag must survive the real write path —
|
||||
create_source -> CREATE_BUILDERS and update_source -> UPDATE_APPLIERS —
|
||||
not just dataclass from_dict/to_dict. A dropped builder/applier line would
|
||||
silently revert/ignore the flag while every dataclass-level test stayed
|
||||
green, so these exercise the factory layer end-to-end (game_event is
|
||||
intentionally excluded — it has no store/CRUD path).
|
||||
"""
|
||||
|
||||
_CASES = [
|
||||
("ha_entity", {"ha_source_id": "ha1", "entity_id": "sensor.temp"}, HAEntityValueSource),
|
||||
("http", {"http_endpoint_id": "ep1"}, HTTPValueSource),
|
||||
("system_metrics", {"metric": "cpu_load"}, SystemMetricsValueSource),
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize("source_type,kwargs,cls", _CASES)
|
||||
def test_create_normalize_false_persists(self, store, source_type, kwargs, cls):
|
||||
s = store.create_source(
|
||||
name=f"{source_type}_n", source_type=source_type, normalize=False, **kwargs
|
||||
)
|
||||
assert isinstance(s, cls)
|
||||
assert s.normalize is False
|
||||
# Re-read exercises the SQLite blob persistence round-trip as well.
|
||||
assert store.get_source(s.id).normalize is False
|
||||
|
||||
@pytest.mark.parametrize("source_type,kwargs,cls", _CASES)
|
||||
def test_create_normalize_defaults_true(self, store, source_type, kwargs, cls):
|
||||
s = store.create_source(name=f"{source_type}_d", source_type=source_type, **kwargs)
|
||||
assert s.normalize is True
|
||||
|
||||
@pytest.mark.parametrize("source_type,kwargs,cls", _CASES)
|
||||
def test_update_normalize_flip(self, store, source_type, kwargs, cls):
|
||||
s = store.create_source(
|
||||
name=f"{source_type}_u", source_type=source_type, normalize=False, **kwargs
|
||||
)
|
||||
assert store.update_source(s.id, normalize=True).normalize is True
|
||||
assert store.update_source(s.id, normalize=False).normalize is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Name uniqueness
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user