-
-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added CSV parsing to rules engine (#159)
* Added CSV parsing to rules engine Co-authored-by: dwindleduck <[email protected]> Co-authored-by: Jonathan Kwan <[email protected]> Co-authored-by: AdamFinkle <[email protected]> Co-authored-by: eriksynn <[email protected]> * fixed lints --------- Co-authored-by: dwindleduck <[email protected]> Co-authored-by: Jonathan Kwan <[email protected]> Co-authored-by: AdamFinkle <[email protected]> Co-authored-by: eriksynn <[email protected]>
- Loading branch information
1 parent
bb8fc68
commit b445949
Showing
4 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
""" | ||
Return lists of gas billing data parsed from Eversource and | ||
National Grid CSVs. | ||
""" | ||
import csv | ||
import io | ||
from datetime import datetime, timedelta | ||
|
||
from .pydantic_models import NaturalGasBillingInput, NaturalGasBillingRecordInput | ||
|
||
|
||
class _GasBillRowEversource: | ||
""" | ||
Holds data for one row of an Eversource gas bill CSV. | ||
The names of the fields correspond to the first row of the Eversource bill. | ||
Example: | ||
Read Date,Usage,Number of Days,Usage per day,Charge,Average Temperature | ||
1/18/2022,184.00,32,5.75,$327.58,30.0 | ||
... | ||
""" | ||
|
||
def __init__(self, row): | ||
self.read_date = row["Read Date"] | ||
self.usage = row["Usage"] | ||
self.number_of_days = row["Number of Days"] | ||
|
||
|
||
class _GasBillRowNationalGrid: | ||
""" | ||
Holds data for one row of an National Grid gas bill CSV. | ||
The names of the fields correspond to the row of the National Grid | ||
bill right before the billing data. | ||
Example: | ||
Name,FIRST LAST,,,,, | ||
Address,"100 PLACE AVE, BOSTON MA 02130",,,,, | ||
Account Number,1111111111,,,,, | ||
Service,Service 1,,,,, | ||
,,,,,, | ||
TYPE,START DATE,END DATE,USAGE,UNITS,COST,NOTES | ||
Natural gas billing,12/29/2012,1/24/2013,149,therms,$206.91 , | ||
... | ||
""" | ||
|
||
def __init__(self, row): | ||
self.start_date = row["START DATE"] | ||
self.end_date = row["END DATE"] | ||
self.usage = row["USAGE"] | ||
|
||
|
||
def parse_gas_bill_eversource(data: str) -> NaturalGasBillingInput: | ||
""" | ||
Return a list of gas bill data parsed from an Eversource CSV | ||
received as a string. | ||
Example: | ||
Read Date,Usage,Number of Days,Usage per day,Charge,Average Temperature | ||
1/18/2022,184.00,32,5.75,$327.58,30.0 | ||
... | ||
""" | ||
f = io.StringIO(data) | ||
reader = csv.DictReader(f) | ||
records = [] | ||
for row in reader: | ||
parsed_row = _GasBillRowEversource(row) | ||
period_end_date = datetime.strptime(parsed_row.read_date, "%m/%d/%Y").date() | ||
# Calculate period_start_date using the end date and number of days in the bill | ||
# Care should be taken here to avoid off-by-one errors | ||
period_start_date = period_end_date - timedelta( | ||
days=(int(parsed_row.number_of_days) - 1) | ||
) | ||
|
||
record = NaturalGasBillingRecordInput( | ||
period_start_date=period_start_date, | ||
period_end_date=period_end_date, | ||
usage_therms=parsed_row.usage, | ||
inclusion_override=None, | ||
) | ||
records.append(record) | ||
|
||
return NaturalGasBillingInput(records=records) | ||
|
||
|
||
def parse_gas_bill_national_grid(data: str) -> NaturalGasBillingInput: | ||
""" | ||
Return a list of gas bill data parsed from an National Grid CSV | ||
received as a string. | ||
Example: | ||
Name,FIRST LAST,,,,, | ||
Address,"100 PLACE AVE, BOSTON MA 02130",,,,, | ||
Account Number,1111111111,,,,, | ||
Service,Service 1,,,,, | ||
,,,,,, | ||
TYPE,START DATE,END DATE,USAGE,UNITS,COST,NOTES | ||
Natural gas billing,12/29/2012,1/24/2013,149,therms,$206.91 , | ||
... | ||
""" | ||
f = io.StringIO(data) | ||
ROWS_TO_SKIP = 5 | ||
for _ in range(ROWS_TO_SKIP): | ||
next(f) | ||
reader = csv.DictReader(f) | ||
|
||
records = [] | ||
for row in reader: | ||
parsed_row = _GasBillRowNationalGrid(row) | ||
|
||
period_start_date = datetime.strptime(parsed_row.start_date, "%m/%d/%Y").date() | ||
period_end_date = datetime.strptime(parsed_row.end_date, "%m/%d/%Y").date() | ||
|
||
record = NaturalGasBillingRecordInput( | ||
period_start_date=period_start_date, | ||
period_end_date=period_end_date, | ||
usage_therms=parsed_row.usage, | ||
inclusion_override=None, | ||
) | ||
records.append(record) | ||
|
||
return NaturalGasBillingInput(records=records) |
1 change: 1 addition & 0 deletions
1
rules-engine/tests/test_rules_engine/cases/examples/feldman/natural-gas-eversource.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
rules-engine/tests/test_rules_engine/cases/examples/quateman/natural-gas-national-grid.csv
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
Name,FIRST LAST,,,,, | ||
Address,"100 STREET AVE, BOSTON MA 02130",,,,, | ||
Account Number,1111111111,,,,, | ||
Service,Service 1,,,,, | ||
,,,,,, | ||
TYPE,START DATE,END DATE,USAGE,UNITS,COST,NOTES | ||
Natural gas billing,10/2/2020,11/4/2020,29,therms,$42.08 , | ||
Natural gas billing,11/5/2020,12/3/2020,36,therms,$65.60 , | ||
Natural gas billing,12/4/2020,1/7/2021,97,therms,$159.49 , | ||
Natural gas billing,1/8/2021,2/5/2021,105,therms,$169.09 , | ||
Natural gas billing,2/6/2021,3/5/2021,98,therms,$158.19 , | ||
Natural gas billing,3/6/2021,4/6/2021,66,therms,$111.79 , | ||
Natural gas billing,4/7/2021,5/5/2021,22,therms,$43.16 , | ||
Natural gas billing,5/6/2021,6/7/2021,19,therms,$32.42 , | ||
Natural gas billing,6/8/2021,7/6/2021,7,therms,$18.68 , | ||
Natural gas billing,7/7/2021,8/4/2021,10,therms,$21.73 , | ||
Natural gas billing,8/5/2021,9/8/2021,11,therms,$25.35 , | ||
Natural gas billing,9/9/2021,10/5/2021,8,therms,$19.58 , | ||
Natural gas billing,10/6/2021,11/3/2021,13,therms,$27.10 , | ||
Natural gas billing,11/4/2021,12/6/2021,41,therms,$87.45 , | ||
Natural gas billing,12/7/2021,1/5/2022,86,therms,$171.92 , | ||
Natural gas billing,1/6/2022,2/3/2022,132,therms,$248.63 , | ||
Natural gas billing,2/4/2022,3/7/2022,116,therms,$226.66 , | ||
Natural gas billing,3/8/2022,4/4/2022,49,therms,$109.44 , | ||
Natural gas billing,4/5/2022,5/5/2022,39,therms,$87.54 , | ||
Natural gas billing,5/6/2022,6/6/2022,20,therms,$44.30 , | ||
Natural gas billing,6/7/2022,7/5/2022,9,therms,$27.71 , | ||
Natural gas billing,7/6/2022,8/3/2022,7,therms,$23.86 , | ||
Natural gas billing,8/4/2022,9/3/2022,8,therms,$24.04 , | ||
Natural gas billing,9/4/2022,10/3/2022,8,therms,$26.41 , | ||
Natural gas billing,10/4/2022,11/3/2022,19,therms,$48.92 , |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import pathlib | ||
from datetime import date | ||
|
||
from rules_engine import parser | ||
from rules_engine.pydantic_models import NaturalGasBillingRecordInput | ||
|
||
ROOT_DIR = pathlib.Path(__file__).parent / "cases" / "examples" | ||
|
||
|
||
def test_parse_gas_bill_eversource(): | ||
with open(ROOT_DIR / "feldman" / "natural-gas-eversource.csv") as f: | ||
s = f.read() | ||
|
||
result = parser.parse_gas_bill_eversource(s) | ||
|
||
assert len(result.records) == 36 | ||
for row in result.records: | ||
assert isinstance(row, NaturalGasBillingRecordInput) | ||
|
||
# input: 12/17/2021,124.00,29,4.28,$224.09,39.0 | ||
# from excel: 11/19/2021,12/17/2021,29,124,,1,4.28,3.82 | ||
|
||
second_row = result.records[1] | ||
assert second_row.period_start_date == date(2021, 11, 19) | ||
assert second_row.period_end_date == date(2021, 12, 17) | ||
assert isinstance(second_row.usage_therms, float) | ||
assert second_row.usage_therms == 124 | ||
assert second_row.inclusion_override == None | ||
|
||
|
||
def test_parse_gas_bill_national_grid(): | ||
with open(ROOT_DIR / "quateman" / "natural-gas-national-grid.csv") as f: | ||
s = f.read() | ||
|
||
result = parser.parse_gas_bill_national_grid(s) | ||
|
||
assert len(result.records) == 25 | ||
for row in result.records: | ||
assert isinstance(row, NaturalGasBillingRecordInput) | ||
|
||
# input: Natural gas billing,11/5/2020,12/3/2020,36,therms,$65.60 , | ||
# from excel: 11/6/2020,12/3/2020,28,36,,1,1.29,0.99 | ||
|
||
second_row = result.records[1] | ||
assert second_row.period_start_date == date(2020, 11, 5) | ||
assert second_row.period_end_date == date(2020, 12, 3) | ||
assert isinstance(second_row.usage_therms, float) | ||
assert second_row.usage_therms == 36 | ||
assert second_row.inclusion_override == None |