From 5433c8a558833eb9f43e41f72b4f0bfdec1fd15b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2026 04:48:53 -0400 Subject: [PATCH] sandbox: trim the compat lane to one runner The one-shot full cross-sweep that produced the original backlog (run_compat_full.py + categorize_failures.py + generate_backlog.py) and its machine-generated outputs (COMPAT_FULL.md/.csv, COMPAT_LATEST.md, COMPAT.csv, BACKLOG_FAILURES.json) were Phase-16 measurement scaffolding; the gate is long cleared. Keep the single ongoing runner (run_compat.py) and the two curated summaries (COMPAT.md, BACKLOG.md). Git-ignore the per-run machine output so it stops being checked in. Living docs updated; recover the full-sweep tooling from git history if a fresh tree-wide sweep is ever needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- sandbox/.gitignore | 4 + sandbox/BACKLOG_FAILURES.json | 646 ------------------------- sandbox/CLAUDE.md | 5 +- sandbox/COMPAT.csv | 38 -- sandbox/COMPAT_FULL.csv | 808 ------------------------------- sandbox/COMPAT_FULL.md | 857 --------------------------------- sandbox/COMPAT_LATEST.md | 57 --- sandbox/OVERVIEW.md | 20 +- sandbox/categorize_failures.py | 386 --------------- sandbox/generate_backlog.py | 264 ---------- sandbox/run_compat_full.py | 572 ---------------------- 11 files changed, 16 insertions(+), 3641 deletions(-) create mode 100644 sandbox/.gitignore delete mode 100644 sandbox/BACKLOG_FAILURES.json delete mode 100644 sandbox/COMPAT.csv delete mode 100644 sandbox/COMPAT_FULL.csv delete mode 100644 sandbox/COMPAT_FULL.md delete mode 100644 sandbox/COMPAT_LATEST.md delete mode 100644 sandbox/categorize_failures.py delete mode 100644 sandbox/generate_backlog.py delete mode 100644 sandbox/run_compat_full.py diff --git a/sandbox/.gitignore b/sandbox/.gitignore new file mode 100644 index 000000000000..554b0e9254fe --- /dev/null +++ b/sandbox/.gitignore @@ -0,0 +1,4 @@ +# Generated compat-lane output (run_compat.py). The curated COMPAT.md +# baseline is committed; the per-run machine output is not. +/COMPAT.csv +/COMPAT_LATEST.md diff --git a/sandbox/BACKLOG_FAILURES.json b/sandbox/BACKLOG_FAILURES.json deleted file mode 100644 index b7abd5e8233a..000000000000 --- a/sandbox/BACKLOG_FAILURES.json +++ /dev/null @@ -1,646 +0,0 @@ -{ - "test-only": { - "aemet": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n }),", - "node_id": "tests.components.aemet.test_diagnostics::test_config_entry_diagnostics" - } - ], - "airly": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.airly.test_diagnostics::test_entry_diagnostics" - } - ], - "airnow": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.airnow.test_diagnostics::test_entry_diagnostics" - } - ], - "airq": [ - { - "excerpt": " 'config_entry': dict({\n - 'created_at': '2025-01-01T00:00:00+00:00',\n + 'created_at': '2026-05-24T04:51:45.904605+00:00',\n 'data': dict({\n ...\n 'minor_version': 1,...", - "node_id": "tests.components.airq.test_diagnostics::test_entry_diagnostics" - } - ], - "airvisual": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.airvisual.test_diagnostics::test_entry_diagnostics" - } - ], - "airvisual_pro": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.airvisual_pro.test_diagnostics::test_entry_diagnostics" - } - ], - "airzone": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'wifi-channel': 6,", - "node_id": "tests.components.airzone.test_diagnostics::test_config_entry_diagnostics" - } - ], - "airzone_cloud": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'temperature-setpoint-max-auto-air': 30.0,", - "node_id": "tests.components.airzone_cloud.test_diagnostics::test_config_entry_diagnostics" - } - ], - "aladdin_connect": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.aladdin_connect.test_diagnostics::test_diagnostics" - } - ], - "alexa_devices": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.alexa_devices.test_diagnostics::test_entry_diagnostics" - } - ], - "ambient_station": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.ambient_station.test_diagnostics::test_entry_diagnostics" - } - ], - "bang_olufsen": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.bang_olufsen.test_diagnostics::test_async_get_config_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.bang_olufsen.test_diagnostics::test_async_get_config_entry_diagnostics_with_battery" - } - ], - "braviatv": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.braviatv.test_diagnostics::test_entry_diagnostics" - } - ], - "co2signal": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.co2signal.test_diagnostics::test_entry_diagnostics" - } - ], - "coinbase": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.coinbase.test_diagnostics::test_entry_diagnostics" - } - ], - "comelit": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.comelit.test_diagnostics::test_entry_diagnostics_bridge" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.comelit.test_diagnostics::test_entry_diagnostics_vedo" - } - ], - "data_grand_lyon": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.data_grand_lyon.test_diagnostics::test_config_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.data_grand_lyon.test_diagnostics::test_config_entry_diagnostics_with_velov" - } - ], - "deconz": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.deconz.test_diagnostics::test_entry_diagnostics" - } - ], - "devolo_home_control": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.devolo_home_control.test_diagnostics::test_entry_diagnostics" - } - ], - "devolo_home_network": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.devolo_home_network.test_diagnostics::test_entry_diagnostics" - } - ], - "dsmr_reader": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.dsmr_reader.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "ecovacs": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.ecovacs.test_diagnostics::test_diagnostics[username@cloud]" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.ecovacs.test_diagnostics::test_diagnostics[username@self-hosted]" - } - ], - "eheimdigital": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.eheimdigital.test_diagnostics::test_entry_diagnostics" - } - ], - "enphase_envoy": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n dict({", - "node_id": "tests.components.enphase_envoy.test_diagnostics::test_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n dict({", - "node_id": "tests.components.enphase_envoy.test_diagnostics::test_entry_diagnostics_with_fixtures" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n dict({", - "node_id": "tests.components.enphase_envoy.test_diagnostics::test_entry_diagnostics_with_fixtures_with_error" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 0,", - "node_id": "tests.components.enphase_envoy.test_diagnostics::test_entry_diagnostics_with_interface_information[envoy]" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 0,", - "node_id": "tests.components.enphase_envoy.test_diagnostics::test_entry_diagnostics_with_interface_information[envoy_metered_batt_relay]" - } - ], - "firefly_iii": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.firefly_iii.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "freshr": [ - { - "excerpt": " ...\n 'entry': dict({\n - 'created_at': '2026-01-01T00:00:00+00:00',\n + 'created_at': '2026-05-24T04:53:55.668547+00:00',\n 'data': dict({\n ......", - "node_id": "tests.components.freshr.test_diagnostics::test_diagnostics" - } - ], - "fritz": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.fritz.test_diagnostics::test_entry_diagnostics" - } - ], - "fronius": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n }),", - "node_id": "tests.components.fronius.test_diagnostics::test_diagnostics" - } - ], - "fyta": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'scientific_name': 'Theobroma cacao',", - "node_id": "tests.components.fyta.test_diagnostics::test_entry_diagnostics" - } - ], - "gios": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.gios.test_diagnostics::test_entry_diagnostics" - } - ], - "goodwe": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.goodwe.test_diagnostics::test_entry_diagnostics" - } - ], - "google_weather": [ - { - "excerpt": " 'entry': dict({\n - 'created_at': '2026-03-20T21:22:23+00:00',\n + 'created_at': '2026-05-24T04:54:19.938808+00:00',\n 'data': dict({\n ...\n 'minor_version': 1,...", - "node_id": "tests.components.google_weather.test_diagnostics::test_diagnostics" - } - ], - "growatt_server": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.growatt_server.test_diagnostics::test_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.growatt_server.test_diagnostics::test_diagnostics_classic_api" - } - ], - "guardian": [ - { - "excerpt": " Omitting 1 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:54:25.511997+00:00', 'data': {'ip_address': '192.168.1.100', 'port': 7777, 'uid': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'ip_address': '192.168.1.100', 'port': 7777, 'uid': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/guardian/test_diagnostics.py:24: in test_entry_diagnostics", - "node_id": "tests.components.guardian.test_diagnostics::test_entry_diagnostics" - } - ], - "heos": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n dict({", - "node_id": "tests.components.heos.test_diagnostics::test_config_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.heos.test_diagnostics::test_config_entry_diagnostics_error_getting_system" - } - ], - "homeassistant_connect_zbt2": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.homeassistant_connect_zbt2.test_diagnostics::test_diagnostics_for_config_entry" - } - ], - "husqvarna_automower": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.husqvarna_automower.test_diagnostics::test_entry_diagnostics" - } - ], - "imgw_pib": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.imgw_pib.test_diagnostics::test_entry_diagnostics" - } - ], - "immich": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.immich.test_diagnostics::test_entry_diagnostics" - } - ], - "iqvia": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.iqvia.test_diagnostics::test_entry_diagnostics" - } - ], - "kostal_plenticore": [ - { - "excerpt": " Omitting 3 identical items, use -vv to show\n Differing items:\n {'config_entry': {'created_at': '2026-05-24T04:55:51.181434+00:00', 'data': {'host': '192.168.1.2', 'password': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'config_entry': {'created_at': , 'data': {'host': '192.168.1.2', 'password': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/kostal_plenticore/test_diagnostics.py:26: in test_entry_diagnostics", - "node_id": "tests.components.kostal_plenticore.test_diagnostics::test_entry_diagnostics" - } - ], - "lacrosse_view": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.lacrosse_view.test_diagnostics::test_entry_diagnostics" - } - ], - "madvr": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.madvr.test_diagnostics::test_entry_diagnostics[positive_payload0]" - } - ], - "melcloud": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.melcloud.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "modern_forms": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.modern_forms.test_diagnostics::test_entry_diagnostics" - } - ], - "motionblinds_ble": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.motionblinds_ble.test_diagnostics::test_diagnostics" - } - ], - "nextdns": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'cache_boost': True,", - "node_id": "tests.components.nextdns.test_diagnostics::test_entry_diagnostics" - } - ], - "nice_go": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.nice_go.test_diagnostics::test_entry_diagnostics" - } - ], - "notion": [ - { - "excerpt": " Omitting 1 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:57:19.794228+00:00', 'data': {'refresh_token': '**REDACTED**', 'user_uuid': '**REDACTED**', 'username': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'refresh_token': '**REDACTED**', 'user_uuid': '**REDACTED**', 'username': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/notion/test_diagnostics.py:19: in test_entry_diagnostics", - "node_id": "tests.components.notion.test_diagnostics::test_entry_diagnostics" - } - ], - "novy_cooker_hood": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.novy_cooker_hood.test_diagnostics::test_diagnostics" - } - ], - "openuv": [ - { - "excerpt": " Omitting 1 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:57:39.013753+00:00', 'data': {'api_key': '**REDACTED**', 'elevation': 0, 'latitude': '**REDACTED**', 'longitude': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'api_key': '**REDACTED**', 'elevation': 0, 'latitude': '**REDACTED**', 'longitude': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/openuv/test_diagnostics.py:20: in test_entry_diagnostics", - "node_id": "tests.components.openuv.test_diagnostics::test_entry_diagnostics" - } - ], - "opower": [ - { - "excerpt": " ...\n 'entry': dict({\n - 'created_at': '2026-03-07T23:00:00+00:00',\n + 'created_at': '2026-05-24T04:57:42.183600+00:00',\n 'data': dict({\n ......", - "node_id": "tests.components.opower.test_diagnostics::test_diagnostics" - } - ], - "p1_monitor": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.p1_monitor.test_init::test_migration" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.p1_monitor.test_init::test_port_migration" - } - ], - "pegel_online": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.pegel_online.test_diagnostics::test_entry_diagnostics" - } - ], - "philips_js": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.philips_js.test_diagnostics::test_entry_diagnostics" - } - ], - "pi_hole": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.pi_hole.test_diagnostics::test_diagnostics" - } - ], - "portainer": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.portainer.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "proximity": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.proximity.test_diagnostics::test_entry_diagnostics" - } - ], - "proxmoxve": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'disk': 1234567890,", - "node_id": "tests.components.proxmoxve.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "purpleair": [ - { - "excerpt": " Omitting 1 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:58:14.127275+00:00', 'data': {'api_key': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'api_key': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/purpleair/test_diagnostics.py:18: in test_entry_diagnostics", - "node_id": "tests.components.purpleair.test_diagnostics::test_entry_diagnostics" - } - ], - "rainforest_raven": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.rainforest_raven.test_diagnostics::test_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.rainforest_raven.test_diagnostics::test_entry_diagnostics_no_meters" - } - ], - "rainmachine": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.rainmachine.test_diagnostics::test_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.rainmachine.test_diagnostics::test_entry_diagnostics_failed_controller_diagnostics" - } - ], - "recollect_waste": [ - { - "excerpt": " Omitting 1 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:58:25.488020+00:00', 'data': {'place_id': '**REDACTED**', 'service_id': '67890'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'place_id': '**REDACTED**', 'service_id': '67890'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/recollect_waste/test_diagnostics.py:20: in test_entry_diagnostics", - "node_id": "tests.components.recollect_waste.test_diagnostics::test_entry_diagnostics" - } - ], - "ridwell": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.ridwell.test_diagnostics::test_entry_diagnostics" - } - ], - "samsungtv": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.samsungtv.test_diagnostics::test_entry_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.samsungtv.test_diagnostics::test_entry_diagnostics_encrypte_offline" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.samsungtv.test_diagnostics::test_entry_diagnostics_encrypted" - } - ], - "scrape": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.scrape.test_init::test_migrate_from_version_1_to_2" - } - ], - "screenlogic": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n ]),", - "node_id": "tests.components.screenlogic.test_diagnostics::test_diagnostics" - } - ], - "simplisafe": [ - { - "excerpt": " Omitting 2 identical items, use -vv to show\n Differing items:\n {'entry': {'created_at': '2026-05-24T04:59:00.139992+00:00', 'data': {'token': '**REDACTED**', 'username': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}} != {'entry': {'created_at': , 'data': {'token': '**REDACTED**', 'username': '**REDACTED**'}, 'disabled_by': None, 'discovery_keys': {}, ...}}\n Use -v to get more diff\n\ntests/components/simplisafe/test_diagnostics.py:18: in test_entry_diagnostics", - "node_id": "tests.components.simplisafe.test_diagnostics::test_entry_diagnostics" - } - ], - "slide_local": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.slide_local.test_diagnostics::test_entry_diagnostics" - } - ], - "sma": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.sma.test_diagnostics::test_get_config_entry_diagnostics" - } - ], - "solarlog": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.solarlog.test_diagnostics::test_entry_diagnostics" - } - ], - "switchbot": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.switchbot.test_diagnostics::test_diagnostics" - } - ], - "switcher_kis": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.switcher_kis.test_diagnostics::test_diagnostics" - } - ], - "systemmonitor": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.systemmonitor.test_diagnostics::test_diagnostics" - }, - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.systemmonitor.test_diagnostics::test_diagnostics_missing_items" - } - ], - "tankerkoenig": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.tankerkoenig.test_diagnostics::test_entry_diagnostics" - } - ], - "tplink_omada": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'memUtil': 20,", - "node_id": "tests.components.tplink_omada.test_diagnostics::test_entry_diagnostics" - } - ], - "tractive": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.tractive.test_diagnostics::test_entry_diagnostics" - } - ], - "twinkly": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.twinkly.test_diagnostics::test_diagnostics" - } - ], - "unifi": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n 'dtim_ng': 1,", - "node_id": "tests.components.unifi.test_diagnostics::test_entry_diagnostics[wlan_payload0-dpi_group_payload0-dpi_app_payload0-device_payload0-client_payload0-config_entry_options0]" - } - ], - "utility_meter": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.utility_meter.test_diagnostics::test_diagnostics" - } - ], - "v2c": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.v2c.test_diagnostics::test_entry_diagnostics" - } - ], - "velbus": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.velbus.test_diagnostics::test_entry_diagnostics" - } - ], - "vicare": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.vicare.test_diagnostics::test_diagnostics" - } - ], - "vodafone_station": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.vodafone_station.test_diagnostics::test_entry_diagnostics" - } - ], - "watts": [ - { - "excerpt": " ...\n 'entry': dict({\n - 'created_at': '2026-01-01T12:00:00+00:00',\n + 'created_at': '2026-05-24T05:01:22.558366+00:00',\n 'data': dict({\n ......", - "node_id": "tests.components.watts.test_diagnostics::test_diagnostics" - } - ], - "watttime": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.watttime.test_diagnostics::test_entry_diagnostics" - } - ], - "webmin": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.webmin.test_diagnostics::test_diagnostics" - } - ], - "webostv": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.webostv.test_diagnostics::test_diagnostics" - } - ], - "whirlpool": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.whirlpool.test_diagnostics::test_entry_diagnostics" - } - ], - "workday": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.workday.test_diagnostics::test_diagnostics" - } - ], - "zha": [ - { - "excerpt": " ...\n 'pref_disable_polling': False,\n + 'sandbox': 'built-in',\n 'source': 'user',\n ...\n })", - "node_id": "tests.components.zha.test_diagnostics::test_diagnostics_for_config_entry" - } - ] - }, - "unknown": { - "bluetooth": [ - { - "excerpt": "tests/components/bluetooth/test_models.py:126: in test_wrapped_bleak_client_local_adapter_only\n await client.connect()\n.venv/lib/python3.14/site-packages/habluetooth/wrappers.py:437: in connect\n self._backend = wrapped_backend.client(\nE TypeError: BleakClientBlueZDBus.__init__() missing 1 required keyword-only argument: 'bluez'", - "node_id": "tests.components.bluetooth.test_models::test_wrapped_bleak_client_local_adapter_only" - } - ], - "chess_com": [ - { - "excerpt": " ...\n 'is_streamer': False,\n - 'joined': '2026-02-20T10:48:14',\n + 'joined': '2026-02-20T05:48:14',\n - 'last_online': '2026-03-06T12:32:59',\n + 'last_online': '2026-03-06T07:32:59',...", - "node_id": "tests.components.chess_com.test_diagnostics::test_diagnostics" - } - ], - "google": [ - { - "excerpt": " - some-updated-token\n + ACCESS_TOKEN\n\ntests/components/google/test_init.py:729: in test_invalid_token_expiry_in_config_entry\n assert entries[0].data[\"token\"][\"access_token\"] == \"some-updated-token\"\nE AssertionError: assert 'ACCESS_TOKEN' == 'some-updated-token'", - "node_id": "tests.components.google.test_init::test_invalid_token_expiry_in_config_entry[timestamp_naive]" - } - ], - "html5": [ - { - "excerpt": " Omitting 3 identical items, use -vv to show\n Differing items:\n {'timestamp': 18000000} != {'timestamp': 0}\n Use -v to get more diff\n\ntests/components/html5/test_notify.py:1165: in test_html5_send_message", - "node_id": "tests.components.html5.test_notify::test_html5_send_message[service_data11-expected_payload11-86400-None]" - } - ], - "mastodon": [ - { - "excerpt": " ...\n 'locked': False,\n - 'created_at': datetime.datetime(2016, 11, 24, 0, 0, tzinfo=tzlocal()),\n + 'created_at': datetime.datetime(2016, 11, 24, 0, 0, tzinfo=tzutc()),\n 'following_count': 328,\n ...", - "node_id": "tests.components.mastodon.test_services::test_get_account_success" - } - ] - } -} diff --git a/sandbox/CLAUDE.md b/sandbox/CLAUDE.md index 6262fff856dd..9839ef0ac098 100644 --- a/sandbox/CLAUDE.md +++ b/sandbox/CLAUDE.md @@ -56,8 +56,9 @@ second condition), as a deliberate call relying on git history for rollback. **Docker test image** (`hass_client/Dockerfile` + `docker-compose.test.yml`) — see [`hass_client/docs/docker.md`](hass_client/docs/docker.md). - `docs/` — per-phase decision write-ups. -- `run_compat.py` + `COMPAT.md` + `COMPAT.csv` — compat-lane runner - and report (Phase 10). +- `run_compat.py` + curated `COMPAT.md` / `BACKLOG.md` — compat-lane + runner and reports. Per-run machine output (`COMPAT.csv` / + `COMPAT_LATEST.md`) is git-ignored. The HA Core side of the integration lives at `../homeassistant/components/sandbox/`. diff --git a/sandbox/COMPAT.csv b/sandbox/COMPAT.csv deleted file mode 100644 index d27ab77cd6be..000000000000 --- a/sandbox/COMPAT.csv +++ /dev/null @@ -1,38 +0,0 @@ -integration,status,passed,failed,errors,skipped -input_boolean,pass,18,0,0,0 -input_button,pass,15,0,0,0 -input_datetime,pass,28,0,0,0 -input_number,pass,24,0,0,0 -input_select,pass,26,0,0,0 -input_text,pass,23,0,0,0 -counter,pass,751,0,0,0 -timer,pass,877,0,0,0 -schedule,pass,387,0,0,0 -zone,pass,32,0,0,0 -tag,pass,12,0,0,0 -group,pass,392,0,0,0 -person,pass,34,0,0,0 -scene,pass,41,0,0,0 -todo,pass,281,0,0,0 -automation,pass,117,0,0,0 -script,pass,64,0,0,0 -alert,pass,18,0,0,0 -template,pass,2470,0,0,0 -plant,pass,11,0,0,0 -proximity,issues,27,1,0,0 -min_max,pass,20,0,0,0 -statistics,pass,56,0,0,0 -utility_meter,issues,94,1,0,0 -derivative,pass,76,0,0,0 -integration,pass,61,0,0,0 -generic_thermostat,pass,114,0,0,0 -generic_hygrostat,pass,76,0,0,0 -history_stats,pass,55,0,0,0 -threshold,pass,114,0,0,0 -filter,pass,32,0,0,0 -mqtt_statestream,pass,17,0,0,0 -recorder,pass,932,0,0,17 -rest,pass,128,0,0,0 -logbook,pass,106,0,0,0 -command_line,pass,78,0,0,0 -trend,pass,39,0,0,0 diff --git a/sandbox/COMPAT_FULL.csv b/sandbox/COMPAT_FULL.csv deleted file mode 100644 index 544777e93107..000000000000 --- a/sandbox/COMPAT_FULL.csv +++ /dev/null @@ -1,808 +0,0 @@ -integration,status,passed,failed,errors,skipped,duration_s,dominant_bucket -acaia,pass,24,0,0,0,3.52, -accuweather,pass,38,0,0,0,5.08, -acmeda,pass,8,0,0,0,2.59, -actron_air,pass,49,0,0,0,5.25, -adax,pass,20,0,0,0,2.86, -adguard,pass,36,0,0,0,3.42, -advantage_air,pass,20,0,0,0,4.91, -aemet,issues,16,1,0,0,3.26, -aftership,pass,2,0,0,0,2.29, -aidot,pass,16,0,0,0,2.90, -airgradient,pass,47,0,0,0,4.70, -airly,issues,21,1,0,0,2.99, -airnow,issues,9,1,0,0,2.55, -airobot,pass,60,0,0,0,4.85, -airos,pass,62,0,0,0,4.89, -airpatrol,pass,25,0,0,0,3.19, -airq,issues,22,1,0,0,3.30, -airthings,pass,12,0,0,0,2.56, -airthings_ble,pass,38,0,0,0,4.13, -airtouch4,pass,5,0,0,0,2.39, -airtouch5,pass,5,0,0,0,2.45, -airvisual,issues,15,1,0,0,2.95, -airvisual_pro,issues,10,1,0,0,2.68, -airzone,issues,37,1,0,0,5.58, -airzone_cloud,issues,26,1,0,0,4.95, -aladdin_connect,issues,32,1,0,0,3.78, -alarmdecoder,pass,8,0,0,0,2.40, -alexa_devices,issues,72,1,0,0,7.34, -altruist,pass,11,0,0,0,2.61, -amberelectric,pass,39,0,0,0,3.62, -ambient_network,pass,7,0,0,0,2.76, -ambient_station,issues,3,1,0,0,2.59, -analytics_insights,pass,20,0,0,0,3.11, -androidtv,pass,79,0,0,0,5.23, -androidtv_remote,pass,40,0,0,0,4.60, -anglian_water,pass,14,0,0,0,3.52, -anova,pass,11,0,0,0,2.65, -anthemav,pass,11,0,0,0,2.59, -aosmith,pass,33,0,0,0,3.64, -apcupsd,pass,44,0,0,0,4.29, -apple_tv,pass,51,0,0,0,4.60, -aprilaire,pass,3,0,0,0,2.51, -apsystems,pass,13,0,0,0,3.03, -aquacell,pass,13,0,0,0,2.89, -aranet,pass,19,0,0,0,3.42, -arcam_fmj,pass,85,0,0,0,5.84, -arve,pass,5,0,0,0,2.70, -aseko_pool_live,pass,13,0,0,0,2.80, -asuswrt,pass,60,0,0,0,4.68, -atag,pass,14,0,0,0,2.94, -aurora,pass,4,0,0,0,2.48, -aurora_abb_powerone,pass,7,0,0,0,6.85, -aussie_broadband,pass,14,0,0,0,2.68, -autarco,pass,13,0,0,0,2.73, -autoskope,pass,22,0,0,0,2.91, -avea,pass,20,0,0,0,3.12, -awair,pass,24,0,0,0,2.73, -aws_s3,pass,51,0,0,0,4.96, -azure_data_explorer,pass,18,0,0,0,2.75, -azure_devops,pass,20,0,0,0,2.82, -azure_event_hub,pass,21,0,0,0,2.65, -azure_storage,pass,25,0,0,0,3.70, -backblaze_b2,pass,77,0,0,0,5.64, -baf,pass,8,0,0,0,2.46, -balboa,pass,42,0,0,0,5.47, -bang_olufsen,issues,100,2,0,0,35.95, -bayesian,pass,48,0,0,0,3.16, -blebox,pass,117,0,0,0,8.95, -blue_current,pass,28,0,0,0,3.03, -bluemaestro,pass,11,0,0,0,2.77, -bluesound,pass,39,0,0,0,4.73, -bluetooth,issues,234,1,0,1,9.76, -bond,pass,141,0,0,0,6.29, -bosch_alarm,pass,121,0,0,0,11.87, -bosch_shc,pass,16,0,0,0,2.66, -braviatv,issues,19,1,0,0,2.74, -bring,pass,72,0,0,0,6.03, -broadlink,pass,104,0,0,0,4.81, -brother,pass,33,0,0,0,7.24, -brottsplatskartan,pass,4,0,0,0,2.30, -brunt,pass,8,0,0,0,2.39, -bryant_evolution,pass,24,0,0,0,3.09, -bsblan,pass,127,0,0,0,11.91, -bthome,pass,107,0,0,0,6.98, -caldav,pass,109,0,0,0,6.70, -cambridge_audio,pass,52,0,0,0,7.84, -casper_glow,pass,67,0,0,0,6.44, -cast,pass,86,0,0,0,6.63, -ccm15,pass,8,0,0,0,2.52, -centriconnect,pass,10,0,0,0,2.49, -cert_expiry,pass,18,0,0,0,2.82, -chacon_dio,pass,16,0,0,0,2.83, -chess_com,issues,8,1,0,0,2.82, -cielo_home,pass,7,0,0,0,18.27, -cloudflare,pass,14,0,0,0,2.53, -cloudflare_r2,pass,59,0,0,0,4.67, -co2signal,issues,15,1,0,0,2.87, -coinbase,issues,15,1,0,0,2.59, -color_extractor,pass,9,0,0,0,4.47, -comelit,issues,93,2,0,0,8.99, -compit,pass,65,0,0,0,6.46, -control4,pass,54,0,0,0,4.60, -cookidoo,pass,52,0,0,0,5.41, -coolmaster,pass,37,0,0,0,3.46, -cpuspeed,pass,8,0,0,0,2.38, -crownstone,pass,11,0,0,0,2.68, -cync,pass,19,0,0,0,2.65, -daikin,pass,31,0,0,0,3.09, -data_grand_lyon,issues,40,2,0,0,3.29, -datadog,pass,12,0,0,0,2.41, -deako,pass,13,0,0,0,2.86, -deconz,issues,172,1,0,1,12.86, -decora_wifi,pass,13,0,0,0,3.01, -deluge,pass,7,0,0,0,2.30, -denon_rs232,pass,23,0,0,0,3.07, -denonavr,pass,16,0,0,0,2.87, -derivative,pass,76,0,0,0,4.62, -devialet,pass,13,0,0,0,2.63, -devolo_home_control,issues,41,1,0,0,4.24, -devolo_home_network,issues,51,1,0,0,6.02, -dexcom,pass,10,0,0,0,2.45, -dialogflow,pass,17,0,0,0,3.17, -directv,pass,24,0,0,0,3.07, -discord,pass,11,0,0,0,2.51, -discovergy,pass,25,0,0,0,3.19, -dlink,pass,15,0,0,0,2.57, -dlna_dmr,pass,72,0,0,0,13.95, -dlna_dms,pass,55,0,0,0,7.98, -dnsip,pass,21,0,0,0,2.72, -dormakaba_dkey,pass,14,0,0,0,2.86, -downloader,pass,21,0,0,0,2.63, -drop_connect,pass,30,0,0,0,3.78, -dropbox,pass,29,0,0,0,3.63, -droplet,pass,16,0,0,0,3.18, -dsmr,pass,75,0,0,0,4.57, -dsmr_reader,issues,8,1,0,0,2.77, -duckdns,pass,18,0,0,0,2.68, -duco,pass,61,0,0,0,7.51, -dunehd,pass,6,0,0,0,2.38, -duotecno,pass,5,0,0,0,2.35, -dwd_weather_warnings,pass,11,0,0,0,2.47, -dynalite,pass,33,0,0,0,3.44, -eafm,pass,18,0,0,0,2.64, -earn_e_p1,pass,25,0,0,0,2.87, -easyenergy,pass,70,0,0,0,5.17, -ecobee,pass,60,0,0,0,3.53, -ecoforest,pass,4,0,0,0,2.35, -econet,pass,4,0,0,0,2.33, -ecovacs,issues,74,2,0,0,6.74, -ecowitt,pass,1,0,0,0,2.32, -edl21,pass,2,0,0,0,2.29, -efergy,pass,12,0,0,0,2.54, -egauge,pass,16,0,0,0,2.81, -eheimdigital,issues,79,1,0,0,8.30, -ekeybionyx,pass,13,0,0,0,7.73, -electrasmart,pass,6,0,0,0,2.33, -electric_kiwi,pass,18,0,0,0,3.04, -elgato,pass,36,0,0,0,3.88, -elkm1,pass,45,0,0,0,3.43, -elmax,pass,29,0,0,0,2.85, -elvia,pass,9,0,0,0,2.72, -emoncms,pass,16,0,0,0,2.71, -emonitor,pass,7,0,0,0,2.38, -emulated_roku,pass,7,0,0,0,2.39, -energenie_power_sockets,pass,13,0,0,0,2.61, -energyid,pass,81,0,0,0,3.92, -energyzero,pass,41,0,0,0,3.82, -enigma2,pass,26,0,0,0,3.30, -enocean,pass,14,0,0,0,2.77, -enphase_envoy,issues,231,5,0,0,24.65, -epic_games_store,pass,20,0,0,0,2.68, -epion,pass,4,0,0,0,2.33, -epson,pass,5,0,0,0,2.46, -eq3btsmart,pass,4,0,0,0,2.56, -escea,pass,3,0,0,0,2.32, -essent,pass,14,0,0,0,2.69, -eufylife_ble,pass,10,0,0,0,2.79, -eurotronic_cometblue,pass,39,0,0,0,4.04, -evil_genius_labs,pass,10,0,0,0,2.53, -faa_delays,pass,4,0,0,0,2.33, -fastdotcom,pass,8,0,0,0,2.50, -feedreader,pass,30,0,0,0,3.02, -fibaro,pass,45,0,0,0,3.93, -file,pass,26,0,0,0,2.84, -filesize,pass,13,0,0,0,2.58, -filter,pass,32,0,0,0,4.13, -fing,pass,17,0,0,0,2.88, -firefly_iii,issues,25,1,0,0,3.30, -fireservicerota,pass,5,0,0,0,2.35, -fitbit,pass,60,0,0,0,6.46, -fivem,pass,5,0,0,0,2.36, -fjaraskupan,pass,11,0,0,0,2.87, -flexit_bacnet,pass,19,0,0,0,2.75, -flipr,pass,18,0,0,0,2.79, -flo,pass,10,0,0,0,2.73, -flume,pass,11,0,0,0,2.50, -fluss,pass,16,0,0,0,2.71, -flux_led,pass,82,0,0,0,6.59, -folder_watcher,pass,10,0,0,0,2.63, -forecast_solar,pass,28,0,0,0,2.97, -forked_daapd,pass,36,0,0,0,4.60, -freedompro,pass,38,0,0,0,4.48, -freshr,issues,28,1,0,0,3.42, -fressnapf_tracker,pass,45,0,0,0,4.49, -fritz,issues,138,1,0,0,10.15, -fritzbox,pass,132,0,0,0,8.57, -fritzbox_callmonitor,pass,12,0,0,0,2.45, -fronius,issues,35,1,0,0,3.89, -frontier_silicon,pass,22,0,0,0,2.64, -fujitsu_fglair,pass,25,0,0,0,3.56, -fumis,pass,72,0,0,0,5.55, -fyta,issues,33,1,0,0,3.71, -garages_amsterdam,pass,6,0,0,0,2.45, -gardena_bluetooth,pass,40,0,0,0,5.17, -gdacs,pass,8,0,0,0,2.70, -generic_hygrostat,pass,76,0,0,0,4.01, -generic_thermostat,pass,114,0,0,0,5.61, -geniushub,pass,23,0,0,0,2.89, -gentex_homelink,pass,13,0,0,0,2.75, -geo_json_events,pass,5,0,0,0,2.55, -geocaching,pass,5,0,0,0,2.81, -geofency,pass,5,0,0,0,3.00, -geonetnz_quakes,pass,9,0,0,0,2.78, -geonetnz_volcano,pass,7,0,0,0,2.64, -ghost,pass,27,0,0,0,3.44, -gios,issues,17,1,0,0,3.07, -github,pass,17,0,0,0,2.90, -glances,pass,16,0,0,0,2.77, -goalzero,pass,15,0,0,0,2.75, -gogogate2,pass,15,0,0,0,2.67, -goodwe,issues,5,1,0,0,2.59, -google,issues,136,1,0,0,8.22, -google_air_quality,pass,19,0,0,0,2.77, -google_assistant_sdk,pass,41,0,0,0,3.71, -google_drive,pass,40,0,0,0,4.21, -google_mail,pass,28,0,0,0,3.57, -google_photos,pass,35,0,0,0,3.52, -google_sheets,pass,24,0,0,0,3.06, -google_tasks,pass,42,0,0,0,4.20, -google_travel_time,pass,44,0,0,0,3.65, -google_weather,issues,44,1,0,0,3.85, -govee_ble,pass,20,0,0,0,3.21, -govee_light_local,pass,23,0,0,0,3.02, -gpsd,pass,2,0,0,0,2.37, -gpslogger,pass,3,0,0,1,2.91, -gree,pass,123,0,0,0,7.31, -green_planet_energy,pass,11,0,0,0,2.75, -group,pass,392,0,0,0,24.47, -growatt_server,issues,140,2,0,0,14.23, -guardian,issues,11,1,0,0,2.54, -guntamatic,pass,15,0,0,0,2.61, -habitica,pass,382,0,0,0,38.88, -hanna,pass,5,0,0,0,2.41, -harmony,pass,22,0,0,0,2.79, -hdfury,pass,50,0,0,0,6.49, -hegel,pass,12,0,0,0,2.51, -heos,issues,147,2,0,0,8.10, -here_travel_time,pass,39,0,0,0,3.12, -hisense_aehw4a1,pass,4,0,0,0,2.32, -history_stats,pass,55,0,0,0,7.60, -hive,pass,19,0,0,0,2.85, -hko,pass,4,0,0,0,2.38, -hlk_sw16,pass,4,0,0,0,2.29, -holiday,pass,34,0,0,0,3.70, -home_connect,pass,311,0,0,0,15.93, -homeassistant_connect_zbt2,issues,24,1,0,0,4.13, -homeassistant_sky_connect,pass,36,0,0,0,4.29, -homee,pass,199,0,0,0,16.24, -homekit,pass,372,0,0,0,13.61, -homematicip_cloud,pass,182,0,0,0,23.98, -homevolt,pass,33,0,0,0,3.29, -homewizard,pass,149,0,0,0,10.98, -homeworks,pass,38,0,0,0,4.28, -honeywell,pass,44,0,0,0,4.33, -honeywell_string_lights,pass,7,0,0,0,2.39, -hr_energy_qube,pass,28,0,0,0,4.07, -html5,issues,64,1,0,0,5.72, -huawei_lte,pass,36,0,0,0,3.65, -hue,pass,113,0,0,0,20.05, -hue_ble,pass,19,0,0,0,3.05, -huisbaasje,pass,13,0,0,0,2.47, -hunterdouglas_powerview,pass,39,0,0,0,3.29, -husqvarna_automower,issues,93,1,0,0,14.73, -husqvarna_automower_ble,pass,42,0,0,0,5.09, -huum,pass,34,0,0,0,3.35, -hvv_departures,pass,9,0,0,0,2.32, -hydrawise,pass,35,0,0,0,4.25, -hypontech,pass,17,0,0,0,2.66, -ialarm,pass,7,0,0,0,2.30, -iaqualink,pass,28,0,0,0,2.86, -ibeacon,pass,24,0,0,0,3.53, -icloud,pass,17,0,0,0,2.47, -idasen_desk,pass,30,0,0,0,3.75, -idrive_e2,pass,65,0,0,0,4.66, -ifttt,pass,1,0,0,0,2.47, -igloohome,pass,8,0,0,0,2.37, -imap,pass,159,0,0,0,12.42, -imeon_inverter,pass,17,0,0,0,2.94, -imgw_pib,issues,15,1,0,0,2.79, -immich,issues,54,1,0,0,3.82, -improv_ble,pass,38,0,0,0,3.69, -incomfort,pass,50,0,0,0,4.05, -indevolt,pass,83,0,0,0,6.18, -inels,pass,28,0,0,0,3.04, -influxdb,pass,145,0,0,0,6.03, -inkbird,pass,16,0,0,0,2.99, -insteon,pass,79,0,0,0,18.55, -integration,pass,61,0,0,0,4.02, -intelliclima,pass,28,0,0,0,3.21, -intellifire,pass,29,0,0,0,4.24, -iometer,pass,17,0,0,0,2.77, -ios,pass,3,0,0,0,2.32, -iotawatt,pass,9,0,0,0,2.45, -iotty,pass,24,0,0,0,3.17, -ipma,pass,15,0,0,0,2.73, -ipp,pass,29,0,0,0,3.05, -iqvia,issues,4,1,0,0,2.40, -irm_kmi,pass,13,0,0,0,2.69, -iron_os,pass,95,0,0,0,8.95, -iskra,pass,13,0,0,0,2.45, -islamic_prayer_times,pass,45,0,0,0,3.82, -israel_rail,pass,9,0,0,0,2.56, -iss,pass,17,0,0,0,2.56, -ista_ecotrend,pass,49,0,0,0,4.07, -isy994,pass,31,0,0,0,3.06, -ituran,pass,19,0,0,0,2.96, -izone,pass,10,0,0,0,2.55, -jellyfin,pass,44,0,0,0,8.58, -jewish_calendar,pass,123,0,0,0,11.89, -justnimbus,pass,7,0,0,0,2.37, -jvc_projector,pass,31,0,0,0,5.73, -kaleidescape,pass,19,0,0,0,4.00, -keenetic_ndms2,pass,14,0,0,0,2.38, -kegtron,pass,12,0,0,0,2.73, -keymitt_ble,pass,7,0,0,0,2.58, -kiosker,pass,43,0,0,0,3.31, -kmtronic,pass,11,0,0,0,2.48, -knocki,pass,17,0,0,0,2.77, -knx,pass,387,0,0,0,28.04, -kodi,pass,21,0,0,0,2.77, -kostal_plenticore,issues,38,1,0,0,4.10, -kraken,pass,11,0,0,0,2.85, -kulersky,pass,17,0,0,0,2.94, -lacrosse_view,issues,28,1,0,0,3.09, -lamarzocco,pass,94,0,0,0,14.30, -lametric,pass,55,0,0,0,5.57, -landisgyr_heat_meter,pass,10,0,0,0,2.77, -lastfm,pass,15,0,0,0,2.52, -launch_library,pass,2,0,0,0,2.28, -laundrify,pass,18,0,0,0,2.79, -lcn,pass,149,0,0,0,13.39, -ld2410_ble,pass,6,0,0,0,2.56, -leaone,pass,6,0,0,0,2.56, -led_ble,pass,9,0,0,0,2.65, -lektrico,pass,13,0,0,0,2.78, -letpot,pass,40,0,0,0,4.13, -lg_infrared,pass,61,0,0,0,4.33, -lg_netcast,pass,22,0,0,0,3.46, -lg_soundbar,pass,11,0,0,0,2.73, -lg_thinq,pass,29,0,0,0,4.08, -libre_hardware_monitor,pass,26,0,0,0,3.19, -lichess,pass,8,0,0,0,2.48, -lidarr,pass,12,0,0,0,2.60, -liebherr,pass,76,0,0,0,5.72, -lifx,pass,72,0,0,0,5.42, -linkplay,pass,9,0,0,0,2.49, -litejet,pass,32,0,0,0,3.77, -litterrobot,pass,63,0,0,0,5.89, -livisi,pass,4,0,0,0,2.36, -local_calendar,pass,50,0,0,0,5.05, -local_ip,pass,3,0,0,0,2.34, -local_todo,pass,55,0,0,0,5.56, -locative,pass,5,0,0,1,2.99, -lojack,pass,16,0,0,0,2.92, -london_underground,pass,12,0,0,0,2.55, -lookin,pass,7,0,0,0,2.37, -loqed,pass,17,0,0,0,2.98, -luftdaten,pass,11,0,0,0,2.59, -lunatone,pass,40,0,0,0,3.78, -lupusec,pass,5,0,0,0,2.38, -lutron,pass,42,0,0,0,3.76, -lutron_caseta,pass,52,0,0,0,4.93, -lyric,pass,4,0,0,0,2.40, -madvr,issues,16,1,0,0,3.19, -mailgun,pass,5,0,0,0,2.69, -marantz_infrared,pass,36,0,0,0,3.44, -mastodon,issues,82,1,0,0,26.20, -matter,pass,372,0,0,1,68.76, -mcp,pass,45,0,0,0,4.15, -mcp_server,pass,47,0,0,0,5.93, -mealie,pass,94,0,0,0,7.84, -meater,pass,10,0,0,0,2.54, -medcom_ble,pass,9,0,0,0,2.62, -media_extractor,pass,20,0,0,0,7.61, -melcloud,issues,23,1,0,0,2.79, -melnor,pass,17,0,0,0,3.53, -met,pass,18,0,0,0,2.57, -met_eireann,pass,10,0,0,0,2.53, -meteo_france,pass,6,0,0,0,2.45, -meteo_lt,pass,7,0,0,0,2.47, -meteoclimatic,pass,3,0,0,0,2.36, -metoffice,pass,18,0,0,0,3.12, -microbees,pass,9,0,0,0,2.54, -miele,pass,115,0,0,0,10.16, -mikrotik,pass,21,0,0,0,2.75, -mill,pass,31,0,0,0,4.49, -min_max,pass,20,0,0,0,2.58, -minecraft_server,pass,40,0,0,0,3.49, -mitsubishi_comfort,pass,66,0,0,0,5.31, -moat,pass,11,0,0,0,2.79, -mobile_app,pass,135,0,0,0,13.24, -modem_callerid,pass,9,0,0,0,2.56, -modern_forms,issues,29,1,0,0,3.44, -moehlenhoff_alpha2,pass,8,0,0,0,2.41, -mold_indicator,pass,37,0,0,0,3.05, -monarch_money,pass,5,0,0,0,2.60, -monoprice,pass,23,0,0,0,3.24, -monzo,pass,11,0,0,0,2.72, -moon,pass,11,0,0,0,2.46, -mopeka,pass,14,0,0,0,2.90, -motion_blinds,pass,12,0,0,0,2.53, -motionblinds_ble,issues,42,1,0,0,6.28, -motionmount,pass,38,0,0,0,3.53, -mpd,pass,7,0,0,0,2.90, -mta,pass,27,0,0,0,2.96, -mullvad,pass,4,0,0,0,2.30, -music_assistant,pass,127,0,0,0,9.42, -mutesync,pass,5,0,0,0,2.32, -myneomitis,pass,43,0,0,0,4.11, -mysensors,pass,61,0,0,0,5.08, -mystrom,pass,23,0,0,0,2.76, -myuplink,pass,38,0,0,0,4.83, -nam,pass,39,0,0,0,3.38, -namecheapdns,pass,20,0,0,0,2.66, -nanoleaf,pass,30,0,0,0,2.86, -nasweb,pass,8,0,0,0,2.44, -nederlandse_spoorwegen,pass,29,0,0,0,3.38, -ness_alarm,pass,39,0,0,0,3.04, -netgear,pass,10,0,0,0,2.41, -netgear_lte,pass,11,0,0,0,2.63, -nexia,pass,21,0,0,0,3.55, -nextbus,pass,28,0,0,0,2.73, -nextcloud,pass,17,0,0,0,2.93, -nextdns,issues,52,1,0,0,5.83, -nfandroidtv,pass,8,0,0,0,2.39, -nibe_heatpump,pass,64,0,0,0,4.34, -nice_go,issues,43,1,0,0,4.51, -nightscout,pass,11,0,0,0,2.52, -niko_home_control,pass,32,0,0,0,3.78, -nina,pass,21,0,0,0,3.17, -nintendo_parental_controls,pass,30,0,0,0,3.33, -nmap_tracker,pass,13,0,0,0,2.57, -nmbs,pass,5,0,0,0,2.50, -nobo_hub,pass,59,0,0,0,4.63, -nordpool,pass,37,0,0,0,4.93, -notion,issues,7,1,0,0,2.55, -novy_cooker_hood,issues,28,1,0,0,3.15, -nrgkick,pass,87,0,0,0,5.68, -ntfy,pass,85,0,0,0,6.82, -nuheat,pass,9,0,0,0,2.46, -nuki,pass,14,0,0,0,2.61, -nut,pass,78,0,0,0,4.64, -nws,pass,30,0,0,0,3.25, -nyt_games,pass,10,0,0,0,2.60, -nzbget,pass,10,0,0,0,2.46, -obihai,pass,5,0,0,0,2.35, -ohme,pass,34,0,0,0,4.42, -omie,pass,19,0,0,0,2.86, -omnilogic,pass,8,0,0,0,2.39, -ondilo_ico,pass,17,0,0,0,2.98, -onedrive,pass,79,0,0,0,6.52, -onedrive_for_business,pass,38,0,0,0,4.62, -onewire,pass,33,0,0,0,3.51, -onkyo,pass,48,0,0,0,4.18, -open_meteo,pass,5,0,0,0,2.70, -opendisplay,pass,67,0,0,0,6.63, -openevse,pass,40,0,0,0,4.50, -openexchangerates,pass,9,0,0,0,2.38, -opengarage,pass,6,0,0,0,2.37, -openhome,pass,11,0,0,0,2.58, -openrgb,pass,69,0,0,0,4.65, -opensky,pass,12,0,0,0,2.64, -opentherm_gw,pass,29,0,0,0,5.67, -openuv,issues,10,1,0,0,2.69, -openweathermap,pass,15,0,0,0,2.65, -opower,issues,35,1,0,0,5.84, -oralb,pass,14,0,0,0,2.89, -orvibo,pass,15,0,0,0,2.60, -osoenergy,pass,17,0,0,0,2.95, -otbr,pass,103,0,0,0,7.85, -otp,pass,7,0,0,0,2.44, -ouman_eh_800,pass,20,0,0,0,3.07, -ourgroceries,pass,21,0,0,0,2.81, -overkiz,pass,181,0,0,0,9.91, -overseerr,pass,45,0,0,0,4.83, -ovo_energy,pass,7,0,0,0,2.39, -owntracks,pass,71,0,0,0,5.75, -p1_monitor,issues,11,2,0,0,2.73, -paj_gps,pass,19,0,0,0,3.06, -palazzetti,pass,17,0,0,0,2.99, -panasonic_viera,pass,32,0,0,0,2.99, -paperless_ngx,pass,41,0,0,0,3.57, -peblar,pass,62,0,0,0,5.20, -peco,pass,33,0,0,0,2.98, -pegel_online,issues,9,1,0,0,2.60, -permobil,pass,9,0,0,0,2.48, -pglab,pass,21,0,0,0,3.50, -philips_js,issues,15,1,0,0,3.73, -pi_hole,issues,25,1,0,0,3.38, -picnic,pass,40,0,0,0,3.35, -ping,pass,21,0,0,0,2.92, -pjlink,pass,29,0,0,0,3.31, -plaato,pass,13,0,0,0,2.95, -playstation_network,pass,68,0,0,0,6.66, -plex,pass,53,0,0,0,6.67, -plugwise,pass,86,0,0,0,8.26, -point,pass,5,0,0,0,2.47, -pooldose,pass,75,0,0,0,5.31, -poolsense,pass,5,0,0,0,2.36, -portainer,issues,86,1,0,0,12.95, -powerfox,pass,22,0,0,0,3.04, -powerfox_local,pass,21,0,0,0,2.87, -powerwall,pass,37,0,0,0,3.57, -prana,pass,37,0,0,0,3.77, -private_ble_device,pass,21,0,0,0,3.29, -probe_plus,pass,6,0,0,0,2.58, -profiler,pass,13,0,0,0,2.57, -progettihwsw,pass,4,0,0,0,2.35, -prowl,pass,24,0,0,0,2.80, -proximity,issues,27,1,0,0,3.03, -proxmoxve,issues,103,1,0,0,9.21, -ps4,pass,41,0,0,0,3.33, -ptdevices,pass,7,0,0,0,2.41, -pterodactyl,pass,14,0,0,0,2.66, -pure_energie,pass,8,0,0,0,2.46, -purpleair,issues,19,1,0,0,3.03, -pushbullet,pass,17,0,0,0,2.62, -pushover,pass,16,0,0,0,2.47, -pvoutput,pass,17,0,0,0,2.75, -pvpc_hourly_pricing,pass,2,0,0,0,2.38, -pyload,pass,67,0,0,0,4.55, -qbittorrent,pass,8,0,0,0,2.54, -qbus,pass,25,0,0,0,4.01, -qingping,pass,15,0,0,0,2.99, -qnap,pass,6,0,0,0,2.39, -qnap_qsw,pass,18,0,0,0,3.07, -rabbitair,pass,7,0,0,0,2.40, -rachio,pass,6,0,0,0,2.52, -radarr,pass,32,0,0,0,3.90, -radio_browser,pass,10,0,0,0,2.58, -radiotherm,pass,7,0,0,0,2.36, -rainbird,pass,70,0,0,0,4.30, -rainforest_eagle,pass,12,0,0,0,2.46, -rainforest_raven,issues,19,2,0,0,3.17, -rainmachine,issues,19,2,0,0,2.88, -random,pass,9,0,0,0,2.41, -rapt_ble,pass,11,0,0,0,2.75, -rdw,pass,10,0,0,0,2.58, -recollect_waste,issues,3,1,0,0,2.50, -redgtech,pass,18,0,0,0,2.77, -refoss,pass,2,0,0,0,2.29, -rehlko,pass,16,0,0,0,2.90, -remote_calendar,pass,44,0,0,0,3.94, -renault,pass,104,0,0,0,5.68, -renson,pass,3,0,0,0,2.29, -rfxtrx,pass,89,0,0,0,7.52, -rhasspy,pass,3,0,0,0,2.28, -ridwell,issues,13,1,0,0,2.69, -risco,pass,69,0,0,0,4.26, -rituals_perfume_genie,pass,25,0,0,0,3.10, -roborock,pass,183,0,0,0,18.48, -roku,pass,67,0,0,0,6.53, -romy,pass,6,0,0,0,2.38, -roomba,pass,26,0,0,0,2.69, -roon,pass,5,0,0,0,2.34, -route_b_smart_meter,pass,7,0,0,0,2.61, -rova,pass,11,0,0,0,2.44, -rpi_power,pass,6,0,0,0,2.33, -ruckus_unleashed,pass,29,0,0,0,2.80, -russound_rio,pass,50,0,0,0,6.05, -ruuvi_gateway,pass,5,0,0,0,2.46, -ruuvitag_ble,pass,13,0,0,0,2.75, -rympro,pass,7,0,0,0,2.28, -sabnzbd,pass,19,0,0,0,2.93, -samsung_infrared,pass,18,0,0,0,2.74, -samsungtv,issues,166,3,0,0,13.46, -sanix,pass,6,0,0,0,2.35, -satel_integra,pass,89,0,0,0,7.63, -saunum,pass,68,0,0,0,4.99, -schlage,pass,49,0,0,0,4.78, -scrape,issues,37,1,0,0,3.12, -screenlogic,issues,33,1,0,0,3.84, -season,pass,21,0,0,0,2.91, -sense,pass,19,0,0,0,3.20, -sensibo,pass,69,0,0,0,8.44, -sensirion_ble,pass,11,0,0,0,2.74, -sensorpro,pass,11,0,0,0,2.74, -sensorpush,pass,11,0,0,0,2.75, -sensorpush_cloud,pass,5,0,0,0,2.45, -sensoterra,pass,5,0,0,0,2.38, -sentry,pass,24,0,0,0,2.52, -senz,pass,21,0,0,0,3.05, -seventeentrack,pass,16,0,0,0,2.61, -sfr_box,pass,26,0,0,0,2.85, -sftp_storage,pass,34,0,0,0,8.68, -sharkiq,pass,42,0,0,0,3.25, -shelly,pass,636,0,0,0,45.69, -shopping_list,pass,60,0,0,0,4.74, -sia,pass,21,0,0,0,2.65, -simplefin,pass,12,0,0,0,2.68, -simplepush,pass,5,0,0,0,2.32, -simplisafe,issues,15,1,0,0,2.98, -sky_remote,pass,9,0,0,0,2.40, -slack,pass,13,0,0,0,2.44, -sleep_as_android,pass,44,0,0,0,4.54, -sleepiq,pass,33,0,0,0,4.94, -slide_local,issues,48,1,0,0,4.02, -slimproto,pass,2,0,0,0,2.34, -sma,issues,33,1,0,0,3.82, -smappee,pass,20,0,0,0,2.72, -smarla,pass,37,0,0,0,4.23, -smart_meter_texas,pass,16,0,0,0,2.58, -smartthings,pass,340,0,0,0,70.72, -smarttub,pass,40,0,0,0,4.88, -smarty,pass,12,0,0,0,2.78, -smhi,pass,20,0,0,0,3.39, -smlight,pass,98,0,0,0,7.10, -snapcast,pass,18,0,0,0,3.34, -snoo,pass,21,0,0,0,3.43, -snooz,pass,35,0,0,0,4.87, -solaredge,pass,62,0,0,0,11.00, -solarlog,issues,25,1,0,0,3.29, -solarman,pass,17,0,0,0,2.79, -solax,pass,3,0,0,0,2.33, -soma,pass,7,0,0,0,2.30, -somfy_mylink,pass,11,0,0,0,2.41, -sonarr,pass,56,0,0,0,9.69, -songpal,pass,26,0,0,0,3.03, -sonos,pass,174,0,0,0,18.92, -soundtouch,pass,31,0,0,0,3.29, -speedtestdotnet,pass,8,0,0,0,2.37, -splunk,pass,36,0,0,0,2.94, -spotify,pass,70,0,0,0,8.97, -sql,pass,80,0,0,0,6.49, -squeezebox,pass,129,0,0,0,12.83, -srp_energy,pass,15,0,0,0,2.70, -starline,pass,4,0,0,0,2.27, -starlink,pass,8,0,0,0,2.69, -statistics,pass,56,0,0,0,4.17, -steam_online,pass,14,0,0,0,2.48, -steamist,pass,23,0,0,0,2.93, -stiebel_eltron,pass,9,0,0,0,2.75, -stookwijzer,pass,12,0,0,0,2.61, -streamlabswater,pass,6,0,0,0,2.38, -subaru,pass,59,0,0,0,4.58, -suez_water,pass,19,0,0,0,4.18, -sun,pass,27,0,0,1,4.86, -sunricher_dali,pass,50,0,0,0,5.83, -sunweg,pass,1,0,0,0,2.23, -surepetcare,pass,15,0,0,0,2.74, -swiss_public_transport,pass,36,0,0,0,3.10, -switch_as_x,pass,176,0,0,0,10.02, -switchbee,pass,6,0,0,0,2.34, -switchbot,issues,352,1,0,0,19.66, -switchbot_cloud,pass,134,0,0,0,10.81, -switcher_kis,issues,76,1,0,0,7.69, -syncthing,pass,5,0,0,0,2.30, -syncthru,pass,9,0,0,0,2.59, -system_bridge,pass,39,0,0,0,4.46, -systemmonitor,issues,34,2,0,0,4.87, -systemnexa2,pass,34,0,0,0,3.57, -tado,pass,40,0,0,0,4.67, -tailscale,pass,14,0,0,0,2.75, -tailwind,pass,36,0,0,0,3.30, -tami4,pass,14,0,0,0,2.56, -tankerkoenig,issues,20,1,0,0,2.84, -tautulli,pass,8,0,0,0,2.35, -technove,pass,40,0,0,0,3.71, -tedee,pass,41,0,0,0,4.48, -telegram_bot,pass,123,0,0,0,13.16, -teleinfo,pass,24,0,0,0,3.22, -tellduslive,pass,15,0,0,0,2.41, -teltonika,pass,50,0,0,0,3.79, -template,pass,2470,0,0,0,60.07, -tesla_fleet,pass,153,0,0,0,12.29, -tesla_wall_connector,pass,12,0,0,0,2.47, -teslemetry,pass,158,0,0,0,16.84, -tessie,pass,67,0,0,0,4.46, -thermobeacon,pass,11,0,0,0,2.72, -thermopro,pass,18,0,0,0,3.01, -thethingsnetwork,pass,8,0,0,0,2.40, -thread,pass,65,0,0,0,4.17, -threshold,pass,114,0,0,0,4.14, -tibber,pass,92,0,0,0,13.32, -tile,pass,11,0,0,0,2.50, -tilt_ble,pass,11,0,0,0,2.79, -tilt_pi,pass,6,0,0,0,2.43, -time_date,pass,18,0,0,0,2.78, -tod,pass,32,0,0,0,3.25, -todoist,pass,50,0,0,0,4.23, -togrill,pass,62,0,0,0,5.73, -tolo,pass,7,0,0,0,2.35, -tomorrowio,pass,21,0,0,0,3.08, -toon,pass,10,0,0,0,2.71, -totalconnect,pass,66,0,0,0,11.65, -touchline,pass,8,0,0,0,2.52, -touchline_sl,pass,15,0,0,0,2.71, -tplink_omada,issues,52,1,0,0,4.67, -traccar,pass,5,0,0,1,2.90, -traccar_server,pass,12,0,0,0,2.88, -tractive,issues,48,1,0,0,4.03, -tradfri,pass,91,0,0,0,6.36, -trafikverket_ferry,pass,13,0,0,0,2.52, -trafikverket_train,pass,41,0,0,0,3.14, -trafikverket_weatherstation,pass,16,0,0,0,2.53, -trane,pass,39,0,0,0,3.98, -transmission,pass,75,0,0,0,4.60, -trend,pass,39,0,0,0,3.25, -triggercmd,pass,5,0,0,0,2.37, -trmnl,pass,36,0,0,0,3.72, -twentemilieu,pass,17,0,0,0,2.87, -twilio,pass,1,0,0,0,2.57, -twinkly,issues,25,1,0,0,4.12, -twitch,pass,16,0,0,0,4.22, -uhoo,pass,24,0,0,0,2.99, -ukraine_alarm,pass,11,0,0,0,2.43, -unifi,issues,158,1,0,0,12.39, -unifi_access,pass,155,0,0,0,10.85, -upb,pass,6,0,0,0,2.42, -upcloud,pass,6,0,0,0,2.35, -upnp,pass,23,0,0,0,3.83, -uptime,pass,4,0,0,0,2.36, -uptime_kuma,pass,38,0,0,0,3.58, -uptimerobot,pass,35,0,0,0,3.18, -utility_meter,issues,94,1,0,0,6.00, -v2c,issues,12,1,0,0,2.86, -vallox,pass,76,0,0,0,5.65, -vegehub,pass,18,0,0,0,2.92, -velbus,issues,57,1,0,0,8.99, -velux,pass,80,0,0,0,4.86, -venstar,pass,12,0,0,0,2.56, -vera,pass,29,0,0,0,3.08, -version,pass,10,0,0,0,2.55, -vesync,pass,175,0,0,0,12.54, -vicare,issues,54,1,0,0,4.48, -victron_ble,pass,34,0,0,0,3.61, -victron_gx,pass,62,0,0,0,14.13, -victron_remote_monitoring,pass,22,0,0,0,3.03, -vilfo,pass,9,0,0,0,2.46, -vizio,pass,73,0,0,0,4.50, -vlc_telnet,pass,39,0,0,0,3.40, -vodafone_station,issues,53,1,0,0,4.60, -volumio,pass,9,0,0,0,2.29, -volvo,pass,110,0,0,0,8.64, -volvooncall,pass,4,0,0,0,2.20, -wake_on_lan,pass,15,0,0,0,2.50, -wallbox,pass,38,0,0,0,3.67, -waqi,pass,20,0,0,0,2.63, -waterfurnace,pass,77,0,0,0,11.93, -watergate,pass,16,0,0,0,3.04, -watts,issues,51,1,0,0,5.17, -watttime,issues,11,1,0,0,2.56, -waze_travel_time,pass,23,0,0,0,14.84, -weatherflow,pass,5,0,0,0,2.95, -weatherflow_cloud,pass,17,0,0,0,3.12, -weatherkit,pass,35,0,0,0,2.92, -webdav,pass,25,0,0,0,3.90, -webmin,issues,11,1,0,0,2.52, -webostv,issues,82,1,0,0,12.04, -weheat,pass,16,0,0,0,2.85, -wemo,pass,93,0,0,0,6.71, -whirlpool,issues,239,1,0,0,12.16, -whois,pass,41,0,0,0,3.40, -wiffi,pass,4,0,0,0,2.19, -wiim,pass,29,0,0,0,3.02, -wilight,pass,22,0,0,0,2.82, -withings,pass,62,0,0,0,8.01, -wiz,pass,56,0,0,0,4.34, -wled,pass,100,0,0,0,7.60, -wmspro,pass,39,0,0,0,4.04, -wolflink,pass,10,0,0,0,2.49, -workday,issues,70,1,0,0,6.14, -worldclock,pass,6,0,0,0,2.42, -ws66i,pass,24,0,0,0,3.12, -wsdot,pass,16,0,0,0,2.66, -xbox,pass,203,0,0,0,14.92, -xiaomi_aqara,pass,12,0,0,0,2.46, -xiaomi_ble,pass,92,0,0,0,6.22, -xiaomi_miio,pass,51,0,0,0,4.11, -xthings_cloud,pass,30,0,0,0,3.72, -yale_smart_alarm,pass,37,0,0,0,3.71, -yalexs_ble,pass,26,0,0,0,3.32, -yamaha_musiccast,pass,9,0,0,0,2.52, -yardian,pass,16,0,0,0,2.79, -yeelight,pass,62,0,0,0,4.73, -yolink,pass,10,0,0,0,3.10, -youless,pass,4,0,0,0,2.41, -youtube,pass,24,0,0,0,3.38, -zamg,pass,9,0,0,0,2.52, -zerproc,pass,10,0,0,0,2.49, -zeversolar,pass,10,0,0,0,2.46, -zha,issues,339,1,0,4,70.49, -zimi,pass,16,0,0,0,2.65, -zinvolt,pass,12,0,0,0,2.77, -zodiac,pass,5,0,0,0,2.31, -zwave_js,pass,611,0,0,0,60.17, -zwave_me,pass,8,0,0,0,2.24, diff --git a/sandbox/COMPAT_FULL.md b/sandbox/COMPAT_FULL.md deleted file mode 100644 index 081b4425feec..000000000000 --- a/sandbox/COMPAT_FULL.md +++ /dev/null @@ -1,857 +0,0 @@ -# Sandbox — full compat sweep (Phase 16) - -**This file is auto-generated by `run_compat_full.py`** — re-run the -script to refresh it. Companion machine-readable CSV is `COMPAT_FULL.csv`, -categorised remediation backlog is `BACKLOG.md`. - -## Sweep parameters - -- Started: `2026-05-24T00:51:34` -- Finished: `2026-05-24T01:03:01` -- Wall time: **687s** -- Outer concurrency: **6** -- Per-integration pytest-xdist: **off** -- Plugin: `hass_client.testing.pytest_plugin` (in-process) - -## Discovery - -Walked `homeassistant/components/`, applied the Phase 16 filters: - -| Filter | Skipped | -| --- | ---: | -| No / invalid manifest | 1 | -| `integration_type` in (`virtual`, `system`) | 206 | -| Domain in `ALWAYS_MAIN` | 3 | -| Ships a platform in `SANDBOX_INCOMPATIBLE_PLATFORMS` | 73 | -| No `config_flow` in manifest | 380 | -| No `test_*.py` files | 0 | -| **Eligible (this sweep)** | **807** | - -## Summary - -- Integrations exercised: **807** -- Fully passing: **711** (88.10%) -- With failures: **96** -- Timeouts: **0** -- Spawn errors: **0** -- No tests collected: **0** - -- Tests passed: **34266** -- Tests failed: **112** -- Test errors: **0** -- Tests skipped: **11** -- **Test-level pass rate: 99.67%** - -## Per-integration results - -| integration | status | passed | failed | errors | skipped | dur (s) | bucket | -| --- | --- | ---: | ---: | ---: | ---: | ---: | --- | -| acaia | pass | 24 | 0 | 0 | 0 | 3.5 | | -| accuweather | pass | 38 | 0 | 0 | 0 | 5.1 | | -| acmeda | pass | 8 | 0 | 0 | 0 | 2.6 | | -| actron_air | pass | 49 | 0 | 0 | 0 | 5.2 | | -| adax | pass | 20 | 0 | 0 | 0 | 2.9 | | -| adguard | pass | 36 | 0 | 0 | 0 | 3.4 | | -| advantage_air | pass | 20 | 0 | 0 | 0 | 4.9 | | -| aemet | issues | 16 | 1 | 0 | 0 | 3.3 | | -| aftership | pass | 2 | 0 | 0 | 0 | 2.3 | | -| aidot | pass | 16 | 0 | 0 | 0 | 2.9 | | -| airgradient | pass | 47 | 0 | 0 | 0 | 4.7 | | -| airly | issues | 21 | 1 | 0 | 0 | 3.0 | | -| airnow | issues | 9 | 1 | 0 | 0 | 2.5 | | -| airobot | pass | 60 | 0 | 0 | 0 | 4.8 | | -| airos | pass | 62 | 0 | 0 | 0 | 4.9 | | -| airpatrol | pass | 25 | 0 | 0 | 0 | 3.2 | | -| airq | issues | 22 | 1 | 0 | 0 | 3.3 | | -| airthings | pass | 12 | 0 | 0 | 0 | 2.6 | | -| airthings_ble | pass | 38 | 0 | 0 | 0 | 4.1 | | -| airtouch4 | pass | 5 | 0 | 0 | 0 | 2.4 | | -| airtouch5 | pass | 5 | 0 | 0 | 0 | 2.4 | | -| airvisual | issues | 15 | 1 | 0 | 0 | 2.9 | | -| airvisual_pro | issues | 10 | 1 | 0 | 0 | 2.7 | | -| airzone | issues | 37 | 1 | 0 | 0 | 5.6 | | -| airzone_cloud | issues | 26 | 1 | 0 | 0 | 4.9 | | -| aladdin_connect | issues | 32 | 1 | 0 | 0 | 3.8 | | -| alarmdecoder | pass | 8 | 0 | 0 | 0 | 2.4 | | -| alexa_devices | issues | 72 | 1 | 0 | 0 | 7.3 | | -| altruist | pass | 11 | 0 | 0 | 0 | 2.6 | | -| amberelectric | pass | 39 | 0 | 0 | 0 | 3.6 | | -| ambient_network | pass | 7 | 0 | 0 | 0 | 2.8 | | -| ambient_station | issues | 3 | 1 | 0 | 0 | 2.6 | | -| analytics_insights | pass | 20 | 0 | 0 | 0 | 3.1 | | -| androidtv | pass | 79 | 0 | 0 | 0 | 5.2 | | -| androidtv_remote | pass | 40 | 0 | 0 | 0 | 4.6 | | -| anglian_water | pass | 14 | 0 | 0 | 0 | 3.5 | | -| anova | pass | 11 | 0 | 0 | 0 | 2.7 | | -| anthemav | pass | 11 | 0 | 0 | 0 | 2.6 | | -| aosmith | pass | 33 | 0 | 0 | 0 | 3.6 | | -| apcupsd | pass | 44 | 0 | 0 | 0 | 4.3 | | -| apple_tv | pass | 51 | 0 | 0 | 0 | 4.6 | | -| aprilaire | pass | 3 | 0 | 0 | 0 | 2.5 | | -| apsystems | pass | 13 | 0 | 0 | 0 | 3.0 | | -| aquacell | pass | 13 | 0 | 0 | 0 | 2.9 | | -| aranet | pass | 19 | 0 | 0 | 0 | 3.4 | | -| arcam_fmj | pass | 85 | 0 | 0 | 0 | 5.8 | | -| arve | pass | 5 | 0 | 0 | 0 | 2.7 | | -| aseko_pool_live | pass | 13 | 0 | 0 | 0 | 2.8 | | -| asuswrt | pass | 60 | 0 | 0 | 0 | 4.7 | | -| atag | pass | 14 | 0 | 0 | 0 | 2.9 | | -| aurora | pass | 4 | 0 | 0 | 0 | 2.5 | | -| aurora_abb_powerone | pass | 7 | 0 | 0 | 0 | 6.8 | | -| aussie_broadband | pass | 14 | 0 | 0 | 0 | 2.7 | | -| autarco | pass | 13 | 0 | 0 | 0 | 2.7 | | -| autoskope | pass | 22 | 0 | 0 | 0 | 2.9 | | -| avea | pass | 20 | 0 | 0 | 0 | 3.1 | | -| awair | pass | 24 | 0 | 0 | 0 | 2.7 | | -| aws_s3 | pass | 51 | 0 | 0 | 0 | 5.0 | | -| azure_data_explorer | pass | 18 | 0 | 0 | 0 | 2.8 | | -| azure_devops | pass | 20 | 0 | 0 | 0 | 2.8 | | -| azure_event_hub | pass | 21 | 0 | 0 | 0 | 2.6 | | -| azure_storage | pass | 25 | 0 | 0 | 0 | 3.7 | | -| backblaze_b2 | pass | 77 | 0 | 0 | 0 | 5.6 | | -| baf | pass | 8 | 0 | 0 | 0 | 2.5 | | -| balboa | pass | 42 | 0 | 0 | 0 | 5.5 | | -| bang_olufsen | issues | 100 | 2 | 0 | 0 | 35.9 | | -| bayesian | pass | 48 | 0 | 0 | 0 | 3.2 | | -| blebox | pass | 117 | 0 | 0 | 0 | 9.0 | | -| blue_current | pass | 28 | 0 | 0 | 0 | 3.0 | | -| bluemaestro | pass | 11 | 0 | 0 | 0 | 2.8 | | -| bluesound | pass | 39 | 0 | 0 | 0 | 4.7 | | -| bluetooth | issues | 234 | 1 | 0 | 1 | 9.8 | | -| bond | pass | 141 | 0 | 0 | 0 | 6.3 | | -| bosch_alarm | pass | 121 | 0 | 0 | 0 | 11.9 | | -| bosch_shc | pass | 16 | 0 | 0 | 0 | 2.7 | | -| braviatv | issues | 19 | 1 | 0 | 0 | 2.7 | | -| bring | pass | 72 | 0 | 0 | 0 | 6.0 | | -| broadlink | pass | 104 | 0 | 0 | 0 | 4.8 | | -| brother | pass | 33 | 0 | 0 | 0 | 7.2 | | -| brottsplatskartan | pass | 4 | 0 | 0 | 0 | 2.3 | | -| brunt | pass | 8 | 0 | 0 | 0 | 2.4 | | -| bryant_evolution | pass | 24 | 0 | 0 | 0 | 3.1 | | -| bsblan | pass | 127 | 0 | 0 | 0 | 11.9 | | -| bthome | pass | 107 | 0 | 0 | 0 | 7.0 | | -| caldav | pass | 109 | 0 | 0 | 0 | 6.7 | | -| cambridge_audio | pass | 52 | 0 | 0 | 0 | 7.8 | | -| casper_glow | pass | 67 | 0 | 0 | 0 | 6.4 | | -| cast | pass | 86 | 0 | 0 | 0 | 6.6 | | -| ccm15 | pass | 8 | 0 | 0 | 0 | 2.5 | | -| centriconnect | pass | 10 | 0 | 0 | 0 | 2.5 | | -| cert_expiry | pass | 18 | 0 | 0 | 0 | 2.8 | | -| chacon_dio | pass | 16 | 0 | 0 | 0 | 2.8 | | -| chess_com | issues | 8 | 1 | 0 | 0 | 2.8 | | -| cielo_home | pass | 7 | 0 | 0 | 0 | 18.3 | | -| cloudflare | pass | 14 | 0 | 0 | 0 | 2.5 | | -| cloudflare_r2 | pass | 59 | 0 | 0 | 0 | 4.7 | | -| co2signal | issues | 15 | 1 | 0 | 0 | 2.9 | | -| coinbase | issues | 15 | 1 | 0 | 0 | 2.6 | | -| color_extractor | pass | 9 | 0 | 0 | 0 | 4.5 | | -| comelit | issues | 93 | 2 | 0 | 0 | 9.0 | | -| compit | pass | 65 | 0 | 0 | 0 | 6.5 | | -| control4 | pass | 54 | 0 | 0 | 0 | 4.6 | | -| cookidoo | pass | 52 | 0 | 0 | 0 | 5.4 | | -| coolmaster | pass | 37 | 0 | 0 | 0 | 3.5 | | -| cpuspeed | pass | 8 | 0 | 0 | 0 | 2.4 | | -| crownstone | pass | 11 | 0 | 0 | 0 | 2.7 | | -| cync | pass | 19 | 0 | 0 | 0 | 2.6 | | -| daikin | pass | 31 | 0 | 0 | 0 | 3.1 | | -| data_grand_lyon | issues | 40 | 2 | 0 | 0 | 3.3 | | -| datadog | pass | 12 | 0 | 0 | 0 | 2.4 | | -| deako | pass | 13 | 0 | 0 | 0 | 2.9 | | -| deconz | issues | 172 | 1 | 0 | 1 | 12.9 | | -| decora_wifi | pass | 13 | 0 | 0 | 0 | 3.0 | | -| deluge | pass | 7 | 0 | 0 | 0 | 2.3 | | -| denon_rs232 | pass | 23 | 0 | 0 | 0 | 3.1 | | -| denonavr | pass | 16 | 0 | 0 | 0 | 2.9 | | -| derivative | pass | 76 | 0 | 0 | 0 | 4.6 | | -| devialet | pass | 13 | 0 | 0 | 0 | 2.6 | | -| devolo_home_control | issues | 41 | 1 | 0 | 0 | 4.2 | | -| devolo_home_network | issues | 51 | 1 | 0 | 0 | 6.0 | | -| dexcom | pass | 10 | 0 | 0 | 0 | 2.5 | | -| dialogflow | pass | 17 | 0 | 0 | 0 | 3.2 | | -| directv | pass | 24 | 0 | 0 | 0 | 3.1 | | -| discord | pass | 11 | 0 | 0 | 0 | 2.5 | | -| discovergy | pass | 25 | 0 | 0 | 0 | 3.2 | | -| dlink | pass | 15 | 0 | 0 | 0 | 2.6 | | -| dlna_dmr | pass | 72 | 0 | 0 | 0 | 13.9 | | -| dlna_dms | pass | 55 | 0 | 0 | 0 | 8.0 | | -| dnsip | pass | 21 | 0 | 0 | 0 | 2.7 | | -| dormakaba_dkey | pass | 14 | 0 | 0 | 0 | 2.9 | | -| downloader | pass | 21 | 0 | 0 | 0 | 2.6 | | -| drop_connect | pass | 30 | 0 | 0 | 0 | 3.8 | | -| dropbox | pass | 29 | 0 | 0 | 0 | 3.6 | | -| droplet | pass | 16 | 0 | 0 | 0 | 3.2 | | -| dsmr | pass | 75 | 0 | 0 | 0 | 4.6 | | -| dsmr_reader | issues | 8 | 1 | 0 | 0 | 2.8 | | -| duckdns | pass | 18 | 0 | 0 | 0 | 2.7 | | -| duco | pass | 61 | 0 | 0 | 0 | 7.5 | | -| dunehd | pass | 6 | 0 | 0 | 0 | 2.4 | | -| duotecno | pass | 5 | 0 | 0 | 0 | 2.4 | | -| dwd_weather_warnings | pass | 11 | 0 | 0 | 0 | 2.5 | | -| dynalite | pass | 33 | 0 | 0 | 0 | 3.4 | | -| eafm | pass | 18 | 0 | 0 | 0 | 2.6 | | -| earn_e_p1 | pass | 25 | 0 | 0 | 0 | 2.9 | | -| easyenergy | pass | 70 | 0 | 0 | 0 | 5.2 | | -| ecobee | pass | 60 | 0 | 0 | 0 | 3.5 | | -| ecoforest | pass | 4 | 0 | 0 | 0 | 2.4 | | -| econet | pass | 4 | 0 | 0 | 0 | 2.3 | | -| ecovacs | issues | 74 | 2 | 0 | 0 | 6.7 | | -| ecowitt | pass | 1 | 0 | 0 | 0 | 2.3 | | -| edl21 | pass | 2 | 0 | 0 | 0 | 2.3 | | -| efergy | pass | 12 | 0 | 0 | 0 | 2.5 | | -| egauge | pass | 16 | 0 | 0 | 0 | 2.8 | | -| eheimdigital | issues | 79 | 1 | 0 | 0 | 8.3 | | -| ekeybionyx | pass | 13 | 0 | 0 | 0 | 7.7 | | -| electrasmart | pass | 6 | 0 | 0 | 0 | 2.3 | | -| electric_kiwi | pass | 18 | 0 | 0 | 0 | 3.0 | | -| elgato | pass | 36 | 0 | 0 | 0 | 3.9 | | -| elkm1 | pass | 45 | 0 | 0 | 0 | 3.4 | | -| elmax | pass | 29 | 0 | 0 | 0 | 2.8 | | -| elvia | pass | 9 | 0 | 0 | 0 | 2.7 | | -| emoncms | pass | 16 | 0 | 0 | 0 | 2.7 | | -| emonitor | pass | 7 | 0 | 0 | 0 | 2.4 | | -| emulated_roku | pass | 7 | 0 | 0 | 0 | 2.4 | | -| energenie_power_sockets | pass | 13 | 0 | 0 | 0 | 2.6 | | -| energyid | pass | 81 | 0 | 0 | 0 | 3.9 | | -| energyzero | pass | 41 | 0 | 0 | 0 | 3.8 | | -| enigma2 | pass | 26 | 0 | 0 | 0 | 3.3 | | -| enocean | pass | 14 | 0 | 0 | 0 | 2.8 | | -| enphase_envoy | issues | 231 | 5 | 0 | 0 | 24.6 | | -| epic_games_store | pass | 20 | 0 | 0 | 0 | 2.7 | | -| epion | pass | 4 | 0 | 0 | 0 | 2.3 | | -| epson | pass | 5 | 0 | 0 | 0 | 2.5 | | -| eq3btsmart | pass | 4 | 0 | 0 | 0 | 2.6 | | -| escea | pass | 3 | 0 | 0 | 0 | 2.3 | | -| essent | pass | 14 | 0 | 0 | 0 | 2.7 | | -| eufylife_ble | pass | 10 | 0 | 0 | 0 | 2.8 | | -| eurotronic_cometblue | pass | 39 | 0 | 0 | 0 | 4.0 | | -| evil_genius_labs | pass | 10 | 0 | 0 | 0 | 2.5 | | -| faa_delays | pass | 4 | 0 | 0 | 0 | 2.3 | | -| fastdotcom | pass | 8 | 0 | 0 | 0 | 2.5 | | -| feedreader | pass | 30 | 0 | 0 | 0 | 3.0 | | -| fibaro | pass | 45 | 0 | 0 | 0 | 3.9 | | -| file | pass | 26 | 0 | 0 | 0 | 2.8 | | -| filesize | pass | 13 | 0 | 0 | 0 | 2.6 | | -| filter | pass | 32 | 0 | 0 | 0 | 4.1 | | -| fing | pass | 17 | 0 | 0 | 0 | 2.9 | | -| firefly_iii | issues | 25 | 1 | 0 | 0 | 3.3 | | -| fireservicerota | pass | 5 | 0 | 0 | 0 | 2.4 | | -| fitbit | pass | 60 | 0 | 0 | 0 | 6.5 | | -| fivem | pass | 5 | 0 | 0 | 0 | 2.4 | | -| fjaraskupan | pass | 11 | 0 | 0 | 0 | 2.9 | | -| flexit_bacnet | pass | 19 | 0 | 0 | 0 | 2.8 | | -| flipr | pass | 18 | 0 | 0 | 0 | 2.8 | | -| flo | pass | 10 | 0 | 0 | 0 | 2.7 | | -| flume | pass | 11 | 0 | 0 | 0 | 2.5 | | -| fluss | pass | 16 | 0 | 0 | 0 | 2.7 | | -| flux_led | pass | 82 | 0 | 0 | 0 | 6.6 | | -| folder_watcher | pass | 10 | 0 | 0 | 0 | 2.6 | | -| forecast_solar | pass | 28 | 0 | 0 | 0 | 3.0 | | -| forked_daapd | pass | 36 | 0 | 0 | 0 | 4.6 | | -| freedompro | pass | 38 | 0 | 0 | 0 | 4.5 | | -| freshr | issues | 28 | 1 | 0 | 0 | 3.4 | | -| fressnapf_tracker | pass | 45 | 0 | 0 | 0 | 4.5 | | -| fritz | issues | 138 | 1 | 0 | 0 | 10.2 | | -| fritzbox | pass | 132 | 0 | 0 | 0 | 8.6 | | -| fritzbox_callmonitor | pass | 12 | 0 | 0 | 0 | 2.5 | | -| fronius | issues | 35 | 1 | 0 | 0 | 3.9 | | -| frontier_silicon | pass | 22 | 0 | 0 | 0 | 2.6 | | -| fujitsu_fglair | pass | 25 | 0 | 0 | 0 | 3.6 | | -| fumis | pass | 72 | 0 | 0 | 0 | 5.6 | | -| fyta | issues | 33 | 1 | 0 | 0 | 3.7 | | -| garages_amsterdam | pass | 6 | 0 | 0 | 0 | 2.5 | | -| gardena_bluetooth | pass | 40 | 0 | 0 | 0 | 5.2 | | -| gdacs | pass | 8 | 0 | 0 | 0 | 2.7 | | -| generic_hygrostat | pass | 76 | 0 | 0 | 0 | 4.0 | | -| generic_thermostat | pass | 114 | 0 | 0 | 0 | 5.6 | | -| geniushub | pass | 23 | 0 | 0 | 0 | 2.9 | | -| gentex_homelink | pass | 13 | 0 | 0 | 0 | 2.8 | | -| geo_json_events | pass | 5 | 0 | 0 | 0 | 2.5 | | -| geocaching | pass | 5 | 0 | 0 | 0 | 2.8 | | -| geofency | pass | 5 | 0 | 0 | 0 | 3.0 | | -| geonetnz_quakes | pass | 9 | 0 | 0 | 0 | 2.8 | | -| geonetnz_volcano | pass | 7 | 0 | 0 | 0 | 2.6 | | -| ghost | pass | 27 | 0 | 0 | 0 | 3.4 | | -| gios | issues | 17 | 1 | 0 | 0 | 3.1 | | -| github | pass | 17 | 0 | 0 | 0 | 2.9 | | -| glances | pass | 16 | 0 | 0 | 0 | 2.8 | | -| goalzero | pass | 15 | 0 | 0 | 0 | 2.7 | | -| gogogate2 | pass | 15 | 0 | 0 | 0 | 2.7 | | -| goodwe | issues | 5 | 1 | 0 | 0 | 2.6 | | -| google | issues | 136 | 1 | 0 | 0 | 8.2 | | -| google_air_quality | pass | 19 | 0 | 0 | 0 | 2.8 | | -| google_assistant_sdk | pass | 41 | 0 | 0 | 0 | 3.7 | | -| google_drive | pass | 40 | 0 | 0 | 0 | 4.2 | | -| google_mail | pass | 28 | 0 | 0 | 0 | 3.6 | | -| google_photos | pass | 35 | 0 | 0 | 0 | 3.5 | | -| google_sheets | pass | 24 | 0 | 0 | 0 | 3.1 | | -| google_tasks | pass | 42 | 0 | 0 | 0 | 4.2 | | -| google_travel_time | pass | 44 | 0 | 0 | 0 | 3.7 | | -| google_weather | issues | 44 | 1 | 0 | 0 | 3.9 | | -| govee_ble | pass | 20 | 0 | 0 | 0 | 3.2 | | -| govee_light_local | pass | 23 | 0 | 0 | 0 | 3.0 | | -| gpsd | pass | 2 | 0 | 0 | 0 | 2.4 | | -| gpslogger | pass | 3 | 0 | 0 | 1 | 2.9 | | -| gree | pass | 123 | 0 | 0 | 0 | 7.3 | | -| green_planet_energy | pass | 11 | 0 | 0 | 0 | 2.7 | | -| group | pass | 392 | 0 | 0 | 0 | 24.5 | | -| growatt_server | issues | 140 | 2 | 0 | 0 | 14.2 | | -| guardian | issues | 11 | 1 | 0 | 0 | 2.5 | | -| guntamatic | pass | 15 | 0 | 0 | 0 | 2.6 | | -| habitica | pass | 382 | 0 | 0 | 0 | 38.9 | | -| hanna | pass | 5 | 0 | 0 | 0 | 2.4 | | -| harmony | pass | 22 | 0 | 0 | 0 | 2.8 | | -| hdfury | pass | 50 | 0 | 0 | 0 | 6.5 | | -| hegel | pass | 12 | 0 | 0 | 0 | 2.5 | | -| heos | issues | 147 | 2 | 0 | 0 | 8.1 | | -| here_travel_time | pass | 39 | 0 | 0 | 0 | 3.1 | | -| hisense_aehw4a1 | pass | 4 | 0 | 0 | 0 | 2.3 | | -| history_stats | pass | 55 | 0 | 0 | 0 | 7.6 | | -| hive | pass | 19 | 0 | 0 | 0 | 2.8 | | -| hko | pass | 4 | 0 | 0 | 0 | 2.4 | | -| hlk_sw16 | pass | 4 | 0 | 0 | 0 | 2.3 | | -| holiday | pass | 34 | 0 | 0 | 0 | 3.7 | | -| home_connect | pass | 311 | 0 | 0 | 0 | 15.9 | | -| homeassistant_connect_zbt2 | issues | 24 | 1 | 0 | 0 | 4.1 | | -| homeassistant_sky_connect | pass | 36 | 0 | 0 | 0 | 4.3 | | -| homee | pass | 199 | 0 | 0 | 0 | 16.2 | | -| homekit | pass | 372 | 0 | 0 | 0 | 13.6 | | -| homematicip_cloud | pass | 182 | 0 | 0 | 0 | 24.0 | | -| homevolt | pass | 33 | 0 | 0 | 0 | 3.3 | | -| homewizard | pass | 149 | 0 | 0 | 0 | 11.0 | | -| homeworks | pass | 38 | 0 | 0 | 0 | 4.3 | | -| honeywell | pass | 44 | 0 | 0 | 0 | 4.3 | | -| honeywell_string_lights | pass | 7 | 0 | 0 | 0 | 2.4 | | -| hr_energy_qube | pass | 28 | 0 | 0 | 0 | 4.1 | | -| html5 | issues | 64 | 1 | 0 | 0 | 5.7 | | -| huawei_lte | pass | 36 | 0 | 0 | 0 | 3.7 | | -| hue | pass | 113 | 0 | 0 | 0 | 20.1 | | -| hue_ble | pass | 19 | 0 | 0 | 0 | 3.0 | | -| huisbaasje | pass | 13 | 0 | 0 | 0 | 2.5 | | -| hunterdouglas_powerview | pass | 39 | 0 | 0 | 0 | 3.3 | | -| husqvarna_automower | issues | 93 | 1 | 0 | 0 | 14.7 | | -| husqvarna_automower_ble | pass | 42 | 0 | 0 | 0 | 5.1 | | -| huum | pass | 34 | 0 | 0 | 0 | 3.3 | | -| hvv_departures | pass | 9 | 0 | 0 | 0 | 2.3 | | -| hydrawise | pass | 35 | 0 | 0 | 0 | 4.3 | | -| hypontech | pass | 17 | 0 | 0 | 0 | 2.7 | | -| ialarm | pass | 7 | 0 | 0 | 0 | 2.3 | | -| iaqualink | pass | 28 | 0 | 0 | 0 | 2.9 | | -| ibeacon | pass | 24 | 0 | 0 | 0 | 3.5 | | -| icloud | pass | 17 | 0 | 0 | 0 | 2.5 | | -| idasen_desk | pass | 30 | 0 | 0 | 0 | 3.8 | | -| idrive_e2 | pass | 65 | 0 | 0 | 0 | 4.7 | | -| ifttt | pass | 1 | 0 | 0 | 0 | 2.5 | | -| igloohome | pass | 8 | 0 | 0 | 0 | 2.4 | | -| imap | pass | 159 | 0 | 0 | 0 | 12.4 | | -| imeon_inverter | pass | 17 | 0 | 0 | 0 | 2.9 | | -| imgw_pib | issues | 15 | 1 | 0 | 0 | 2.8 | | -| immich | issues | 54 | 1 | 0 | 0 | 3.8 | | -| improv_ble | pass | 38 | 0 | 0 | 0 | 3.7 | | -| incomfort | pass | 50 | 0 | 0 | 0 | 4.0 | | -| indevolt | pass | 83 | 0 | 0 | 0 | 6.2 | | -| inels | pass | 28 | 0 | 0 | 0 | 3.0 | | -| influxdb | pass | 145 | 0 | 0 | 0 | 6.0 | | -| inkbird | pass | 16 | 0 | 0 | 0 | 3.0 | | -| insteon | pass | 79 | 0 | 0 | 0 | 18.5 | | -| integration | pass | 61 | 0 | 0 | 0 | 4.0 | | -| intelliclima | pass | 28 | 0 | 0 | 0 | 3.2 | | -| intellifire | pass | 29 | 0 | 0 | 0 | 4.2 | | -| iometer | pass | 17 | 0 | 0 | 0 | 2.8 | | -| ios | pass | 3 | 0 | 0 | 0 | 2.3 | | -| iotawatt | pass | 9 | 0 | 0 | 0 | 2.5 | | -| iotty | pass | 24 | 0 | 0 | 0 | 3.2 | | -| ipma | pass | 15 | 0 | 0 | 0 | 2.7 | | -| ipp | pass | 29 | 0 | 0 | 0 | 3.0 | | -| iqvia | issues | 4 | 1 | 0 | 0 | 2.4 | | -| irm_kmi | pass | 13 | 0 | 0 | 0 | 2.7 | | -| iron_os | pass | 95 | 0 | 0 | 0 | 8.9 | | -| iskra | pass | 13 | 0 | 0 | 0 | 2.4 | | -| islamic_prayer_times | pass | 45 | 0 | 0 | 0 | 3.8 | | -| israel_rail | pass | 9 | 0 | 0 | 0 | 2.6 | | -| iss | pass | 17 | 0 | 0 | 0 | 2.6 | | -| ista_ecotrend | pass | 49 | 0 | 0 | 0 | 4.1 | | -| isy994 | pass | 31 | 0 | 0 | 0 | 3.1 | | -| ituran | pass | 19 | 0 | 0 | 0 | 3.0 | | -| izone | pass | 10 | 0 | 0 | 0 | 2.5 | | -| jellyfin | pass | 44 | 0 | 0 | 0 | 8.6 | | -| jewish_calendar | pass | 123 | 0 | 0 | 0 | 11.9 | | -| justnimbus | pass | 7 | 0 | 0 | 0 | 2.4 | | -| jvc_projector | pass | 31 | 0 | 0 | 0 | 5.7 | | -| kaleidescape | pass | 19 | 0 | 0 | 0 | 4.0 | | -| keenetic_ndms2 | pass | 14 | 0 | 0 | 0 | 2.4 | | -| kegtron | pass | 12 | 0 | 0 | 0 | 2.7 | | -| keymitt_ble | pass | 7 | 0 | 0 | 0 | 2.6 | | -| kiosker | pass | 43 | 0 | 0 | 0 | 3.3 | | -| kmtronic | pass | 11 | 0 | 0 | 0 | 2.5 | | -| knocki | pass | 17 | 0 | 0 | 0 | 2.8 | | -| knx | pass | 387 | 0 | 0 | 0 | 28.0 | | -| kodi | pass | 21 | 0 | 0 | 0 | 2.8 | | -| kostal_plenticore | issues | 38 | 1 | 0 | 0 | 4.1 | | -| kraken | pass | 11 | 0 | 0 | 0 | 2.9 | | -| kulersky | pass | 17 | 0 | 0 | 0 | 2.9 | | -| lacrosse_view | issues | 28 | 1 | 0 | 0 | 3.1 | | -| lamarzocco | pass | 94 | 0 | 0 | 0 | 14.3 | | -| lametric | pass | 55 | 0 | 0 | 0 | 5.6 | | -| landisgyr_heat_meter | pass | 10 | 0 | 0 | 0 | 2.8 | | -| lastfm | pass | 15 | 0 | 0 | 0 | 2.5 | | -| launch_library | pass | 2 | 0 | 0 | 0 | 2.3 | | -| laundrify | pass | 18 | 0 | 0 | 0 | 2.8 | | -| lcn | pass | 149 | 0 | 0 | 0 | 13.4 | | -| ld2410_ble | pass | 6 | 0 | 0 | 0 | 2.6 | | -| leaone | pass | 6 | 0 | 0 | 0 | 2.6 | | -| led_ble | pass | 9 | 0 | 0 | 0 | 2.7 | | -| lektrico | pass | 13 | 0 | 0 | 0 | 2.8 | | -| letpot | pass | 40 | 0 | 0 | 0 | 4.1 | | -| lg_infrared | pass | 61 | 0 | 0 | 0 | 4.3 | | -| lg_netcast | pass | 22 | 0 | 0 | 0 | 3.5 | | -| lg_soundbar | pass | 11 | 0 | 0 | 0 | 2.7 | | -| lg_thinq | pass | 29 | 0 | 0 | 0 | 4.1 | | -| libre_hardware_monitor | pass | 26 | 0 | 0 | 0 | 3.2 | | -| lichess | pass | 8 | 0 | 0 | 0 | 2.5 | | -| lidarr | pass | 12 | 0 | 0 | 0 | 2.6 | | -| liebherr | pass | 76 | 0 | 0 | 0 | 5.7 | | -| lifx | pass | 72 | 0 | 0 | 0 | 5.4 | | -| linkplay | pass | 9 | 0 | 0 | 0 | 2.5 | | -| litejet | pass | 32 | 0 | 0 | 0 | 3.8 | | -| litterrobot | pass | 63 | 0 | 0 | 0 | 5.9 | | -| livisi | pass | 4 | 0 | 0 | 0 | 2.4 | | -| local_calendar | pass | 50 | 0 | 0 | 0 | 5.1 | | -| local_ip | pass | 3 | 0 | 0 | 0 | 2.3 | | -| local_todo | pass | 55 | 0 | 0 | 0 | 5.6 | | -| locative | pass | 5 | 0 | 0 | 1 | 3.0 | | -| lojack | pass | 16 | 0 | 0 | 0 | 2.9 | | -| london_underground | pass | 12 | 0 | 0 | 0 | 2.5 | | -| lookin | pass | 7 | 0 | 0 | 0 | 2.4 | | -| loqed | pass | 17 | 0 | 0 | 0 | 3.0 | | -| luftdaten | pass | 11 | 0 | 0 | 0 | 2.6 | | -| lunatone | pass | 40 | 0 | 0 | 0 | 3.8 | | -| lupusec | pass | 5 | 0 | 0 | 0 | 2.4 | | -| lutron | pass | 42 | 0 | 0 | 0 | 3.8 | | -| lutron_caseta | pass | 52 | 0 | 0 | 0 | 4.9 | | -| lyric | pass | 4 | 0 | 0 | 0 | 2.4 | | -| madvr | issues | 16 | 1 | 0 | 0 | 3.2 | | -| mailgun | pass | 5 | 0 | 0 | 0 | 2.7 | | -| marantz_infrared | pass | 36 | 0 | 0 | 0 | 3.4 | | -| mastodon | issues | 82 | 1 | 0 | 0 | 26.2 | | -| matter | pass | 372 | 0 | 0 | 1 | 68.8 | | -| mcp | pass | 45 | 0 | 0 | 0 | 4.1 | | -| mcp_server | pass | 47 | 0 | 0 | 0 | 5.9 | | -| mealie | pass | 94 | 0 | 0 | 0 | 7.8 | | -| meater | pass | 10 | 0 | 0 | 0 | 2.5 | | -| medcom_ble | pass | 9 | 0 | 0 | 0 | 2.6 | | -| media_extractor | pass | 20 | 0 | 0 | 0 | 7.6 | | -| melcloud | issues | 23 | 1 | 0 | 0 | 2.8 | | -| melnor | pass | 17 | 0 | 0 | 0 | 3.5 | | -| met | pass | 18 | 0 | 0 | 0 | 2.6 | | -| met_eireann | pass | 10 | 0 | 0 | 0 | 2.5 | | -| meteo_france | pass | 6 | 0 | 0 | 0 | 2.5 | | -| meteo_lt | pass | 7 | 0 | 0 | 0 | 2.5 | | -| meteoclimatic | pass | 3 | 0 | 0 | 0 | 2.4 | | -| metoffice | pass | 18 | 0 | 0 | 0 | 3.1 | | -| microbees | pass | 9 | 0 | 0 | 0 | 2.5 | | -| miele | pass | 115 | 0 | 0 | 0 | 10.2 | | -| mikrotik | pass | 21 | 0 | 0 | 0 | 2.8 | | -| mill | pass | 31 | 0 | 0 | 0 | 4.5 | | -| min_max | pass | 20 | 0 | 0 | 0 | 2.6 | | -| minecraft_server | pass | 40 | 0 | 0 | 0 | 3.5 | | -| mitsubishi_comfort | pass | 66 | 0 | 0 | 0 | 5.3 | | -| moat | pass | 11 | 0 | 0 | 0 | 2.8 | | -| mobile_app | pass | 135 | 0 | 0 | 0 | 13.2 | | -| modem_callerid | pass | 9 | 0 | 0 | 0 | 2.6 | | -| modern_forms | issues | 29 | 1 | 0 | 0 | 3.4 | | -| moehlenhoff_alpha2 | pass | 8 | 0 | 0 | 0 | 2.4 | | -| mold_indicator | pass | 37 | 0 | 0 | 0 | 3.0 | | -| monarch_money | pass | 5 | 0 | 0 | 0 | 2.6 | | -| monoprice | pass | 23 | 0 | 0 | 0 | 3.2 | | -| monzo | pass | 11 | 0 | 0 | 0 | 2.7 | | -| moon | pass | 11 | 0 | 0 | 0 | 2.5 | | -| mopeka | pass | 14 | 0 | 0 | 0 | 2.9 | | -| motion_blinds | pass | 12 | 0 | 0 | 0 | 2.5 | | -| motionblinds_ble | issues | 42 | 1 | 0 | 0 | 6.3 | | -| motionmount | pass | 38 | 0 | 0 | 0 | 3.5 | | -| mpd | pass | 7 | 0 | 0 | 0 | 2.9 | | -| mta | pass | 27 | 0 | 0 | 0 | 3.0 | | -| mullvad | pass | 4 | 0 | 0 | 0 | 2.3 | | -| music_assistant | pass | 127 | 0 | 0 | 0 | 9.4 | | -| mutesync | pass | 5 | 0 | 0 | 0 | 2.3 | | -| myneomitis | pass | 43 | 0 | 0 | 0 | 4.1 | | -| mysensors | pass | 61 | 0 | 0 | 0 | 5.1 | | -| mystrom | pass | 23 | 0 | 0 | 0 | 2.8 | | -| myuplink | pass | 38 | 0 | 0 | 0 | 4.8 | | -| nam | pass | 39 | 0 | 0 | 0 | 3.4 | | -| namecheapdns | pass | 20 | 0 | 0 | 0 | 2.7 | | -| nanoleaf | pass | 30 | 0 | 0 | 0 | 2.9 | | -| nasweb | pass | 8 | 0 | 0 | 0 | 2.4 | | -| nederlandse_spoorwegen | pass | 29 | 0 | 0 | 0 | 3.4 | | -| ness_alarm | pass | 39 | 0 | 0 | 0 | 3.0 | | -| netgear | pass | 10 | 0 | 0 | 0 | 2.4 | | -| netgear_lte | pass | 11 | 0 | 0 | 0 | 2.6 | | -| nexia | pass | 21 | 0 | 0 | 0 | 3.6 | | -| nextbus | pass | 28 | 0 | 0 | 0 | 2.7 | | -| nextcloud | pass | 17 | 0 | 0 | 0 | 2.9 | | -| nextdns | issues | 52 | 1 | 0 | 0 | 5.8 | | -| nfandroidtv | pass | 8 | 0 | 0 | 0 | 2.4 | | -| nibe_heatpump | pass | 64 | 0 | 0 | 0 | 4.3 | | -| nice_go | issues | 43 | 1 | 0 | 0 | 4.5 | | -| nightscout | pass | 11 | 0 | 0 | 0 | 2.5 | | -| niko_home_control | pass | 32 | 0 | 0 | 0 | 3.8 | | -| nina | pass | 21 | 0 | 0 | 0 | 3.2 | | -| nintendo_parental_controls | pass | 30 | 0 | 0 | 0 | 3.3 | | -| nmap_tracker | pass | 13 | 0 | 0 | 0 | 2.6 | | -| nmbs | pass | 5 | 0 | 0 | 0 | 2.5 | | -| nobo_hub | pass | 59 | 0 | 0 | 0 | 4.6 | | -| nordpool | pass | 37 | 0 | 0 | 0 | 4.9 | | -| notion | issues | 7 | 1 | 0 | 0 | 2.5 | | -| novy_cooker_hood | issues | 28 | 1 | 0 | 0 | 3.1 | | -| nrgkick | pass | 87 | 0 | 0 | 0 | 5.7 | | -| ntfy | pass | 85 | 0 | 0 | 0 | 6.8 | | -| nuheat | pass | 9 | 0 | 0 | 0 | 2.5 | | -| nuki | pass | 14 | 0 | 0 | 0 | 2.6 | | -| nut | pass | 78 | 0 | 0 | 0 | 4.6 | | -| nws | pass | 30 | 0 | 0 | 0 | 3.3 | | -| nyt_games | pass | 10 | 0 | 0 | 0 | 2.6 | | -| nzbget | pass | 10 | 0 | 0 | 0 | 2.5 | | -| obihai | pass | 5 | 0 | 0 | 0 | 2.4 | | -| ohme | pass | 34 | 0 | 0 | 0 | 4.4 | | -| omie | pass | 19 | 0 | 0 | 0 | 2.9 | | -| omnilogic | pass | 8 | 0 | 0 | 0 | 2.4 | | -| ondilo_ico | pass | 17 | 0 | 0 | 0 | 3.0 | | -| onedrive | pass | 79 | 0 | 0 | 0 | 6.5 | | -| onedrive_for_business | pass | 38 | 0 | 0 | 0 | 4.6 | | -| onewire | pass | 33 | 0 | 0 | 0 | 3.5 | | -| onkyo | pass | 48 | 0 | 0 | 0 | 4.2 | | -| open_meteo | pass | 5 | 0 | 0 | 0 | 2.7 | | -| opendisplay | pass | 67 | 0 | 0 | 0 | 6.6 | | -| openevse | pass | 40 | 0 | 0 | 0 | 4.5 | | -| openexchangerates | pass | 9 | 0 | 0 | 0 | 2.4 | | -| opengarage | pass | 6 | 0 | 0 | 0 | 2.4 | | -| openhome | pass | 11 | 0 | 0 | 0 | 2.6 | | -| openrgb | pass | 69 | 0 | 0 | 0 | 4.6 | | -| opensky | pass | 12 | 0 | 0 | 0 | 2.6 | | -| opentherm_gw | pass | 29 | 0 | 0 | 0 | 5.7 | | -| openuv | issues | 10 | 1 | 0 | 0 | 2.7 | | -| openweathermap | pass | 15 | 0 | 0 | 0 | 2.6 | | -| opower | issues | 35 | 1 | 0 | 0 | 5.8 | | -| oralb | pass | 14 | 0 | 0 | 0 | 2.9 | | -| orvibo | pass | 15 | 0 | 0 | 0 | 2.6 | | -| osoenergy | pass | 17 | 0 | 0 | 0 | 3.0 | | -| otbr | pass | 103 | 0 | 0 | 0 | 7.9 | | -| otp | pass | 7 | 0 | 0 | 0 | 2.4 | | -| ouman_eh_800 | pass | 20 | 0 | 0 | 0 | 3.1 | | -| ourgroceries | pass | 21 | 0 | 0 | 0 | 2.8 | | -| overkiz | pass | 181 | 0 | 0 | 0 | 9.9 | | -| overseerr | pass | 45 | 0 | 0 | 0 | 4.8 | | -| ovo_energy | pass | 7 | 0 | 0 | 0 | 2.4 | | -| owntracks | pass | 71 | 0 | 0 | 0 | 5.8 | | -| p1_monitor | issues | 11 | 2 | 0 | 0 | 2.7 | | -| paj_gps | pass | 19 | 0 | 0 | 0 | 3.1 | | -| palazzetti | pass | 17 | 0 | 0 | 0 | 3.0 | | -| panasonic_viera | pass | 32 | 0 | 0 | 0 | 3.0 | | -| paperless_ngx | pass | 41 | 0 | 0 | 0 | 3.6 | | -| peblar | pass | 62 | 0 | 0 | 0 | 5.2 | | -| peco | pass | 33 | 0 | 0 | 0 | 3.0 | | -| pegel_online | issues | 9 | 1 | 0 | 0 | 2.6 | | -| permobil | pass | 9 | 0 | 0 | 0 | 2.5 | | -| pglab | pass | 21 | 0 | 0 | 0 | 3.5 | | -| philips_js | issues | 15 | 1 | 0 | 0 | 3.7 | | -| pi_hole | issues | 25 | 1 | 0 | 0 | 3.4 | | -| picnic | pass | 40 | 0 | 0 | 0 | 3.4 | | -| ping | pass | 21 | 0 | 0 | 0 | 2.9 | | -| pjlink | pass | 29 | 0 | 0 | 0 | 3.3 | | -| plaato | pass | 13 | 0 | 0 | 0 | 2.9 | | -| playstation_network | pass | 68 | 0 | 0 | 0 | 6.7 | | -| plex | pass | 53 | 0 | 0 | 0 | 6.7 | | -| plugwise | pass | 86 | 0 | 0 | 0 | 8.3 | | -| point | pass | 5 | 0 | 0 | 0 | 2.5 | | -| pooldose | pass | 75 | 0 | 0 | 0 | 5.3 | | -| poolsense | pass | 5 | 0 | 0 | 0 | 2.4 | | -| portainer | issues | 86 | 1 | 0 | 0 | 12.9 | | -| powerfox | pass | 22 | 0 | 0 | 0 | 3.0 | | -| powerfox_local | pass | 21 | 0 | 0 | 0 | 2.9 | | -| powerwall | pass | 37 | 0 | 0 | 0 | 3.6 | | -| prana | pass | 37 | 0 | 0 | 0 | 3.8 | | -| private_ble_device | pass | 21 | 0 | 0 | 0 | 3.3 | | -| probe_plus | pass | 6 | 0 | 0 | 0 | 2.6 | | -| profiler | pass | 13 | 0 | 0 | 0 | 2.6 | | -| progettihwsw | pass | 4 | 0 | 0 | 0 | 2.3 | | -| prowl | pass | 24 | 0 | 0 | 0 | 2.8 | | -| proximity | issues | 27 | 1 | 0 | 0 | 3.0 | | -| proxmoxve | issues | 103 | 1 | 0 | 0 | 9.2 | | -| ps4 | pass | 41 | 0 | 0 | 0 | 3.3 | | -| ptdevices | pass | 7 | 0 | 0 | 0 | 2.4 | | -| pterodactyl | pass | 14 | 0 | 0 | 0 | 2.7 | | -| pure_energie | pass | 8 | 0 | 0 | 0 | 2.5 | | -| purpleair | issues | 19 | 1 | 0 | 0 | 3.0 | | -| pushbullet | pass | 17 | 0 | 0 | 0 | 2.6 | | -| pushover | pass | 16 | 0 | 0 | 0 | 2.5 | | -| pvoutput | pass | 17 | 0 | 0 | 0 | 2.7 | | -| pvpc_hourly_pricing | pass | 2 | 0 | 0 | 0 | 2.4 | | -| pyload | pass | 67 | 0 | 0 | 0 | 4.6 | | -| qbittorrent | pass | 8 | 0 | 0 | 0 | 2.5 | | -| qbus | pass | 25 | 0 | 0 | 0 | 4.0 | | -| qingping | pass | 15 | 0 | 0 | 0 | 3.0 | | -| qnap | pass | 6 | 0 | 0 | 0 | 2.4 | | -| qnap_qsw | pass | 18 | 0 | 0 | 0 | 3.1 | | -| rabbitair | pass | 7 | 0 | 0 | 0 | 2.4 | | -| rachio | pass | 6 | 0 | 0 | 0 | 2.5 | | -| radarr | pass | 32 | 0 | 0 | 0 | 3.9 | | -| radio_browser | pass | 10 | 0 | 0 | 0 | 2.6 | | -| radiotherm | pass | 7 | 0 | 0 | 0 | 2.4 | | -| rainbird | pass | 70 | 0 | 0 | 0 | 4.3 | | -| rainforest_eagle | pass | 12 | 0 | 0 | 0 | 2.5 | | -| rainforest_raven | issues | 19 | 2 | 0 | 0 | 3.2 | | -| rainmachine | issues | 19 | 2 | 0 | 0 | 2.9 | | -| random | pass | 9 | 0 | 0 | 0 | 2.4 | | -| rapt_ble | pass | 11 | 0 | 0 | 0 | 2.8 | | -| rdw | pass | 10 | 0 | 0 | 0 | 2.6 | | -| recollect_waste | issues | 3 | 1 | 0 | 0 | 2.5 | | -| redgtech | pass | 18 | 0 | 0 | 0 | 2.8 | | -| refoss | pass | 2 | 0 | 0 | 0 | 2.3 | | -| rehlko | pass | 16 | 0 | 0 | 0 | 2.9 | | -| remote_calendar | pass | 44 | 0 | 0 | 0 | 3.9 | | -| renault | pass | 104 | 0 | 0 | 0 | 5.7 | | -| renson | pass | 3 | 0 | 0 | 0 | 2.3 | | -| rfxtrx | pass | 89 | 0 | 0 | 0 | 7.5 | | -| rhasspy | pass | 3 | 0 | 0 | 0 | 2.3 | | -| ridwell | issues | 13 | 1 | 0 | 0 | 2.7 | | -| risco | pass | 69 | 0 | 0 | 0 | 4.3 | | -| rituals_perfume_genie | pass | 25 | 0 | 0 | 0 | 3.1 | | -| roborock | pass | 183 | 0 | 0 | 0 | 18.5 | | -| roku | pass | 67 | 0 | 0 | 0 | 6.5 | | -| romy | pass | 6 | 0 | 0 | 0 | 2.4 | | -| roomba | pass | 26 | 0 | 0 | 0 | 2.7 | | -| roon | pass | 5 | 0 | 0 | 0 | 2.3 | | -| route_b_smart_meter | pass | 7 | 0 | 0 | 0 | 2.6 | | -| rova | pass | 11 | 0 | 0 | 0 | 2.4 | | -| rpi_power | pass | 6 | 0 | 0 | 0 | 2.3 | | -| ruckus_unleashed | pass | 29 | 0 | 0 | 0 | 2.8 | | -| russound_rio | pass | 50 | 0 | 0 | 0 | 6.0 | | -| ruuvi_gateway | pass | 5 | 0 | 0 | 0 | 2.5 | | -| ruuvitag_ble | pass | 13 | 0 | 0 | 0 | 2.8 | | -| rympro | pass | 7 | 0 | 0 | 0 | 2.3 | | -| sabnzbd | pass | 19 | 0 | 0 | 0 | 2.9 | | -| samsung_infrared | pass | 18 | 0 | 0 | 0 | 2.7 | | -| samsungtv | issues | 166 | 3 | 0 | 0 | 13.5 | | -| sanix | pass | 6 | 0 | 0 | 0 | 2.4 | | -| satel_integra | pass | 89 | 0 | 0 | 0 | 7.6 | | -| saunum | pass | 68 | 0 | 0 | 0 | 5.0 | | -| schlage | pass | 49 | 0 | 0 | 0 | 4.8 | | -| scrape | issues | 37 | 1 | 0 | 0 | 3.1 | | -| screenlogic | issues | 33 | 1 | 0 | 0 | 3.8 | | -| season | pass | 21 | 0 | 0 | 0 | 2.9 | | -| sense | pass | 19 | 0 | 0 | 0 | 3.2 | | -| sensibo | pass | 69 | 0 | 0 | 0 | 8.4 | | -| sensirion_ble | pass | 11 | 0 | 0 | 0 | 2.7 | | -| sensorpro | pass | 11 | 0 | 0 | 0 | 2.7 | | -| sensorpush | pass | 11 | 0 | 0 | 0 | 2.7 | | -| sensorpush_cloud | pass | 5 | 0 | 0 | 0 | 2.5 | | -| sensoterra | pass | 5 | 0 | 0 | 0 | 2.4 | | -| sentry | pass | 24 | 0 | 0 | 0 | 2.5 | | -| senz | pass | 21 | 0 | 0 | 0 | 3.0 | | -| seventeentrack | pass | 16 | 0 | 0 | 0 | 2.6 | | -| sfr_box | pass | 26 | 0 | 0 | 0 | 2.8 | | -| sftp_storage | pass | 34 | 0 | 0 | 0 | 8.7 | | -| sharkiq | pass | 42 | 0 | 0 | 0 | 3.3 | | -| shelly | pass | 636 | 0 | 0 | 0 | 45.7 | | -| shopping_list | pass | 60 | 0 | 0 | 0 | 4.7 | | -| sia | pass | 21 | 0 | 0 | 0 | 2.6 | | -| simplefin | pass | 12 | 0 | 0 | 0 | 2.7 | | -| simplepush | pass | 5 | 0 | 0 | 0 | 2.3 | | -| simplisafe | issues | 15 | 1 | 0 | 0 | 3.0 | | -| sky_remote | pass | 9 | 0 | 0 | 0 | 2.4 | | -| slack | pass | 13 | 0 | 0 | 0 | 2.4 | | -| sleep_as_android | pass | 44 | 0 | 0 | 0 | 4.5 | | -| sleepiq | pass | 33 | 0 | 0 | 0 | 4.9 | | -| slide_local | issues | 48 | 1 | 0 | 0 | 4.0 | | -| slimproto | pass | 2 | 0 | 0 | 0 | 2.3 | | -| sma | issues | 33 | 1 | 0 | 0 | 3.8 | | -| smappee | pass | 20 | 0 | 0 | 0 | 2.7 | | -| smarla | pass | 37 | 0 | 0 | 0 | 4.2 | | -| smart_meter_texas | pass | 16 | 0 | 0 | 0 | 2.6 | | -| smartthings | pass | 340 | 0 | 0 | 0 | 70.7 | | -| smarttub | pass | 40 | 0 | 0 | 0 | 4.9 | | -| smarty | pass | 12 | 0 | 0 | 0 | 2.8 | | -| smhi | pass | 20 | 0 | 0 | 0 | 3.4 | | -| smlight | pass | 98 | 0 | 0 | 0 | 7.1 | | -| snapcast | pass | 18 | 0 | 0 | 0 | 3.3 | | -| snoo | pass | 21 | 0 | 0 | 0 | 3.4 | | -| snooz | pass | 35 | 0 | 0 | 0 | 4.9 | | -| solaredge | pass | 62 | 0 | 0 | 0 | 11.0 | | -| solarlog | issues | 25 | 1 | 0 | 0 | 3.3 | | -| solarman | pass | 17 | 0 | 0 | 0 | 2.8 | | -| solax | pass | 3 | 0 | 0 | 0 | 2.3 | | -| soma | pass | 7 | 0 | 0 | 0 | 2.3 | | -| somfy_mylink | pass | 11 | 0 | 0 | 0 | 2.4 | | -| sonarr | pass | 56 | 0 | 0 | 0 | 9.7 | | -| songpal | pass | 26 | 0 | 0 | 0 | 3.0 | | -| sonos | pass | 174 | 0 | 0 | 0 | 18.9 | | -| soundtouch | pass | 31 | 0 | 0 | 0 | 3.3 | | -| speedtestdotnet | pass | 8 | 0 | 0 | 0 | 2.4 | | -| splunk | pass | 36 | 0 | 0 | 0 | 2.9 | | -| spotify | pass | 70 | 0 | 0 | 0 | 9.0 | | -| sql | pass | 80 | 0 | 0 | 0 | 6.5 | | -| squeezebox | pass | 129 | 0 | 0 | 0 | 12.8 | | -| srp_energy | pass | 15 | 0 | 0 | 0 | 2.7 | | -| starline | pass | 4 | 0 | 0 | 0 | 2.3 | | -| starlink | pass | 8 | 0 | 0 | 0 | 2.7 | | -| statistics | pass | 56 | 0 | 0 | 0 | 4.2 | | -| steam_online | pass | 14 | 0 | 0 | 0 | 2.5 | | -| steamist | pass | 23 | 0 | 0 | 0 | 2.9 | | -| stiebel_eltron | pass | 9 | 0 | 0 | 0 | 2.8 | | -| stookwijzer | pass | 12 | 0 | 0 | 0 | 2.6 | | -| streamlabswater | pass | 6 | 0 | 0 | 0 | 2.4 | | -| subaru | pass | 59 | 0 | 0 | 0 | 4.6 | | -| suez_water | pass | 19 | 0 | 0 | 0 | 4.2 | | -| sun | pass | 27 | 0 | 0 | 1 | 4.9 | | -| sunricher_dali | pass | 50 | 0 | 0 | 0 | 5.8 | | -| sunweg | pass | 1 | 0 | 0 | 0 | 2.2 | | -| surepetcare | pass | 15 | 0 | 0 | 0 | 2.7 | | -| swiss_public_transport | pass | 36 | 0 | 0 | 0 | 3.1 | | -| switch_as_x | pass | 176 | 0 | 0 | 0 | 10.0 | | -| switchbee | pass | 6 | 0 | 0 | 0 | 2.3 | | -| switchbot | issues | 352 | 1 | 0 | 0 | 19.7 | | -| switchbot_cloud | pass | 134 | 0 | 0 | 0 | 10.8 | | -| switcher_kis | issues | 76 | 1 | 0 | 0 | 7.7 | | -| syncthing | pass | 5 | 0 | 0 | 0 | 2.3 | | -| syncthru | pass | 9 | 0 | 0 | 0 | 2.6 | | -| system_bridge | pass | 39 | 0 | 0 | 0 | 4.5 | | -| systemmonitor | issues | 34 | 2 | 0 | 0 | 4.9 | | -| systemnexa2 | pass | 34 | 0 | 0 | 0 | 3.6 | | -| tado | pass | 40 | 0 | 0 | 0 | 4.7 | | -| tailscale | pass | 14 | 0 | 0 | 0 | 2.8 | | -| tailwind | pass | 36 | 0 | 0 | 0 | 3.3 | | -| tami4 | pass | 14 | 0 | 0 | 0 | 2.6 | | -| tankerkoenig | issues | 20 | 1 | 0 | 0 | 2.8 | | -| tautulli | pass | 8 | 0 | 0 | 0 | 2.3 | | -| technove | pass | 40 | 0 | 0 | 0 | 3.7 | | -| tedee | pass | 41 | 0 | 0 | 0 | 4.5 | | -| telegram_bot | pass | 123 | 0 | 0 | 0 | 13.2 | | -| teleinfo | pass | 24 | 0 | 0 | 0 | 3.2 | | -| tellduslive | pass | 15 | 0 | 0 | 0 | 2.4 | | -| teltonika | pass | 50 | 0 | 0 | 0 | 3.8 | | -| template | pass | 2470 | 0 | 0 | 0 | 60.1 | | -| tesla_fleet | pass | 153 | 0 | 0 | 0 | 12.3 | | -| tesla_wall_connector | pass | 12 | 0 | 0 | 0 | 2.5 | | -| teslemetry | pass | 158 | 0 | 0 | 0 | 16.8 | | -| tessie | pass | 67 | 0 | 0 | 0 | 4.5 | | -| thermobeacon | pass | 11 | 0 | 0 | 0 | 2.7 | | -| thermopro | pass | 18 | 0 | 0 | 0 | 3.0 | | -| thethingsnetwork | pass | 8 | 0 | 0 | 0 | 2.4 | | -| thread | pass | 65 | 0 | 0 | 0 | 4.2 | | -| threshold | pass | 114 | 0 | 0 | 0 | 4.1 | | -| tibber | pass | 92 | 0 | 0 | 0 | 13.3 | | -| tile | pass | 11 | 0 | 0 | 0 | 2.5 | | -| tilt_ble | pass | 11 | 0 | 0 | 0 | 2.8 | | -| tilt_pi | pass | 6 | 0 | 0 | 0 | 2.4 | | -| time_date | pass | 18 | 0 | 0 | 0 | 2.8 | | -| tod | pass | 32 | 0 | 0 | 0 | 3.3 | | -| todoist | pass | 50 | 0 | 0 | 0 | 4.2 | | -| togrill | pass | 62 | 0 | 0 | 0 | 5.7 | | -| tolo | pass | 7 | 0 | 0 | 0 | 2.4 | | -| tomorrowio | pass | 21 | 0 | 0 | 0 | 3.1 | | -| toon | pass | 10 | 0 | 0 | 0 | 2.7 | | -| totalconnect | pass | 66 | 0 | 0 | 0 | 11.7 | | -| touchline | pass | 8 | 0 | 0 | 0 | 2.5 | | -| touchline_sl | pass | 15 | 0 | 0 | 0 | 2.7 | | -| tplink_omada | issues | 52 | 1 | 0 | 0 | 4.7 | | -| traccar | pass | 5 | 0 | 0 | 1 | 2.9 | | -| traccar_server | pass | 12 | 0 | 0 | 0 | 2.9 | | -| tractive | issues | 48 | 1 | 0 | 0 | 4.0 | | -| tradfri | pass | 91 | 0 | 0 | 0 | 6.4 | | -| trafikverket_ferry | pass | 13 | 0 | 0 | 0 | 2.5 | | -| trafikverket_train | pass | 41 | 0 | 0 | 0 | 3.1 | | -| trafikverket_weatherstation | pass | 16 | 0 | 0 | 0 | 2.5 | | -| trane | pass | 39 | 0 | 0 | 0 | 4.0 | | -| transmission | pass | 75 | 0 | 0 | 0 | 4.6 | | -| trend | pass | 39 | 0 | 0 | 0 | 3.2 | | -| triggercmd | pass | 5 | 0 | 0 | 0 | 2.4 | | -| trmnl | pass | 36 | 0 | 0 | 0 | 3.7 | | -| twentemilieu | pass | 17 | 0 | 0 | 0 | 2.9 | | -| twilio | pass | 1 | 0 | 0 | 0 | 2.6 | | -| twinkly | issues | 25 | 1 | 0 | 0 | 4.1 | | -| twitch | pass | 16 | 0 | 0 | 0 | 4.2 | | -| uhoo | pass | 24 | 0 | 0 | 0 | 3.0 | | -| ukraine_alarm | pass | 11 | 0 | 0 | 0 | 2.4 | | -| unifi | issues | 158 | 1 | 0 | 0 | 12.4 | | -| unifi_access | pass | 155 | 0 | 0 | 0 | 10.9 | | -| upb | pass | 6 | 0 | 0 | 0 | 2.4 | | -| upcloud | pass | 6 | 0 | 0 | 0 | 2.4 | | -| upnp | pass | 23 | 0 | 0 | 0 | 3.8 | | -| uptime | pass | 4 | 0 | 0 | 0 | 2.4 | | -| uptime_kuma | pass | 38 | 0 | 0 | 0 | 3.6 | | -| uptimerobot | pass | 35 | 0 | 0 | 0 | 3.2 | | -| utility_meter | issues | 94 | 1 | 0 | 0 | 6.0 | | -| v2c | issues | 12 | 1 | 0 | 0 | 2.9 | | -| vallox | pass | 76 | 0 | 0 | 0 | 5.6 | | -| vegehub | pass | 18 | 0 | 0 | 0 | 2.9 | | -| velbus | issues | 57 | 1 | 0 | 0 | 9.0 | | -| velux | pass | 80 | 0 | 0 | 0 | 4.9 | | -| venstar | pass | 12 | 0 | 0 | 0 | 2.6 | | -| vera | pass | 29 | 0 | 0 | 0 | 3.1 | | -| version | pass | 10 | 0 | 0 | 0 | 2.5 | | -| vesync | pass | 175 | 0 | 0 | 0 | 12.5 | | -| vicare | issues | 54 | 1 | 0 | 0 | 4.5 | | -| victron_ble | pass | 34 | 0 | 0 | 0 | 3.6 | | -| victron_gx | pass | 62 | 0 | 0 | 0 | 14.1 | | -| victron_remote_monitoring | pass | 22 | 0 | 0 | 0 | 3.0 | | -| vilfo | pass | 9 | 0 | 0 | 0 | 2.5 | | -| vizio | pass | 73 | 0 | 0 | 0 | 4.5 | | -| vlc_telnet | pass | 39 | 0 | 0 | 0 | 3.4 | | -| vodafone_station | issues | 53 | 1 | 0 | 0 | 4.6 | | -| volumio | pass | 9 | 0 | 0 | 0 | 2.3 | | -| volvo | pass | 110 | 0 | 0 | 0 | 8.6 | | -| volvooncall | pass | 4 | 0 | 0 | 0 | 2.2 | | -| wake_on_lan | pass | 15 | 0 | 0 | 0 | 2.5 | | -| wallbox | pass | 38 | 0 | 0 | 0 | 3.7 | | -| waqi | pass | 20 | 0 | 0 | 0 | 2.6 | | -| waterfurnace | pass | 77 | 0 | 0 | 0 | 11.9 | | -| watergate | pass | 16 | 0 | 0 | 0 | 3.0 | | -| watts | issues | 51 | 1 | 0 | 0 | 5.2 | | -| watttime | issues | 11 | 1 | 0 | 0 | 2.6 | | -| waze_travel_time | pass | 23 | 0 | 0 | 0 | 14.8 | | -| weatherflow | pass | 5 | 0 | 0 | 0 | 3.0 | | -| weatherflow_cloud | pass | 17 | 0 | 0 | 0 | 3.1 | | -| weatherkit | pass | 35 | 0 | 0 | 0 | 2.9 | | -| webdav | pass | 25 | 0 | 0 | 0 | 3.9 | | -| webmin | issues | 11 | 1 | 0 | 0 | 2.5 | | -| webostv | issues | 82 | 1 | 0 | 0 | 12.0 | | -| weheat | pass | 16 | 0 | 0 | 0 | 2.8 | | -| wemo | pass | 93 | 0 | 0 | 0 | 6.7 | | -| whirlpool | issues | 239 | 1 | 0 | 0 | 12.2 | | -| whois | pass | 41 | 0 | 0 | 0 | 3.4 | | -| wiffi | pass | 4 | 0 | 0 | 0 | 2.2 | | -| wiim | pass | 29 | 0 | 0 | 0 | 3.0 | | -| wilight | pass | 22 | 0 | 0 | 0 | 2.8 | | -| withings | pass | 62 | 0 | 0 | 0 | 8.0 | | -| wiz | pass | 56 | 0 | 0 | 0 | 4.3 | | -| wled | pass | 100 | 0 | 0 | 0 | 7.6 | | -| wmspro | pass | 39 | 0 | 0 | 0 | 4.0 | | -| wolflink | pass | 10 | 0 | 0 | 0 | 2.5 | | -| workday | issues | 70 | 1 | 0 | 0 | 6.1 | | -| worldclock | pass | 6 | 0 | 0 | 0 | 2.4 | | -| ws66i | pass | 24 | 0 | 0 | 0 | 3.1 | | -| wsdot | pass | 16 | 0 | 0 | 0 | 2.7 | | -| xbox | pass | 203 | 0 | 0 | 0 | 14.9 | | -| xiaomi_aqara | pass | 12 | 0 | 0 | 0 | 2.5 | | -| xiaomi_ble | pass | 92 | 0 | 0 | 0 | 6.2 | | -| xiaomi_miio | pass | 51 | 0 | 0 | 0 | 4.1 | | -| xthings_cloud | pass | 30 | 0 | 0 | 0 | 3.7 | | -| yale_smart_alarm | pass | 37 | 0 | 0 | 0 | 3.7 | | -| yalexs_ble | pass | 26 | 0 | 0 | 0 | 3.3 | | -| yamaha_musiccast | pass | 9 | 0 | 0 | 0 | 2.5 | | -| yardian | pass | 16 | 0 | 0 | 0 | 2.8 | | -| yeelight | pass | 62 | 0 | 0 | 0 | 4.7 | | -| yolink | pass | 10 | 0 | 0 | 0 | 3.1 | | -| youless | pass | 4 | 0 | 0 | 0 | 2.4 | | -| youtube | pass | 24 | 0 | 0 | 0 | 3.4 | | -| zamg | pass | 9 | 0 | 0 | 0 | 2.5 | | -| zerproc | pass | 10 | 0 | 0 | 0 | 2.5 | | -| zeversolar | pass | 10 | 0 | 0 | 0 | 2.5 | | -| zha | issues | 339 | 1 | 0 | 4 | 70.5 | | -| zimi | pass | 16 | 0 | 0 | 0 | 2.7 | | -| zinvolt | pass | 12 | 0 | 0 | 0 | 2.8 | | -| zodiac | pass | 5 | 0 | 0 | 0 | 2.3 | | -| zwave_js | pass | 611 | 0 | 0 | 0 | 60.2 | | -| zwave_me | pass | 8 | 0 | 0 | 0 | 2.2 | | - -Per-failure tracebacks are dumped under `${SANDBOX_ERRORS_DIR:-/tmp/sandbox_errors}//`. diff --git a/sandbox/COMPAT_LATEST.md b/sandbox/COMPAT_LATEST.md deleted file mode 100644 index b50bf15a561e..000000000000 --- a/sandbox/COMPAT_LATEST.md +++ /dev/null @@ -1,57 +0,0 @@ -# Sandbox compat report - -Plugin: `hass_client.testing.pytest_plugin` - -## Summary - -- Integrations passing: **35** -- Integrations with issues: **2** -- Timeouts: **0** -- No tests collected: **0** - -- Tests passed: **7646** -- Tests failed: **2** -- Test errors: **0** -- Tests skipped: **17** - -## Per-integration results - -| integration | status | passed | failed | errors | skipped | -| --- | --- | ---: | ---: | ---: | ---: | -| input_boolean | pass | 18 | 0 | 0 | 0 | -| input_button | pass | 15 | 0 | 0 | 0 | -| input_datetime | pass | 28 | 0 | 0 | 0 | -| input_number | pass | 24 | 0 | 0 | 0 | -| input_select | pass | 26 | 0 | 0 | 0 | -| input_text | pass | 23 | 0 | 0 | 0 | -| counter | pass | 751 | 0 | 0 | 0 | -| timer | pass | 877 | 0 | 0 | 0 | -| schedule | pass | 387 | 0 | 0 | 0 | -| zone | pass | 32 | 0 | 0 | 0 | -| tag | pass | 12 | 0 | 0 | 0 | -| group | pass | 392 | 0 | 0 | 0 | -| person | pass | 34 | 0 | 0 | 0 | -| scene | pass | 41 | 0 | 0 | 0 | -| todo | pass | 281 | 0 | 0 | 0 | -| automation | pass | 117 | 0 | 0 | 0 | -| script | pass | 64 | 0 | 0 | 0 | -| alert | pass | 18 | 0 | 0 | 0 | -| template | pass | 2470 | 0 | 0 | 0 | -| plant | pass | 11 | 0 | 0 | 0 | -| proximity | issues | 27 | 1 | 0 | 0 | -| min_max | pass | 20 | 0 | 0 | 0 | -| statistics | pass | 56 | 0 | 0 | 0 | -| utility_meter | issues | 94 | 1 | 0 | 0 | -| derivative | pass | 76 | 0 | 0 | 0 | -| integration | pass | 61 | 0 | 0 | 0 | -| generic_thermostat | pass | 114 | 0 | 0 | 0 | -| generic_hygrostat | pass | 76 | 0 | 0 | 0 | -| history_stats | pass | 55 | 0 | 0 | 0 | -| threshold | pass | 114 | 0 | 0 | 0 | -| filter | pass | 32 | 0 | 0 | 0 | -| mqtt_statestream | pass | 17 | 0 | 0 | 0 | -| recorder | pass | 932 | 0 | 0 | 17 | -| rest | pass | 128 | 0 | 0 | 0 | -| logbook | pass | 106 | 0 | 0 | 0 | -| command_line | pass | 78 | 0 | 0 | 0 | -| trend | pass | 39 | 0 | 0 | 0 | diff --git a/sandbox/OVERVIEW.md b/sandbox/OVERVIEW.md index f822662f7bc0..b5ed4bcba8ab 100644 --- a/sandbox/OVERVIEW.md +++ b/sandbox/OVERVIEW.md @@ -515,18 +515,16 @@ only thing that differs is how the channel pair is materialised. The compat lane runner [`run_compat.py`](run_compat.py) drives either plugin against a list of -integration test directories, parses pytest's summary line, and writes -[`COMPAT.csv`](COMPAT.csv) + [`COMPAT_LATEST.md`](COMPAT_LATEST.md) -(the curated baseline lives in [`COMPAT.md`](COMPAT.md)). Per-failure -output lands in `${SANDBOX_ERRORS_DIR:-/tmp/sandbox_errors}`. +integration test directories, parses pytest's summary line, and writes a +machine CSV plus a `COMPAT_LATEST.md` per-run report (both git-ignored). The +curated baseline lives in [`COMPAT.md`](COMPAT.md) and the curated residual +backlog in [`BACKLOG.md`](BACKLOG.md). Per-failure output lands in +`${SANDBOX_ERRORS_DIR:-/tmp/sandbox_errors}`. -[`run_compat_full.py`](run_compat_full.py) is the wider cross-sweep -runner Phase 16 landed: asyncio + JUnit XML + outer concurrency, -exercises every classifier-routable integration in the tree and writes -[`COMPAT_FULL.md`](COMPAT_FULL.md) + [`COMPAT_FULL.csv`](COMPAT_FULL.csv). -[`categorize_failures.py`](categorize_failures.py) buckets the -JUnit failures into [`BACKLOG.md`](BACKLOG.md) + -[`BACKLOG_FAILURES.json`](BACKLOG_FAILURES.json). +The one-shot full cross-sweep tooling that produced the original backlog +(`run_compat_full.py` + `categorize_failures.py` + `generate_backlog.py`) was +removed once the measurement was done; recover it from git history if a fresh +tree-wide sweep is ever needed. **Baseline numbers (Phase 17):** 35/37 integrations pass on the v1-baseline 37-integration set (99.97 % test-level); 711/807 diff --git a/sandbox/categorize_failures.py b/sandbox/categorize_failures.py deleted file mode 100644 index 6d230ead8618..000000000000 --- a/sandbox/categorize_failures.py +++ /dev/null @@ -1,386 +0,0 @@ -"""Bucket sandbox compat-sweep failures into named categories. - -Pairs with ``run_compat_full.py``. The runner dumps a per-test traceback -into ``$SANDBOX_ERRORS_DIR//__.txt``; this -script walks that tree, runs each file through an ordered list of -signature matchers, and writes: - -- ``BACKLOG_FAILURES.json`` — machine-readable rollup - (``{bucket → {integration → [test_node, …]}}``) for downstream tooling. -- A short stdout summary suitable for `tee`-ing onto an issue. - -Categories are -ordered most-specific → most-generic; the first match wins, so unknown -genuinely means "no rule fired". A "≥95% of failures buckets out of -``unknown``" smoke gate lives at the bottom of the run; if that fails, -add more rules and re-run. - -Usage:: - - cd sandbox - # After run_compat_full.py finishes: - uv run python categorize_failures.py - - # Or against a custom errors dir: - uv run python categorize_failures.py --errors-dir /tmp/sandbox_errors - -The rules are intentionally regex-based on the per-test traceback excerpt -(JUnit's ```` text + the ``--tb=short`` body). They're meant to -be cheap to extend — every rule is "if regex matches, bucket name" — so -adding a new category as failure shapes appear is one tuple entry. -""" - -# Standalone CLI driver. Same Ruff carve-out as ``run_compat_full.py``. -# ruff: noqa: INP001, T201, S108 - -import argparse -from collections import defaultdict -from dataclasses import dataclass -import json -import os -from pathlib import Path -import re - -_HERE = Path(__file__).resolve().parent -DEFAULT_ERRORS_DIR = Path( - os.environ.get("SANDBOX_ERRORS_DIR", "/tmp/sandbox_errors") -) -DEFAULT_BACKLOG_JSON = _HERE / "BACKLOG_FAILURES.json" - - -@dataclass(frozen=True) -class Rule: - """One signature rule: name + compiled regex pattern.""" - - bucket: str - pattern: re.Pattern[str] - - -# Order matters — first hit wins. Specific signatures (test-only autotag -# noise, named protocol gaps) sit above generic catch-alls. Patterns are -# compiled with ``re.IGNORECASE | re.DOTALL`` for resilience. -def _compile(pattern: str) -> re.Pattern[str]: - return re.compile(pattern, re.IGNORECASE | re.DOTALL) - - -RULES: tuple[Rule, ...] = ( - # ---- test-only ------------------------------------------------------- - # The autotag patch mutates entry.data; tests that assert - # ``entry.data == `` or snapshot it see the new - # ``__sandbox_group`` key. Same root cause whether it surfaces in an - # `assert`, a `mappingproxy(...)` repr (pytest truncates the diff into - # the JUnit `message` attribute and the visible slice often omits the - # tag itself), or a Syrupy snapshot diff. - Rule("test-only", _compile(r"__sandbox_group")), - Rule("test-only", _compile(r"mappingproxy\(.+?'built-in'")), - Rule("test-only", _compile(r"mappingproxy\(.+?'custom'")), - # The autotag is the only thing that injects the literal value - # ``'built-in'`` into a config-entry data assertion's diff. - Rule("test-only", _compile(r"'built-in'.*?(==|expected|snapshot)")), - # A `+ '__sandbox_group'` in a Syrupy diff is unambiguously the - # autotag. - Rule("test-only", _compile(r"\+\s+'__sandbox_group'")), - # Catch-all for the `assert .data == ` shape. - # `entry.data` is always a `MappingProxyType`; the only time the - # assertion fails is when an extra key was added — and the only - # extra-key source the compat lane adds is the autotag. - Rule( - "test-only", - _compile(r"assert\s+mappingproxy\(.+?\)\s*==\s*[\{\[]"), - ), - # Diagnostic snapshots that include the entry's - # full ``as_dict()`` now see a top-level ``+ 'sandbox': ''`` - # line in the diff. Same root cause as ``__sandbox_group`` — the - # autotag synthesises the field for compat coverage, the snapshot - # was captured pre-autotag. Fix: refresh the snapshot in the - # integration's test tree (out of sandbox scope). - Rule("test-only", _compile(r"\+\s+'sandbox'\s*:\s*'(?:built-in|custom|main)'")), - # Snapshot drift on ``'created_at'`` / ``'modified_at'``: tests that - # didn't pin the wall clock with freezegun, so the snapshot's - # baked-in timestamp doesn't match the new run's. Environmental, - # not a sandbox bridge issue; fix is for the integration's test to use - # ``@pytest.mark.freeze_time``. Matches both Syrupy diff form - # (``+ 'created_at': ...``) and pytest dict-diff form - # (``'created_at': '', 'data': ...``). - Rule( - "test-only", - _compile(r"'(?:created_at|modified_at)'\s*:\s*'?\d{4}-\d{2}-\d{2}T"), - ), - - # ---- proxy-missing --------------------------------------------------- - Rule("proxy-missing", _compile(r"KeyError.*?_DOMAIN_PROXIES")), - Rule("proxy-missing", _compile(r"no proxy (class )?for domain")), - Rule("proxy-missing", _compile(r"unknown sandbox entity domain")), - # Bridge gap: integration registers an entity in the sandbox but it - # never shows up in main's entity registry. `proxy-missing` reuses - # this bucket since the fix story is the same — make the bridge - # surface the entity to main. - Rule( - "proxy-missing", - _compile(r"async_is_registered.+?\n.*?AssertionError.*?False"), - ), - # Same root cause as the registry case: an integration that runs in - # the sandbox registers an entity, but main's state machine never - # sees it — `hass.states.get(...)` returns None. - Rule( - "proxy-missing", - _compile( - r"hass\.states\.get\(.+?\)\.state.+?\n.*?AttributeError.+?NoneType" - ), - ), - - # ---- protocol gaps --------------------------------------------------- - # data_schema serialisation drift. The bridge does voluptuous-serialize - # round-tripping; a regression would surface as None / wrong type. - Rule( - "data-schema-stripped", - _compile(r"data_schema.*?(is None|None type|stripped|missing)"), - ), - Rule( - "data-schema-stripped", - _compile(r"FlowResult.*?data_schema.*?None"), - ), - Rule( - "service-schema-missing", - _compile(r"hass\.services\.async_register.*?schema"), - ), - Rule( - "service-schema-missing", - _compile(r"ServiceValidationError.*?bridge"), - ), - # Be conservative — `unique_id=None` shows up incidentally in many - # `ConfigEntry` reprs, so only match assertions where the line in - # question is *about* unique_id, not where it merely mentions one. - Rule( - "unique-id-not-propagated", - _compile(r"assert\s+[^=]+unique_id[^=]*?(==|is)"), - ), - Rule( - "unique-id-not-propagated", - _compile(r"AssertionError.+?unique_id\s+(does not match|mismatch)"), - ), - - # ---- restore_state --------------------------------------------------- - Rule( - "restore-state-not-applied", - _compile(r"async_get_last_state.*?(None|missing|not restored)"), - ), - Rule( - "restore-state-not-applied", - _compile(r"restore.*?state.*?did not survive"), - ), - - # ---- context propagation -------------------------------------------- - Rule("context-not-propagated", _compile(r"context\.user_id\b")), - Rule("context-not-propagated", _compile(r"context\.parent_id\b")), - Rule("context-not-propagated", _compile(r"Context\(.*?user_id=.+?\).*?==")), - - # ---- channel / re-entrancy ------------------------------------------ - Rule( - "re-entrant-channel", - _compile(r"sandbox.*?channel\.call.*?(deadlock|timeout|never returned)"), - ), - Rule( - "re-entrant-channel", - _compile(r"TimeoutError.*?sandbox.*?call"), - ), - - # ---- store ---------------------------------------------------------- - Rule("store-key-rejected", _compile(r"_require_key.*?invalid")), - Rule("store-key-rejected", _compile(r"sandbox.*?store.*?(rejected|invalid key)")), - - # ---- flow / async --------------------------------------------------- - Rule( - "flow-step-not-async", - _compile(r"RuntimeWarning.*?coroutine.*?async_step_"), - ), - Rule("flow-step-not-async", _compile(r"async_step_.*?never awaited")), - - # ---- platform deny-list --------------------------------------------- - Rule( - "integration-uses-deny-listed-platform", - _compile(r"sandbox.*?incompatible platform|deny-list.*?platform"), - ), - - # ---- non-idempotent service handlers -------------------------------- - Rule( - "non-idempotent-service-handler", - _compile(r"_resolve_attachments|service handler.*?pre-?dispatch"), - ), - - # ---- dependencies-not-shared ---------------------------------------- - Rule( - "dependencies-not-shared", - _compile(r"state for .+? not found.*?sandbox"), - ), - Rule( - "dependencies-not-shared", - _compile(r"main-side .+? not available to sandbox"), - ), - # An integration test mocks a dependency (HTTP client, websocket, …) - # in the main process, but the sandbox runs in a separate process and - # never sees the mock — setup fails with SETUP_ERROR / SETUP_RETRY. - # Same root cause as the explicit "share state" patterns above. - Rule( - "dependencies-not-shared", - _compile( - r"ConfigEntryState\.SETUP_ERROR.+?is\s+" - r" str: - """Return the contents of a per-test error dump (utf-8, lossy).""" - try: - return path.read_text(encoding="utf-8", errors="replace") - except OSError: - return "" - - -def categorise(traceback: str) -> str: - """Match ``traceback`` against the rule list and return the bucket name.""" - for rule in RULES: - if rule.pattern.search(traceback): - return rule.bucket - return "unknown" - - -def walk_errors(errors_dir: Path) -> dict[str, dict[str, list[tuple[str, str]]]]: - """Walk per-integration error dumps; return ``{bucket → {domain → [(node, excerpt)]}}``.""" - out: dict[str, dict[str, list[tuple[str, str]]]] = defaultdict( - lambda: defaultdict(list) - ) - if not errors_dir.is_dir(): - return out - for integration_dir in sorted(errors_dir.iterdir()): - # The per-integration log lives at ``/.log`` - # alongside its sibling directory. JUnit XML lives in ``_junit/``; - # skip both — they aren't per-test failure dumps. - if not integration_dir.is_dir(): - continue - if integration_dir.name.startswith("_"): - continue - for traceback_file in sorted(integration_dir.rglob("*.txt")): - text = _load_traceback(traceback_file) - if not text: - continue - node_id = _extract_node_id(text, traceback_file) - excerpt = _short_excerpt(text) - bucket = categorise(text) - out[bucket][integration_dir.name].append((node_id, excerpt)) - return out - - -_NODE_LINE = re.compile(r"^node_id:\s*(.+)$", re.MULTILINE) - - -def _extract_node_id(text: str, path: Path) -> str: - """Pull the ``node_id`` header from a dump; fall back to the file stem.""" - if (match := _NODE_LINE.search(text)) is not None: - return match.group(1).strip() - return path.stem - - -def _short_excerpt(text: str, *, lines: int = 6) -> str: - """Return the first ``lines`` lines after the header so backlog entries stay terse.""" - body = text.split("\n", maxsplit=4)[-1] if "\n" in text else text - return "\n".join(body.splitlines()[:lines]) - - -def render_summary( - buckets: dict[str, dict[str, list[tuple[str, str]]]], -) -> list[str]: - """Render a stdout-friendly summary block.""" - total_failures = sum( - len(node_list) - for integrations in buckets.values() - for node_list in integrations.values() - ) - lines = [ - "Failure categorisation", - "-" * 32, - f"Total failures bucketed: {total_failures}", - "", - "Per-bucket counts (most → least):", - ] - per_bucket = sorted( - ( - ( - bucket, - sum(len(nodes) for nodes in integrations.values()), - len(integrations), - ) - for bucket, integrations in buckets.items() - ), - key=lambda r: (-r[1], r[0]), - ) - for bucket, count, integration_count in per_bucket: - lines.append( - f" {bucket:<40} {count:>5} failures across {integration_count:>3} integrations" - ) - unknown_count = next( - (count for bucket, count, _ in per_bucket if bucket == "unknown"), - 0, - ) - if total_failures: - ratio = (total_failures - unknown_count) / total_failures - lines.append("") - lines.append( - f"Categorisation hit rate: {ratio:.1%}" - f" (target ≥95%; unknown bucket = {unknown_count})" - ) - return lines - - -def serialise_buckets( - buckets: dict[str, dict[str, list[tuple[str, str]]]], -) -> dict[str, dict[str, list[dict[str, str]]]]: - """Convert tuples to plain dicts for JSON output.""" - return { - bucket: { - integration: [ - {"node_id": node, "excerpt": excerpt} - for node, excerpt in items - ] - for integration, items in integrations.items() - } - for bucket, integrations in buckets.items() - } - - -def main(argv: list[str] | None = None) -> int: - """CLI entry point.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "--errors-dir", type=Path, default=DEFAULT_ERRORS_DIR, - help=("Per-test error dump root " - "(default: $SANDBOX_ERRORS_DIR or /tmp/sandbox_errors)."), - ) - parser.add_argument( - "--out", type=Path, default=DEFAULT_BACKLOG_JSON, - help="Where to write the bucket → integration → nodes JSON rollup.", - ) - args = parser.parse_args(argv) - - buckets = walk_errors(args.errors_dir) - args.out.write_text(json.dumps(serialise_buckets(buckets), indent=2, sort_keys=True)) - print("\n".join(render_summary(buckets))) - print(f"\nWrote {args.out}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/sandbox/generate_backlog.py b/sandbox/generate_backlog.py deleted file mode 100644 index 06556d6ad7f1..000000000000 --- a/sandbox/generate_backlog.py +++ /dev/null @@ -1,264 +0,0 @@ -"""Render a draft ``BACKLOG.md`` from the categorizer's JSON rollup. - -Pairs with ``categorize_failures.py``. Reads ``BACKLOG_FAILURES.json`` -(``{bucket → {integration → [{node_id, excerpt}]}}``) and writes the -section-per-bucket Markdown backlog. - -The "Proposed fix" stub is intentionally left as a TODO marker per -section — that field needs human judgement (file paths, rough size, -complexity), and the sweep should produce the draft skeleton so the -backlog landing is reviewable without a 600-line Markdown blob in the -commit diff. - -Usage:: - - cd sandbox - uv run python generate_backlog.py # writes BACKLOG.md - uv run python generate_backlog.py --out FOO.md # custom output path -""" - -# ruff: noqa: INP001, T201 - -import argparse -import json -from pathlib import Path - -_HERE = Path(__file__).resolve().parent -DEFAULT_BACKLOG_JSON = _HERE / "BACKLOG_FAILURES.json" -DEFAULT_BACKLOG_MD = _HERE / "BACKLOG.md" - -# Per-bucket prose. Keeps the generator output reviewable by anchoring -# each section to a one-liner from the plan; the proposed-fix block is a -# TODO marker the human fills in after looking at the actual rows. -BUCKET_BLURB: dict[str, str] = { - "test-only": ( - "Test scaffolding noise — the compat-lane autotag patch mutates" - " `entry.data` to add `__sandbox_group`, and some helper-integration" - " tests or Syrupy snapshots assert the entry data is empty / matches" - " a snapshot that pre-dates the tag. Not a sandbox bridge bug." - ), - "proxy-missing": ( - "The bridge ships proxies for all 32 entity domains. Any hit here" - " means the integration registers an entity in a domain the bridge" - " doesn't recognise — either a new domain landed in HA Core or the" - " dispatch map missed one." - ), - "data-schema-stripped": ( - "The bridge does a `voluptuous_serialize`-based round-trip so flow" - " schemas survive the bridge. Any hit here means a `vol.Schema`" - " shape the serializer can't represent (custom validators, untagged" - " `vol.Any`, etc.)." - ), - "service-schema-missing": ( - "Mirror of `data-schema-stripped` for `hass.services.async_register`." - " The same bridge serialises service schemas; gaps here are" - " edge cases (custom validators, schema-on-the-fly registrations)." - ), - "unique-id-not-propagated": ( - "The bridge marshals `flow.context['unique_id']` into the proxy. Hits" - " here are flow shapes that set unique_id outside the standard" - " `async_set_unique_id` path (e.g. discovery flows that abort" - " inside `async_step_user`)." - ), - "restore-state-not-applied": ( - "The runtime warm-loads `RestoreStateData` from" - " `/.storage/sandbox//core.restore_state`. Hits" - " here mean an integration's restore-state assertion fires before" - " the warm-load completes, or expects state the previous run never" - " persisted." - ), - "context-not-propagated": ( - "The event mirror forwards events with the sandbox-side `context_id`" - " but does NOT honour it for main's user/origin resolution. Integration" - " tests" - " that assert on `context.user_id` or `context.parent_id` fail here." - " Carrying a richer Context shape is post-v2 work." - ), - "re-entrant-channel": ( - "The channel dispatcher is concurrent so a handler can" - " issue `channel.call(...)`. Hits here mean a re-entrant shape we" - " haven't seen — the semaphore cap might be too aggressive, or a" - " handler chain exceeds the in-flight ceiling." - ), - "store-key-rejected": ( - "The `RemoteStore` validator rejects keys containing `/`, `\\`," - " NUL, `.`, or `..`. An integration using one of those characters" - " trips this. Probably wants a translation step on the sandbox side" - " rather than relaxing the validator." - ), - "flow-step-not-async": ( - "The flow proxy expects every step on the integration's" - " `ConfigFlow` to be an async coroutine. A sync step would emit a" - " `coroutine 'async_step_*' was never awaited` warning that this" - " bucket catches." - ), - "integration-uses-deny-listed-platform": ( - "Classifier let an integration through that ships a" - " `SANDBOX_INCOMPATIBLE_PLATFORMS` platform. Either the classifier" - " has a bug or the platform-set should grow." - ), - "non-idempotent-service-handler": ( - "The `ai_task`/`image` shape — the service handler" - " does material work before calling the entity method. Today's" - " resolution is `ALWAYS_MAIN`; integrations that hit this should" - " join that set or sandbox-handler interception needs designing" - " (post-v2 work)." - ), - "dependencies-not-shared": ( - "Integration depends on a main-side service or piece of state that" - " opt-in sharing doesn't expose. Tracked alongside the unfinished" - " state-sharing consumer (see" - " `sandbox/docs/design-share-states.md`)." - ), - "unknown": ( - "Catch-all bucket of last resort. Every entry here means the" - " categorizer's regex set didn't fire — add a more-specific rule" - " in `categorize_failures.py::RULES`." - ), -} - - -def _bucket_priority(name: str) -> int: - """Stable sort key — most-actionable buckets first when counts tie.""" - order = [ - "proxy-missing", - "context-not-propagated", - "data-schema-stripped", - "service-schema-missing", - "unique-id-not-propagated", - "restore-state-not-applied", - "re-entrant-channel", - "non-idempotent-service-handler", - "integration-uses-deny-listed-platform", - "store-key-rejected", - "flow-step-not-async", - "dependencies-not-shared", - "test-only", - "unknown", - ] - try: - return order.index(name) - except ValueError: - return len(order) - - -def render_backlog(payload: dict[str, dict[str, list[dict[str, str]]]]) -> str: - """Render the BACKLOG.md draft.""" - lines: list[str] = [ - "# Sandbox — compat sweep backlog", - "", - "**Auto-generated draft** from `BACKLOG_FAILURES.json`." - " `generate_backlog.py` writes the skeleton; the *Proposed fix* and" - " *Estimated size* lines need human curation before this lands as the" - " final deliverable.", - "", - "Sections are ordered by integration count (largest first). Within" - " each section the affected-integration roll-up is capped at 10 — the" - " rest live in `BACKLOG_FAILURES.json` if a reviewer wants the full" - " set.", - "", - ] - - bucket_rows = [] - for bucket, integrations in payload.items(): - if not integrations: - continue - failure_count = sum(len(nodes) for nodes in integrations.values()) - bucket_rows.append((bucket, failure_count, len(integrations))) - - bucket_rows.sort(key=lambda row: (-row[2], _bucket_priority(row[0]), row[0])) - - lines.append("## Bucket overview") - lines.append("") - lines.append("| Bucket | Failures | Integrations |") - lines.append("| --- | ---: | ---: |") - for bucket, failures, integrations_count in bucket_rows: - lines.append(f"| `{bucket}` | {failures} | {integrations_count} |") - lines.append("") - - for bucket, failures, integrations_count in bucket_rows: - lines.append(f"## {bucket}") - lines.append("") - lines.append(BUCKET_BLURB.get(bucket, "_No description on file._")) - lines.append("") - lines.append(f"- **Failures:** {failures}") - lines.append(f"- **Integrations affected:** {integrations_count}") - lines.append("") - integrations = payload[bucket] - sorted_ints = sorted( - integrations.items(), key=lambda pair: (-len(pair[1]), pair[0]) - ) - top = sorted_ints[:10] - if top: - lines.append("### Top integrations") - lines.append("") - lines.append("| Integration | Failure count |") - lines.append("| --- | ---: |") - for integration, nodes in top: - lines.append(f"| `{integration}` | {len(nodes)} |") - if len(sorted_ints) > 10: - lines.append( - f"| _… +{len(sorted_ints) - 10} more in" - " `BACKLOG_FAILURES.json`_ | |" - ) - lines.append("") - sample_int, sample_nodes = top[0] - sample = sample_nodes[0] - lines.append("### Representative failure") - lines.append("") - lines.append(f"`{sample_int}::{sample['node_id'].split('::')[-1]}`") - lines.append("") - lines.append("```") - lines.append((sample["excerpt"] or "").strip()) - lines.append("```") - lines.append("") - lines.append("### Proposed fix") - lines.append("") - lines.append("_TODO: write before landing._") - lines.append("") - lines.append("### Estimated size") - lines.append("") - lines.append("_TODO: file/lines/complexity rough estimate._") - lines.append("") - - lines.append("## `ALWAYS_MAIN` additions recommended") - lines.append("") - lines.append( - "_TODO: list integrations whose dominant bucket is" - " `non-idempotent-service-handler` or `dependencies-not-shared` and" - " whose fix story is \"keep them on main\"._" - ) - lines.append("") - lines.append("## Classifier rule changes recommended") - lines.append("") - lines.append( - "_TODO: list classifier deltas suggested by the sweep — e.g.," - " a new platform name to add to `SANDBOX_INCOMPATIBLE_PLATFORMS`," - " or an integration-type rule the classifier should tighten._" - ) - lines.append("") - - return "\n".join(lines) - - -def main(argv: list[str] | None = None) -> int: - """CLI entry point.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "--input", type=Path, default=DEFAULT_BACKLOG_JSON, - help="Input JSON produced by `categorize_failures.py`.", - ) - parser.add_argument( - "--out", type=Path, default=DEFAULT_BACKLOG_MD, - help="Where to write BACKLOG.md draft.", - ) - args = parser.parse_args(argv) - - payload = json.loads(args.input.read_text()) - args.out.write_text(render_backlog(payload)) - print(f"Wrote {args.out}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/sandbox/run_compat_full.py b/sandbox/run_compat_full.py deleted file mode 100644 index 01d648130634..000000000000 --- a/sandbox/run_compat_full.py +++ /dev/null @@ -1,572 +0,0 @@ -"""Run the full sandbox compat sweep across every routable integration. - -This sweep broadens the 37-integration baseline to cover -**every config-entry-based integration** that the sandbox classifier would -route to a sandbox. The goal of this run is not to fix anything — it is -to *measure* and *categorize* so the resulting backlog is grounded in real -data. - -Discovery rules (see ``discover_integrations``): - -- Walk ``homeassistant/components/``. -- Skip ``integration_type`` ``virtual`` or ``system`` (no runtime to - sandbox). -- Skip the ``ALWAYS_MAIN`` set (decided by the classifier already). -- Skip integrations whose ``manifest.json`` has ``config_flow: false`` - (YAML-only integrations are out of scope). -- Skip integrations whose ``tests/components//`` directory has no - ``test_*.py`` files (no signal either way). -- Skip integrations whose source ships a platform file in - ``SANDBOX_INCOMPATIBLE_PLATFORMS`` (classifier would route them to main, - so the sweep wouldn't be measuring the bridge). - -For every surviving integration the runner spawns a pytest subprocess with -``-p hass_client.testing.pytest_plugin`` (the in-process plugin used by -the baseline), captures the JUnit XML output, and dumps the full -text output + per-test failure tracebacks into the per-integration error -directory so the categorizer (``categorize_failures.py``) can bucket -every failure. - -Outer concurrency comes from asyncio + a semaphore: by default four -integrations run in parallel. Per-integration parallelism via -``pytest-xdist`` ``-n auto`` is opt-in (``--xdist``) because the -``xdist`` worker spin-up cost dominates for small integrations and the -outer concurrency already keeps the box busy. - -Usage:: - - cd sandbox - # Full sweep (every classifier-routable integration) - uv run python run_compat_full.py - - # Restrict to a list (handy for iterating on the categorizer) - uv run python run_compat_full.py input_boolean light switch - - # Validation: re-run the 37-integration baseline - uv run python run_compat_full.py --baseline-37 - -The two committed deliverables are written at the end: - -- ``COMPAT_FULL.md`` — per-integration results table + sweep header. -- ``COMPAT_FULL.csv`` — machine-readable version of the same table. -""" - -# Standalone CLI driver, not a package module — Ruff's package-naming / -# print-for-status / /tmp-fallback rules don't apply here, same as -# ``run_compat.py``. -# ruff: noqa: INP001, T201, S108, PERF401 - -import argparse -import asyncio -import csv as csv_module -from dataclasses import dataclass, field -from datetime import datetime -import json -import os -from pathlib import Path -import shutil -import sys -import time -from xml.etree.ElementTree import ParseError, parse as et_parse - -# Paths are relative to this script (sandbox/run_compat_full.py). -_HERE = Path(__file__).resolve().parent -CORE_ROOT = _HERE.parent -COMPONENTS_DIR = CORE_ROOT / "homeassistant" / "components" -CORE_TESTS_DIR = CORE_ROOT / "tests" / "components" -DEFAULT_CSV = _HERE / "COMPAT_FULL.csv" -DEFAULT_REPORT_MD = _HERE / "COMPAT_FULL.md" -ERRORS_DIR = Path(os.environ.get("SANDBOX_ERRORS_DIR", "/tmp/sandbox_errors")) - -# Mirrors homeassistant/components/sandbox/const.py. Duplicated here -# because importing the live module would require booting the core test -# env from this stand-alone driver. The unit tests guard against -# behavioural drift; the per-integration sweep is allowed to lag by an -# entry — if either set changes, update both copies and re-run. -ALWAYS_MAIN: frozenset[str] = frozenset( - {"script", "automation", "scene", "cloud", "ai_task", "image"} -) -SANDBOX_INCOMPATIBLE_PLATFORMS: frozenset[str] = frozenset( - {"stt", "tts", "conversation", "assist_satellite", "wake_word", "camera"} -) - -# The 37-integration baseline list, for the ``--baseline-37`` shortcut. The -# list lives both here and in ``COMPAT.md``; keep them in sync. -BASELINE_37: tuple[str, ...] = ( - "input_boolean", "input_button", "input_datetime", "input_number", - "input_select", "input_text", "counter", "timer", "schedule", "zone", - "tag", "group", "person", "scene", "todo", "automation", "script", - "alert", "template", "plant", "proximity", "min_max", "statistics", - "utility_meter", "derivative", "integration", "generic_thermostat", - "generic_hygrostat", "history_stats", "threshold", "filter", - "mqtt_statestream", "recorder", "rest", "logbook", "command_line", - "trend", -) - - -@dataclass -class Result: - """Single-integration pytest-run summary.""" - - integration: str - passed: int = 0 - failed: int = 0 - errors: int = 0 - skipped: int = 0 - duration: float = 0.0 - status: str = "no_tests" - failure_signatures: list[str] = field(default_factory=list) - dominant_bucket: str | None = None - - @property - def total(self) -> int: - """Total tests collected and run for this integration.""" - return self.passed + self.failed + self.errors + self.skipped - - -def _has_test_files(test_dir: Path) -> bool: - """Return True if ``test_dir`` ships at least one ``test_*.py``.""" - if not test_dir.is_dir(): - return False - return any( - p.name.startswith("test_") and p.suffix == ".py" - for p in test_dir.iterdir() - ) - - -def _ships_incompatible_platform(component_dir: Path) -> bool: - """Return True if ``component_dir`` has a platform in the deny-list.""" - return any( - (component_dir / f"{plat}.py").exists() - for plat in SANDBOX_INCOMPATIBLE_PLATFORMS - ) - - -def discover_integrations() -> tuple[list[str], dict[str, int]]: - """Return the (sorted) eligible integration names and filter stats.""" - stats: dict[str, int] = { - "no_manifest": 0, - "virtual_or_system": 0, - "always_main": 0, - "incompatible_platform": 0, - "no_config_flow": 0, - "no_tests": 0, - "eligible": 0, - } - eligible: list[str] = [] - for component in sorted(COMPONENTS_DIR.iterdir()): - if not component.is_dir(): - continue - manifest_path = component / "manifest.json" - if not manifest_path.exists(): - stats["no_manifest"] += 1 - continue - try: - manifest = json.loads(manifest_path.read_text()) - except (json.JSONDecodeError, OSError): - stats["no_manifest"] += 1 - continue - itype = manifest.get("integration_type", "hub") - if itype in ("virtual", "system"): - stats["virtual_or_system"] += 1 - continue - if component.name in ALWAYS_MAIN: - stats["always_main"] += 1 - continue - if _ships_incompatible_platform(component): - stats["incompatible_platform"] += 1 - continue - if not manifest.get("config_flow", False): - stats["no_config_flow"] += 1 - continue - if not _has_test_files(CORE_TESTS_DIR / component.name): - stats["no_tests"] += 1 - continue - eligible.append(component.name) - stats["eligible"] += 1 - return eligible, stats - - -def _parse_junit(xml_path: Path, *, errors_dir: Path, integration: str) -> Result: - """Parse a JUnit XML report into a ``Result`` + per-test traceback dump.""" - result = Result(integration=integration) - try: - # XML is produced by pytest's own --junit-xml in the same - # subprocess we control — not untrusted input. S314 carve-out. - tree = et_parse(xml_path) # noqa: S314 - except (ParseError, FileNotFoundError): - result.status = "no_tests" - return result - root = tree.getroot() - # JUnit format: - suite = root[0] if len(root) > 0 else None - if suite is None or suite.tag != "testsuite": - return result - - result.passed = int(suite.attrib.get("tests", 0)) - result.failed = int(suite.attrib.get("failures", 0)) - result.errors = int(suite.attrib.get("errors", 0)) - result.skipped = int(suite.attrib.get("skipped", 0)) - result.duration = float(suite.attrib.get("time", 0.0)) - # JUnit's ``tests`` counts every test including failures/errors/skipped. - # Re-derive ``passed`` so the dataclass meaning matches the baseline CSV. - result.passed = max(0, result.passed - result.failed - result.errors - result.skipped) - - integration_errors_dir = errors_dir / integration - if integration_errors_dir.exists(): - shutil.rmtree(integration_errors_dir) - - for testcase in suite: - if testcase.tag != "testcase": - continue - failure_node = None - for child in testcase: - if child.tag in ("failure", "error"): - failure_node = child - break - if failure_node is None: - continue - integration_errors_dir.mkdir(parents=True, exist_ok=True) - classname = testcase.attrib.get("classname", "unknown").replace(".", "/") - # Best-effort filename: ``::`` with characters that - # are awkward in a filename replaced by underscores. The dot→slash - # swap on classname keeps the path layout close to the on-disk test - # tree so reviewers can find the originating file quickly. - name = testcase.attrib.get("name", "unknown") - safe_name = "".join( - c if c.isalnum() or c in "_.-[]" else "_" for c in name - ) - node_path = integration_errors_dir / f"{classname}__{safe_name}.txt" - node_path.parent.mkdir(parents=True, exist_ok=True) - body = [ - f"node_id: {testcase.attrib.get('classname', '')}::{name}", - f"kind: {failure_node.tag}", - f"message: {failure_node.attrib.get('message', '')}", - "", - failure_node.text or "", - ] - node_path.write_text("\n".join(body)) - # Cheap dominant-bucket hint: store the failure message so the - # caller (and categorize_failures.py) can guess without re-reading. - result.failure_signatures.append(failure_node.attrib.get("message", "")) - - if result.total == 0: - result.status = "no_tests" - elif result.failed == 0 and result.errors == 0: - result.status = "pass" - else: - result.status = "issues" - return result - - -async def run_one( - integration: str, - *, - semaphore: asyncio.Semaphore, - timeout: float, - use_xdist: bool, - errors_dir: Path, - junit_dir: Path, -) -> Result: - """Spawn pytest for one integration, parse JUnit, return a ``Result``.""" - test_dir = CORE_TESTS_DIR / integration - junit_path = junit_dir / f"{integration}.xml" - log_path = errors_dir / f"{integration}.log" - cmd = [ - "uv", "run", "python", "-m", "pytest", - "-p", "hass_client.testing.pytest_plugin", - str(test_dir), - "--tb=short", "-q", "--no-header", "--no-cov", - f"--junit-xml={junit_path}", - ] - if use_xdist: - cmd.extend(["-n", "auto"]) - - async with semaphore: - start = time.monotonic() - try: - proc = await asyncio.create_subprocess_exec( - *cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.STDOUT, - cwd=CORE_ROOT, - ) - try: - stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout) - except TimeoutError: - proc.kill() - await proc.wait() - duration = time.monotonic() - start - errors_dir.mkdir(parents=True, exist_ok=True) - log_path.write_text( - f"timed out after {timeout:.0f}s\n" - ) - return Result( - integration=integration, status="timeout", duration=duration - ) - except (FileNotFoundError, PermissionError) as exc: - errors_dir.mkdir(parents=True, exist_ok=True) - log_path.write_text(f"failed to spawn pytest: {exc}\n") - return Result(integration=integration, status="error", duration=0.0) - - duration = time.monotonic() - start - output = stdout.decode("utf-8", errors="replace") if stdout else "" - - result = _parse_junit(junit_path, errors_dir=errors_dir, integration=integration) - result.duration = duration - - if result.status in ("issues", "timeout") or result.total == 0: - # Keep the full pytest output around for awkward-failure debugging - # (e.g. collection errors that the JUnit report can't represent). - errors_dir.mkdir(parents=True, exist_ok=True) - log_path.write_text(output) - return result - - -def write_csv(results: list[Result], path: Path) -> None: - """Persist per-integration results in the same column order as COMPAT.csv.""" - with path.open("w", newline="") as fh: - writer = csv_module.writer(fh) - writer.writerow([ - "integration", "status", "passed", "failed", "errors", "skipped", - "duration_s", "dominant_bucket", - ]) - for result in results: - writer.writerow([ - result.integration, result.status, result.passed, result.failed, - result.errors, result.skipped, f"{result.duration:.2f}", - result.dominant_bucket or "", - ]) - - -def write_report( - results: list[Result], - *, - path: Path, - discovery_stats: dict[str, int], - started_at: datetime, - finished_at: datetime, - concurrency: int, - use_xdist: bool, -) -> None: - """Render the COMPAT_FULL.md report. See module docstring for shape.""" - counts: dict[str, int] = {"pass": 0, "issues": 0, "timeout": 0, "no_tests": 0, "error": 0} - totals = {"passed": 0, "failed": 0, "errors": 0, "skipped": 0} - for result in results: - counts[result.status] = counts.get(result.status, 0) + 1 - totals["passed"] += result.passed - totals["failed"] += result.failed - totals["errors"] += result.errors - totals["skipped"] += result.skipped - - test_total = totals["passed"] + totals["failed"] + totals["errors"] - pass_rate = (totals["passed"] / test_total * 100.0) if test_total else 0.0 - integration_total = len(results) - integration_pass_rate = ( - counts.get("pass", 0) / integration_total * 100.0 - if integration_total else 0.0 - ) - - lines: list[str] = [ - "# Sandbox — full compat sweep", - "", - "**This file is auto-generated by `run_compat_full.py`** — re-run the", - "script to refresh it. Companion machine-readable CSV is `COMPAT_FULL.csv`,", - "categorised remediation backlog is `BACKLOG.md`.", - "", - "## Sweep parameters", - "", - f"- Started: `{started_at.isoformat(timespec='seconds')}`", - f"- Finished: `{finished_at.isoformat(timespec='seconds')}`", - f"- Wall time: **{(finished_at - started_at).total_seconds():.0f}s**", - f"- Outer concurrency: **{concurrency}**", - f"- Per-integration pytest-xdist: **{'on' if use_xdist else 'off'}**", - "- Plugin: `hass_client.testing.pytest_plugin` (in-process)", - "", - "## Discovery", - "", - "Walked `homeassistant/components/`, applied the discovery filters:", - "", - "| Filter | Skipped |", - "| --- | ---: |", - f"| No / invalid manifest | {discovery_stats.get('no_manifest', 0)} |", - f"| `integration_type` in (`virtual`, `system`) | {discovery_stats.get('virtual_or_system', 0)} |", - f"| Domain in `ALWAYS_MAIN` | {discovery_stats.get('always_main', 0)} |", - f"| Ships a platform in `SANDBOX_INCOMPATIBLE_PLATFORMS` | {discovery_stats.get('incompatible_platform', 0)} |", - f"| No `config_flow` in manifest | {discovery_stats.get('no_config_flow', 0)} |", - f"| No `test_*.py` files | {discovery_stats.get('no_tests', 0)} |", - f"| **Eligible (this sweep)** | **{discovery_stats.get('eligible', 0)}** |", - "", - "## Summary", - "", - f"- Integrations exercised: **{integration_total}**", - f"- Fully passing: **{counts.get('pass', 0)}** ({integration_pass_rate:.2f}%)", - f"- With failures: **{counts.get('issues', 0)}**", - f"- Timeouts: **{counts.get('timeout', 0)}**", - f"- Spawn errors: **{counts.get('error', 0)}**", - f"- No tests collected: **{counts.get('no_tests', 0)}**", - "", - f"- Tests passed: **{totals['passed']}**", - f"- Tests failed: **{totals['failed']}**", - f"- Test errors: **{totals['errors']}**", - f"- Tests skipped: **{totals['skipped']}**", - f"- **Test-level pass rate: {pass_rate:.2f}%**", - "", - "## Per-integration results", - "", - "| integration | status | passed | failed | errors | skipped | dur (s) | bucket |", - "| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |", - ] - for result in sorted(results, key=lambda r: r.integration): - lines.append( - f"| {result.integration} | {result.status} | {result.passed} | " - f"{result.failed} | {result.errors} | {result.skipped} | " - f"{result.duration:.1f} | {result.dominant_bucket or ''} |" - ) - lines.append("") - lines.append("Per-failure tracebacks are dumped under " - "`${SANDBOX_ERRORS_DIR:-/tmp/sandbox_errors}//`.") - path.write_text("\n".join(lines) + "\n") - - -async def _gather( - integrations: list[str], - *, - concurrency: int, - timeout: float, - use_xdist: bool, - errors_dir: Path, - junit_dir: Path, -) -> list[Result]: - """Run the per-integration pytest invocations with outer concurrency.""" - semaphore = asyncio.Semaphore(concurrency) - results: list[Result] = [] - completed = 0 - total = len(integrations) - start = time.monotonic() - - async def _run_and_log(integration: str) -> Result: - nonlocal completed - result = await run_one( - integration, - semaphore=semaphore, - timeout=timeout, - use_xdist=use_xdist, - errors_dir=errors_dir, - junit_dir=junit_dir, - ) - completed += 1 - elapsed = time.monotonic() - start - rate = completed / elapsed if elapsed > 0 else 0.0 - eta_min = (total - completed) / rate / 60 if rate else 0.0 - print( - f"[{completed}/{total}] {integration} -> {result.status} " - f"({result.passed}p/{result.failed}f/{result.errors}e/{result.skipped}s) " - f"in {result.duration:.1f}s | ETA: {eta_min:.0f}m", - flush=True, - ) - return result - - tasks = [asyncio.create_task(_run_and_log(d)) for d in integrations] - for task in asyncio.as_completed(tasks): - results.append(await task) - return results - - -def main(argv: list[str] | None = None) -> int: - """Drive the full sweep.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "integrations", - nargs="*", - help=( - "Specific integration names. Defaults to every classifier-routable" - " integration discovered in `homeassistant/components/`." - ), - ) - parser.add_argument( - "--baseline-37", action="store_true", - help="Restrict to the 37-integration baseline list.", - ) - parser.add_argument( - "--concurrency", type=int, default=4, - help="How many integrations to run in parallel (default: 4).", - ) - parser.add_argument( - "--timeout", type=float, default=300.0, - help="Per-integration timeout in seconds (default: 300).", - ) - parser.add_argument( - "--xdist", action="store_true", - help="Pass `-n auto` to per-integration pytest (default: off).", - ) - parser.add_argument( - "--csv", type=Path, default=DEFAULT_CSV, - help="Where to write the per-integration CSV.", - ) - parser.add_argument( - "--report", type=Path, default=DEFAULT_REPORT_MD, - help="Where to write the Markdown report.", - ) - parser.add_argument( - "--errors-dir", type=Path, default=ERRORS_DIR, - help="Per-test error dump root (default: $SANDBOX_ERRORS_DIR or /tmp/sandbox_errors).", - ) - parser.add_argument( - "--junit-dir", type=Path, default=ERRORS_DIR / "_junit", - help="Where to keep per-integration JUnit XML files.", - ) - args = parser.parse_args(argv) - - args.errors_dir.mkdir(parents=True, exist_ok=True) - args.junit_dir.mkdir(parents=True, exist_ok=True) - - if args.baseline_37 and args.integrations: - print("--baseline-37 conflicts with explicit integrations.", file=sys.stderr) - return 1 - if args.baseline_37: - integrations = list(BASELINE_37) - discovery_stats: dict[str, int] = {"eligible": len(integrations)} - elif args.integrations: - integrations = list(args.integrations) - discovery_stats = {"eligible": len(integrations)} - else: - integrations, discovery_stats = discover_integrations() - - if not integrations: - print("No integrations to run.", file=sys.stderr) - return 1 - - print(f"Running {len(integrations)} integrations " - f"(concurrency={args.concurrency}, timeout={args.timeout:.0f}s, " - f"xdist={'on' if args.xdist else 'off'})") - - started_at = datetime.now() - results = asyncio.run(_gather( - integrations, - concurrency=args.concurrency, - timeout=args.timeout, - use_xdist=args.xdist, - errors_dir=args.errors_dir, - junit_dir=args.junit_dir, - )) - finished_at = datetime.now() - results.sort(key=lambda r: r.integration) - - write_csv(results, args.csv) - write_report( - results, - path=args.report, - discovery_stats=discovery_stats, - started_at=started_at, - finished_at=finished_at, - concurrency=args.concurrency, - use_xdist=args.xdist, - ) - print(f"\nWrote {args.csv}") - print(f"Wrote {args.report}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())