diff --git a/python/README.md b/python/README.md index 13a4997d..534c63fd 100644 --- a/python/README.md +++ b/python/README.md @@ -4,12 +4,17 @@ This is the workspace for the HEAT rules engine. For an outline of the logic behind the rules engine and a glossary of common terms, see the [Intro to Rules Engine wiki page](https://github.com/codeforboston/home-energy-analysis-tool/wiki/Intro-to-Rules-Engine). ## Development + +### Setup Simple steps for development setup: 1. Clone the git repository. -3. Navigate to any directory and create a [virtual environment](https://docs.python.org/3/library/venv.html#creating-virtual-environments) and activate it -4. The following commands can be run from inside the rules-engine folder while the virtual environment is active -2. `pip install -e .` builds the [python egg](https://stackoverflow.com/questions/2051192/what-is-a-python-egg) for the rules engine and then installs the rules engine -3. `pip install -r requirements-dev.txt` which installs the required libraries +2. Navigate to python by typing `cd python` +3. Type `source setup-python.sh` + +Then, you should be able to run `pytest` and see tests run successfully. -Then, you should be able to run `pytest`, also from any directory, and see tests run successfully. +### Continuous Integration +Type `make` to see lint, type errors, and more. The terminal will reveal individual tests to run again. +* If `black` alone is a problem, then run `black .` to automatically reformat your code. +* If `mypy` is a problem, then run `mypy .` to run `mypy` again. \ No newline at end of file diff --git a/python/setup-python.sh b/python/setup-python.sh index 72f54326..a016ee3d 100755 --- a/python/setup-python.sh +++ b/python/setup-python.sh @@ -1,4 +1,5 @@ -python -m venv .venv +python3 -m venv .venv source .venv/bin/activate pip install -e . -pip install -r requirements-dev.txt \ No newline at end of file +pip install -r requirements-dev.txt +pip install --upgrade pip \ No newline at end of file diff --git a/python/src/rules_engine/engine.py b/python/src/rules_engine/engine.py index 2715412d..95fbc889 100644 --- a/python/src/rules_engine/engine.py +++ b/python/src/rules_engine/engine.py @@ -34,7 +34,8 @@ def get_outputs_oil_propane( oil_propane_billing_input: OilPropaneBillingInput, ) -> RulesEngineResult: """ - Analyze the heat load for a home that is using oil or propane as its current heating system fuel. + Analyzes the heat load for a home that is using oil or propane as + its current heating system fuel """ processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] @@ -62,7 +63,8 @@ def get_outputs_natural_gas( natural_gas_billing_input: NaturalGasBillingInput, ) -> RulesEngineResult: """ - Analyze the heat load for a home that is using natural gas as its current heating system fuel. + Analyze the heat load for a home that is using natural gas as its + current heating system fuel. """ processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] @@ -91,7 +93,8 @@ def get_outputs_normalized( processed_energy_bill_inputs: list[ProcessedEnergyBillInput], ) -> RulesEngineResult: """ - Analyze the heat load for a home based on normalized, fuel-type-agnostic billing records. + Analyze the heat load for a home based on normalized, fuel-type- + agnostic billing records. """ initial_balance_point = 60 intermediate_processed_energy_bills = ( @@ -169,7 +172,8 @@ def convert_to_intermediate_processed_energy_bills( fuel_type: FuelType, ) -> list[IntermediateEnergyBill]: """ - Converts temperature data and billing period inputs into internal classes used for heat loss calculations. + Converts temperature data and billing period inputs into internal + classes used for heat loss calculations. TODO: Extract this method to another class or make it private """ @@ -219,7 +223,8 @@ def _date_to_analysis_type_oil_propane( start_date: date, end_date: date ) -> tuple[AnalysisType, bool]: """ - Converts the dates from a billing period into an enum representing the period's usage in the rules engine. + Converts the dates from a billing period into an enum representing + the period's usage in the rules engine. """ if ( (end_date.month > 4 and end_date.month < 11) @@ -237,7 +242,8 @@ def _date_to_analysis_type_oil_propane( def _date_to_analysis_type_natural_gas(d: date) -> tuple[AnalysisType, bool]: """ - Converts the dates from a billing period into an enum representing the period's usage in the rules engine. + Converts the dates from a billing period into an enum representing + the period's usage in the rules engine. """ months = { 1: AnalysisType.ALLOWED_HEATING_USAGE, @@ -311,11 +317,11 @@ def get_average_indoor_temperature( Calculates the average indoor temperature. Args: - thermostat_set_point: the temp in F at which the home is normally set - setback_temperature: temp in F at which the home is set during off - hours - setback_hours_per_day: average # of hours per day the home is at - setback temp + thermostat_set_point: the temp in F at which the home is + normally set setback_temperature: temp in F at which the home + is set during off hours + setback_hours_per_day: average # of hours per day the home is + at setback temp """ if setback_temperature is None: setback_temperature = thermostat_set_point @@ -398,14 +404,20 @@ def calculate_dhw_usage(dhw_input: DhwInput, heating_system_efficiency: float) - class Home: """ - Defines attributes and methods for calculating home heat metrics. + Defines attributes and methods for calculating home heat metrics - The information associated with the energy usage of a single home owner - is used to instantiate this class. Using that information and the type - of fuel used, calculates the UA for different billing periods and the - standard deviation of the UA values across them. + The information associated with the energy usage of a single home + owner is used to instantiate this class. Using that information + and the type of fuel used, calculates the UA for different billing + periods and the standard deviation of the UA values across them """ + # TODO: re-evaluate below + avg_non_heating_usage = 0.0 + avg_ua = 0.0 + stdev_pct = 0.0 + balance_point_graph = BalancePointGraph(records=[]) + def _init( self, heat_load_input: HeatLoadInput, @@ -414,54 +426,70 @@ def _init( initial_balance_point: float = 60, ) -> None: self.fuel_type = heat_load_input.fuel_type - self.heat_sys_efficiency = heat_load_input.heating_system_efficiency + self.heat_system_efficiency = heat_load_input.heating_system_efficiency self.thermostat_set_point = heat_load_input.thermostat_set_point self.balance_point = initial_balance_point self.dhw_input = dhw_input - self._initialize_processed_energy_bill_inputs(intermediate_energy_bills) - def _initialize_processed_energy_bill_inputs( - self, intermediate_energy_bills: list[IntermediateEnergyBill] - ) -> None: - self.winter_processed_energy_bills = [] - self.summer_processed_energy_bills = [] - self.shoulder_processed_energy_bills = [] + ( + self.winter_processed_energy_bills, + self.summer_processed_energy_bills, + ) = Home._processed_energy_bill_inputs( + intermediate_energy_bills, self.balance_point + ) + self.avg_summer_usage = Home._avg_summer_usage( + self.summer_processed_energy_bills + ) + self.avg_non_heating_usage = Home._avg_non_heating_usage( + self.fuel_type, + self.avg_summer_usage, + self.dhw_input, + self.heat_system_efficiency, + ) + for processed_energy_bill in self.winter_processed_energy_bills: + self.initialize_ua( + processed_energy_bill, + fuel_type=self.fuel_type, + heat_system_efficiency=self.heat_system_efficiency, + avg_non_heating_usage=self.avg_non_heating_usage, + ) - # winter months 1 (ALLOWED_HEATING_USAGE); summer months -1 (ALLOWED_NON_HEATING_USAGE); shoulder months 0 (NOT_ALLOWED...) - for processed_energy_bill in intermediate_energy_bills: - processed_energy_bill.set_initial_balance_point(self.balance_point) + @staticmethod + def _processed_energy_bill_inputs( + intermediate_energy_bills: list[IntermediateEnergyBill], balance_point: float + ) -> tuple[list[IntermediateEnergyBill], list[IntermediateEnergyBill]]: + winter_processed_energy_bills = [] + summer_processed_energy_bills = [] + # winter months 1 (ALLOWED_HEATING_USAGE) + # summer months -1 (ALLOWED_NON_HEATING_USAGE) + # shoulder months 0 (NOT_ALLOWED...) + for processed_energy_bill in intermediate_energy_bills: + processed_energy_bill.set_initial_balance_point(balance_point) """ - The UI depicts billing period usage as several distinctive icons on the left hand column of the screen; "analysis_type" - For winter "cusp" months, for example April and November in MA, the UI will show those rows grayed out; "default_inclusion" - The user has the ability to "include" those cusp months in calculations by checking a box on far right; "inclusion_override" - The user may also choose to "exclude" any other "allowed" month by checking a box on the far right; "inclusion_override" - The following code implements this algorithm and adds bills accordingly to winter, summer, or shoulder (i.e. excluded) lists - """ + The UI depicts billing period usage as several distinctive + icons on the left hand column of the screen: + "analysis_type". + + For winter "cusp" months, for example April and November + in MA, the UI will show those rows grayed out: + "default_inclusion". + + The user has the ability to "include" those cusp months in + calculations by checking a box on far right: + "inclusion_override". + + The user may also choose to "exclude" any other "allowed" + month by checking a box on the far right: + "inclusion_override". + The following code implements this algorithm and adds bills + accordingly to winter, summer, or shoulder (i.e. excluded) + lists. + """ _analysis_type = processed_energy_bill.analysis_type _default_inclusion = processed_energy_bill.default_inclusion - # Only bills deemed ALLOWED by the AnalysisType algorithm can be included/excluded by the user - # if ( - # _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE - # or _analysis_type == AnalysisType.ALLOWED_NON_HEATING_USAGE - # ): - # if processed_energy_bill_input.inclusion_override: - # # The user has requested we override an inclusion algorithm decision - # if processed_energy_bill_input.winter_cusp_month == True: - # # This bill is on the cusp of winter; the user has requested we include it - # _analysis_type = AnalysisType.ALLOWED_HEATING_USAGE - # else: - # # The user has requested we exclude this bill from our calculations - # _analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS - # else: - # # The user has chosen to not override our automatic calculations, even for a winter cusp month - # if processed_energy_bill_input.winter_cusp_month == True: - # _analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS - - # Assign the bill to the appropriate list for winter or summer calculations - if processed_energy_bill.inclusion_override: _default_inclusion = not _default_inclusion @@ -469,47 +497,44 @@ def _initialize_processed_energy_bill_inputs( _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE and _default_inclusion ): - self.winter_processed_energy_bills.append(processed_energy_bill) + winter_processed_energy_bills.append(processed_energy_bill) elif ( _analysis_type == AnalysisType.ALLOWED_NON_HEATING_USAGE and _default_inclusion ): - self.summer_processed_energy_bills.append(processed_energy_bill) - else: # the rest are excluded from calculations - self.shoulder_processed_energy_bills.append(processed_energy_bill) + summer_processed_energy_bills.append(processed_energy_bill) - self._calculate_avg_summer_usage() - self._calculate_avg_non_heating_usage() - for processed_energy_bill in self.winter_processed_energy_bills: - self.initialize_ua(processed_energy_bill) + return winter_processed_energy_bills, summer_processed_energy_bills - def _calculate_avg_summer_usage(self) -> None: + @staticmethod + def _avg_summer_usage( + summer_processed_energy_bills: list[IntermediateEnergyBill], + ) -> float: """ - Calculate average daily summer usage + Calculates average daily summer usage """ - summer_usage_total = sum( - [bp.usage for bp in self.summer_processed_energy_bills] - ) - summer_days = sum([bp.days for bp in self.summer_processed_energy_bills]) + summer_usage_total = sum([bp.usage for bp in summer_processed_energy_bills]) + summer_days = sum([bp.days for bp in summer_processed_energy_bills]) if summer_days != 0: - self.avg_summer_usage = summer_usage_total / summer_days + return summer_usage_total / summer_days else: - self.avg_summer_usage = 0 + return 0 - def _calculate_avg_non_heating_usage(self) -> None: - """ - Calculate avg non heating usage for this home - """ - - if self.fuel_type == FuelType.GAS: - self.avg_non_heating_usage = self.avg_summer_usage - elif self.dhw_input is not None and self.fuel_type == FuelType.OIL: + @staticmethod + def _avg_non_heating_usage( + fuel_type: FuelType, + avg_summer_usage: float, + dhw_input: Optional[DhwInput], + heat_system_efficiency: float, + ) -> float: + """Calculates avg non heating usage for this home""" + if fuel_type == FuelType.GAS: + return avg_summer_usage + elif dhw_input is not None and fuel_type == FuelType.OIL: # TODO: support non-heating usage for Propane in addition to fuel oil - self.avg_non_heating_usage = calculate_dhw_usage( - self.dhw_input, self.heat_sys_efficiency - ) + return calculate_dhw_usage(dhw_input, heat_system_efficiency) else: - self.avg_non_heating_usage = 0 + return 0.0 def _calculate_balance_point_and_ua( self, @@ -521,18 +546,9 @@ def _calculate_balance_point_and_ua( """ Calculates the estimated balance point and UA coefficient for the home, removing UA outliers based on a normalized standard - deviation threshold. + deviation threshold """ - self.balance_point_graph = BalancePointGraph(records=[]) - - self.uas = [ - processed_energy_bill.ua - for processed_energy_bill in self.winter_processed_energy_bills - ] - 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, @@ -543,12 +559,35 @@ def _calculate_balance_point_and_ua( self.balance_point_graph.records.append(balance_point_graph_row) - self._refine_balance_point(initial_balance_point_sensitivity) + results = self._refine_balance_point( + balance_point=self.balance_point, + balance_point_sensitivity=next_balance_point_sensitivity, + avg_ua=self.avg_ua, + stdev_pct=self.stdev_pct, + thermostat_set_point=self.thermostat_set_point, + winter_processed_energy_bills=self.winter_processed_energy_bills, + ) + + if isinstance(results["balance_point"], float): + self.balance_point = results["balance_point"] + if isinstance(results["avg_ua"], float): + self.avg_ua = results["avg_ua"] + if isinstance(results["stdev_pct"], float): + self.stdev_pct = results["stdev_pct"] + balance_point_graph_records_extension = results[ + "balance_point_graph_records_extension" + ] + + if isinstance(balance_point_graph_records_extension, list): + self.balance_point_graph.records.extend( + balance_point_graph_records_extension + ) while self.stdev_pct > stdev_pct_max: outliers = [ abs(bill.ua - self.avg_ua) for bill in self.winter_processed_energy_bills + if bill.ua is not None ] biggest_outlier = max(outliers) biggest_outlier_idx = outliers.index(biggest_outlier) @@ -559,6 +598,7 @@ def _calculate_balance_point_and_ua( uas_i = [ processed_energy_bill.ua for processed_energy_bill in self.winter_processed_energy_bills + if processed_energy_bill.ua is not None ] avg_ua_i = sts.mean(uas_i) stdev_pct_i = sts.pstdev(uas_i) / avg_ua_i @@ -576,9 +616,40 @@ def _calculate_balance_point_and_ua( else: self.uas, self.avg_ua, self.stdev_pct = uas_i, avg_ua_i, stdev_pct_i - self._refine_balance_point(next_balance_point_sensitivity) + results = self._refine_balance_point( + balance_point=self.balance_point, + balance_point_sensitivity=next_balance_point_sensitivity, + avg_ua=self.avg_ua, + stdev_pct=self.stdev_pct, + thermostat_set_point=self.thermostat_set_point, + winter_processed_energy_bills=self.winter_processed_energy_bills, + ) + + if isinstance(results["balance_point"], float): + self.balance_point = results["balance_point"] + if isinstance(results["avg_ua"], float): + self.avg_ua = results["avg_ua"] + if isinstance(results["stdev_pct"], float): + self.stdev_pct = results["stdev_pct"] + balance_point_graph_records_extension = results[ + "balance_point_graph_records_extension" + ] + + if isinstance(balance_point_graph_records_extension, list): + self.balance_point_graph.records.extend( + balance_point_graph_records_extension + ) - def _refine_balance_point(self, balance_point_sensitivity: float) -> None: + @staticmethod + def _refine_balance_point( + *, + balance_point: float, + balance_point_sensitivity: float, + avg_ua: float, + stdev_pct: float, + thermostat_set_point: float, + winter_processed_energy_bills: list[IntermediateEnergyBill], + ) -> dict[str, float | list[BalancePointGraphRow]]: """ Tries different balance points plus or minus a given number of degrees, choosing whichever one minimizes the standard @@ -586,26 +657,26 @@ def _refine_balance_point(self, balance_point_sensitivity: float) -> None: """ directions_to_check = [1, -1] + balance_point_graph_records_extension = [] + while directions_to_check: - bp_i = ( - self.balance_point + directions_to_check[0] * balance_point_sensitivity - ) + bp_i = balance_point + directions_to_check[0] * balance_point_sensitivity - if bp_i > self.thermostat_set_point: + if bp_i > thermostat_set_point: break # may want to raise some kind of warning as well period_hdds_i = [ period_hdd(bill.avg_temps, bp_i) - for bill in self.winter_processed_energy_bills + for bill in winter_processed_energy_bills ] uas_i = [ bill.partial_ua / period_hdds_i[n] - for n, bill in enumerate(self.winter_processed_energy_bills) + for n, bill in enumerate(winter_processed_energy_bills) ] avg_ua_i = sts.mean(uas_i) stdev_pct_i = sts.pstdev(uas_i) / avg_ua_i - change_in_heat_loss_rate = avg_ua_i - self.avg_ua + change_in_heat_loss_rate = avg_ua_i - avg_ua percent_change_in_heat_loss_rate = 100 * change_in_heat_loss_rate / avg_ua_i balance_point_graph_row = BalancePointGraphRow( @@ -615,33 +686,40 @@ def _refine_balance_point(self, balance_point_sensitivity: float) -> None: percent_change_in_heat_loss_rate=percent_change_in_heat_loss_rate, standard_deviation=stdev_pct_i, ) - self.balance_point_graph.records.append(balance_point_graph_row) + balance_point_graph_records_extension.append(balance_point_graph_row) - if stdev_pct_i >= self.stdev_pct: + if stdev_pct_i >= stdev_pct: directions_to_check.pop(0) else: - self.balance_point, self.avg_ua, self.stdev_pct = ( + balance_point, avg_ua, 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, + balance_point=balance_point, + heat_loss_rate=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, + standard_deviation=stdev_pct, ) - self.balance_point_graph.records.append(balance_point_graph_row) + balance_point_graph_records_extension.append(balance_point_graph_row) - for n, bill in enumerate(self.winter_processed_energy_bills): + for n, bill in enumerate(winter_processed_energy_bills): bill.total_hdd = period_hdds_i[n] bill.ua = uas_i[n] if len(directions_to_check) == 2: directions_to_check.pop(-1) + return { + "balance_point": balance_point, + "avg_ua": avg_ua, + "stdev_pct": stdev_pct, + "balance_point_graph_records_extension": balance_point_graph_records_extension, + } + @classmethod def calculate( cls, @@ -655,9 +733,10 @@ def calculate( next_balance_point_sensitivity: float = 0.5, ) -> "Home": """ - For this Home, calculates avg non heating usage and then the estimated balance point - and UA coefficient for the home, removing UA outliers based on a normalized standard - deviation threshold. + For this Home, calculates avg non heating usage and then the + estimated balance point and UA coefficient for the home, + removing UA outliers based on a normalized standard deviation + threshold """ home_instance = object.__new__(cls) home_instance._init( @@ -666,7 +745,24 @@ def calculate( dhw_input=dhw_input, initial_balance_point=initial_balance_point, ) - home_instance._calculate_avg_non_heating_usage() + home_instance.avg_non_heating_usage = Home._avg_non_heating_usage( + home_instance.fuel_type, + home_instance.avg_summer_usage, + home_instance.dhw_input, + home_instance.heat_system_efficiency, + ) + + home_instance.uas = [ + processed_energy_bill.ua + for processed_energy_bill in home_instance.winter_processed_energy_bills + if processed_energy_bill.ua is not None + ] + + home_instance.balance_point_graph = BalancePointGraph(records=[]) + + home_instance.avg_ua = sts.mean(home_instance.uas) + home_instance.stdev_pct = sts.pstdev(home_instance.uas) / home_instance.avg_ua + home_instance._calculate_balance_point_and_ua( initial_balance_point_sensitivity, stdev_pct_max, @@ -676,23 +772,32 @@ def calculate( return home_instance - def initialize_ua(self, intermediate_energy_bill: IntermediateEnergyBill) -> None: + @staticmethod + def initialize_ua( + intermediate_energy_bill: IntermediateEnergyBill, + fuel_type: FuelType, + heat_system_efficiency: float, + avg_non_heating_usage: float, + ) -> None: """ Average heating usage, partial UA, initial UA. requires that self.home have non heating usage calculated. """ intermediate_energy_bill.avg_heating_usage = ( intermediate_energy_bill.usage / intermediate_energy_bill.days - ) - self.avg_non_heating_usage - intermediate_energy_bill.partial_ua = self.calculate_partial_ua( - intermediate_energy_bill + ) - avg_non_heating_usage + intermediate_energy_bill.partial_ua = Home.calculate_partial_ua( + intermediate_energy_bill, fuel_type, heat_system_efficiency ) intermediate_energy_bill.ua = ( intermediate_energy_bill.partial_ua / intermediate_energy_bill.total_hdd ) + @staticmethod def calculate_partial_ua( - self, intermediate_energy_bill: IntermediateEnergyBill + intermediate_energy_bill: IntermediateEnergyBill, + fuel_type: FuelType, + heat_system_efficiency: float, ) -> float: """ The portion of UA that is not dependent on the balance point @@ -700,8 +805,8 @@ def calculate_partial_ua( return ( intermediate_energy_bill.days * intermediate_energy_bill.avg_heating_usage # gallons or therms - * self.fuel_type.value # therm or gallon to BTU - * self.heat_sys_efficiency # unitless + * fuel_type.value # therm or gallon to BTU + * heat_system_efficiency # unitless / 24 # days * gallons/day * (BTU/gallon)/1 day (24 hours) # BTUs/hour @@ -710,8 +815,17 @@ def calculate_partial_ua( class IntermediateEnergyBill: """ - An internal class storing data whence heating usage per billing - period is calculated. + An internal class storing data whence heating usage per billing + period is calculated. + self.avg_non_heating_usage = Home. + self.fuel_type, self.avg_summer_usage, self.dhw_input, + self.heat_system_efficiency + @staticmethod + fuel_type: FuelType, + avg_summer_usage: float, + dhw_input: DhwInput, + heat_system_efficiency: float + floaterae-return self.return return """ input: ProcessedEnergyBillInput diff --git a/python/src/rules_engine/refactor.md b/python/src/rules_engine/refactor.md deleted file mode 100644 index f13b00fb..00000000 --- a/python/src/rules_engine/refactor.md +++ /dev/null @@ -1,166 +0,0 @@ -Qs: -- stand_by_losses - -home variables used -``` - avg_ua - balance_point - non_heating_usage - stdev_pct - balance_point_graph -``` - -1. Remove temporary_rules_engine.py -2. Rename rules-engine to "python" -3. Rename NormalizedBillingRecordBase class to BillingInput -4. Rename BillingPeriod to IntermediateEnergyBill and processed_energy_bill_input to processed_energy_bill_intermediate -5. Combine get_outputs_normalized and convert_to_intermediate_processed_energy_bill and get rid of NormalizedBillingRecord. There is only one place NormalizedBillingRecord is used and combining code -gets rid of the need for the class. -- Change -``` - processed_energy_bill_inputs: list[ProcessedEnergyBillInput] = [] - - for input_val in natural_gas_billing_input.records: - processed_energy_bill_inputs.append( - ProcessedEnergyBillInput( - period_start_date=input_val.period_start_date, - period_end_date=input_val.period_end_date, - usage=input_val.usage_therms, - inclusion_override=bool(input_val.inclusion_override), - ) - ) - - return get_outputs_normalized( - heat_load_input, None, temperature_input, processed_energy_bill_inputs - ) - - def get_outputs_normalized - loops through processed_energy_bill_inputs and does a bunch of stuff - -``` -to -``` - inputBill = - ProcessedEnergyBillInput( - period_start_date=input_val.period_start_date, - period_end_date=input_val.period_end_date, - usage=input_val.usage_therms, - analysis_type_override=input_val.inclusion_override, - inclusion_override=True, - ) - ) - avg_temps = derive_avg_temps(temparature_input) - default_analysis_type = derive_default_analysis_type - ( fuel_type, - inputBill.start_date, - inputBill.end_date - ) - processedBill = IntermediateEnergyBill( - input = inputBill, - avg_temps = avg_temps, - default_analysis_type = default_analysis_type - // usage not needed, it is part of inputBill - ) -``` -6. Home - Call home.calculate from home.init or move the code from home.init into home.calculate. Otherwise, the fact that Home.init does calculations is hidden. Code looks like this: -``` -home=Home(args) # does some calculations -home.calculate() # does some calculations -``` -This would change to either -``` -home=Home(args) -``` -or -``` -home=Home.calculate(args) -``` -7. Home - change to functional programming paradigm where you provide inputs and outputs - - Change - `_calculate_avg_summer_usage()` => `avg_summer_usage = _get_avg_summer_usage(summer_bills)` - - Change - `_calculate_avg_non_heating_usage ()` => -to -``` -avg_non_heating_usage = _get_avg_non_heating_usage ( - fuel_type, - avg_summer_usage, - dhw_input.estimated_water_heating_efficiency, - dhw_input.number_of_occupants, - stand_by_losses, - heating_system_efficiency -) -``` -================== -- change -`for processed_energy_bill_input in processed_energy_bill_inputs ...` => -to -``` -for processed_energy_bill_input in processed_energy_bill_inputs - { summer_processed_energy_bills, winter_processed_energy_bills, shoulder_processed_energy_bills } - =_categorize_bills_by_season (processed_energy_bill_inputs) -``` -================== -- Change -``` -calculate_dhw_usage() -``` -to -``` - dhw_usage = _calculate_dhw_usage ( - estimated_water_heating_efficiency, - dhw_input.number_of_occupants, - stand_by_losses, - heating_system_efficiency - ) -``` -================== -- Change -`self.initialize_ua(processed_energy_bill_input)` => `_set_ua(processed_energy_bill_input,avg_non_heating_usage)` - -- Change?? Parameters are never set -``` -def calculate( - self, - initial_balance_point_sensitivity: float = 0.5, - stdev_pct_max: float = 0.10, - max_stdev_pct_diff: float = 0.01, - next_balance_point_sensitivity: float = 0.5, - ) -``` -=> -``` -def calculate ( - - initial_balance_point_sensitivity: float = 0.5, - stdev_pct_max: float = 0.10, - max_stdev_pct_diff: float = 0.01, - next_balance_point_sensitivity: float = 0.5, -``` -================== -- calculate method -`self._calculate_avg_non_heating_usage` - duplicated. Remove one of the calls -================== - -- Change -``` -self._calculate_balance_point_and_ua( - initial_balance_point_sensitivity, - stdev_pct_max, - max_stdev_pct_diff, - next_balance_point_sensitivity, - ) -``` => -``` -{ balance_point, stdev_pct - balance_point_graph } = - _get_graph (initial_balance_point_sensitivity, - stdev_pct_max, - max_stdev_pct_diff, - next_balance_point_sensitivity, - winter_processed_energy_bills) -``` -================== - - - diff --git a/python/tests/test_rules_engine/test_engine.py b/python/tests/test_rules_engine/test_engine.py index e4e165b9..870f31c8 100644 --- a/python/tests/test_rules_engine/test_engine.py +++ b/python/tests/test_rules_engine/test_engine.py @@ -116,7 +116,7 @@ def sample_intermediate_energy_bill_inputs_with_outlier() -> ( @pytest.fixture() def sample_heat_load_inputs() -> HeatLoadInput: - heat_sys_efficiency = 0.88 + heat_system_efficiency = 0.88 living_area = 1000 thermostat_set_point = 68 @@ -127,7 +127,7 @@ def sample_heat_load_inputs() -> HeatLoadInput: heat_load_input = HeatLoadInput( living_area=living_area, fuel_type=fuel_type, - heating_system_efficiency=heat_sys_efficiency, + heating_system_efficiency=heat_system_efficiency, thermostat_set_point=thermostat_set_point, setback_temperature=setback_temperature, setback_hours_per_day=setback_hours_per_day,