Skip to content

Commit 130596b

Browse files
Merge pull request #5108 from springfall2008/feat/manual-select-off-first-5105
Move the off option to the top of the manual override dropdowns
2 parents aa4f08b + 6a25581 commit 130596b

3 files changed

Lines changed: 81 additions & 23 deletions

File tree

‎apps/predbat/tests/test_manual_select.py‎

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,77 @@
99
# pylint: disable=attribute-defined-outside-init
1010

1111

12+
def first_future_option(my_predbat, item):
13+
"""
14+
Pick the first selectable time option from a manual dropdown, preferring a non-today day label
15+
16+
A today label can parse as a past slot at day boundaries, so one is only used as a fallback.
17+
"""
18+
today_label = my_predbat.now_utc.strftime("%a")
19+
fallback = None
20+
for option in item.get("options", []):
21+
if not option or option == "off" or option.startswith("+") or option.startswith("["):
22+
continue
23+
if not option.startswith(today_label + " "):
24+
return option
25+
if fallback is None:
26+
fallback = option
27+
return fallback
28+
29+
30+
def test_off_option_first(my_predbat):
31+
"""
32+
Test that off is the first option in each manual override dropdown (#5105)
33+
34+
The dropdowns are up to 48 hours long, so off at the end of the list means scrolling
35+
through every slot just to cancel the overrides.
36+
"""
37+
failed = 0
38+
print("Test manual dropdown off option ordering")
39+
40+
# One config item per dropdown builder: manual_times(), manual_rates() and api_select_update()
41+
for config_item, select in (("manual_charge", my_predbat.manual_select), ("manual_import_rates", my_predbat.manual_select), ("manual_api", my_predbat.api_select)):
42+
select(config_item, "off")
43+
options = my_predbat.config_index.get(config_item, {}).get("options", [])
44+
if not options:
45+
print(f"ERROR: T9 No options found for {config_item}")
46+
failed = 1
47+
elif options[0] != "off":
48+
print(f"ERROR: T10 Expected off to be the first option of {config_item}, got {options[0]} from {options[:3]}")
49+
failed = 1
50+
elif options.count("off") != 1:
51+
print(f"ERROR: T11 Expected exactly one off option in {config_item}, got {options.count('off')}")
52+
failed = 1
53+
54+
# Off stays first once a slot is selected, and still cancels the selection
55+
charge_item = my_predbat.config_index.get("manual_charge")
56+
future_time = first_future_option(my_predbat, charge_item)
57+
if not future_time:
58+
print("ERROR: T12 No future time options found for manual_charge")
59+
return 1
60+
61+
my_predbat.manual_select("manual_charge", future_time)
62+
options = charge_item.get("options", [])
63+
if options[0] != "off":
64+
print(f"ERROR: T13 Expected off to remain the first option after selecting {future_time}, got {options[0]}")
65+
failed = 1
66+
if "[" + future_time + "]" not in options:
67+
print(f"ERROR: T14 Expected {future_time} to be marked as selected in the options, got {options[:3]}")
68+
failed = 1
69+
70+
my_predbat.manual_select("manual_charge", "off")
71+
charge_value = charge_item.get("value", "")
72+
if charge_value != "off":
73+
print(f"ERROR: T15 Expected off to clear the manual_charge selection, got {charge_value}")
74+
failed = 1
75+
76+
# Clean up
77+
my_predbat.manual_select("manual_import_rates", "off")
78+
my_predbat.api_select("manual_api", "off")
79+
80+
return failed
81+
82+
1283
def run_test_manual_select(my_predbat):
1384
"""
1485
Test manual select dropdowns for force charge, export, demand
@@ -32,19 +103,7 @@ def run_test_manual_select(my_predbat):
32103
return 1
33104

34105
# Find first selectable option, preferring a non-today day label to avoid past-slot parsing at day boundaries
35-
future_time_label = None
36-
today_label = my_predbat.now_utc.strftime("%a")
37-
for option in charge_item["options"]:
38-
if option != "off" and not option.startswith("+") and not option.startswith("[") and not option.startswith(today_label + " "):
39-
future_time_label = option
40-
break
41-
42-
# Fallback to any selectable option if all options are for today
43-
if not future_time_label:
44-
for option in charge_item["options"]:
45-
if option != "off" and not option.startswith("+") and not option.startswith("["):
46-
future_time_label = option
47-
break
106+
future_time_label = first_future_option(my_predbat, charge_item)
48107

49108
if not future_time_label:
50109
print("ERROR: T2 No future time options found")
@@ -102,6 +161,8 @@ def run_test_manual_select(my_predbat):
102161
my_predbat.manual_select("manual_export", "off")
103162
my_predbat.manual_select("manual_demand", "off")
104163

164+
failed |= test_off_option_first(my_predbat)
165+
105166
if failed:
106167
print("Manual select tests FAILED")
107168
else:

‎apps/predbat/userinterface.py‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,15 +1548,14 @@ def api_select_update(self, config_item, new_value=None):
15481548
if values:
15491549
values = "+" + values
15501550

1551-
# Create the new dropdown
1552-
time_values = []
1551+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
1552+
time_values = ["off"]
15531553
for minute_str in time_overrides:
15541554
minute_str = "[" + minute_str + "]"
15551555
time_values.append(minute_str)
15561556

15571557
if values not in time_values:
15581558
time_values.append(values)
1559-
time_values.append("off")
15601559
item["options"] = time_values
15611560
if not values:
15621561
values = "off"
@@ -1644,9 +1643,9 @@ def manual_rates(self, config_item, exclude=None, new_value=None, default_rate=0
16441643
if values:
16451644
values = "+" + values
16461645

1647-
# Create the new dropdown
1646+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
16481647
if update:
1649-
time_values = []
1648+
time_values = ["off"]
16501649
for minute in range(minutes_now, minutes_now + manual_rate_max, plan_interval):
16511650
minute_str = (midnight_utc + timedelta(minutes=minute)).strftime("%a %H:%M")
16521651
if minute in rate_overrides_minutes:
@@ -1657,7 +1656,6 @@ def manual_rates(self, config_item, exclude=None, new_value=None, default_rate=0
16571656

16581657
if values not in time_values:
16591658
time_values.append(values)
1660-
time_values.append("off")
16611659
item["options"] = time_values
16621660
if not values:
16631661
values = "off"
@@ -1724,9 +1722,9 @@ def manual_times(self, config_item, exclude=None, new_value=None, update=True):
17241722
if values:
17251723
values = "+" + values
17261724

1727-
# Create the new dropdown
1725+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
17281726
if update:
1729-
time_values = []
1727+
time_values = ["off"]
17301728
for minute in range(minutes_now, minutes_now + manual_time_max, plan_interval):
17311729
minute_str = (midnight_utc + timedelta(minutes=minute)).strftime("%a %H:%M")
17321730
if minute in time_overrides:
@@ -1735,7 +1733,6 @@ def manual_times(self, config_item, exclude=None, new_value=None, update=True):
17351733

17361734
if values not in time_values:
17371735
time_values.append(values)
1738-
time_values.append("off")
17391736
item["options"] = time_values
17401737
if not values:
17411738
values = "off"

‎docs/customisation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ Selected slots will be shown in the list in square brackets, and you can cancel
763763

764764
When you use the Manual Control features you can select the day and time from the next 48 hours, the overrides will be removed once their time slot expires (they do not repeat).
765765

766-
The **off** option at the bottom of the list will cancel all selected force charges.
766+
The **off** option at the top of the list will cancel all selected force charges.
767767

768768
![image](images/manual_select.png)
769769

0 commit comments

Comments
 (0)