From 3d5263d7928a280e170d4761d501c97540f8fb02 Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Wed, 22 Jul 2026 19:51:37 +0000 Subject: [PATCH] Add common.UNIVERSAL/POWER bundles to stop hand-duplicating capabilities FIRMWARE_UPDATE and ALARMS were already copy-pasted into all 6 device-type registries by hand; POWER/KIDS_LOCK/REMOTE_CONTROL into 5 of 6. Consolidate into two bundles in common.py, unpacked via *common.UNIVERSAL / *common.POWER the same way ignored.IGNORED already is: - UNIVERSAL: ALARMS, ENERGY_METER, FIRMWARE_UPDATE (moved from fridge.py), SELF_CHECK (moved from fridge.py), AI_ENERGY_LEVEL, and the kids-lock/ remote-control pairs. Safe everywhere -- discover() only binds a href actually present in a device's dump, so a capability with no known conflicting family is a no-op where the href is absent and a real, wanted entity where it's present. This also broadens AI_ENERGY_LEVEL, ENERGY_METER, and SELF_CHECK to device types they weren't confirmed on before, on the same reasoning. - POWER: just POWER_GENERIC/POWER_VS_FALLBACK, applied to the 5 non-AC registries. Airconditioner keeps its own opt-out: its climate entity already owns /power/0 and /power/vs/0 via a bare, no-entity claim (airconditioner.COVERAGE), and a real power capability on the same href would make _build() raise (a href with multiple caps requires every cap to have rt_filter or match_fn; the bare COVERAGE cap has neither). Full test suite (327 tests, all 6 device-type golden fixtures) passes unchanged -- none of the newly-broadened capabilities bind on any existing fixture, confirming the no-op reasoning held in practice, not just theory. --- .../registry/by_type/airconditioner.py | 17 ++-- .../registry/by_type/dishwasher.py | 13 +-- .../localthings/registry/by_type/dryer.py | 13 +-- .../localthings/registry/by_type/oven.py | 12 +-- .../registry/by_type/refrigerator.py | 13 +-- .../localthings/registry/by_type/washer.py | 14 +-- .../registry/capabilities/airconditioner.py | 5 +- .../registry/capabilities/common.py | 86 ++++++++++++++++++- .../registry/capabilities/fridge.py | 51 +---------- tests/test_common_capabilities.py | 69 +++++++++++++++ tests/test_fridge_capabilities.py | 36 -------- 11 files changed, 180 insertions(+), 149 deletions(-) diff --git a/custom_components/localthings/registry/by_type/airconditioner.py b/custom_components/localthings/registry/by_type/airconditioner.py index 0d5f050..9de96e0 100644 --- a/custom_components/localthings/registry/by_type/airconditioner.py +++ b/custom_components/localthings/registry/by_type/airconditioner.py @@ -3,23 +3,22 @@ The first device whose core controls surface as a single composite HA `climate` entity (see capabilities/airconditioner.py and climate.py). Power/mode/temp/wind are consumed by that entity rather than exposed as separate switches/selects, so -this registry deliberately does NOT include the common POWER caps -- on/off is -the climate entity's HVACMode.OFF / TURN_ON/OFF. +this registry includes *common.UNIVERSAL but deliberately NOT common.POWER -- +on/off is the climate entity's HVACMode.OFF / TURN_ON/OFF, and /power/0 + +/power/vs/0 are already pre-claimed (no-entity) by airconditioner.COVERAGE via +CLIMATE_CONSUMED_HREFS. A real POWER_GENERIC/POWER_VS_FALLBACK cap on either +href alongside that bare claim would make _build() raise (see its docstring). -Reuses common.ALARMS + common.ENERGY_METER, fridge.FIRMWARE_UPDATE (as every -registry does), and dishwasher.DIAGNOSIS for /diagnosis/vs/0. +Reuses dishwasher.DIAGNOSIS for /diagnosis/vs/0. """ -from ..capabilities import airconditioner, common, dishwasher, fridge, ignored +from ..capabilities import airconditioner, common, dishwasher, ignored from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( name='airconditioner', capabilities=_build([ *ignored.IGNORED, - common.ALARMS, - common.ENERGY_METER, - fridge.FIRMWARE_UPDATE, - fridge.SELF_CHECK, + *common.UNIVERSAL, dishwasher.DIAGNOSIS, airconditioner.CLIMATE, airconditioner.AIR_PURIFY, diff --git a/custom_components/localthings/registry/by_type/dishwasher.py b/custom_components/localthings/registry/by_type/dishwasher.py index e12dbd7..1f099f3 100644 --- a/custom_components/localthings/registry/by_type/dishwasher.py +++ b/custom_components/localthings/registry/by_type/dishwasher.py @@ -1,19 +1,13 @@ """Dishwasher device registry.""" -from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational +from ..capabilities import common, dishwasher, ignored, laundry, operational from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( name='dishwasher', capabilities=_build([ *ignored.IGNORED, - common.POWER_GENERIC, - common.POWER_VS_FALLBACK, - common.KIDS_LOCK_GENERIC, - common.KIDS_LOCK_VS_FALLBACK, - common.REMOTE_CONTROL_GENERIC, - common.REMOTE_CONTROL_VS_FALLBACK, - common.ALARMS, - common.ENERGY_METER, + *common.UNIVERSAL, + *common.POWER, common.WATER_METER, common.WATER_FILTER, operational.OPERATIONAL_STATE, @@ -25,6 +19,5 @@ REGISTRY = DeviceRegistry( laundry.DOOR_LED, laundry.SOUND_MODE, laundry.SOUND_VOLUME, - fridge.FIRMWARE_UPDATE, ]), ) diff --git a/custom_components/localthings/registry/by_type/dryer.py b/custom_components/localthings/registry/by_type/dryer.py index 71122f1..81d2891 100644 --- a/custom_components/localthings/registry/by_type/dryer.py +++ b/custom_components/localthings/registry/by_type/dryer.py @@ -9,21 +9,15 @@ objects the washer registry uses -- washer and dryer expose the same DA_WM_-family surface, so they stay consistent instead of each carrying a bespoke variant. """ -from ..capabilities import common, dryer, fridge, ignored, laundry, operational +from ..capabilities import common, dryer, ignored, laundry, operational from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( name='dryer', capabilities=_build([ *ignored.IGNORED, - common.POWER_GENERIC, - common.POWER_VS_FALLBACK, - common.KIDS_LOCK_GENERIC, - common.KIDS_LOCK_VS_FALLBACK, - common.REMOTE_CONTROL_GENERIC, - common.REMOTE_CONTROL_VS_FALLBACK, - common.ALARMS, - common.ENERGY_METER, + *common.UNIVERSAL, + *common.POWER, operational.OPERATIONAL_STATE, laundry.DOOR_LED, laundry.SOUND_MODE, @@ -32,6 +26,5 @@ REGISTRY = DeviceRegistry( dryer.DRYER_SETTINGS, dryer.DRYER_COURSE, dryer.DRYER_DIAGNOSIS, - fridge.FIRMWARE_UPDATE, ]), ) diff --git a/custom_components/localthings/registry/by_type/oven.py b/custom_components/localthings/registry/by_type/oven.py index 69a0096..2ae3341 100644 --- a/custom_components/localthings/registry/by_type/oven.py +++ b/custom_components/localthings/registry/by_type/oven.py @@ -1,24 +1,18 @@ """Oven device registry.""" -from ..capabilities import common, fridge, ignored, oven +from ..capabilities import common, ignored, oven from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( name='oven', capabilities=_build([ *ignored.IGNORED, - common.POWER_GENERIC, - common.POWER_VS_FALLBACK, - common.KIDS_LOCK_GENERIC, - common.KIDS_LOCK_VS_FALLBACK, - common.REMOTE_CONTROL_GENERIC, - common.REMOTE_CONTROL_VS_FALLBACK, - common.ALARMS, + *common.UNIVERSAL, + *common.POWER, oven.OVEN_CAVITY, oven.OVEN_SETPOINT, oven.OVEN_MODE, oven.OVEN_OPERATIONAL_STATE, oven.OVEN_DOOR, oven.OVEN_CONNECTED, - fridge.FIRMWARE_UPDATE, ]), ) diff --git a/custom_components/localthings/registry/by_type/refrigerator.py b/custom_components/localthings/registry/by_type/refrigerator.py index ecd63f6..b893475 100644 --- a/custom_components/localthings/registry/by_type/refrigerator.py +++ b/custom_components/localthings/registry/by_type/refrigerator.py @@ -6,21 +6,14 @@ REGISTRY = DeviceRegistry( name='refrigerator', capabilities=_build([ *ignored.IGNORED, - common.POWER_GENERIC, - common.POWER_VS_FALLBACK, + *common.UNIVERSAL, + *common.POWER, fridge.STATUS_LOCK, fridge.DOOR_ALERT, - common.KIDS_LOCK_GENERIC, - common.KIDS_LOCK_VS_FALLBACK, - common.REMOTE_CONTROL_GENERIC, - common.REMOTE_CONTROL_VS_FALLBACK, - common.ALARMS, - common.ENERGY_METER, common.WATER_FILTER, dishwasher.DIAGNOSIS, fridge.ICEMAKER_NIGHTTIME, fridge.FLEX_ZONE, - common.AI_ENERGY_LEVEL, fridge.REFRIGERATION, fridge.AUTOFILL, fridge.WELCOME_LIGHTING, @@ -29,11 +22,9 @@ REGISTRY = DeviceRegistry( fridge.SABBATH, fridge.BEVERAGE_ZONE, fridge.PANTRY_ZONE, - fridge.FIRMWARE_UPDATE, fridge.DEFROST_DELAY, fridge.DEFROST_DELAY_NATIVE_DUPLICATE, fridge.DEFROST_BLOCK_STATUS, - fridge.SELF_CHECK, fridge.DOORS_FALLBACK, fridge.TEMPERATURES_FALLBACK, fridge.ICEMAKER_STATUS_FALLBACK, diff --git a/custom_components/localthings/registry/by_type/washer.py b/custom_components/localthings/registry/by_type/washer.py index ac46cf4..c7732d8 100644 --- a/custom_components/localthings/registry/by_type/washer.py +++ b/custom_components/localthings/registry/by_type/washer.py @@ -1,27 +1,19 @@ """Washer device registry.""" -from ..capabilities import common, dishwasher, fridge, ignored, laundry, operational, washer +from ..capabilities import common, dishwasher, ignored, laundry, operational, washer from ._base import DeviceRegistry, _build REGISTRY = DeviceRegistry( name='washer', capabilities=_build([ *ignored.IGNORED, - common.POWER_GENERIC, - common.POWER_VS_FALLBACK, - common.KIDS_LOCK_GENERIC, - common.KIDS_LOCK_VS_FALLBACK, - common.REMOTE_CONTROL_GENERIC, - common.REMOTE_CONTROL_VS_FALLBACK, + *common.UNIVERSAL, + *common.POWER, washer.WASHER_SETTINGS, washer.WASHER_COURSE, laundry.BUZZER_SOUND, laundry.JOB_BEGINNING_STATUS, - common.ALARMS, - common.ENERGY_METER, common.WATER_METER, - common.AI_ENERGY_LEVEL, operational.OPERATIONAL_STATE, dishwasher.DIAGNOSIS, - fridge.FIRMWARE_UPDATE, ]), ) diff --git a/custom_components/localthings/registry/capabilities/airconditioner.py b/custom_components/localthings/registry/capabilities/airconditioner.py index bfcb374..0abc0b7 100644 --- a/custom_components/localthings/registry/capabilities/airconditioner.py +++ b/custom_components/localthings/registry/capabilities/airconditioner.py @@ -219,8 +219,9 @@ _AC_IGNORED = [ # user-actionable state or no documented write contract, so per the # 'don't guess' rule they are ignored rather than modeled. # /option/muteonce/vs/0 and /selfcheck/vs/0 are deliberately NOT here -- - # see MUTE_ONCE and fridge.SELF_CHECK above/in the by_type registry, - # both of which have a confirmed, cleanly modelable contract. + # see MUTE_ONCE above and common.SELF_CHECK (via common.UNIVERSAL) in + # the by_type registry, both of which have a confirmed, cleanly + # modelable contract. '/airlevelcheck/vs/0', # periodic air-quality sensing scheduler plumbing '/aisleep/vs/0', # AI-sleep feedback state (no actionable control) '/availablecontrolsets/vs/0', # opaque hex-encoded control-set bitmap diff --git a/custom_components/localthings/registry/capabilities/common.py b/custom_components/localthings/registry/capabilities/common.py index a7660d2..d2b2ac5 100644 --- a/custom_components/localthings/registry/capabilities/common.py +++ b/custom_components/localthings/registry/capabilities/common.py @@ -11,7 +11,9 @@ against live device dumps: /filter/waterfilter/vs/0 -> x.com.samsung.da.filterUsage / filterStatus """ from ..capability import Capability -from ..entities import BinarySensorDesc, SelectDesc, SensorDesc, SwitchDesc +from ..entities import ( + BinarySensorDesc, ButtonDesc, SelectDesc, SensorDesc, SwitchDesc, +) def _num(v): @@ -291,3 +293,85 @@ AI_ENERGY_LEVEL = Capability( write_fn=_ai_energy_level_write), ), ) + +FIRMWARE_UPDATE = Capability( + href='/otninformation/vs/0', + poll_tier='cold', + entities=( + BinarySensorDesc( + key='firmware_update', + field='x.com.samsung.da.newVersionAvailable', + name='Firmware update available', + device_class='update', + entity_category='diagnostic', + value_fn=lambda v: str(v).lower() == 'true' if v is not None else None, + ), + ), +) + +SELF_CHECK = Capability( + href='/selfcheck/vs/0', + poll_tier='cold', + entities=( + SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status', + name='Self-check status', icon='mdi:stethoscope', + entity_category='diagnostic'), + SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result', + name='Self-check result', icon='mdi:clipboard-check-outline', + entity_category='diagnostic'), + # List of error codes from the last self-check; joined for display. + # Not every fridge reports the field, hence the exists_fn. + SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error', + name='Self-check error', icon='mdi:alert-circle-outline', + entity_category='diagnostic', + exists_fn=lambda rep, resources: ( + not rep or 'x.com.samsung.da.error' in rep), + value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v), + ButtonDesc(key='selfcheck_start', field='', name='Start self-check', + payload='Start', icon='mdi:play-circle-outline', + entity_category='diagnostic', + write_fn=lambda p, rep, href=None: ( + ['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})), + ), +) + +# --------------------------------------------------------------------------- +# Cross-family bundles, unpacked into every by_type registry's _build([...]) +# call the same way ignored.IGNORED is (*common.UNIVERSAL / *common.POWER). +# discover() only binds a capability whose href is actually present in a +# given device's resource dump, so listing one here for a family that +# doesn't expose the href is a no-op, not a phantom entity -- see the +# adding-device-support skill's coverage-discipline section. +# +# UNIVERSAL holds every capability with no known family that both (a) has +# the href and (b) needs to model it some other way -- broadening one of +# these to a new family is a safe, harmless guess (issue #40's AI energy +# level: 2 of 6 families confirmed, blanket-added everywhere else). +# +# POWER is kept separate: airconditioner deliberately does NOT include it. +# AC's climate entity (registry/capabilities/airconditioner.py) already +# owns /power/0 and /power/vs/0 -- they're pre-claimed there via bare, +# no-entity Capability objects (COVERAGE) so a second, real POWER_GENERIC/ +# POWER_VS_FALLBACK cap on the same href would make _build() raise +# (a href with >1 cap must have every cap discriminated by rt_filter/ +# match_fn, and the bare COVERAGE cap has neither). Kids-lock and remote- +# control don't have this conflict -- no AC dump has ever reported those +# hrefs -- so they stay in UNIVERSAL. +# --------------------------------------------------------------------------- + +UNIVERSAL = ( + ALARMS, + ENERGY_METER, + FIRMWARE_UPDATE, + SELF_CHECK, + AI_ENERGY_LEVEL, + KIDS_LOCK_GENERIC, + KIDS_LOCK_VS_FALLBACK, + REMOTE_CONTROL_GENERIC, + REMOTE_CONTROL_VS_FALLBACK, +) + +POWER = ( + POWER_GENERIC, + POWER_VS_FALLBACK, +) diff --git a/custom_components/localthings/registry/capabilities/fridge.py b/custom_components/localthings/registry/capabilities/fridge.py index 5529526..b6d6dd1 100644 --- a/custom_components/localthings/registry/capabilities/fridge.py +++ b/custom_components/localthings/registry/capabilities/fridge.py @@ -19,7 +19,7 @@ import datetime from ..capability import Capability from ..entities import ( - BinarySensorDesc, ButtonDesc, NumberDesc, SelectDesc, SensorDesc, + BinarySensorDesc, NumberDesc, SelectDesc, SensorDesc, SwitchDesc, TimeDesc, ) from .common import normalize_temp_unit @@ -228,36 +228,6 @@ DEFROST_BLOCK_STATUS = Capability( ), ) -# --------------------------------------------------------------------------- -# Self-check diagnostic -# --------------------------------------------------------------------------- - -SELF_CHECK = Capability( - href='/selfcheck/vs/0', - poll_tier='cold', - entities=( - SensorDesc(key='selfcheck_status', field='x.com.samsung.da.status', - name='Self-check status', icon='mdi:stethoscope', - entity_category='diagnostic'), - SensorDesc(key='selfcheck_result', field='x.com.samsung.da.result', - name='Self-check result', icon='mdi:clipboard-check-outline', - entity_category='diagnostic'), - # List of error codes from the last self-check; joined for display. - # Not every fridge reports the field, hence the exists_fn. - SensorDesc(key='selfcheck_error', field='x.com.samsung.da.error', - name='Self-check error', icon='mdi:alert-circle-outline', - entity_category='diagnostic', - exists_fn=lambda rep, resources: ( - not rep or 'x.com.samsung.da.error' in rep), - value_fn=lambda v: (', '.join(v) if v else None) if isinstance(v, list) else v), - ButtonDesc(key='selfcheck_start', field='', name='Start self-check', - payload='Start', icon='mdi:play-circle-outline', - entity_category='diagnostic', - write_fn=lambda p, rep, href=None: ( - ['selfcheck', 'vs', '0'], {'x.com.samsung.da.status': p})), - ), -) - # --------------------------------------------------------------------------- # Refrigeration modes (rapid cooling) # --------------------------------------------------------------------------- @@ -588,25 +558,6 @@ FLEX_ZONE = Capability( ), ) -# --------------------------------------------------------------------------- -# Firmware update -# --------------------------------------------------------------------------- - -FIRMWARE_UPDATE = Capability( - href='/otninformation/vs/0', - poll_tier='cold', - entities=( - BinarySensorDesc( - key='firmware_update', - field='x.com.samsung.da.newVersionAvailable', - name='Firmware update available', - device_class='update', - entity_category='diagnostic', - value_fn=lambda v: str(v).lower() == 'true' if v is not None else None, - ), - ), -) - # --------------------------------------------------------------------------- # Generic door pattern capability (href=None — use as pattern_cap only) # --------------------------------------------------------------------------- diff --git a/tests/test_common_capabilities.py b/tests/test_common_capabilities.py index adc1431..4a38654 100644 --- a/tests/test_common_capabilities.py +++ b/tests/test_common_capabilities.py @@ -227,3 +227,72 @@ class TestAiEnergyLevelSelect: path, body = desc.write_fn('2', {}) assert path == ['energy', 'ailevel', 'vs', '0'] assert body == {'aiLevel': '2'} + + +class TestSelfCheckError: + """Self-check diagnostic error list -- surfaced on hardware that reports + x.com.samsung.da.error, joined into a single display string.""" + + def _desc(self): + return next(e for e in common.SELF_CHECK.entities if e.key == 'selfcheck_error') + + def test_exists_when_field_present(self): + desc = self._desc() + assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True + + def test_does_not_exist_when_field_absent(self): + desc = self._desc() + assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False + + def test_exists_for_empty_stub_rep(self): + """An empty {} rep is /device/0's not-yet-fetched-stub carve-out -- + must be included-for-now, same as ENERGY_METER's fields.""" + desc = self._desc() + assert desc.exists_fn({}, {}) is True + + def test_value_joins_list(self): + desc = self._desc() + assert desc.value_fn(['E1', 'E2']) == 'E1, E2' + + def test_value_passes_through_scalar(self): + desc = self._desc() + assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE' + + def test_value_none_for_empty_list(self): + """An empty error list means no value to show -- None (unknown), + not an empty string.""" + desc = self._desc() + assert desc.value_fn([]) is None + + +# --------------------------------------------------------------------------- +# Cross-family bundles (UNIVERSAL / POWER) -- unpacked into every by_type +# registry's _build([...]) call in place of the hand-duplicated capability +# lists that used to live there. +# --------------------------------------------------------------------------- + + +class TestUniversalAndPowerBundles: + def test_universal_contains_the_no_conflict_capabilities(self): + assert set(common.UNIVERSAL) == { + common.ALARMS, common.ENERGY_METER, common.FIRMWARE_UPDATE, + common.SELF_CHECK, common.AI_ENERGY_LEVEL, + common.KIDS_LOCK_GENERIC, common.KIDS_LOCK_VS_FALLBACK, + common.REMOTE_CONTROL_GENERIC, common.REMOTE_CONTROL_VS_FALLBACK, + } + + def test_power_kept_separate_for_airconditioners_sake(self): + """airconditioner.py deliberately doesn't unpack this bundle -- its + climate entity already owns /power/0 and /power/vs/0 via a bare, + no-entity claim (airconditioner.COVERAGE); a real POWER_GENERIC/ + POWER_VS_FALLBACK cap on the same href would make _build() raise.""" + assert set(common.POWER) == {common.POWER_GENERIC, common.POWER_VS_FALLBACK} + + def test_no_overlap_between_the_two_bundles(self): + assert not (set(common.UNIVERSAL) & set(common.POWER)) + + def test_airconditioner_registry_does_not_include_power(self): + from custom_components.localthings.registry.by_type import airconditioner + bound_caps = {c for caps in airconditioner.REGISTRY.capabilities.values() for c in caps} + assert common.POWER_GENERIC not in bound_caps + assert common.POWER_VS_FALLBACK not in bound_caps diff --git a/tests/test_fridge_capabilities.py b/tests/test_fridge_capabilities.py index c76ddfe..11dc501 100644 --- a/tests/test_fridge_capabilities.py +++ b/tests/test_fridge_capabilities.py @@ -231,42 +231,6 @@ class TestArtik051AndTp2xFixturesHaveCompleteCoverage: assert state['flex_zone_mode'] == 'CV_FDR_BEVERAGE' -class TestSelfCheckError: - """Self-check diagnostic error list -- surfaced on hardware that reports - x.com.samsung.da.error, joined into a single display string.""" - - def _desc(self): - return next(e for e in fridge.SELF_CHECK.entities if e.key == 'selfcheck_error') - - def test_exists_when_field_present(self): - desc = self._desc() - assert desc.exists_fn({'x.com.samsung.da.error': ['DA_ERROR_NONE']}, {}) is True - - def test_does_not_exist_when_field_absent(self): - desc = self._desc() - assert desc.exists_fn({'x.com.samsung.da.status': 'Ready'}, {}) is False - - def test_exists_for_empty_stub_rep(self): - """An empty {} rep is /device/0's not-yet-fetched-stub carve-out -- - must be included-for-now, same as ENERGY_METER's fields.""" - desc = self._desc() - assert desc.exists_fn({}, {}) is True - - def test_value_joins_list(self): - desc = self._desc() - assert desc.value_fn(['E1', 'E2']) == 'E1, E2' - - def test_value_passes_through_scalar(self): - desc = self._desc() - assert desc.value_fn('DA_ERROR_NONE') == 'DA_ERROR_NONE' - - def test_value_none_for_empty_list(self): - """An empty error list means no value to show -- None (unknown), - not an empty string.""" - desc = self._desc() - assert desc.value_fn([]) is None - - class TestRefrigeratorAiEnergyLevelFixtureCoverage: """AI energy-saving level (common.AI_ENERGY_LEVEL, see test_common_capabilities.py) is exercised end-to-end here against the