Skip to content

Commit

Permalink
Handle missing prices for today
Browse files Browse the repository at this point in the history
  • Loading branch information
slovdahl committed May 28, 2023
1 parent 720a00c commit 1c65f7a
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 28 deletions.
11 changes: 6 additions & 5 deletions examples/energy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,23 @@ async def main() -> None:

async def print_prices(client: SpotHinta, region: Region) -> None:
"""Print prices for the given region."""
local = pytz.timezone("Europe/Helsinki")
pytz.timezone("Europe/Helsinki")

energy_today = await client.energy_prices(region=region)
utc_next_hour = energy_today.utcnow() + timedelta(hours=1)
print(f"--- ENERGY TODAY FOR REGION {region.name}---")
print(f"Extremas price: {energy_today.extreme_prices}")
print(f"Lowest price today: {energy_today.lowest_price_today}")
print(f"Highest price today: {energy_today.highest_price_today}")
print(f"Average price: {energy_today.average_price}")
print()

highest_time = energy_today.highest_price_time.astimezone(local)
highest_time = energy_today.highest_price_time
print(f"Highest price time: {highest_time}")
lowest_time = energy_today.lowest_price_time.astimezone(local)
lowest_time = energy_today.lowest_price_time
print(f"Lowest price time: {lowest_time}")
print()
print(f"Current price: {energy_today.current_price}")
print(f"Next hourprice: {energy_today.price_at_time(utc_next_hour)}")
print(f"Next hour price: {energy_today.price_at_time(utc_next_hour)}")

lower_hours: int = energy_today.hours_priced_equal_or_lower
print(f"Lower hours: {lower_hours}")
Expand Down
56 changes: 40 additions & 16 deletions spothinta_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _timed_value(moment: datetime, prices: dict[datetime, float]) -> float | Non
def _get_pricetime(
prices: dict[datetime, float],
func: Callable[[dict[datetime, float]], datetime],
) -> datetime:
) -> datetime | None:
"""Return the time of the price.
Args:
Expand All @@ -42,8 +42,11 @@ def _get_pricetime(
Returns:
-------
The time of the price.
The time of the price or None if prices is None or empty.
"""
if prices is None or len(prices) == 0:
return None

return func(prices, key=prices.get) # type: ignore[call-arg]


Expand All @@ -59,32 +62,53 @@ def current_price(self) -> float | None:
Returns
-------
The price for the current hour.
The price for the current hour or None if no price is available.
"""
return self.price_at_time(self.utcnow())

@property
def extreme_prices(self) -> tuple[float, float]:
"""Return the minimum and maximum price.
def lowest_price_today(self) -> float | None:
"""Return the minimum price today.
Returns
-------
The minimum and maximum price.
The minimum price today or None if no prices are available for today.
"""
return round(min(self.prices_today().values()), 5), round(
max(self.prices_today().values()),
5,
)
prices = self.prices_today()

if len(prices) == 0:
return None

return round(min(prices.values()), 5)

@property
def average_price(self) -> float:
"""Return the average price.
def highest_price_today(self) -> float | None:
"""Return the maximum price today.
Returns
-------
The average price.
The maximum price today or None if no prices are available for today.
"""
prices = self.prices_today()

if len(prices) == 0:
return None

return round(max(prices.values()), 5)

@property
def average_price(self) -> float | None:
"""Return the average price today.
Returns
-------
The average price today or None if no prices are available for today.
"""
prices_today = self.prices_today()

if len(prices_today) == 0:
return None

return round(sum(prices_today.values()) / len(prices_today), 5)

@property
Expand All @@ -93,17 +117,17 @@ def highest_price_time(self) -> datetime:
Returns
-------
The time of the highest price.
The time of the highest price or None if no prices are available for today.
"""
return _get_pricetime(self.prices_today(), max)

@property
def lowest_price_time(self) -> datetime:
def lowest_price_time(self) -> datetime | None:
"""Return the time of the lowest price.
Returns
-------
The time of the lowest price.
The time of the lowest price or None if no prices are available for today.
"""
return _get_pricetime(self.prices_today(), min)

