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

Merge v0.1.18 #14

Merged
merged 8 commits into from
Oct 2, 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
80,076 changes: 55,725 additions & 24,351 deletions data/dyntar/sample_output.json

Large diffs are not rendered by default.

275 changes: 233 additions & 42 deletions demo_dyntar_analysis.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion openenergyid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Open Energy ID Python SDK."""

__version__ = "0.1.17"
__version__ = "0.1.18"

from .enums import Granularity
from .models import TimeDataFrame, TimeSeries
Expand Down
4 changes: 4 additions & 0 deletions openenergyid/dyntar/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
HEATMAP_DELIVERED = "heatmap_delivered"
HEATMAP_EXPORTED = "heatmap_exported"
HEATMAP_TOTAL = "heatmap_total"

HEATMAP_DELIVERED_DESCRIPTION = "heatmap_delivered_description"
HEATMAP_EXPORTED_DESCRIPTION = "heatmap_exported_description"
HEATMAP_TOTAL_DESCRIPTION = "heatmap_total_description"
117 changes: 115 additions & 2 deletions openenergyid/dyntar/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
HEATMAP_DELIVERED,
HEATMAP_EXPORTED,
HEATMAP_TOTAL,
HEATMAP_DELIVERED_DESCRIPTION,
HEATMAP_EXPORTED_DESCRIPTION,
HEATMAP_TOTAL_DESCRIPTION,
)


Expand Down Expand Up @@ -147,8 +150,7 @@ def extend_dataframe_with_heatmap(df: pd.DataFrame, inplace: bool = False) -> pd

# Invert scores so that positive values indicate a positive impact
heatmap_score_delivered = -heatmap_score_delivered
heatmap_score_exported = -heatmap_score_exported
heatmap_score_combined = heatmap_score_delivered - heatmap_score_exported
heatmap_score_combined = heatmap_score_delivered + heatmap_score_exported

df[HEATMAP_DELIVERED] = heatmap_score_delivered
df[HEATMAP_EXPORTED] = heatmap_score_exported
Expand All @@ -159,6 +161,116 @@ def extend_dataframe_with_heatmap(df: pd.DataFrame, inplace: bool = False) -> pd
return None


def extend_dataframe_with_heatmap_description(
df: pd.DataFrame, inplace: bool = False
) -> pd.DataFrame | None:
"""Extend a DataFrame with the heatmap description columns."""
if not inplace:
df = df.copy()

# Delivered

# Where Heatmap is 0, we put a desription of 0 (No impact)
df[HEATMAP_DELIVERED_DESCRIPTION] = df[HEATMAP_DELIVERED].apply(
lambda x: 0 if x == 0 else float("NaN")
)
# When the energy delta is positive, and the price delta is positive, we put a description of 1 (high consumption, high price)
df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
lambda x: 1
if x[PRICE_ELECTRICITY_DELIVERED] > x[RLP_WEIGHTED_PRICE_DELIVERED]
and x[ELECTRICITY_DELIVERED_SMR3] > x[ELECTRICITY_DELIVERED_SMR2]
else x[HEATMAP_DELIVERED_DESCRIPTION],
axis=1,
)
# When the energy delta is negative, and the price delta is positive, we put a description of 2 (low consumption, high price)
df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
lambda x: 2
if x[PRICE_ELECTRICITY_DELIVERED] > x[RLP_WEIGHTED_PRICE_DELIVERED]
and x[ELECTRICITY_DELIVERED_SMR3] < x[ELECTRICITY_DELIVERED_SMR2]
else x[HEATMAP_DELIVERED_DESCRIPTION],
axis=1,
)
# When the energy delta is positive, and the price delta is negative, we put a description of 3 (high consumption, low price)
df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
lambda x: 3
if x[PRICE_ELECTRICITY_DELIVERED] < x[RLP_WEIGHTED_PRICE_DELIVERED]
and x[ELECTRICITY_DELIVERED_SMR3] > x[ELECTRICITY_DELIVERED_SMR2]
else x[HEATMAP_DELIVERED_DESCRIPTION],
axis=1,
)
# When the energy delta is negative, and the price delta is negative, we put a description of 4 (low consumption, low price)
df[HEATMAP_DELIVERED_DESCRIPTION] = df.apply(
lambda x: 4
if x[PRICE_ELECTRICITY_DELIVERED] < x[RLP_WEIGHTED_PRICE_DELIVERED]
and x[ELECTRICITY_DELIVERED_SMR3] < x[ELECTRICITY_DELIVERED_SMR2]
else x[HEATMAP_DELIVERED_DESCRIPTION],
axis=1,
)
# All other cases are put as 0
df[HEATMAP_DELIVERED_DESCRIPTION] = df[HEATMAP_DELIVERED_DESCRIPTION].replace(np.nan, 0)

# Exported

# Where Heatmap is 0, we put a desription of 0 (No impact)
df[HEATMAP_EXPORTED_DESCRIPTION] = df[HEATMAP_EXPORTED].apply(
lambda x: 0 if x == 0 else float("NaN")
)
# When the energy delta is positive, and the price delta is positive, we put a description of 5 (high injection, high price)
df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
lambda x: 5
if x[PRICE_ELECTRICITY_EXPORTED] > x[SPP_WEIGHTED_PRICE_EXPORTED]
and x[ELECTRICITY_EXPORTED_SMR3] > x[ELECTRICITY_EXPORTED_SMR2]
else x[HEATMAP_EXPORTED_DESCRIPTION],
axis=1,
)
# When the energy delta is negative, and the price delta is positive, we put a description of 6 (low injection, high price)
df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
lambda x: 6
if x[PRICE_ELECTRICITY_EXPORTED] > x[SPP_WEIGHTED_PRICE_EXPORTED]
and x[ELECTRICITY_EXPORTED_SMR3] < x[ELECTRICITY_EXPORTED_SMR2]
else x[HEATMAP_EXPORTED_DESCRIPTION],
axis=1,
)
# When the energy delta is positive, and the price delta is negative, we put a description of 7 (high injection, low price)
df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
lambda x: 7
if x[PRICE_ELECTRICITY_EXPORTED] < x[SPP_WEIGHTED_PRICE_EXPORTED]
and x[ELECTRICITY_EXPORTED_SMR3] > x[ELECTRICITY_EXPORTED_SMR2]
else x[HEATMAP_EXPORTED_DESCRIPTION],
axis=1,
)
# When the energy delta is negative, and the price delta is negative, we put a description of 8 (low injection, low price)
df[HEATMAP_EXPORTED_DESCRIPTION] = df.apply(
lambda x: 8
if x[PRICE_ELECTRICITY_EXPORTED] < x[SPP_WEIGHTED_PRICE_EXPORTED]
and x[ELECTRICITY_EXPORTED_SMR3] < x[ELECTRICITY_EXPORTED_SMR2]
else x[HEATMAP_EXPORTED_DESCRIPTION],
axis=1,
)
# All other cases are put as 0
df[HEATMAP_EXPORTED_DESCRIPTION] = df[HEATMAP_EXPORTED_DESCRIPTION].replace(np.nan, 0)

# Total

# We see which of the individual heatmaps has the highest absolute value
# We put the description of the highest absolute value
df[HEATMAP_TOTAL_DESCRIPTION] = df.apply(
lambda x: x[HEATMAP_DELIVERED_DESCRIPTION]
if abs(x[HEATMAP_DELIVERED]) > abs(x[HEATMAP_EXPORTED])
else x[HEATMAP_EXPORTED_DESCRIPTION],
axis=1,
)
# Where Heatmap is 0, we put a desription of 0 (No impact)
df[HEATMAP_TOTAL_DESCRIPTION] = df.apply(
lambda x: 0 if x[HEATMAP_TOTAL] == 0 else x[HEATMAP_TOTAL_DESCRIPTION], axis=1
)
# All other cases are put as 0
df[HEATMAP_TOTAL_DESCRIPTION] = df[HEATMAP_TOTAL_DESCRIPTION].replace(np.nan, 0)

if not inplace:
return df


def calculate_dyntar_columns(df: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
"""Calculate all columns required for the dynamic tariff analysis."""
if not inplace:
Expand All @@ -168,6 +280,7 @@ def calculate_dyntar_columns(df: pd.DataFrame, inplace: bool = False) -> pd.Data
extend_dataframe_with_costs(df, inplace=True)
extend_dataframe_with_weighted_prices(df, inplace=True)
extend_dataframe_with_heatmap(df, inplace=True)
extend_dataframe_with_heatmap_description(df, inplace=True)

if not inplace:
return df
Expand Down
17 changes: 12 additions & 5 deletions openenergyid/dyntar/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Models for dynamic tariff analysis."""

