There is always room: at the top, and for improvement.
In case anyone reads this later…
Better SQL query to match a given sensor directly, and to round the unix-seconds, improved JSONata to return details of the days when temperature is below target.
Can be done in one go inside Node-RED, so here is a NR flow to do just that, using the SQLite node configured for the standard Home Assistant database. [Uses a prepared statement and in read-only mode]
With Node-RED it would be easy to trigger the flow as required, use variable parameters, and to take any part of the return and send this back to Home Assistant in a sensor node.
[{"id":"5b8bd7cbe05f7465","type":"inject","z":"bf1c409ead401b04","name":"Get Long Term stats","props":[{"p":"params","v":"{\"$entity\":\"sensor.bedroom_aircon_room_temperature\",\"$date1\":\"2023-10-01\",\"$date2\":\"2024-10-01\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":440,"wires":[["b2cbfb61ec7195b9"]]},{"id":"b2cbfb61ec7195b9","type":"sqlite","z":"bf1c409ead401b04","mydb":"56ce7a19f8e148e7","sqlquery":"prepared","sql":"SELECT mean, min, max, FLOOR(created_ts/100)*100 AS unixsec, date(created_ts, \"unixepoch\") AS udate, time(created_ts, \"unixepoch\") AS utime\nFROM \"statistics\"\nWHERE metadata_id = (SELECT id FROM \"statistics_meta\" \n WHERE statistic_id = $entity)\nAND udate>=$date1 AND udate<$date2","name":"Long term stats array","x":180,"y":500,"wires":[["53c9e133662f71f6"]]},{"id":"53c9e133662f71f6","type":"change","z":"bf1c409ead401b04","name":"Calculate when below given temperature","rules":[{"t":"set","p":"temperature","pt":"msg","to":"17","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"params.$entity","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"(\t $test:=temperature; /* test temperature */\t $array:=payload;\t\t /* sample anything less than test temperature, and add 'one hour earlier-later' time rounded*/ \t $samp:=$array[min<$test]~>|$|{\"this_hour\": unixsec, \"last_hour\": unixsec-3600, \"next_hour\": unixsec+3600}|;\t $dayarray:=$distinct($samp.udate); /* array of specific days on which temp less than test */\t\t /* reduction functions for adding last and next flags */\t $clast:=function($acc, $val){$append($acc, $merge([$val, {\"last\": $acc[-1].this_hour = $val.last_hour} ] ) )};\t $cnext:=function($acc, $val){$append($acc, $merge([$val, {\"next\": $acc[-1].this_hour = $val.next_hour} ] ) )};\t /* go forwards to check last record, then backwards to check next record */\t $temp:=$reduce($samp[[1..$count($samp)]], $clast, $merge([$samp[0],{\"last\": false}]))^(>this_hour);\t $temp:=$reduce($temp[[1..$count($temp)]], $cnext, $merge([$temp[0],{\"next\": false}]))^(this_hour);\t\t /* select only start and ends of contiguous groups */\t $periods:=$temp.(\t ($.last or $.next) and $not($.last and $.next) ? $.{\"place\": $.next ? \"start\":\"end\", \"time\": this_hour, \"temp\": min}\t );\t\t /* find longest consecutive hour period */\t $longesthours:= $periods#$i.($.place=\"end\" ? (time-$periods[$i-1].time)/3600)~>$max();\t\t /* array of cold days - the hour and temperature */\t $cold_days:= $dayarray.(\t $day:=$;\t $data:=$samp[udate=$day];\t $recs:=($data.{\"hour \" & $substringBefore(utime, \":\") : min})~>$merge();\t {\"date\": $day, \"hours\": $count($keys($recs)), \"records\": $recs}\t\t );\t\t {\"days\": $count($array)/24,\t \"start\": $array[0].udate & \" \" & $array[0].utime,\t \"endat\": $array[-1].udate & \" \" & $array[-1].utime,\t \"minimum\": $min($array.min),\t \"maximum\": $max($array.max),\t \"test_min\": $test,\t \"total-hours-below\": $count($samp), \t \"days-where-below\": $count($dayarray),\t \"most-hours-sequence\": $longesthours +1, /* period is lasthour - firsthour, so add one to include first-hour period */\t \"cold-day-array\": $cold_days \t };\t\t)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":500,"wires":[["bec78f9ab9cd88fd"]]},{"id":"bec78f9ab9cd88fd","type":"debug","z":"bf1c409ead401b04","name":"debug 395","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":500,"wires":[]},{"id":"56ce7a19f8e148e7","type":"sqlitedb","db":"/homeassistant/home-assistant_v2.db","mode":"RO"}]