Expand Down
146 changes: 146 additions & 0 deletions tests/fixtures/energy-only-tomorrow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
[
{
"Rank": 22,
"DateTime": "2023-05-07T00:00:00+03:00",
"PriceNoTax": 0.0732,
"PriceWithTax": 0.0907
},
{
"Rank": 8,
"DateTime": "2023-05-07T01:00:00+03:00",
"PriceNoTax": 0.0614,
"PriceWithTax": 0.0761
},
{
"Rank": 10,
"DateTime": "2023-05-07T02:00:00+03:00",
"PriceNoTax": 0.0618,
"PriceWithTax": 0.0766
},
{
"Rank": 9,
"DateTime": "2023-05-07T03:00:00+03:00",
"PriceNoTax": 0.0617,
"PriceWithTax": 0.0765
},
{
"Rank": 11,
"DateTime": "2023-05-07T04:00:00+03:00",
"PriceNoTax": 0.062,
"PriceWithTax": 0.0769
},
{
"Rank": 12,
"DateTime": "2023-05-07T05:00:00+03:00",
"PriceNoTax": 0.0637,
"PriceWithTax": 0.079
},
{
"Rank": 13,
"DateTime": "2023-05-07T06:00:00+03:00",
"PriceNoTax": 0.0645,
"PriceWithTax": 0.08
},
{
"Rank": 14,
"DateTime": "2023-05-07T07:00:00+03:00",
"PriceNoTax": 0.066,
"PriceWithTax": 0.0819
},
{
"Rank": 24,
"DateTime": "2023-05-07T08:00:00+03:00",
"PriceNoTax": 0.0784,
"PriceWithTax": 0.0972
},
{
"Rank": 23,
"DateTime": "2023-05-07T09:00:00+03:00",
"PriceNoTax": 0.0759,
"PriceWithTax": 0.0941
},
{
"Rank": 17,
"DateTime": "2023-05-07T10:00:00+03:00",
"PriceNoTax": 0.0676,
"PriceWithTax": 0.0838
},
{
"Rank": 5,
"DateTime": "2023-05-07T11:00:00+03:00",
"PriceNoTax": 0.0374,
"PriceWithTax": 0.0464
},
{
"Rank": 2,
"DateTime": "2023-05-07T12:00:00+03:00",
"PriceNoTax": 0.0305,
"PriceWithTax": 0.0378
},
{
"Rank": 6,
"DateTime": "2023-05-07T13:00:00+03:00",
"PriceNoTax": 0.0393,
"PriceWithTax": 0.0487
},
{
"Rank": 4,
"DateTime": "2023-05-07T14:00:00+03:00",
"PriceNoTax": 0.0351,
"PriceWithTax": 0.0435
},
{
"Rank": 3,
"DateTime": "2023-05-07T15:00:00+03:00",
"PriceNoTax": 0.0307,
"PriceWithTax": 0.038
},
{
"Rank": 1,
"DateTime": "2023-05-07T16:00:00+03:00",
"PriceNoTax": 0.0301,
"PriceWithTax": 0.0373
},
{
"Rank": 7,
"DateTime": "2023-05-07T17:00:00+03:00",
"PriceNoTax": 0.0566,
"PriceWithTax": 0.0702
},
{
"Rank": 19,
"DateTime": "2023-05-07T18:00:00+03:00",
"PriceNoTax": 0.0689,
"PriceWithTax": 0.0854
},
{
"Rank": 20,
"DateTime": "2023-05-07T19:00:00+03:00",
"PriceNoTax": 0.0722,
"PriceWithTax": 0.0895
},
{
"Rank": 21,
"DateTime": "2023-05-07T20:00:00+03:00",
"PriceNoTax": 0.0723,
"PriceWithTax": 0.0896
},
{
"Rank": 18,
"DateTime": "2023-05-07T21:00:00+03:00",
"PriceNoTax": 0.0679,
"PriceWithTax": 0.0842
},
{
"Rank": 15,
"DateTime": "2023-05-07T22:00:00+03:00",
"PriceNoTax": 0.066,
"PriceWithTax": 0.0819
},
{
"Rank": 16,
"DateTime": "2023-05-07T23:00:00+03:00",
"PriceNoTax": 0.0668,
"PriceWithTax": 0.0828
}
]
45 changes: 38 additions & 7 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@


