Skip to content

Commit a59212f

Browse files
CopilotMaStr
andcommitted
Fix TypeError: int and str - ensure interval_minutes is int and always_allow_discharge_limit is float
Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com>
1 parent e87e9c9 commit a59212f

4 files changed

Lines changed: 75 additions & 19 deletions

File tree

src/batcontrol/logic/common.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@
1616
logger = logging.getLogger(__name__)
1717

1818
# Singleton pattern to ensure only one instance of CommonLogic exists
19+
20+
1921
class CommonLogic:
2022
""" General logic for battery control that is not specific to control strategies. """
2123

2224
_instance = None # Singleton instance
2325
charge_rate_multiplier: float
2426
always_allow_discharge_limit: float
2527
max_capacity: float # Maximum capacity of the battery in Wh
26-
min_charge_energy: float = 100 # Minimum amount of energy before charging from grid in Wh
28+
# Minimum amount of energy before charging from grid in Wh
29+
min_charge_energy: float = 100
2730

2831
@classmethod
2932
def get_instance(cls, charge_rate_multiplier=1.1,
@@ -34,9 +37,9 @@ def get_instance(cls, charge_rate_multiplier=1.1,
3437
if cls._instance is None:
3538
cls._instance = cls.__new__(cls)
3639
cls._instance.initialize(charge_rate_multiplier,
37-
always_allow_discharge_limit,
38-
max_capacity,
39-
min_charge_energy)
40+
always_allow_discharge_limit,
41+
max_capacity,
42+
min_charge_energy)
4043
return cls._instance
4144

4245
def __init__(self, *args, **kwargs):
@@ -47,25 +50,41 @@ def __init__(self, *args, **kwargs):
4750
self.initialize(*args, **kwargs)
4851

4952
def initialize(self, charge_rate_multiplier=1.1,
50-
always_allow_discharge_limit=0.9,
51-
max_capacity=10000,
52-
min_charge_energy=100):
53+
always_allow_discharge_limit=0.9,
54+
max_capacity=10000,
55+
min_charge_energy=100):
5356
""" Private initialization method. """
54-
self.charge_rate_multiplier = charge_rate_multiplier
55-
self.always_allow_discharge_limit = always_allow_discharge_limit
56-
self.max_capacity = max_capacity
57-
self.min_charge_energy = min_charge_energy
57+
self.charge_rate_multiplier = float(charge_rate_multiplier)
58+
self.always_allow_discharge_limit = self._to_float(
59+
always_allow_discharge_limit)
60+
self.max_capacity = float(max_capacity)
61+
self.min_charge_energy = float(min_charge_energy)
5862

5963
def set_charge_rate_multiplier(self, multiplier: float):
6064
""" Set the charge rate multiplier. """
6165
logger.debug('Setting charge rate multiplier to %s', multiplier)
6266
self.charge_rate_multiplier = multiplier
6367

68+
@staticmethod
69+
def _to_float(value) -> float:
70+
""" Convert a value to float, handling European comma decimal notation.
71+
Args:
72+
value: The value to convert. Can be a float, int, or string
73+
(including European notation like '0,9').
74+
Returns:
75+
float: The converted value.
76+
Raises:
77+
ValueError: If the value cannot be converted to float.
78+
"""
79+
if isinstance(value, str):
80+
return float(value.replace(',', '.'))
81+
return float(value)
82+
6483
def set_always_allow_discharge_limit(self, limit: float):
6584
""" Set the always allowed discharge limit. """
6685
logger.debug(
6786
'Setting always allowed discharge limit to %s', limit)
68-
self.always_allow_discharge_limit = limit
87+
self.always_allow_discharge_limit = self._to_float(limit)
6988

7089
def get_always_allow_discharge_limit(self) -> float:
7190
""" Get the always allowed discharge limit. """
@@ -92,11 +111,11 @@ def is_discharge_always_allowed_capacity(self, capacity: float) -> bool:
92111

93112
if capacity >= self.max_capacity * self.always_allow_discharge_limit:
94113
logger.debug(
95-
'Discharge is \'always allowed\' for current capacity: %.0f Wh', round(capacity,0))
114+
'Discharge is \'always allowed\' for current capacity: %.0f Wh', round(capacity, 0))
96115
return True
97116

98117
logger.debug(
99-
'Discharge is NOT \'always allowed\' for current capacity: %.0f Wh', round(capacity,0))
118+
'Discharge is NOT \'always allowed\' for current capacity: %.0f Wh', round(capacity, 0))
100119
return False
101120

102121
def is_charging_above_minimum(self, needed_energy: float) -> bool:

src/batcontrol/logic/logic.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66

77
logger = logging.getLogger(__name__)
88

