77
88SAVED = (
99 "pv_light_dark" ,
10+ "iboost_today" ,
11+ "load_forecast_only" ,
12+ "dynamic_load_baseline" ,
1013 "pv_forecast_minute" ,
1114 "pv_forecast_minute10" ,
1215 "pv_forecast_minute90" ,
@@ -120,9 +123,11 @@ def test_load_forecast_logged(my_predbat):
120123 if lines [0 ].count ("[" ) - 1 != steps :
121124 print ("ERROR: the line should cover every step the plan builds ({}), got {}" .format (steps , lines [0 ].count ("[" ) - 1 ))
122125 return 1
126+ # No forecast is a state the replay must follow too, so it is logged (once - see test_empty_load_forecast_logged_on_change)
123127 my_predbat .load_forecast = {}
124- if capture (my_predbat , my_predbat .log_replay_load_forecast ):
125- print ("ERROR: no load forecast should log nothing" )
128+ my_predbat .replay_load_empty = None
129+ if capture (my_predbat , my_predbat .log_replay_load_forecast ) != ["Replay input: load forecast empty" ]:
130+ print ("ERROR: no load forecast should be logged as empty" )
126131 return 1
127132 return 0
128133
@@ -238,8 +243,10 @@ def test_state_logged(my_predbat):
238243 my_predbat .charge_rate_now = 25 / 60000.0
239244 my_predbat .discharge_rate_now = 0.09625
240245 my_predbat .battery_temperature = 24
246+ my_predbat .iboost_today = 1.25
247+ my_predbat .load_forecast_only = True
241248 lines = capture (my_predbat , my_predbat .log_replay_state )
242- expected = "Replay input: state soc_kw 4.104 soc_max 18.08 inday 0.8347826086956521 cost_today 54.2213 load_today 3.09 import_today 17.05 export_today 7.64 pv_today None charge_rate_now {!r} discharge_rate_now 0.09625 battery_temperature 24.0" .format (25 / 60000.0 )
249+ expected = "Replay input: state soc_kw 4.104 soc_max 18.08 inday 0.8347826086956521 cost_today 54.2213 load_today 3.09 import_today 17.05 export_today 7.64 pv_today None charge_rate_now {!r} discharge_rate_now 0.09625 battery_temperature 24.0 iboost_today 1.25 load_forecast_only 1.0 " .format (25 / 60000.0 )
243250 if lines != [expected ]:
244251 print ("ERROR: state line wrong:\n got {}\n expected {}" .format (lines , expected ))
245252 return 1
@@ -342,6 +349,44 @@ def test_logging_never_raises(my_predbat):
342349 return 0
343350
344351
352+ def test_empty_load_forecast_logged_on_change (my_predbat ):
353+ """An empty load forecast is logged when the forecast becomes empty, not every cycle, and again after it returns."""
354+ my_predbat .replay_load_empty = None
355+ my_predbat .load_forecast = {}
356+ first = capture (my_predbat , my_predbat .log_replay_load_forecast )
357+ second = capture (my_predbat , my_predbat .log_replay_load_forecast )
358+ if first != ["Replay input: load forecast empty" ] or second :
359+ print ("ERROR: an empty forecast should be logged once, got {} then {}" .format (first , second ))
360+ return 1
361+ my_predbat .minutes_now = 9 * 60
362+ my_predbat .forecast_minutes = 10
363+ my_predbat .load_forecast = {minute : minute / 10000.0 for minute in range (0 , 24 * 60 )}
364+ capture (my_predbat , my_predbat .log_replay_load_forecast )
365+ my_predbat .load_forecast = {}
366+ if capture (my_predbat , my_predbat .log_replay_load_forecast ) != ["Replay input: load forecast empty" ]:
367+ print ("ERROR: a forecast that empties again should be logged again" )
368+ return 1
369+ return 0
370+
371+
372+ def test_baseline_logged_on_change (my_predbat ):
373+ """The dynamic load baseline is logged as a dict that reads back exactly, and only when it changes."""
374+ my_predbat .replay_baseline_text = None
375+ my_predbat .dynamic_load_baseline = {600 : 0.0625 , 605 : 0.0625 }
376+ lines = capture (my_predbat , my_predbat .log_replay_baseline )
377+ if len (lines ) != 1 or ast .literal_eval (lines [0 ].split ("baseline " , 1 )[1 ]) != {600 : 0.0625 , 605 : 0.0625 }:
378+ print ("ERROR: expected one baseline line that reads back, got {}" .format (lines ))
379+ return 1
380+ if capture (my_predbat , my_predbat .log_replay_baseline ):
381+ print ("ERROR: an unchanged baseline should not be logged again" )
382+ return 1
383+ my_predbat .dynamic_load_baseline = {}
384+ if capture (my_predbat , my_predbat .log_replay_baseline ) != ["Replay input: dynamic load baseline {}" ]:
385+ print ("ERROR: a baseline that clears should be logged" )
386+ return 1
387+ return 0
388+
389+
345390def test_unreadable_state_warns_once (my_predbat ):
346391 """A value whose repr does not read back warns once per change, not on every cycle."""
347392 my_predbat .num_cars = 1
@@ -371,7 +416,7 @@ def test_comparison_runs_not_logged(my_predbat):
371416def run_log_replay_inputs_tests (my_predbat ):
372417 """Run the replay-input logging tests, restoring the shared fixture afterwards."""
373418 missing = object ()
374- saved = {key : getattr (my_predbat , key , missing ) for key in SAVED + ("replay_pv_signature" , "replay_ml_signature" , "replay_rates_signature" , "replay_cars_text" , "replay_inverter_text" )}
419+ saved = {key : getattr (my_predbat , key , missing ) for key in SAVED + ("replay_pv_signature" , "replay_ml_signature" , "replay_rates_signature" , "replay_cars_text" , "replay_inverter_text" , "replay_load_empty" , "replay_baseline_text" )}
375420 failed = 0
376421 try :
377422 failed += test_pv_forecast_logged_on_change (my_predbat )
@@ -385,6 +430,8 @@ def run_log_replay_inputs_tests(my_predbat):
385430 failed += test_inverter_logged_on_change (my_predbat )
386431 failed += test_logging_never_raises (my_predbat )
387432 failed += test_unreadable_state_warns_once (my_predbat )
433+ failed += test_empty_load_forecast_logged_on_change (my_predbat )
434+ failed += test_baseline_logged_on_change (my_predbat )
388435 failed += test_comparison_runs_not_logged (my_predbat )
389436 finally :
390437 for key , value in saved .items ():
0 commit comments