Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pycheckwatt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def _extract_content_and_logbook(self, input_string):

def _extract_fcr_d_state(self):
pattern = re.compile(
r"\[ FCR-D (ACTIVATED|DEACTIVATE|FAIL ACTIVATION) \] (\S+) --(\d+)-- ((\d+,\d+)/(\d+,\d+)/(\d+,\d+) %) \((\d+) kW\) (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})" # noqa: E501
r"\[ FCR-D (ACTIVATED|DEACTIVATE|FAIL ACTIVATION) \] (?:(?:\d+x)?\s?(\S+) --(\d+)-- | (?:(?:UP|DOWN) (?:\d+,\d+) Hz ))((?:(\d+,\d+)\/(\d+,\d+)\/)?(\d+,\d+|[A-Z]+) %)\s+\((\d+,\d+\/\d+,\d+|\d+\/\d+|\d+) kW\)\s*-?\s*.*?(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})" # noqa: E501
)
for entry in self.logbook_entries:
match = pattern.search(entry)
Expand Down
71 changes: 67 additions & 4 deletions tests/unit/test_checkwatt_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,10 @@ async def test_fcrd_properties_after_data_load(self, authenticated_manager):

# FCR-D state properties should be populated from logbook
assert manager.fcrd_state == "ACTIVATED"
assert manager.fcrd_power == "7"
assert manager.fcrd_timestamp == "2024-07-07 00:08:19"
assert manager.fcrd_percentage_up == "97,7"
assert manager.fcrd_percentage_down == "99,3"
assert manager.fcrd_power == "10,0/10,0"
assert manager.fcrd_timestamp == "2025-01-01 00:04:45"
assert manager.fcrd_percentage_up == "96,5"
assert manager.fcrd_percentage_down == "106,3"

def test_properties_fail_without_data(self):
"""Test that properties fail appropriately when data isn't loaded."""
Expand Down Expand Up @@ -535,3 +535,66 @@ async def test_ems_settings_property_requires_get_ems_settings(self):
await manager.get_ems_settings()

assert manager.ems_settings == "Currently optimized (CO)"


class TestFCRDStateExtraction:
"""Test FCR-D state extraction from logbook entries."""

def setup_method(self):
"""Set up test fixtures."""
self.manager = CheckwattManager("test_user", "test_pass")

def test_fail_activation_with_retry_count_and_complex_power(self):
"""Test parsing of FAIL ACTIVATION entries with retry count and complex power format."""
log_entry = "[ FCR-D FAIL ACTIVATION ] 54x test@example.com --12345-- 85,9/0,6/97,0 % (10,0/10,0 kW) 2025-04-24 00:02:57 API-BACKEND"

self.manager.logbook_entries = [log_entry]
self.manager._extract_fcr_d_state()

assert self.manager.fcrd_state == "FAIL ACTIVATION"
assert self.manager.fcrd_percentage_up == "85,9"
assert self.manager.fcrd_percentage_response == "0,6"
assert self.manager.fcrd_percentage_down == "97,0"
assert self.manager.fcrd_power == "10,0/10,0"
assert self.manager.fcrd_timestamp == "2025-04-24 00:02:57"

def test_activated_with_complex_power_format(self):
"""Test parsing of ACTIVATED entries with complex power format."""
log_entry = "[ FCR-D ACTIVATED ] test@example.com --12345-- 96,5/4,0/106,3 % (10,0/10,0 kW) 2025-08-07 00:04:45 API-BACKEND"

self.manager.logbook_entries = [log_entry]
self.manager._extract_fcr_d_state()

assert self.manager.fcrd_state == "ACTIVATED"
assert self.manager.fcrd_percentage_up == "96,5"
assert self.manager.fcrd_percentage_response == "4,0"
assert self.manager.fcrd_percentage_down == "106,3"
assert self.manager.fcrd_power == "10,0/10,0"
assert self.manager.fcrd_timestamp == "2025-08-07 00:04:45"

def test_deactivate_with_frequency_up_hz(self):
"""Test parsing of DEACTIVATE entries with UP frequency."""
log_entry = "[ FCR-D DEACTIVATE ] UP 49,83 Hz 0,0 % (10 kW) - 2025-08-06 17:58:07 API-BACKEND"

self.manager.logbook_entries = [log_entry]
self.manager._extract_fcr_d_state()

assert self.manager.fcrd_state == "DEACTIVATE"
# For DEACTIVATE, the percentage info goes to fcrd_info
assert self.manager.fcrd_power == "10"
assert self.manager.fcrd_timestamp == "2025-08-06 17:58:07"

def test_multiple_entries_first_match_used(self):
"""Test that only the first matching entry is processed."""
log_entries = [
"[ FCR-D ACTIVATED ] test@example.com --12345-- 97,7/0,5/99,3 % (7 kW) 2024-07-07 00:08:19 API-BACKEND",
"[ FCR-D FAIL ACTIVATION ] 54x test@example.com --12345-- 85,9/0,6/97,0 % (10,0/10,0 kW) 2025-04-24 00:02:57 API-BACKEND",
]

self.manager.logbook_entries = log_entries
self.manager._extract_fcr_d_state()

# Should use the first entry (ACTIVATED)
assert self.manager.fcrd_state == "ACTIVATED"
assert self.manager.fcrd_power == "7"
assert self.manager.fcrd_timestamp == "2024-07-07 00:08:19"
Loading