From b6c2b541f29d25b285b31b11105cd133ba5b62ea Mon Sep 17 00:00:00 2001 From: David Egan Date: Sat, 20 Jan 2024 22:29:51 -0500 Subject: [PATCH] expose billing periods w/analysis types --- rules-engine/src/rules_engine/engine.py | 9 + .../tests/test_rules_engine/test_engine.py | 182 ++++++++++++++++-- .../tests/test_rules_engine/test_examples.py | 2 +- 3 files changed, 177 insertions(+), 16 deletions(-) diff --git a/rules-engine/src/rules_engine/engine.py b/rules-engine/src/rules_engine/engine.py index 78caeaba..aa9e2379 100644 --- a/rules-engine/src/rules_engine/engine.py +++ b/rules-engine/src/rules_engine/engine.py @@ -151,6 +151,8 @@ def convert_to_intermediate_billing_periods( analysis_type = billing_period.inclusion_override intermediate_billing_period = BillingPeriod( + start_date=billing_period.period_start_date, + end_date=billing_period.period_end_date, avg_temps=temperature_input.temperatures[start_idx:end_idx], usage=billing_period.usage, analysis_type=analysis_type, @@ -518,6 +520,9 @@ def calculate_partial_ua(self, billing_period: BillingPeriod) -> float: # BTUs/hour ) + def get_all_billing_periods(self) -> List[BillingPeriod]: + return self.bills_winter + self.bills_shoulder + self.bills_summer + class BillingPeriod: avg_heating_usage: float @@ -528,10 +533,14 @@ class BillingPeriod: def __init__( self, + start_date: date, + end_date: date, avg_temps: List[float], usage: float, analysis_type: AnalysisType, ) -> None: + self.start_date = start_date + self.end_date = end_date self.avg_temps = avg_temps self.usage = usage self.analysis_type = analysis_type diff --git a/rules-engine/tests/test_rules_engine/test_engine.py b/rules-engine/tests/test_rules_engine/test_engine.py index de304612..2c2e2249 100644 --- a/rules-engine/tests/test_rules_engine/test_engine.py +++ b/rules-engine/tests/test_rules_engine/test_engine.py @@ -1,4 +1,4 @@ -from datetime import date +from datetime import date, datetime import pytest from pytest import approx @@ -20,10 +20,34 @@ @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), + engine.BillingPeriod( + datetime(2023, 1, 1), + datetime(2023, 1, 4), + [28, 29, 30, 29], + 50, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 8), + datetime(2023, 1, 11), + [32, 35, 35, 38], + 45, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 15), + datetime(2023, 1, 18), + [41, 43, 42, 42], + 30, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 5, 1), + datetime(2023, 5, 4), + [72, 71, 70, 69], + 0.96, + AnalysisType.DO_NOT_INCLUDE, + ), ] return billing_periods @@ -31,11 +55,41 @@ def sample_billing_periods() -> list[engine.BillingPeriod]: @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), - 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), + engine.BillingPeriod( + datetime(2023, 1, 1), + datetime(2023, 1, 4), + [41.7, 41.6, 32, 25.4], + 60, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 8), + datetime(2023, 1, 11), + [28, 29, 30, 29], + 50, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 15), + datetime(2023, 1, 18), + [32, 35, 35, 38], + 45, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 22), + datetime(2023, 1, 25), + [41, 43, 42, 42], + 30, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 5, 1), + datetime(2023, 5, 4), + [72, 71, 70, 69], + 0.96, + AnalysisType.DO_NOT_INCLUDE, + ), ] return billing_periods @@ -243,6 +297,74 @@ def test_bp_ua_with_outlier(sample_summary_inputs, sample_billing_periods_with_o assert home.stdev_pct == approx(0.0474, abs=0.01) +def test_analysis_type_outputs(sample_summary_inputs): + # these would already be constructed in convert_to_intermediate_billing_periods before Home is built + analysis_type_sample_billing_periods = [ + engine.BillingPeriod( + datetime(2023, 1, 1), + datetime(2023, 1, 4), + [28, 29, 30, 29], + 50, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 8), + datetime(2023, 1, 11), + [32, 35, 35, 38], + 45, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 9, 1), + datetime(2023, 9, 4), + [60, 55, 49, 40], + 5, + AnalysisType.INCLUDE_IN_OTHER_ANALYSIS, + ), + engine.BillingPeriod( + datetime(2023, 9, 9), + datetime(2023, 9, 12), + [60, 54, 55, 52], + 21, + AnalysisType.INCLUDE_IN_OTHER_ANALYSIS, + ), + engine.BillingPeriod( + datetime(2023, 5, 1), + datetime(2023, 5, 4), + [72, 71, 70, 69], + 0.96, + AnalysisType.DO_NOT_INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 5, 9), + datetime(2023, 5, 12), + [80, 65, 70, 75], + 0.95, + AnalysisType.DO_NOT_INCLUDE, + ), + ] + + home = engine.Home( + sample_summary_inputs, + analysis_type_sample_billing_periods, + initial_balance_point=58, + ) + + home.calculate() + + results = home.get_all_billing_periods() + # expecting results back in same order as input as they are presorted to INCLUDE, INCLUDE_IN_OTHER, DO_NOT_INCLUDE + expected_results = analysis_type_sample_billing_periods + + for i in range(len(expected_results)): + result = results[i] + expected_result = expected_results[i] + + assert result.start_date == expected_result.start_date + assert result.end_date == expected_result.end_date + assert result.analysis_type == expected_result.analysis_type + + def test_convert_to_intermediate_billing_periods( sample_temp_inputs, sample_normalized_billing_periods ): @@ -251,11 +373,41 @@ def test_convert_to_intermediate_billing_periods( ) expected_results = [ - engine.BillingPeriod([41.7, 41.6, 32, 25.4], 60, AnalysisType.INCLUDE), - 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), + engine.BillingPeriod( + datetime(2023, 1, 1), + datetime(2023, 1, 4), + [41.7, 41.6, 32, 25.4], + 60, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 8), + datetime(2023, 1, 11), + [28, 29, 30, 29], + 50, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 15), + datetime(2023, 1, 18), + [32, 35, 35, 38], + 45, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 1, 22), + datetime(2023, 1, 25), + [41, 43, 42, 42], + 30, + AnalysisType.INCLUDE, + ), + engine.BillingPeriod( + datetime(2023, 5, 1), + datetime(2023, 5, 4), + [72, 71, 70, 69], + 0.96, + AnalysisType.DO_NOT_INCLUDE, + ), ] for i in range(len(expected_results)): diff --git a/rules-engine/tests/test_rules_engine/test_examples.py b/rules-engine/tests/test_rules_engine/test_examples.py index 821e9c92..984ffd7b 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.1) -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 )