#!/usr/bin/env python3 """Static release checks for the two-node Simple day/night heating Flows. Does not replace `cargo test` or hardware verification. """ from __future__ import annotations import json import subprocess import sys import tomllib from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PROFILE_DIR = ROOT / "flows" / "simple-heating" ROOMS = { "sypialnia": ("9a0bb37d-0306-4f87-9b14-fabf3b4c6164", 20.5, 10), "jan-pieczara": ("7feae66a-747e-4205-8cff-91cc0b7924f6", 20.8, 8), "igor": ("dd451f3e-4fc0-4189-b1cf-0c814b65cefc", 20.8, 10), } TRANSLATIONS = [ "flow.node.simpleHeat", "flow.simpleHeatDay", "flow.simpleHeatNight", "flow.simpleHeatMinimum", "flow.simpleHeatBoostMinutes", "flow.simpleHeatOffMinutes", "flow.simpleHeatHint", "zones.flowDayOff", "zones.flowDayPendingOff", "flow.constantDescription", "flow.constantHelp", "flow.simpleHeatDescription", "flow.simpleHeatHowItWorks", "flow.simpleHeatHelpIntro", "flow.simpleHeatHelpNightTitle", "flow.simpleHeatHelpNightSleep", "flow.simpleHeatHelpNightBrake", "flow.simpleHeatHelpNightGentle", "flow.simpleHeatHelpNightBoost", "flow.simpleHeatHelpDayTitle", "flow.simpleHeatHelpDayOff", "flow.simpleHeatHelpDayHeat", "flow.simpleHeatHelpDayStop", "flow.simpleHeatHelpSensors", "flow.simpleHeatHelpSchedule", "flow.simpleHeatHelpControl", "flow.simpleHeatDayTip", "flow.simpleHeatNightTip", "flow.simpleHeatMinimumTip", "flow.simpleHeatBoostTip", "flow.simpleHeatOffTip", ] def main() -> None: version = tomllib.loads((ROOT / "Cargo.toml").read_text())["package"]["version"] for name, (zone_id, target, boost) in ROOMS.items(): path = PROFILE_DIR / f"{name}-simple-heat-v5.flow.json" doc = json.loads(path.read_text()) assert doc["format"] == "gree-controller-flow" and doc["version"] == 1, name graph = doc["flow"] assert graph["enabled"] and not graph["draft"], name assert len(graph["nodes"]) == 2 and len(graph["edges"]) == 1, name conditions = [node for node in graph["nodes"] if node["kind"] == "constant"] actions = [node for node in graph["nodes"] if node["kind"] == "zone_heat_cycle"] assert len(conditions) == len(actions) == 1, name assert conditions[0]["config"]["value"] is True, name assert graph["edges"][0]["from"] == conditions[0]["id"], name assert graph["edges"][0]["to"] == actions[0]["id"], name conf = actions[0]["config"] assert conf["zone_id"] == zone_id, name assert conf["night_target"] == target, name assert conf["day_target"] == 20 and conf["day_min"] == 19.7, name assert conf["boost_after_minutes"] == boost, name assert conf["day_off_after_minutes"] == 30, name assert "Wymaga wersji aplikacji 0.16.2" in graph["description"], name for lang in ("pl", "en"): translations = json.loads((ROOT / "lang" / f"{lang}.json").read_text())["translations"] assert all(translations.get(key) for key in TRANSLATIONS), lang # Validate every distributable JSON input, not only the three new Flow exports. json_files = list(ROOT.rglob("*.json")) for path in json_files: json.loads(path.read_text()) assert len(json_files) > 30, len(json_files) js_files = list((ROOT / "web").rglob("*.js")) for path in js_files: subprocess.run(["node", "--check", str(path)], check=True, capture_output=True, text=True) rust = (ROOT / "src" / "api" / "flows.rs").read_text() engine = (ROOT / "src" / "engine" / "zone_control.rs").read_text() auto = (ROOT / "src" / "engine" / "automations.rs").read_text() status = (ROOT / "web" / "js-dynamic" / "entities.js").read_text() ui = (ROOT / "web" / "js-dynamic" / "flows.js").read_text() html = (ROOT / "web" / "index.html").read_text() for value in ("zone_heat_cycle", "boost_after_minutes", "day_off_after_minutes"): assert value in rust and value in ui, value assert "simple_heat_step(" in engine and "action_zone_simple_heat" in auto assert "zone.simple_heat" in engine and "active_flow_zone_action_ids" in engine assert "automation.flow" in status and "zones.flowDayOff" in status assert 'data-flow-add="zone_heat_cycle"' in html assert "day_target_20_starts_at_21_then_boosts_to_22" in (ROOT / "src" / "engine" / "simple_heat.rs").read_text() lock = (ROOT / "Cargo.lock").read_text() assert f'name = "gree-controller"\nversion = "{version}"' in lock for manifest in [ ROOT / "ha-addon" / "repository" / "gree-controller" / "config.yaml", ROOT / "ha-addon" / "home-assistant" / "custom_components" / "gree_controller" / "manifest.json", ]: assert version in manifest.read_text(), manifest assert json.loads((ROOT / "docs" / "openapi.json").read_text())["info"]["version"] == version print(f"OK: {len(ROOMS)} Flow imports, {len(json_files)} JSON files, {len(js_files)} JS scripts, translations, linked controller source, version {version}") if __name__ == "__main__": try: main() except (AssertionError, subprocess.CalledProcessError) as exc: print(f"FAIL: {exc}", file=sys.stderr) raise