@pytest.mark.freeze_time("2023-05-06 15:00:00+03:00")
async def test_electricity_model(aresponses: ResponsesMockServer) -> None:
"""Test the electricity model for usage at 15:00:00 UTC+3."""
async def test_model(aresponses: ResponsesMockServer) -> None:
"""Test the model for usage at 15:00:00 UTC+3."""
aresponses.add(
"api.spot-hinta.fi",
"/TodayAndDayForward",
Expand All @@ -28,8 +28,8 @@ async def test_electricity_model(aresponses: ResponsesMockServer) -> None:
energy: Electricity = await client.energy_prices()
assert energy is not None
assert isinstance(energy, Electricity)
assert energy.extreme_prices[1] == 0.1055
assert energy.extreme_prices[0] == 0.062
assert energy.highest_price_today == 0.1055
assert energy.lowest_price_today == 0.062
assert energy.average_price == 0.08935
assert energy.current_price == 0.062
assert energy.hours_priced_equal_or_lower == 1
Expand All @@ -48,8 +48,8 @@ async def test_electricity_model(aresponses: ResponsesMockServer) -> None:


@pytest.mark.freeze_time("2023-05-06 15:00:00+03:00")
async def test_electricity_midnight(aresponses: ResponsesMockServer) -> None:
"""Test the electricity model between 00:00 and 01:00 in UTC+3."""
async def test_midnight(aresponses: ResponsesMockServer) -> None:
"""Test the model between 00:00 and 01:00 in UTC+3."""
aresponses.add(
"api.spot-hinta.fi",
"/TodayAndDayForward",
Expand All @@ -67,7 +67,7 @@ async def test_electricity_midnight(aresponses: ResponsesMockServer) -> None:
assert energy.current_price == 0.062


async def test_electricity_none_data(aresponses: ResponsesMockServer) -> None:
async def test_none_data(aresponses: ResponsesMockServer) -> None:
"""Test when there is no data for the current datetime."""
aresponses.add(
"api.spot-hinta.fi",
Expand All @@ -87,6 +87,37 @@ async def test_electricity_none_data(aresponses: ResponsesMockServer) -> None:
assert energy.current_price is None


@pytest.mark.freeze_time("2023-05-06 19:01:32+03:00")
async def test_only_data_for_tomorrow(aresponses: ResponsesMockServer) -> None:
"""Test when there is only data for tomorrow."""
aresponses.add(
"api.spot-hinta.fi",
"/TodayAndDayForward",
"GET",
aresponses.Response(
status=200,
headers={"Content-Type": "application/json"},
text=load_fixtures("energy-only-tomorrow.json"),
),
)
async with ClientSession() as session:
client = SpotHinta(session=session)
energy: Electricity = await client.energy_prices()
assert energy is not None
assert isinstance(energy, Electricity)
assert energy.current_price is None
assert energy.average_price is None
assert energy.highest_price_today is None
assert energy.lowest_price_today is None
assert energy.hours_priced_equal_or_lower == 0
# The price for another hour
another_hour = datetime(2023, 5, 6, 17, 0, tzinfo=timezone.utc)
assert energy.price_at_time(another_hour) is None
assert energy.lowest_price_time is None
assert energy.highest_price_time is None
assert isinstance(energy.timestamp_prices, list)


async def test_no_electricity_data(aresponses: ResponsesMockServer) -> None:
"""Test when there is no electricity data."""
aresponses.add(
Expand Down

0 comments on commit 1c65f7a

Please sign in to comment.