Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes issue236 by implementing inclusion_override logic
Browse files Browse the repository at this point in the history
eriksynn committed Aug 27, 2024
1 parent 21febff commit 9c6a2ec
Showing 4 changed files with 50 additions and 51 deletions.
43 changes: 24 additions & 19 deletions rules-engine/src/rules_engine/engine.py
Original file line number Diff line number Diff line change
@@ -46,8 +46,7 @@ def get_outputs_oil_propane(
period_start_date=start_date,
period_end_date=input_val.period_end_date,
usage=input_val.gallons,
analysis_type_override=input_val.inclusion_override,
inclusion_override=True,
inclusion_override=input_val.inclusion_override,
)
)
last_date = input_val.period_end_date
@@ -73,8 +72,7 @@ def get_outputs_natural_gas(
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,
inclusion_override=input_val.inclusion_override,
)
)

@@ -141,19 +139,13 @@ def get_outputs_normalized(

billing_records = []
for billing_period in intermediate_billing_periods:
# TODO: fix inclusion override to come from inputs
default_inclusion_by_calculation = True
if billing_period.analysis_type == AnalysisType.NOT_ALLOWED_IN_CALCULATIONS:
default_inclusion_by_calculation = False

billing_record = NormalizedBillingPeriodRecord(
period_start_date=billing_period.input.period_start_date,
period_end_date=billing_period.input.period_end_date,
usage=billing_period.input.usage,
analysis_type_override=billing_period.input.analysis_type_override,
inclusion_override=False,
inclusion_override=billing_period.input.inclusion_override,
analysis_type=billing_period.analysis_type,
default_inclusion_by_calculation=default_inclusion_by_calculation,
eliminated_as_outlier=billing_period.eliminated_as_outlier,
whole_home_heat_loss_rate=billing_period.ua,
)
@@ -199,15 +191,15 @@ def convert_to_intermediate_billing_periods(
analysis_type = _date_to_analysis_type_oil_propane(
billing_period.period_start_date, billing_period.period_end_date
)

if billing_period.analysis_type_override is not None:
analysis_type = billing_period.analysis_type_override
else:
raise ValueError("Unsupported fuel type.")

intermediate_billing_period = BillingPeriod(
input=billing_period,
avg_temps=temperature_input.temperatures[start_idx:end_idx],
usage=billing_period.usage,
analysis_type=analysis_type,
inclusion_override=billing_period.inclusion_override,
)
intermediate_billing_periods.append(intermediate_billing_period)

@@ -402,15 +394,26 @@ def _initialize_billing_periods(self, billing_periods: list[BillingPeriod]) -> N
self.bills_summer = []
self.bills_shoulder = []

# winter months 1; summer months -1; shoulder months 0
# winter months 1 (ALLOWED_HEATING_USAGE); summer months -1 (ALLOWED_NON_HEATING_USAGE); shoulder months 0 (NOT_ALLOWED...)
for billing_period in billing_periods:
billing_period.set_initial_balance_point(self.balance_point)

if billing_period.analysis_type == AnalysisType.ALLOWED_HEATING_USAGE:
_analysis_type = billing_period.analysis_type

if billing_period.inclusion_override: # The user has requested we override an inclusion decision
if _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE:
# In this case we assume the intent is to exclude this bill from winter calculations
_analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS
elif _analysis_type == AnalysisType.NOT_ALLOWED_IN_CALCULATIONS:
# In this case we assume the intent is to include this bill in heating calculations
_analysis_type = AnalysisType.ALLOWED_HEATING_USAGE
else:
# In this case we assume the intent is to exclude this bill from summer calculations
_analysis_type = AnalysisType.NOT_ALLOWED_IN_CALCULATIONS

if _analysis_type == AnalysisType.ALLOWED_HEATING_USAGE:
self.bills_winter.append(billing_period)
elif (
billing_period.analysis_type == AnalysisType.NOT_ALLOWED_IN_CALCULATIONS
):
elif _analysis_type == AnalysisType.NOT_ALLOWED_IN_CALCULATIONS:
self.bills_shoulder.append(billing_period)
else:
self.bills_summer.append(billing_period)
@@ -633,11 +636,13 @@ def __init__(
avg_temps: list[float],
usage: float,
analysis_type: AnalysisType,
inclusion_override: bool,
) -> None:
self.input = input
self.avg_temps = avg_temps
self.usage = usage
self.analysis_type = analysis_type
self.inclusion_override = inclusion_override
self.eliminated_as_outlier = False
self.ua = None

17 changes: 4 additions & 13 deletions rules-engine/src/rules_engine/pydantic_models.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ class AnalysisType(Enum):
Enum for analysis type.
'Inclusion' in calculations is now determined by
the default_inclusion_by_calculation and inclusion_override variables
the date_to_analysis_type calculation and the inclusion_override variable
Use HDDs to determine if shoulder months
are heating or non-heating or not allowed,
or included or excluded
@@ -83,10 +83,7 @@ class OilPropaneBillingRecordInput(BaseModel):

period_end_date: date = Field(description="Oil-Propane!B")
gallons: float = Field(description="Oil-Propane!C")
inclusion_override: Optional[
Literal[AnalysisType.ALLOWED_HEATING_USAGE]
| Literal[AnalysisType.NOT_ALLOWED_IN_CALCULATIONS]
] = Field(description="Oil-Propane!F")
inclusion_override: Optional[bool] = Field(description="Oil-Propane!F")


class OilPropaneBillingInput(BaseModel):
@@ -102,7 +99,7 @@ class NaturalGasBillingRecordInput(BaseModel):
period_start_date: date = Field(description="Natural Gas!A")
period_end_date: date = Field(description="Natural Gas!B")
usage_therms: float = Field(description="Natural Gas!D")
inclusion_override: Optional[AnalysisType] = Field(description="Natural Gas!E")
inclusion_override: Optional[bool] = Field(description="Natural Gas!E")


class NaturalGasBillingInput(BaseModel):
@@ -148,19 +145,14 @@ class NormalizedBillingPeriodRecordBase(BaseModel):
Base class for a normalized billing period record.
Holds data as fields.
analysis_type_override - for testing only, preserving compatibility
with the original heat calc spreadsheet, which allows users to override
the analysis type whereas the rules engine does not
"""

model_config = ConfigDict(validate_assignment=True)

period_start_date: date = Field(frozen=True)
period_end_date: date = Field(frozen=True)
usage: float = Field(frozen=True)
analysis_type_override: Optional[AnalysisType] = Field(frozen=True)
inclusion_override: bool
inclusion_override: bool = Field(frozen=True)


class NormalizedBillingPeriodRecord(NormalizedBillingPeriodRecordBase):
@@ -173,7 +165,6 @@ class NormalizedBillingPeriodRecord(NormalizedBillingPeriodRecordBase):
model_config = ConfigDict(validate_assignment=True)

analysis_type: AnalysisType = Field(frozen=True)
default_inclusion_by_calculation: bool = Field(frozen=True)
eliminated_as_outlier: bool = Field(frozen=True)
whole_home_heat_loss_rate: Optional[float] = Field(frozen=True)

35 changes: 21 additions & 14 deletions rules-engine/tests/test_rules_engine/test_engine.py
Original file line number Diff line number Diff line change
@@ -21,8 +21,7 @@
period_start_date=date(2024, 1, 1),
period_end_date=date(2024, 2, 1),
usage=1.0,
analysis_type_override=None,
inclusion_override=True,
inclusion_override=False,
)


@@ -34,24 +33,28 @@ def sample_billing_periods() -> list[engine.BillingPeriod]:
[28, 29, 30, 29],
50,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[32, 35, 35, 38],
45,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[41, 43, 42, 42],
30,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[72, 71, 70, 69],
0.96,
AnalysisType.NOT_ALLOWED_IN_CALCULATIONS,
False,
),
]
return billing_periods
@@ -65,30 +68,35 @@ def sample_billing_periods_with_outlier() -> list[engine.BillingPeriod]:
[41.7, 41.6, 32, 25.4],
60,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[28, 29, 30, 29],
50,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[32, 35, 35, 38],
45,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[41, 43, 42, 42],
30,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[72, 71, 70, 69],
0.96,
AnalysisType.NOT_ALLOWED_IN_CALCULATIONS,
False,
),
]

@@ -176,43 +184,37 @@ def sample_normalized_billing_periods() -> list[NormalizedBillingPeriodRecordBas
"period_start_date": "2022-12-01",
"period_end_date": "2022-12-04",
"usage": 60,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
{
"period_start_date": "2023-01-01",
"period_end_date": "2023-01-04",
"usage": 50,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
{
"period_start_date": "2023-02-01",
"period_end_date": "2023-02-04",
"usage": 45,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
{
"period_start_date": "2023-03-01",
"period_end_date": "2023-03-04",
"usage": 30,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
{
"period_start_date": "2023-04-01",
"period_end_date": "2023-04-04",
"usage": 0.96,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
{
"period_start_date": "2023-05-01",
"period_end_date": "2023-05-04",
"usage": 0.96,
"analysis_type_override": None,
"inclusion_override": True,
"inclusion_override": False,
},
]

@@ -332,30 +334,35 @@ def test_convert_to_intermediate_billing_periods(
[41.7, 41.6, 32, 25.4],
60,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[28, 29, 30, 29],
50,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[32, 35, 35, 38],
45,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[41, 43, 42, 42],
30,
AnalysisType.ALLOWED_HEATING_USAGE,
False,
),
engine.BillingPeriod(
dummy_billing_period_record,
[72, 71, 70, 69],
0.96,
AnalysisType.NOT_ALLOWED_IN_CALCULATIONS,
False,
),
]

6 changes: 1 addition & 5 deletions rules-engine/tests/test_rules_engine/test_utils.py
Original file line number Diff line number Diff line change
@@ -94,11 +94,7 @@ def load_fuel_billing_example_input(
reader = csv.DictReader(f)
row: Any
for i, row in enumerate(reader):
inclusion_override = row["inclusion_override"]
if inclusion_override == "":
inclusion_override = None
else:
inclusion_override = int(inclusion_override)
inclusion_override = bool(row["inclusion_override"])

# Choose the correct billing period heat loss (aka "ua")
# column based on the estimated balance point provided in

0 comments on commit 9c6a2ec

Please sign in to comment.