9+
910
class Logic:
1011
""" Factory for logic classes. """
1112
print_class_message = True
13+
1214
@staticmethod
1315
def create_logic(config: dict, timezone) -> LogicInterface:
1416
""" Select and configure a logic class based on the given configuration """
1517
request_type = config.get('type', 'default').lower()
16-
interval_minutes = config.get('time_resolution_minutes', 60)
18+
interval_minutes = int(config.get('time_resolution_minutes', 60))
1719
logic = None
1820
if request_type == 'default':
1921
if Logic.print_class_message:
2022
logger.info('Using default logic')
2123
Logic.print_class_message = False
2224
logic = DefaultLogic(timezone, interval_minutes=interval_minutes)
2325
if config.get('battery_control_expert', None) is not None:
24-
battery_control_expert = config.get( 'battery_control_expert', {})
26+
battery_control_expert = config.get(
27+
'battery_control_expert', {})
2528
attribute_list = [
2629
'soften_price_difference_on_charging',
2730
'soften_price_difference_on_charging_factor',
@@ -30,9 +33,10 @@ def create_logic(config: dict, timezone) -> LogicInterface:
3033
]
3134
for attribute in attribute_list:
3235
if attribute in battery_control_expert:
33-
logger.debug('Setting %s to %s', attribute ,
34-
battery_control_expert[attribute])
35-
setattr(logic, attribute, battery_control_expert[attribute])
36+
logger.debug('Setting %s to %s', attribute,
37+
battery_control_expert[attribute])
38+
setattr(logic, attribute,
39+
battery_control_expert[attribute])
3640
else:
3741
raise RuntimeError(f'[Logic] Unknown logic type {config["type"]}')
3842
return logic

tests/batcontrol/logic/test_common.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ def test_set_always_allowed_discharge_limit(self):
4343
self.assertEqual(self.logic.always_allow_discharge_limit, new_limit)
4444
self.assertEqual(self.logic.get_always_allow_discharge_limit(), new_limit)
4545

46+
def test_set_always_allowed_discharge_limit_string(self):
47+
"""Test setting the always allowed discharge limit with a string value (dot notation)"""
48+
self.logic.set_always_allow_discharge_limit('0.85')
49+
self.assertIsInstance(self.logic.always_allow_discharge_limit, float)
50+
self.assertAlmostEqual(self.logic.always_allow_discharge_limit, 0.85)
51+
52+
def test_set_always_allowed_discharge_limit_european_comma(self):
53+
"""Test setting the always allowed discharge limit with European comma notation"""
54+
self.logic.set_always_allow_discharge_limit('0,85')
55+
self.assertIsInstance(self.logic.always_allow_discharge_limit, float)
56+
self.assertAlmostEqual(self.logic.always_allow_discharge_limit, 0.85)
57+
58+
def test_initialize_always_allow_discharge_limit_from_string(self):
59+
"""Test that initialize converts always_allow_discharge_limit string to float"""
60+
CommonLogic._instance = None
61+
logic = CommonLogic.get_instance(always_allow_discharge_limit='0,75')
62+
self.assertIsInstance(logic.always_allow_discharge_limit, float)
63+
self.assertAlmostEqual(logic.always_allow_discharge_limit, 0.75)
64+
4665
def test_is_discharge_always_allowed_soc(self):
4766
"""Test discharge always allowed when SOC is above threshold"""
4867
# SOC above the threshold (90%)

tests/batcontrol/logic/test_default.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import numpy as np
55

66
from batcontrol.logic.default import DefaultLogic
7+
from batcontrol.logic.logic import Logic as LogicFactory
78
from batcontrol.logic.logic_interface import CalculationInput, CalculationParameters, InverterControlSettings
89
from batcontrol.logic.common import CommonLogic
910

@@ -46,6 +47,19 @@ def test_set_calculation_parameters(self):
4647
"""Test setting calculation parameters"""
4748
self.assertEqual(self.logic.calculation_parameters, self.calculation_parameters)
4849

50+
def test_interval_minutes_is_int(self):
51+
"""Test that interval_minutes is always an int even when config provides a string"""
52+
logic = LogicFactory.create_logic({'time_resolution_minutes': '60'}, datetime.timezone.utc)
53+
self.assertIsInstance(logic.interval_minutes, int)
54+
self.assertEqual(logic.interval_minutes, 60)
55+
56+
def test_interval_minutes_is_int_default(self):
57+
"""Test that interval_minutes defaults to int 60 when not in config"""
58+
CommonLogic._instance = None
59+
logic = LogicFactory.create_logic({}, datetime.timezone.utc)
60+
self.assertIsInstance(logic.interval_minutes, int)
61+
self.assertEqual(logic.interval_minutes, 60)
62+
4963
def test_calculate_inverter_mode_high_soc(self):
5064
"""Test calculate_inverter_mode with high SOC should allow discharge"""
5165
#max_capacity = 10000 # 10 kWh

0 commit comments

Comments
 (0)