From 7a88184de60caa110bf70dfc7c4272d6ecee0ce3 Mon Sep 17 00:00:00 2001 From: Jonathan Kwan Date: Wed, 3 Jan 2024 02:15:30 +0000 Subject: [PATCH 1/6] Updated test_engine to use JSON inputs Co-authored-by: Erika Nesse Co-authored-by: dwindleduck Co-authored-by: Alan Pinkert Co-authored-by: David Egan Co-authored-by: thatoldplatitude --- .../tests/test_rules_engine/test_engine.py | 116 +++++++++--------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/rules-engine/tests/test_rules_engine/test_engine.py b/rules-engine/tests/test_rules_engine/test_engine.py index 57603850..56ed3b55 100644 --- a/rules-engine/tests/test_rules_engine/test_engine.py +++ b/rules-engine/tests/test_rules_engine/test_engine.py @@ -153,30 +153,31 @@ def test_bp_ua_with_outlier(): def test_convert_to_intermediate_billing_periods(): - temperature_input = TemperatureInput( - dates=[ - date(2022, 12, 1), - date(2022, 12, 2), - date(2022, 12, 3), - date(2022, 12, 4), - date(2023, 1, 1), - date(2023, 1, 2), - date(2023, 1, 3), - date(2023, 1, 4), - date(2023, 2, 1), - date(2023, 2, 2), - date(2023, 2, 3), - date(2023, 2, 4), - date(2023, 3, 1), - date(2023, 3, 2), - date(2023, 3, 3), - date(2023, 3, 4), - date(2023, 4, 1), - date(2023, 4, 2), - date(2023, 4, 3), - date(2023, 4, 4), + + temperature_dict = { + 'dates':[ + '2022-12-01', + '2022-12-02', + '2022-12-03', + '2022-12-04', + '2023-01-01', + '2023-01-02', + '2023-01-03', + '2023-01-04', + '2023-02-01', + '2023-02-02', + '2023-02-03', + '2023-02-04', + '2023-03-01', + '2023-03-02', + '2023-03-03', + '2023-03-04', + '2023-04-01', + '2023-04-02', + '2023-04-03', + '2023-04-04', ], - temperatures=[ + 'temperatures':[ 41.7, 41.6, 32, @@ -198,41 +199,46 @@ def test_convert_to_intermediate_billing_periods(): 70, 69, ], - ) - billing_periods = [ - NormalizedBillingPeriodRecordInput( - period_start_date=date(2022, 12, 1), - period_end_date=date(2022, 12, 4), - usage=60, - inclusion_override=None, - ), - NormalizedBillingPeriodRecordInput( - period_start_date=date(2023, 1, 1), - period_end_date=date(2023, 1, 4), - usage=50, - inclusion_override=None, - ), - NormalizedBillingPeriodRecordInput( - period_start_date=date(2023, 2, 1), - period_end_date=date(2023, 2, 4), - usage=45, - inclusion_override=None, - ), - NormalizedBillingPeriodRecordInput( - period_start_date=date(2023, 3, 1), - period_end_date=date(2023, 3, 4), - usage=30, - inclusion_override=None, - ), - NormalizedBillingPeriodRecordInput( - period_start_date=date(2023, 4, 1), - period_end_date=date(2023, 4, 4), - usage=0.96, - inclusion_override=None, - ), + } + + temperature_input = TemperatureInput(**temperature_dict) + + billing_periods_dict = [ + { + 'period_start_date':'2022-12-01', + 'period_end_date':'2022-12-04', + 'usage':60, + 'inclusion_override':None, + }, + { + 'period_start_date':'2023-01-01', + 'period_end_date':'2023-01-04', + 'usage':50, + 'inclusion_override':None, + }, + { + 'period_start_date':'2023-02-01', + 'period_end_date':'2023-02-04', + 'usage':45, + 'inclusion_override':None, + }, + { + 'period_start_date':'2023-03-01', + 'period_end_date':'2023-03-04', + 'usage':30, + 'inclusion_override':None, + }, + { + 'period_start_date':'2023-04-01', + 'period_end_date':'2023-04-04', + 'usage':0.96, + 'inclusion_override':None, + }, ] + billing_periods = [NormalizedBillingPeriodRecordInput(**x) for x in billing_periods_dict] + results = engine.convert_to_intermediate_billing_periods( temperature_input, billing_periods ) From 8627b21eb80f84f19863c09da1c6f55275674c09 Mon Sep 17 00:00:00 2001 From: Erika Nesse Date: Wed, 10 Jan 2024 02:00:10 +0000 Subject: [PATCH 2/6] Collected BalancePointGraph info within Home Co-authored-by: dwindleduck Co-authored-by: David Egan Co-authored-by: harry Co-authored-by: Debajyoti Debnath --- rules-engine/src/rules_engine/engine.py | 40 +++++++-- .../src/rules_engine/pydantic_models.py | 6 +- .../tests/test_rules_engine/test_engine.py | 90 +++++++++---------- 3 files changed, 84 insertions(+), 52 deletions(-) diff --git a/rules-engine/src/rules_engine/engine.py b/rules-engine/src/rules_engine/engine.py index 41e3a928..9e6f2a7a 100644 --- a/rules-engine/src/rules_engine/engine.py +++ b/rules-engine/src/rules_engine/engine.py @@ -374,9 +374,23 @@ def _calculate_balance_point_and_ua( the home, removing UA outliers based on a normalized standard deviation threshold. """ - self.uas = [bp.ua for bp in self.bills_winter] + + self.balance_point_graph = BalancePointGraph(records=[]) + + self.uas = [billing_period.ua for billing_period in self.bills_winter] self.avg_ua = sts.mean(self.uas) self.stdev_pct = sts.pstdev(self.uas) / self.avg_ua + + balance_point_graph_row = BalancePointGraphRow( + balance_point=self.balance_point, + heat_loss_rate=self.avg_ua, + change_in_heat_loss_rate=0, + percent_change_in_heat_loss_rate=0, + standard_deviation=self.stdev_pct, + ) + + self.balance_point_graph.records.append(balance_point_graph_row) + self._refine_balance_point(initial_balance_point_sensitivity) while self.stdev_pct > stdev_pct_max: @@ -386,7 +400,7 @@ def _calculate_balance_point_and_ua( outlier = self.bills_winter.pop( biggest_outlier_idx ) # removes the biggest outlier - uas_i = [bp.ua for bp in self.bills_winter] + uas_i = [billing_period.ua for billing_period in self.bills_winter] avg_ua_i = sts.mean(uas_i) stdev_pct_i = sts.pstdev(uas_i) / avg_ua_i if ( @@ -433,12 +447,26 @@ def _refine_balance_point(self, balance_point_sensitivity: float) -> None: # TODO: For balance point graph, store the old balance # point in a list to keep track of all intermediate balance # point temperatures? + + change_in_heat_loss_rate = avg_ua_i - self.avg_ua + percent_change_in_heat_loss_rate = ( + 100 * change_in_heat_loss_rate / avg_ua_i + ) self.balance_point, self.avg_ua, self.stdev_pct = ( bp_i, avg_ua_i, stdev_pct_i, ) + balance_point_graph_row = BalancePointGraphRow( + balance_point=self.balance_point, + heat_loss_rate=self.avg_ua, + change_in_heat_loss_rate=change_in_heat_loss_rate, + percent_change_in_heat_loss_rate=percent_change_in_heat_loss_rate, + standard_deviation=self.stdev_pct, + ) + self.balance_point_graph.records.append(balance_point_graph_row) + for n, bill in enumerate(self.bills_winter): bill.total_hdd = period_hdds_i[n] bill.ua = uas_i[n] @@ -483,10 +511,12 @@ def calculate_partial_ua(self, billing_period: BillingPeriod) -> float: """ return ( billing_period.days - * billing_period.avg_heating_usage - * self.fuel_type.value - * self.heat_sys_efficiency + * billing_period.avg_heating_usage # gallons or therms + * self.fuel_type.value # therm or gallon to BTU + * self.heat_sys_efficiency # unitless / 24 + # days * gallons/day * (BTU/gallon)/1 day (24 hours) + # BTUs/hour ) diff --git a/rules-engine/src/rules_engine/pydantic_models.py b/rules-engine/src/rules_engine/pydantic_models.py index ab40b66e..b916d778 100644 --- a/rules-engine/src/rules_engine/pydantic_models.py +++ b/rules-engine/src/rules_engine/pydantic_models.py @@ -123,10 +123,12 @@ class BalancePointGraphRow(BaseModel): """From Summary page""" balance_point: float = Field(description="Summary!G33:35") # degree F - heat_loss_rate: float = Field(description="Summary!H33:35") # BTU / (hr-deg. F) + heat_loss_rate: float = Field( + description="Summary!H33:35" + ) # BTU / (hr-deg. F) (UA) change_in_heat_loss_rate: float = Field( description="Summary!I33:35" - ) # BTU / (hr-deg. F) + ) # BTU / (hr-deg. F) (change in UA) percent_change_in_heat_loss_rate: float = Field(description="Summary!J33:35") standard_deviation: float = Field(description="Summary!K33:35") diff --git a/rules-engine/tests/test_rules_engine/test_engine.py b/rules-engine/tests/test_rules_engine/test_engine.py index 56ed3b55..9be38230 100644 --- a/rules-engine/tests/test_rules_engine/test_engine.py +++ b/rules-engine/tests/test_rules_engine/test_engine.py @@ -153,31 +153,30 @@ def test_bp_ua_with_outlier(): def test_convert_to_intermediate_billing_periods(): - temperature_dict = { - 'dates':[ - '2022-12-01', - '2022-12-02', - '2022-12-03', - '2022-12-04', - '2023-01-01', - '2023-01-02', - '2023-01-03', - '2023-01-04', - '2023-02-01', - '2023-02-02', - '2023-02-03', - '2023-02-04', - '2023-03-01', - '2023-03-02', - '2023-03-03', - '2023-03-04', - '2023-04-01', - '2023-04-02', - '2023-04-03', - '2023-04-04', + "dates": [ + "2022-12-01", + "2022-12-02", + "2022-12-03", + "2022-12-04", + "2023-01-01", + "2023-01-02", + "2023-01-03", + "2023-01-04", + "2023-02-01", + "2023-02-02", + "2023-02-03", + "2023-02-04", + "2023-03-01", + "2023-03-02", + "2023-03-03", + "2023-03-04", + "2023-04-01", + "2023-04-02", + "2023-04-03", + "2023-04-04", ], - 'temperatures':[ + "temperatures": [ 41.7, 41.6, 32, @@ -199,45 +198,46 @@ def test_convert_to_intermediate_billing_periods(): 70, 69, ], - } temperature_input = TemperatureInput(**temperature_dict) billing_periods_dict = [ { - 'period_start_date':'2022-12-01', - 'period_end_date':'2022-12-04', - 'usage':60, - 'inclusion_override':None, + "period_start_date": "2022-12-01", + "period_end_date": "2022-12-04", + "usage": 60, + "inclusion_override": None, }, { - 'period_start_date':'2023-01-01', - 'period_end_date':'2023-01-04', - 'usage':50, - 'inclusion_override':None, + "period_start_date": "2023-01-01", + "period_end_date": "2023-01-04", + "usage": 50, + "inclusion_override": None, }, { - 'period_start_date':'2023-02-01', - 'period_end_date':'2023-02-04', - 'usage':45, - 'inclusion_override':None, + "period_start_date": "2023-02-01", + "period_end_date": "2023-02-04", + "usage": 45, + "inclusion_override": None, }, { - 'period_start_date':'2023-03-01', - 'period_end_date':'2023-03-04', - 'usage':30, - 'inclusion_override':None, + "period_start_date": "2023-03-01", + "period_end_date": "2023-03-04", + "usage": 30, + "inclusion_override": None, }, { - 'period_start_date':'2023-04-01', - 'period_end_date':'2023-04-04', - 'usage':0.96, - 'inclusion_override':None, + "period_start_date": "2023-04-01", + "period_end_date": "2023-04-04", + "usage": 0.96, + "inclusion_override": None, }, ] - billing_periods = [NormalizedBillingPeriodRecordInput(**x) for x in billing_periods_dict] + billing_periods = [ + NormalizedBillingPeriodRecordInput(**x) for x in billing_periods_dict + ] results = engine.convert_to_intermediate_billing_periods( temperature_input, billing_periods From e5b4a740e3aaf56d93249a49f77b6f768f65e8b2 Mon Sep 17 00:00:00 2001 From: Erika Nesse Date: Wed, 17 Jan 2024 02:03:31 +0000 Subject: [PATCH 3/6] Added test_get_outputs_natural gas to test_examples, refactored test_engine Co-authored-by: dwindleduck Co-authored-by: thatoldplatitude Co-authored-by: Alan Pinkert Co-authored-by: David Egan Co-authored-by: Debajyoti Debnath Co-authored-by: Jonathan Kwan --- rules-engine/src/rules_engine/engine.py | 3 +- .../src/rules_engine/pydantic_models.py | 6 +- .../tests/test_rules_engine/test_engine.py | 229 ++++++++++-------- .../tests/test_rules_engine/test_examples.py | 8 + 4 files changed, 139 insertions(+), 107 deletions(-) diff --git a/rules-engine/src/rules_engine/engine.py b/rules-engine/src/rules_engine/engine.py index 9e6f2a7a..78caeaba 100644 --- a/rules-engine/src/rules_engine/engine.py +++ b/rules-engine/src/rules_engine/engine.py @@ -124,8 +124,7 @@ def get_outputs_normalized( maximum_heat_load=maximum_heat_load, ) - # TODO: fill out balance point graph - balance_point_graph = BalancePointGraph(records=[]) + balance_point_graph = home.balance_point_graph return (summary_output, balance_point_graph) diff --git a/rules-engine/src/rules_engine/pydantic_models.py b/rules-engine/src/rules_engine/pydantic_models.py index b916d778..3b36de42 100644 --- a/rules-engine/src/rules_engine/pydantic_models.py +++ b/rules-engine/src/rules_engine/pydantic_models.py @@ -113,8 +113,10 @@ class SummaryOutput(BaseModel): design_temperature: float = Field(description="Summary!B26") whole_home_heat_loss_rate: float = Field( description="Summary!B27" - ) # UA = heat loss rate - standard_deviation_of_heat_loss_rate: float = Field(description="Summary!B28") + ) # Whole Home UA. UA = heat loss rate + standard_deviation_of_heat_loss_rate: float = Field( + description="Summary!B28" + ) # Standard deviation of UA average_heat_load: float = Field(description="Summary!B29") maximum_heat_load: float = Field(description="Summary!B30") diff --git a/rules-engine/tests/test_rules_engine/test_engine.py b/rules-engine/tests/test_rules_engine/test_engine.py index 9be38230..de304612 100644 --- a/rules-engine/tests/test_rules_engine/test_engine.py +++ b/rules-engine/tests/test_rules_engine/test_engine.py @@ -17,97 +17,19 @@ ) -@pytest.mark.parametrize( - "avg_temp, balance_point, expected_result", - [ - (72, 60, 0), # outside hotter than balance point - (60, 60, 0), # outside equal to balance point - (57, 60, 3), # outside cooler than balance point - ], -) -def test_hdd(avg_temp, balance_point, expected_result): - assert engine.hdd(avg_temp, balance_point) == expected_result - - -@pytest.mark.parametrize( - "temps, expected_result", - [ - ([72, 60, 55, 61], 5), # one day with HDDs - ([52, 60, 55], 13), # two days with HDDs - ([72, 60, 65, 60, 80], 0), # no days with HDDs - ], -) -def test_period_hdd(temps, expected_result): - assert engine.period_hdd(temps, 60) == expected_result - - -def test_date_to_analysis_type(): - test_date = date.fromisoformat("2019-01-04") - assert engine.date_to_analysis_type(test_date) == AnalysisType.INCLUDE - - dates = ["2019-01-04", "2019-07-04", "2019-12-04"] - types = [engine.date_to_analysis_type(date.fromisoformat(d)) for d in dates] - expected_types = [ - AnalysisType.INCLUDE, - AnalysisType.INCLUDE_IN_OTHER_ANALYSIS, - AnalysisType.INCLUDE, - ] - assert types == expected_types - - -def test_get_average_indoor_temperature(): - set_temp = 68 - setback = 62 - setback_hrs = 8 - - # when there is no setback, just put 0 for the setback parameters - assert engine.get_average_indoor_temperature(set_temp, 0, 0) == set_temp - assert engine.get_average_indoor_temperature(set_temp, setback, setback_hrs) == 66 - - -def test_bp_ua_estimates(): +@pytest.fixture() +def sample_billing_periods() -> list[engine.BillingPeriod]: billing_periods = [ engine.BillingPeriod([28, 29, 30, 29], 50, AnalysisType.INCLUDE), engine.BillingPeriod([32, 35, 35, 38], 45, AnalysisType.INCLUDE), engine.BillingPeriod([41, 43, 42, 42], 30, AnalysisType.INCLUDE), engine.BillingPeriod([72, 71, 70, 69], 0.96, AnalysisType.DO_NOT_INCLUDE), ] - heat_sys_efficiency = 0.88 - living_area = 1000 - thermostat_set_point = 68 - setback_temperature = 60 - setback_hours_per_day = 8 - fuel_type = FuelType.GAS - design_temperature = 60 - summary_input = SummaryInput( - living_area=living_area, - fuel_type=fuel_type, - heating_system_efficiency=heat_sys_efficiency, - thermostat_set_point=thermostat_set_point, - setback_temperature=setback_temperature, - setback_hours_per_day=setback_hours_per_day, - design_temperature=design_temperature, - ) - - home = engine.Home( - summary_input, - billing_periods, - initial_balance_point=58, - ) - - home.calculate() - - ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] - - assert home.balance_point == 60 - assert ua_1 == approx(1450.5, abs=1) - assert ua_2 == approx(1615.3, abs=1) - assert ua_3 == approx(1479.6, abs=1) - assert home.avg_ua == approx(1515.1, abs=1) - assert home.stdev_pct == approx(0.0474, abs=0.01) + return billing_periods -def test_bp_ua_with_outlier(): +@pytest.fixture() +def sample_billing_periods_with_outlier() -> list[engine.BillingPeriod]: billing_periods = [ engine.BillingPeriod([41.7, 41.6, 32, 25.4], 60, AnalysisType.INCLUDE), engine.BillingPeriod([28, 29, 30, 29], 50, AnalysisType.INCLUDE), @@ -116,6 +38,11 @@ def test_bp_ua_with_outlier(): engine.BillingPeriod([72, 71, 70, 69], 0.96, AnalysisType.DO_NOT_INCLUDE), ] + return billing_periods + + +@pytest.fixture() +def sample_summary_inputs() -> SummaryInput: heat_sys_efficiency = 0.88 living_area = 1000 @@ -133,26 +60,11 @@ def test_bp_ua_with_outlier(): setback_hours_per_day=setback_hours_per_day, design_temperature=design_temperature, ) + return summary_input - home = engine.Home( - summary_input, - billing_periods, - initial_balance_point=58, - ) - home.calculate() - - ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] - - assert home.balance_point == 60 - assert ua_1 == approx(1450.5, abs=1) - assert ua_2 == approx(1615.3, abs=1) - assert ua_3 == approx(1479.6, abs=1) - assert home.avg_ua == approx(1515.1, abs=1) - assert home.stdev_pct == approx(0.0474, abs=0.01) - - -def test_convert_to_intermediate_billing_periods(): +@pytest.fixture() +def sample_temp_inputs() -> TemperatureInput: temperature_dict = { "dates": [ "2022-12-01", @@ -200,8 +112,11 @@ def test_convert_to_intermediate_billing_periods(): ], } - temperature_input = TemperatureInput(**temperature_dict) + return TemperatureInput(**temperature_dict) + +@pytest.fixture() +def sample_normalized_billing_periods() -> list[NormalizedBillingPeriodRecordInput]: billing_periods_dict = [ { "period_start_date": "2022-12-01", @@ -239,8 +154,100 @@ def test_convert_to_intermediate_billing_periods(): NormalizedBillingPeriodRecordInput(**x) for x in billing_periods_dict ] + return billing_periods + + +@pytest.mark.parametrize( + "avg_temp, balance_point, expected_result", + [ + (72, 60, 0), # outside hotter than balance point + (60, 60, 0), # outside equal to balance point + (57, 60, 3), # outside cooler than balance point + ], +) +def test_hdd(avg_temp, balance_point, expected_result): + assert engine.hdd(avg_temp, balance_point) == expected_result + + +@pytest.mark.parametrize( + "temps, expected_result", + [ + ([72, 60, 55, 61], 5), # one day with HDDs + ([52, 60, 55], 13), # two days with HDDs + ([72, 60, 65, 60, 80], 0), # no days with HDDs + ], +) +def test_period_hdd(temps, expected_result): + assert engine.period_hdd(temps, 60) == expected_result + + +def test_date_to_analysis_type(): + test_date = date.fromisoformat("2019-01-04") + assert engine.date_to_analysis_type(test_date) == AnalysisType.INCLUDE + + dates = ["2019-01-04", "2019-07-04", "2019-12-04"] + types = [engine.date_to_analysis_type(date.fromisoformat(d)) for d in dates] + expected_types = [ + AnalysisType.INCLUDE, + AnalysisType.INCLUDE_IN_OTHER_ANALYSIS, + AnalysisType.INCLUDE, + ] + assert types == expected_types + + +def test_get_average_indoor_temperature(): + set_temp = 68 + setback = 62 + setback_hrs = 8 + + # when there is no setback, just put 0 for the setback parameters + assert engine.get_average_indoor_temperature(set_temp, 0, 0) == set_temp + assert engine.get_average_indoor_temperature(set_temp, setback, setback_hrs) == 66 + + +def test_bp_ua_estimates(sample_summary_inputs, sample_billing_periods): + home = engine.Home( + sample_summary_inputs, + sample_billing_periods, + initial_balance_point=58, + ) + + home.calculate() + + ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] + + assert home.balance_point == 60 + assert ua_1 == approx(1450.5, abs=1) + assert ua_2 == approx(1615.3, abs=1) + assert ua_3 == approx(1479.6, abs=1) + assert home.avg_ua == approx(1515.1, abs=1) + assert home.stdev_pct == approx(0.0474, abs=0.01) + + +def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_outlier): + home = engine.Home( + sample_summary_inputs, + sample_billing_periods_with_outlier, + initial_balance_point=58, + ) + + home.calculate() + + ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] + + assert home.balance_point == 60 + assert ua_1 == approx(1450.5, abs=1) + assert ua_2 == approx(1615.3, abs=1) + assert ua_3 == approx(1479.6, abs=1) + assert home.avg_ua == approx(1515.1, abs=1) + assert home.stdev_pct == approx(0.0474, abs=0.01) + + +def test_convert_to_intermediate_billing_periods( + sample_temp_inputs, sample_normalized_billing_periods +): results = engine.convert_to_intermediate_billing_periods( - temperature_input, billing_periods + sample_temp_inputs, sample_normalized_billing_periods ) expected_results = [ @@ -258,3 +265,19 @@ def test_convert_to_intermediate_billing_periods(): assert result.avg_temps == expected_result.avg_temps assert result.usage == expected_result.usage assert result.analysis_type == expected_result.analysis_type + + +def test_get_outputs_normalized( + sample_summary_inputs, sample_temp_inputs, sample_normalized_billing_periods +): + summary_output, balance_point_graph = engine.get_outputs_normalized( + sample_summary_inputs, + None, + sample_temp_inputs, + sample_normalized_billing_periods, + ) + + assert summary_output.estimated_balance_point == 60 + # assert summary_output.home.avg_ua == approx(1515.1, abs=1) + # assert home.stdev_pct == approx(0.0474, abs=0.01) + pass diff --git a/rules-engine/tests/test_rules_engine/test_examples.py b/rules-engine/tests/test_rules_engine/test_examples.py index 84f320ee..821e9c92 100644 --- a/rules-engine/tests/test_rules_engine/test_examples.py +++ b/rules-engine/tests/test_rules_engine/test_examples.py @@ -22,6 +22,7 @@ # Test inputs are provided as separate directory within the "cases/examples" directory # Each subdirectory contains a JSON file (named summary.json) which specifies the inputs for the test runner ROOT_DIR = pathlib.Path(__file__).parent / "cases" / "examples" + # Filter out example 2 for now, since it's for oil fuel type INPUT_DATA = filter(lambda d: d != "example-2", next(os.walk(ROOT_DIR))[1]) @@ -111,6 +112,13 @@ def test_average_indoor_temp(data: Example) -> None: assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.1) +def test_get_outputs_natural_gas(data: Example): + summary_output, balance_point_graph = engine.get_outputs_natural_gas( + data.summary, data.temperature_data, data.natural_gas_usage + ) + # assert data.summary.estimated_balance_point == approx(summary_output.estimated_balance_point, rel=0.1) + + # def test_ua(data: Example) -> None: # """ # Test how the rules engine calculates UA from energy bills. From fbe011a1ab4d3cf49439831fbdfddda821096574 Mon Sep 17 00:00:00 2001 From: Jonathan Kwan Date: Wed, 31 Jan 2024 02:07:36 +0000 Subject: [PATCH 4/6] Fixed bug in determining summer months. Co-authored-by: dwindleduck Co-authored-by: Erika Nesse Co-authored-by: Debajyoti Debnath Co-authored-by: David Egan Co-authored-by: Alan Pinkert Co-authored-by: thatoldplatitude --- rules-engine/src/rules_engine/engine.py | 4 ++-- rules-engine/tests/test_rules_engine/test_examples.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rules-engine/src/rules_engine/engine.py b/rules-engine/src/rules_engine/engine.py index 78caeaba..8e43e156 100644 --- a/rules-engine/src/rules_engine/engine.py +++ b/rules-engine/src/rules_engine/engine.py @@ -313,9 +313,9 @@ def _initialize_billing_periods(self, billing_periods: List[BillingPeriod]) -> N if billing_period.analysis_type == AnalysisType.INCLUDE: self.bills_winter.append(billing_period) elif billing_period.analysis_type == AnalysisType.DO_NOT_INCLUDE: - self.bills_summer.append(billing_period) - else: self.bills_shoulder.append(billing_period) + else: + self.bills_summer.append(billing_period) self._calculate_avg_summer_usage() self._calculate_avg_non_heating_usage() diff --git a/rules-engine/tests/test_rules_engine/test_examples.py b/rules-engine/tests/test_rules_engine/test_examples.py index 821e9c92..2bfbda08 100644 --- a/rules-engine/tests/test_rules_engine/test_examples.py +++ b/rules-engine/tests/test_rules_engine/test_examples.py @@ -109,14 +109,14 @@ def test_average_indoor_temp(data: Example) -> None: data.summary.setback_temperature or 0, data.summary.setback_hours_per_day or 0, ) - assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.1) + assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.05) def test_get_outputs_natural_gas(data: Example): summary_output, balance_point_graph = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - # assert data.summary.estimated_balance_point == approx(summary_output.estimated_balance_point, rel=0.1) + assert data.summary.estimated_balance_point == approx(summary_output.estimated_balance_point, rel=0.05) # def test_ua(data: Example) -> None: From db946bccc8d84638b665043fc5fe287e31b7c4d7 Mon Sep 17 00:00:00 2001 From: Erika Nesse Date: Wed, 7 Feb 2024 01:29:19 +0000 Subject: [PATCH 5/6] Updated test_engine test cases based on spreadsheet Co-authored-by: dwindleduck Co-authored-by: Debajyoti Debnath Co-authored-by: harry Co-authored-by: Alan Pinkert Co-authored-by: Jonathan Kwan Co-authored-by: David Egan --- .../tests/test_rules_engine/test_engine.py | 39 ++++++++++--------- .../tests/test_rules_engine/test_examples.py | 6 ++- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/rules-engine/tests/test_rules_engine/test_engine.py b/rules-engine/tests/test_rules_engine/test_engine.py index de304612..06b2c70d 100644 --- a/rules-engine/tests/test_rules_engine/test_engine.py +++ b/rules-engine/tests/test_rules_engine/test_engine.py @@ -1,4 +1,5 @@ from datetime import date +from typing import Any import pytest from pytest import approx @@ -65,7 +66,7 @@ def sample_summary_inputs() -> SummaryInput: @pytest.fixture() def sample_temp_inputs() -> TemperatureInput: - temperature_dict = { + temperature_dict: Any = { "dates": [ "2022-12-01", "2022-12-02", @@ -117,7 +118,7 @@ def sample_temp_inputs() -> TemperatureInput: @pytest.fixture() def sample_normalized_billing_periods() -> list[NormalizedBillingPeriodRecordInput]: - billing_periods_dict = [ + billing_periods_dict: Any = [ { "period_start_date": "2022-12-01", "period_end_date": "2022-12-04", @@ -217,11 +218,11 @@ def test_bp_ua_estimates(sample_summary_inputs, sample_billing_periods): ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] assert home.balance_point == 60 - assert ua_1 == approx(1450.5, abs=1) - assert ua_2 == approx(1615.3, abs=1) - assert ua_3 == approx(1479.6, abs=1) - assert home.avg_ua == approx(1515.1, abs=1) - assert home.stdev_pct == approx(0.0474, abs=0.01) + assert ua_1 == approx(1478.50, abs=0.01) + assert ua_2 == approx(1650.00, abs=0.01) + assert ua_3 == approx(1527.78, abs=0.01) + assert home.avg_ua == approx(1552.09, abs=0.01) + assert home.stdev_pct == approx(0.0465, abs=0.01) def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_outlier): @@ -233,14 +234,15 @@ def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_o home.calculate() - ua_1, ua_2, ua_3 = [bill.ua for bill in home.bills_winter] + # expect that ua_1 is considered an outlier and not used in bills_winter + ua_2, ua_3, ua_4 = [bill.ua for bill in home.bills_winter] - assert home.balance_point == 60 - assert ua_1 == approx(1450.5, abs=1) - assert ua_2 == approx(1615.3, abs=1) - assert ua_3 == approx(1479.6, abs=1) - assert home.avg_ua == approx(1515.1, abs=1) - assert home.stdev_pct == approx(0.0474, abs=0.01) + assert home.balance_point == 60.5 + assert ua_2 == approx(1455.03, abs=0.01) + assert ua_3 == approx(1617.65, abs=0.01) + assert ua_4 == approx(1486.49, abs=0.01) + assert home.avg_ua == approx(1519.72, abs=1) + assert home.stdev_pct == approx(0.0463, abs=0.01) def test_convert_to_intermediate_billing_periods( @@ -277,7 +279,8 @@ def test_get_outputs_normalized( sample_normalized_billing_periods, ) - assert summary_output.estimated_balance_point == 60 - # assert summary_output.home.avg_ua == approx(1515.1, abs=1) - # assert home.stdev_pct == approx(0.0474, abs=0.01) - pass + assert summary_output.estimated_balance_point == 60.5 + assert summary_output.whole_home_heat_loss_rate == approx(1519.72, abs=1) + assert summary_output.standard_deviation_of_heat_loss_rate == approx( + 0.0463, abs=0.01 + ) diff --git a/rules-engine/tests/test_rules_engine/test_examples.py b/rules-engine/tests/test_rules_engine/test_examples.py index 2bfbda08..91c36668 100644 --- a/rules-engine/tests/test_rules_engine/test_examples.py +++ b/rules-engine/tests/test_rules_engine/test_examples.py @@ -109,14 +109,16 @@ def test_average_indoor_temp(data: Example) -> None: data.summary.setback_temperature or 0, data.summary.setback_hours_per_day or 0, ) - assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.05) + assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.01) def test_get_outputs_natural_gas(data: Example): summary_output, balance_point_graph = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage ) - assert data.summary.estimated_balance_point == approx(summary_output.estimated_balance_point, rel=0.05) + assert data.summary.estimated_balance_point == approx( + summary_output.estimated_balance_point, rel=0.05 + ) # def test_ua(data: Example) -> None: From f05bf19c4b55753eb7ed826b1a9e7d7cb0b61370 Mon Sep 17 00:00:00 2001 From: Erika Nesse Date: Wed, 7 Feb 2024 01:56:45 +0000 Subject: [PATCH 6/6] Fixed mypy --- rules-engine/tests/test_rules_engine/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules-engine/tests/test_rules_engine/test_examples.py b/rules-engine/tests/test_rules_engine/test_examples.py index 91c36668..0b7ec9a9 100644 --- a/rules-engine/tests/test_rules_engine/test_examples.py +++ b/rules-engine/tests/test_rules_engine/test_examples.py @@ -112,7 +112,7 @@ def test_average_indoor_temp(data: Example) -> None: assert data.summary.average_indoor_temperature == approx(avg_indoor_temp, rel=0.01) -def test_get_outputs_natural_gas(data: Example): +def test_get_outputs_natural_gas(data: Example) -> None: summary_output, balance_point_graph = engine.get_outputs_natural_gas( data.summary, data.temperature_data, data.natural_gas_usage )