Skip to content

Commit

Permalink
add test_utils to consolidate fuel oil & naturalgas tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dwindleduck committed Jul 3, 2024
1 parent b93495d commit 9586319
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 65 deletions.
26 changes: 19 additions & 7 deletions rules-engine/tests/test_rules_engine/test_fuel_oil.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
"""
Tests for fuel-oil related methods.
"""
import pathlib
import csv

from pydantic import BaseModel

from test_utils import Summary

from rules_engine.pydantic_models import (
OilPropaneBillingRecordInput,
OilPropaneBillingInput
)

# 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"
NATURAL_GAS_DIR = ROOT_DIR / "fuel_oil"

FUEL_OIL_DIR = ROOT_DIR / "fuel_oil"

class Example(BaseModel):
summary: Summary
natural_gas_usage: NaturalGasBillingExampleInput
fuel_oil_usage: OilPropaneBillingExampleInput
temperature_data: TemperatureInput

class OilPropaneBillingRecordExampleInput(OilPropaneBillingInput):
records: list[OilPropaneBillingRecordInput]

@pytest.fixture(scope="module", params=INPUT_DATA)
def data(request):
Expand All @@ -22,19 +34,19 @@ def data(request):
"""
summary = load_summary(request.param)

if summary.fuel_type == engine.FuelType.GAS:
natural_gas_usage = load_natural_gas(
if summary.fuel_type == engine.FuelType.OIL:
fuel_oil_usage = load_fuel_oil(
request.param, summary.estimated_balance_point
)
else:
natural_gas_usage = None
fuel_oil_usage = None

weather_station_short_name = summary.local_weather_station[:4]
temperature_data = load_temperature_data(weather_station_short_name)

example = Example(
summary=summary,
natural_gas_usage=natural_gas_usage,
fuel_oil_usage=fuel_oil_usage,
temperature_data=temperature_data,
)
yield example
75 changes: 17 additions & 58 deletions rules-engine/tests/test_rules_engine/test_natural_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from pytest import approx
from typing_extensions import Annotated

from test_utils import Summary

from rules_engine import engine
from rules_engine.pydantic_models import (
NaturalGasBillingInput,
Expand All @@ -19,6 +21,8 @@
TemperatureInput,
)

from test_utils import load_fuel_billing_example_input

# 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"
Expand All @@ -38,11 +42,21 @@ class Summary(SummaryInput, SummaryOutput):

# Extend NG Billing Record Input to capture whole home heat loss input from example data
class NaturalGasBillingRecordExampleInput(NaturalGasBillingRecordInput):
"""
whole_home_heat_loss_rate is added to this class solely because of testing needs
and must be included, and this class must be used instead of NaturalGasBillingRecordInput,
which would otherwise intuitively be used, and which is used in production.
"""
whole_home_heat_loss_rate: float


# Then overload NG Billing Input to contain new NG Billing Record Example Input subclass
class NaturalGasBillingExampleInput(NaturalGasBillingInput):
"""
This class exists to contain a list of NaturalGasBillingRecordExampleInput, which
must be used for testing purposes rather than NaturalGasBillingInput, which would
otherwise intuitively used, and which is used in production.
"""
records: list[NaturalGasBillingRecordExampleInput]


Expand All @@ -58,61 +72,6 @@ def load_summary(folder: str) -> Summary:
return Summary(**d)


def load_natural_gas(
folder: str, estimated_balance_point: float
) -> NaturalGasBillingExampleInput:
records = []

with open(NATURAL_GAS_DIR / folder / "natural-gas.csv") as f:
reader = csv.DictReader(f)
row: Any
for row in reader:
inclusion_override = row["inclusion_override"]
if inclusion_override == "":
inclusion_override = None
else:
inclusion_override = int(inclusion_override)

# Choose the correct billing period heat loss (aka "ua") column based on the estimated balance point provided in SummaryOutput
ua_column_name = None
# First we will look for an exact match to the value of the estimated balance point
for column_name in row:
if (
"ua_at_" in column_name
and str(estimated_balance_point) in column_name
):
ua_column_name = column_name
break
# If we don't find that exact match, we round the balance point up to find our match
# It's possible that with further updates to summary data in xls and regen csv files, we wouldn't have this case
if ua_column_name == None:
ua_column_name = (
"ua_at_" + str(int(round(estimated_balance_point, 0))) + "f"
)
ua = (
row[ua_column_name].replace(",", "").strip()
) # Remove commas and whitespace to cleanup the data
if bool(ua):
whole_home_heat_loss_rate = float(ua)
else:
whole_home_heat_loss_rate = 0

item = NaturalGasBillingRecordExampleInput(
period_start_date=datetime.strptime(
row["start_date"].split(maxsplit=1)[0], "%Y-%m-%d"
).date(),
period_end_date=datetime.strptime(
row["end_date"].split(maxsplit=1)[0], "%Y-%m-%d"
).date(),
usage_therms=row["usage"],
inclusion_override=inclusion_override,
whole_home_heat_loss_rate=whole_home_heat_loss_rate,
)
records.append(item)

return NaturalGasBillingExampleInput(records=records)


def load_temperature_data(weather_station: str) -> TemperatureInput:
with open(ROOT_DIR / "temperature-data.csv", encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
Expand All @@ -130,14 +89,14 @@ def load_temperature_data(weather_station: str) -> TemperatureInput:
@pytest.fixture(scope="module", params=INPUT_DATA)
def data(request):
"""
Loads the usage and temperature data and summary inputs into an
Loads the usage and temperature data and summary inputs into an
Example instance.
"""
summary = load_summary(request.param)

if summary.fuel_type == engine.FuelType.GAS:
natural_gas_usage = load_natural_gas(
request.param, summary.estimated_balance_point
natural_gas_usage = load_fuel_billing_example_input(
request.param, "GAS", summary.estimated_balance_point
)
else:
natural_gas_usage = None
Expand Down
84 changes: 84 additions & 0 deletions rules-engine/tests/test_rules_engine/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from pydantic import BaseModel

import NaturalGasBillingExampleInput
import OilPropaneBillingExampleInput


class Summary(SummaryInput, SummaryOutput):
"""
Holds summary.json information alongside a string referring to a
local weather station.
"""
local_weather_station: str


def load_fuel_billing_example_input(
folder: str, fuel_type: str, estimated_balance_point: float
) -> NaturalGasBillingExampleInput | OilPropaneBillingExampleInput:
"""
Loads a NaturalGasBillingExampleInput or
OilPropaneBillingExampleInput from an appropriate csv.
Arguments:
folder - the string path to the file
fuel_type - GAS or OIL, the latter of which refers to propane
too
estimated_balance_point - TODO: Document what this argument is.
"""
records = []

with open(folder / fuel_type) as f:
reader = csv.DictReader(f)
row: Any
for row in reader:
inclusion_override = row["inclusion_override"]
if inclusion_override == "":
inclusion_override = None
else:
inclusion_override = int(inclusion_override)

# Choose the correct billing period heat loss (aka "ua")
# column based on the estimated balance point provided in
# SummaryOutput
ua_column_name = None
# First we will look for an exact match to the value of
# the estimated balance point
for column_name in row:
if (
"ua_at_" in column_name
and str(estimated_balance_point) in column_name
):
ua_column_name = column_name
break
# If we don't find that exact match, we round the balance
# point up to find our match.
# It's possible that with further updates to summary data
# in xls and regen csv files, we wouldn't have this case.
if ua_column_name == None:
ua_column_name = (
"ua_at_" + str(int(round(estimated_balance_point, 0))) + "f"
)
ua = (
row[ua_column_name].replace(",", "").strip()
) # Remove commas and whitespace to cleanup the data
if bool(ua):
whole_home_heat_loss_rate = float(ua)
else:
whole_home_heat_loss_rate = 0

item = NaturalGasBillingRecordExampleInput(
period_start_date=datetime.strptime(
row["start_date"].split(maxsplit=1)[0], "%Y-%m-%d"
).date(),
period_end_date=datetime.strptime(
row["end_date"].split(maxsplit=1)[0], "%Y-%m-%d"
).date(),
usage_therms=row["usage"],
inclusion_override=inclusion_override,
whole_home_heat_loss_rate=whole_home_heat_loss_rate,
)
records.append(item)

return (NaturalGasBillingExampleInput(records=records)
if fuel_type == "GAS"
else OilPropaneBillingExample(records=records))

0 comments on commit 9586319

Please sign in to comment.