Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added greenbutton_objects to parse XML versions of natural gas and el… #1

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ node_modules
# Easy way to create temporary files/folders that won't accidentally be added to git
*.local.*

# PyCharm
.idea/

#local temporary folders
heat-app
venv
Expand Down
3 changes: 2 additions & 1 deletion rules-engine/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ name="rules-engine"
version="0.0.1"
requires-python=">=3.11.3"
dependencies = [
"pydantic"
"pydantic",
"greenbutton_objects"
]

[project.optional-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions rules-engine/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#
annotated-types==0.5.0
# via pydantic
greenbutton-objects==2024.7.3
# via rules-engine (pyproject.toml)
pydantic==2.3.0
# via rules-engine (pyproject.toml)
pydantic-core==2.6.3
Expand Down
56 changes: 55 additions & 1 deletion rules-engine/src/rules_engine/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
from datetime import datetime, timedelta
from enum import StrEnum

from .pydantic_models import NaturalGasBillingInput, NaturalGasBillingRecordInput
from greenbutton_objects import parse
from .pydantic_models import (
NaturalGasBillingInput,
NaturalGasBillingRecordInput,
ElectricBillingInput,
ElectricBillingRecordInput
)


class NaturalGasCompany(StrEnum):
Expand Down Expand Up @@ -177,3 +183,51 @@ def _parse_gas_bill_national_grid(data: str) -> NaturalGasBillingInput:
records.append(record)

return NaturalGasBillingInput(records=records)


def _parse_gas_bill_xml(path: str) -> NaturalGasBillingInput:
"""
Return a list of gas bill data parsed from a Green Button XML
received as a string.
"""
ups = parse.parse_feed(path)
records = []
for up in ups:
for mr in up.meterReadings:
for ir in mr.intervalReadings:
records.append(
NaturalGasBillingRecordInput(
period_start_date=ir.timePeriod.start.date(),
period_end_date=(
ir.timePeriod.start + ir.timePeriod.duration
).date(),
usage_therms=ir.value,
inclusion_override=None,
)
)

return NaturalGasBillingInput(records=records)


def _parse_electric_bill_xml(path: str) -> NaturalGasBillingInput:
"""
Return a list of gas bill data parsed from a Green Button XML
received as a string.
"""
ups = parse.parse_feed(path)
records = []
for up in ups:
for mr in up.meterReadings:
for ir in mr.intervalReadings:
records.append(
ElectricBillingRecordInput(
period_start_date=ir.timePeriod.start.date(),
period_end_date=(
ir.timePeriod.start + ir.timePeriod.duration
).date(),
usage_watt_hours=ir.value,
inclusion_override=None,
)
)

return ElectricBillingInput(records=records)
59 changes: 53 additions & 6 deletions rules-engine/src/rules_engine/pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,53 @@ class NaturalGasBillingRecordInput(BaseModel):
inclusion_override: Optional[AnalysisType] = Field(description="Natural Gas!E")


class ElectricBillingRecordInput(BaseModel):
"""From Electric tab. A single row of the Billing input table."""

period_start_date: date = Field(description="Electric!A")
period_end_date: date = Field(description="Electric!B")
usage_watt_hours: float = Field(description="Electric!D")
inclusion_override: Optional[AnalysisType] = Field(description="Electric!E")


class ElectricBillingInput(BaseModel):
"""From Electric tab. Container for holding all rows of the billing input table."""

records: Sequence[ElectricBillingRecordInput]

# Suppress mypy error when computed_field is used with cached_property; see https://github.com/python/mypy/issues/1362
@computed_field # type: ignore[misc]
@cached_property
def overall_start_date(self) -> date:
if len(self.records) == 0:
raise ValueError(
"Electric billing records cannot be empty.\n"
+ "Could not calculate overall start date from empty electricicity billing records.\n"
+ "Try again with non-empty electricicity billing records.\n"
)

min_date = date.max
for record in self.records:
min_date = min(min_date, record.period_start_date)
return min_date

# Suppress mypy error when computed_field is used with cached_property; see https://github.com/python/mypy/issues/1362
@computed_field # type: ignore[misc]
@cached_property
def overall_end_date(self) -> date:
if len(self.records) == 0:
raise ValueError(
"Electric billing records cannot be empty.\n"
+ "Could not calculate overall start date from empty electricicity billing records.\n"
+ "Try again with non-empty electricicity billing records.\n"
)

max_date = date.min
for record in self.records:
max_date = max(max_date, record.period_end_date)
return max_date


class NaturalGasBillingInput(BaseModel):
"""From Natural Gas tab. Container for holding all rows of the billing input table."""

Expand All @@ -113,9 +160,9 @@ class NaturalGasBillingInput(BaseModel):
def overall_start_date(self) -> date:
if len(self.records) == 0:
raise ValueError(
"Natural gas billing records cannot be empty."
+ "Could not calculate overall start date from empty natural gas billing records."
+ "Try again with non-empty natural gas billing records."
"Natural gas billing records cannot be empty.\n"
+ "Could not calculate overall start date from empty natural gas billing records.\n"
+ "Try again with non-empty natural gas billing records.\n"
)

min_date = date.max
Expand All @@ -129,9 +176,9 @@ def overall_start_date(self) -> date:
def overall_end_date(self) -> date:
if len(self.records) == 0:
raise ValueError(
"Natural gas billing records cannot be empty."
+ "Could not calculate overall start date from empty natural gas billing records."
+ "Try again with non-empty natural gas billing records."
"Natural gas billing records cannot be empty.\n"
+ "Could not calculate overall start date from empty natural gas billing records.\n"
+ "Try again with non-empty natural gas billing records.\n"
)

max_date = date.min
Expand Down
Loading
Loading