history: one Flux script per chart, and rows are json

Two things the installation found that the checks did not.

A script with three `from()` statements in it produces three results all
called `_result`, and InfluxDB refuses that outright — so the measurements go
into one filter and the rows come back tagged with which one they are.

And the answer a database node hands back holds a *list* of rows, which a
record may not: a record is flat scalars. It was declared one, so every chart
failed on the type check the moment a real answer arrived.

The second one is now caught before anything is pushed: the preflight runs
each sample shape past the port that would receive it, which is what turns
"expected record, got dict" from a runtime surprise into a line of output.

Also records the two engine faults this seeding session surfaced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 16:24:38 +02:00
co-authored by Claude Opus 5
parent 8734e51ef1
commit 1b455b5e55
4 changed files with 112 additions and 32 deletions
+35 -8
View File
@@ -286,16 +286,33 @@ def power(h: dict[str, Any]) -> Flow:
"id": "say",
"type": "python",
"title": "Worth a push?",
"requires": [{"name": "alert_changed", "port": "alert", "dtype": "record"}],
"provides": [{"name": "push", "dtype": "str"}],
"requires": [
{"name": "alert_changed", "port": "alert", "dtype": "record"},
{
"name": "seen_trouble",
"port": "seen",
"dtype": "bool",
"trigger": False,
},
],
"provides": [
{"name": "push", "dtype": "str"},
{"name": "seen_trouble", "port": "seen_trouble", "dtype": "bool"},
],
},
'''"""A level going back to normal is worth knowing; an info line is not."""
'''"""A level going back to normal is worth knowing; the first one is not.
Recovery only reads as recovery if something went wrong first. An engine that
has just started has nothing to recover from, so it says nothing — otherwise
every restart pushes "power levels are normal" at somebody's phone.
"""
def process(alert):
if alert.get("severity") == "info" and "normal" not in alert.get("body", ""):
return None
return {"push": alert.get("body", "")}
def process(alert, seen=False):
trouble = alert.get("severity") != "info"
if not trouble and not seen:
return {"seen_trouble": False}
return {"push": alert.get("body", ""), "seen_trouble": trouble}
''',
)
flow.add(
@@ -312,6 +329,7 @@ def process(alert):
"requires": [{"name": "push", "dtype": "str"}],
}
)
flow.input("seen_trouble", "bool", False)
flow.add(
{
"id": "history",
@@ -1023,7 +1041,16 @@ def presence(h: dict[str, Any]) -> Flow:
"requires": [
{"name": "anyone_home", "dtype": "bool"},
{"name": "at_desk", "dtype": "bool"},
{"name": "shutters.bed_down", "port": "bed_down", "dtype": "bool"},
# Read, never waited on: this flow has to run while the
# shutters are still Node-RED's, which is most of the
# changeover. Someone going to bed is noticed on the next
# minute rather than the same second, which is soon enough.
{
"name": "shutters.bed_down",
"port": "bed_down",
"dtype": "bool",
"trigger": False,
},
{"name": "clock.hour", "port": "hour", "dtype": "int"},
{
"name": "clock.is_weekend",