from typing import Literal
from pydantic import Field, conlist
from pydantic import Field, conlist, confloat

from openenergyid.models import TimeDataFrame

Expand Down Expand Up @@ -33,6 +33,9 @@
"heatmap_delivered",
"heatmap_exported",
"heatmap_total",
"heatmap_delivered_description",
"heatmap_exported_description",
"heatmap_total_description",
]


Expand All @@ -46,11 +49,11 @@ class DynamicTariffAnalysisInput(TimeDataFrame):
)
data: list[
conlist(
item_type=float,
item_type=confloat(allow_inf_nan=True),
min_length=len(RequiredColumns.__args__),
max_length=len(RequiredColumns.__args__),
) # type: ignore
] = Field(examples=[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]])
] = Field(examples=[[0.0] * len(RequiredColumns.__args__)])


class DynamicTariffAnalysisOutput(TimeDataFrame):
Expand All @@ -62,5 +65,9 @@ class DynamicTariffAnalysisOutput(TimeDataFrame):
examples=[OutputColumns.__args__],
)
data: list[
conlist(item_type=float, min_length=1, max_length=len(OutputColumns.__args__)) # type: ignore
] = Field(examples=[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]])
conlist(
item_type=confloat(allow_inf_nan=True),
min_length=1,
max_length=len(OutputColumns.__args__),
) # type: ignore
] = Field(examples=[[0.0] * len(OutputColumns.__args__)])
18 changes: 3 additions & 15 deletions openenergyid/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Self

import pandas as pd
from pydantic import BaseModel, field_validator
from pydantic import BaseModel


class TimeSeriesBase(BaseModel):
Expand Down Expand Up @@ -78,13 +78,7 @@ class TimeSeries(TimeSeriesBase):
"""

name: str | None = None
data: list[float | None]

@field_validator("data")
@classmethod
def replace_nan_with_none(cls, data: list[float]) -> list[float | None]:
"""Replace NaN values with None."""
return [None if pd.isna(value) else value for value in data]
data: list[float]

@classmethod
def from_pandas(cls, data: pd.Series) -> Self:
Expand All @@ -102,13 +96,7 @@ class TimeDataFrame(TimeSeriesBase):
"""Time series data with multiple columns."""

columns: list[str]
data: list[list[float | None]]

@field_validator("data")
@classmethod
def replace_nan_with_none(cls, data: list[list[float]]) -> list[list[float | None]]:
"""Replace NaN values with None."""
return [[None if pd.isna(value) else value for value in row] for row in data]
data: list[list[float]]

@classmethod
def from_pandas(cls, data: pd.DataFrame) -> Self:
Expand Down
Loading
Loading