From d2eaa8ae3cc7756df1e084b3bbf9d15596e937d9 Mon Sep 17 00:00:00 2001 From: Alan Pinkert Date: Wed, 5 Jun 2024 00:37:55 +0000 Subject: [PATCH 1/2] added start and end dates to parser output Co-authored-by: Debajyoti Debnath Co-authored-by: eriksynn Co-authored-by: AdamFinkle Co-authored-by: Jonathan Kwan Co-authored-by: thatoldplatitude --- .../src/rules_engine/pydantic_models.py | 33 +++++++++++- .../test_rules_engine/test_pydantic_models.py | 53 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 rules-engine/tests/test_rules_engine/test_pydantic_models.py diff --git a/rules-engine/src/rules_engine/pydantic_models.py b/rules-engine/src/rules_engine/pydantic_models.py index 3c233b14..497c19e5 100644 --- a/rules-engine/src/rules_engine/pydantic_models.py +++ b/rules-engine/src/rules_engine/pydantic_models.py @@ -5,9 +5,10 @@ from dataclasses import dataclass from datetime import date from enum import Enum +from functools import cached_property from typing import Annotated, Any, Optional, Sequence -from pydantic import BaseModel, BeforeValidator, ConfigDict, Field +from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, computed_field class AnalysisType(Enum): @@ -106,6 +107,36 @@ class NaturalGasBillingInput(BaseModel): records: Sequence[NaturalGasBillingRecordInput] + @computed_field # type: ignore[misc] + @cached_property + 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." + ) + + min_date = date.max + for record in self.records: + min_date = min(min_date, record.period_start_date) + return min_date + + @computed_field # type: ignore[misc] + @cached_property + 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." + ) + + max_date = date.min + for record in self.records: + max_date = max(max_date, record.period_end_date) + return max_date + class NormalizedBillingPeriodRecordBase(BaseModel): """ diff --git a/rules-engine/tests/test_rules_engine/test_pydantic_models.py b/rules-engine/tests/test_rules_engine/test_pydantic_models.py new file mode 100644 index 00000000..1ca88ce9 --- /dev/null +++ b/rules-engine/tests/test_rules_engine/test_pydantic_models.py @@ -0,0 +1,53 @@ +from datetime import date + +import pytest + +from rules_engine.pydantic_models import ( + NaturalGasBillingInput, + NaturalGasBillingRecordInput, +) + +_EXAMPLE_VALID_RECORDS = NaturalGasBillingInput( + records=[ + NaturalGasBillingRecordInput( + period_start_date=date(2020, 1, 1), + period_end_date=date(2020, 1, 31), + usage_therms=10, + inclusion_override=None, + ), + NaturalGasBillingRecordInput( + period_start_date=date(2020, 2, 1), + period_end_date=date(2020, 2, 28), + usage_therms=10, + inclusion_override=None, + ), + ] +) + +_EXAMPLE_INVALID_RECORDS = NaturalGasBillingInput( + records=[] # create billing input with no records +) + + +def test_natural_gas_billing_input_overall_start_date(): + expected_overall_start_date = date(2020, 1, 1) + actual_overall_start_date = _EXAMPLE_VALID_RECORDS.overall_start_date + + assert expected_overall_start_date == actual_overall_start_date + + +def test_natural_gas_billing_input_overall_end_date(): + expected_overall_end_date = date(2020, 2, 28) + actual_overall_end_date = _EXAMPLE_VALID_RECORDS.overall_end_date + + assert expected_overall_end_date == actual_overall_end_date + + +def test_natural_gas_billing_input_overall_start_date_invalid(): + with pytest.raises(ValueError): + _EXAMPLE_INVALID_RECORDS.overall_start_date + + +def test_natural_gas_billing_input_overall_end_date_invalid(): + with pytest.raises(ValueError): + _EXAMPLE_INVALID_RECORDS.overall_end_date From 9799e6e82bbcce01726534aff52c4000cb35d561 Mon Sep 17 00:00:00 2001 From: Alan Pinkert Date: Wed, 5 Jun 2024 00:46:12 +0000 Subject: [PATCH 2/2] added comment tagging mypy issue --- rules-engine/src/rules_engine/pydantic_models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules-engine/src/rules_engine/pydantic_models.py b/rules-engine/src/rules_engine/pydantic_models.py index 497c19e5..dfb5a081 100644 --- a/rules-engine/src/rules_engine/pydantic_models.py +++ b/rules-engine/src/rules_engine/pydantic_models.py @@ -107,6 +107,7 @@ class NaturalGasBillingInput(BaseModel): records: Sequence[NaturalGasBillingRecordInput] + # 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: @@ -122,6 +123,7 @@ def overall_start_date(self) -> date: 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: