diff --git a/packages/helpermodules/measurement_logging/process_log.py b/packages/helpermodules/measurement_logging/process_log.py index 3c2723188d..901869037f 100644 --- a/packages/helpermodules/measurement_logging/process_log.py +++ b/packages/helpermodules/measurement_logging/process_log.py @@ -315,12 +315,8 @@ def _analyse_energy_source(data, calc_cp: Optional[str] = None) -> Dict: data["message"] = "" for i in range(0, len(data["entries"])): data["entries"][i], message_analyse = analyse_percentage(data["entries"][i]) - if calc_cp is not None: - data["entries"][i], message_calc = calc_energy_imported_by_source_cp( - data["entries"][i], calc_cp, data["names"][calc_cp]) - else: - data["entries"][i], message_calc = calc_energy_imported_by_source_all( - data["entries"][i], data["names"]) + data["entries"][i], message_calc = calc_energy_imported_by_source( + data["entries"][i], data["names"], message_key_filter=calc_cp) data["message"] += message_analyse + message_calc data["totals"] = analyse_percentage_totals(data["entries"], data["totals"]) except Exception: @@ -405,76 +401,53 @@ def get_grid_counter(entry) -> Dict: "Fehlerzustand befindet. Die Verbräuche werden mit 0 kWh angesetzt.\n") -def calc_energy_imported_by_source_all(entry, names) -> Tuple[Dict, str]: +def calc_energy_imported_by_source(entry, names, message_key_filter: Optional[str] = None) -> Tuple[Dict, str]: try: - if "energy_source" not in entry.keys(): - return entry, "" - energy_source = entry["energy_source"] message = "" - hc_section = entry.get("hc") - if isinstance(hc_section, dict) and "all" in hc_section: - hc_all = hc_section["all"] - if isinstance(hc_all, dict): - if hc_all.get("fault_state", 0) != 2 and "energy_imported" in hc_all: - for source in ("grid", "pv", "bat", "cp"): - hc_all[f"energy_imported_{source}"] = decimal_multiply( - hc_all["energy_imported"], energy_source[source]) - else: - for source in ("grid", "pv", "bat", "cp"): - hc_all[f"energy_imported_{source}"] = 0 - message += ERROR_STATE_MESSAGE.format("den Hausverbrauch") - - cp_section = entry.get("cp") - if isinstance(cp_section, dict): - for cp_key, cp_data in cp_section.items(): - if isinstance(cp_data, dict): - if cp_data.get("fault_state", 0) != 2 and "energy_imported" in cp_data: + if "energy_source" in entry.keys(): + energy_source = entry["energy_source"] + hc_section = entry.get("hc") + if isinstance(hc_section, dict) and "all" in hc_section: + hc_all = hc_section["all"] + if isinstance(hc_all, dict): + if hc_all.get("fault_state", 0) != 2 and "energy_imported" in hc_all: for source in ("grid", "pv", "bat", "cp"): - cp_data[f"energy_imported_{source}"] = decimal_multiply( - cp_data["energy_imported"], energy_source[source]) + hc_all[f"energy_imported_{source}"] = decimal_multiply( + hc_all["energy_imported"], energy_source[source]) else: for source in ("grid", "pv", "bat", "cp"): - cp_data[f"energy_imported_{source}"] = 0 - message += ERROR_STATE_MESSAGE.format(f"Ladepunkt {names.get(cp_key, cp_key)}") - - counter_section = entry.get("counter") - if isinstance(counter_section, dict): - for counter_key, counter_data in counter_section.items(): - if isinstance(counter_data, dict) and counter_data.get("grid") is False: - if counter_data.get("fault_state", 0) != 2 and "energy_imported" in counter_data: - for source in ("grid", "pv", "bat", "cp"): - counter_data[f"energy_imported_{source}"] = decimal_multiply( - counter_data["energy_imported"], energy_source[source]) - else: - for source in ("grid", "pv", "bat", "cp"): - counter_data[f"energy_imported_{source}"] = 0 - message += ERROR_STATE_MESSAGE.format(f"Zähler {names[counter_key]}") - except Exception: - log.exception(f"Fehler beim Berechnen der Energie-Anteile aus dem Strom-Mix von {entry['timestamp']}") - message += f"Fehler beim Berechnen des Strom-Mix von {entry['timestamp']}.\n" - finally: - return entry, message - - -def calc_energy_imported_by_source_cp(entry, cp: str, name: str) -> Tuple[Dict, str]: - try: - if "energy_source" not in entry.keys(): - return entry, "" - - energy_source = entry["energy_source"] - message = "" - - cp_data = entry["cp"][cp] - if cp_data.get("fault_state", 0) != 2 and "energy_imported" in cp_data: - for source in ("grid", "pv", "bat", "cp"): - cp_data[f"energy_imported_{source}"] = decimal_multiply( - cp_data["energy_imported"], energy_source[source]) - else: - for source in ("grid", "pv", "bat", "cp"): - cp_data[f"energy_imported_{source}"] = 0 - message += ERROR_STATE_MESSAGE.format(f"Ladepunkt {name}") - + hc_all[f"energy_imported_{source}"] = 0 + if message_key_filter is None or message_key_filter == "hc": + message += ERROR_STATE_MESSAGE.format("den Hausverbrauch") + + cp_section = entry.get("cp") + if isinstance(cp_section, dict): + for cp_key, cp_data in cp_section.items(): + if isinstance(cp_data, dict): + if cp_data.get("fault_state", 0) != 2 and "energy_imported" in cp_data: + for source in ("grid", "pv", "bat", "cp"): + cp_data[f"energy_imported_{source}"] = decimal_multiply( + cp_data["energy_imported"], energy_source[source]) + else: + for source in ("grid", "pv", "bat", "cp"): + cp_data[f"energy_imported_{source}"] = 0 + if message_key_filter is None or message_key_filter == cp_key: + message += ERROR_STATE_MESSAGE.format(f"Ladepunkt {names.get(cp_key, cp_key)}") + + counter_section = entry.get("counter") + if isinstance(counter_section, dict): + for counter_key, counter_data in counter_section.items(): + if isinstance(counter_data, dict) and counter_data.get("grid") is False: + if counter_data.get("fault_state", 0) != 2 and "energy_imported" in counter_data: + for source in ("grid", "pv", "bat", "cp"): + counter_data[f"energy_imported_{source}"] = decimal_multiply( + counter_data["energy_imported"], energy_source[source]) + else: + for source in ("grid", "pv", "bat", "cp"): + counter_data[f"energy_imported_{source}"] = 0 + if message_key_filter is None or message_key_filter == counter_key: + message += ERROR_STATE_MESSAGE.format(f"Zähler {names.get(counter_key, counter_key)}") except Exception: log.exception(f"Fehler beim Berechnen der Energie-Anteile aus dem Strom-Mix von {entry['timestamp']}") message += f"Fehler beim Berechnen des Strom-Mix von {entry['timestamp']}.\n" diff --git a/packages/helpermodules/measurement_logging/process_log_unit_test.py b/packages/helpermodules/measurement_logging/process_log_unit_test.py index f4649f6b2b..30b56ec065 100644 --- a/packages/helpermodules/measurement_logging/process_log_unit_test.py +++ b/packages/helpermodules/measurement_logging/process_log_unit_test.py @@ -11,7 +11,7 @@ process_entry, get_totals, _collect_daily_log_data, - calc_energy_imported_by_source_all, + calc_energy_imported_by_source, analyse_percentage_totals, CalculationType) @@ -114,7 +114,7 @@ def test_calculate_average_power(): assert power == 1800 -def test_calc_energy_imported_by_source_all(): +def test_calc_energy_imported_by_source(): # setup entry = { "timestamp": 1234567890, @@ -131,7 +131,7 @@ def test_calc_energy_imported_by_source_all(): } # execution - result, message = calc_energy_imported_by_source_all(entry, {}) + result, message = calc_energy_imported_by_source(entry, {}) # evaluation - realistic Wh values with decimal precision assert result["hc"]["all"]["energy_imported_grid"] == 1530.035 @@ -222,6 +222,54 @@ def test_analyse_percentage_totals(): assert result["counter"]["counter2"]["energy_imported_cp"] == 824 +def test_calc_energy_imported_by_source_message_filtering(): + """Test message filtering when component is in fault state and name is missing.""" + # setup + entry = { + "timestamp": 1234567890, + "energy_source": {"grid": 0.6523, "pv": 0.2487, "bat": 0.0789, "cp": 0.0201}, + "cp": { + "cp1": {"energy_imported": 15723.4, "fault_state": 0}, + "cp2": {"energy_imported": 22108.7, "fault_state": 2} # fault state + }, + "counter": { + "counter0": {"grid": True, "energy_imported": 45892.3, "fault_state": 0}, + "counter1": {"grid": False, "energy_imported": 8956.7, "fault_state": 2} # fault state + } + } + + # Names dict is missing keys for cp2 and counter1 + names = { + "cp1": "Ladepunkt 1", + "counter0": "EVU-Zähler" + # cp2 and counter1 intentionally missing + } + + # execution - filter messages only for cp2 + result, message = calc_energy_imported_by_source(entry, names, message_key_filter="cp2") + + # evaluation + # Should only get message for cp2, not counter1 (due to filtering) + expected_message = ("Die Anteile der Energiequellen für Ladepunkt cp2 konnten nicht berechnet werden, da er sich " + "im Fehlerzustand befindet. Die Verbräuche werden mit 0 kWh angesetzt.\n") + assert message == expected_message + + # cp2 should have zero values for all energy sources due to fault state + assert result["cp"]["cp2"]["energy_imported_grid"] == 0 + assert result["cp"]["cp2"]["energy_imported_pv"] == 0 + assert result["cp"]["cp2"]["energy_imported_bat"] == 0 + assert result["cp"]["cp2"]["energy_imported_cp"] == 0 + + # cp1 should have normal calculated values (not in fault state) + assert result["cp"]["cp1"]["energy_imported_grid"] == 10256.374 + + # counter1 should have zero values but no message (filtered out) + assert result["counter"]["counter1"]["energy_imported_grid"] == 0 + assert result["counter"]["counter1"]["energy_imported_pv"] == 0 + assert result["counter"]["counter1"]["energy_imported_bat"] == 0 + assert result["counter"]["counter1"]["energy_imported_cp"] == 0 + + def test_convert(daily_log_entry_processed, daily_log_sample): # setup and execution entry = process_entry(daily_log_sample[0], daily_log_sample[1], CalculationType.ALL)