openenergyid 0.1.28__tar.gz → 0.1.30__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of openenergyid might be problematic. Click here for more details.
- openenergyid-0.1.30/PKG-INFO +32 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/__init__.py +1 -1
- openenergyid-0.1.30/openenergyid/abstractsim/__init__.py +5 -0
- openenergyid-0.1.30/openenergyid/abstractsim/abstract.py +102 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/baseload/__init__.py +1 -1
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/baseload/analysis.py +5 -3
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/capacity/main.py +1 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/capacity/models.py +2 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/const.py +11 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/dyntar/main.py +10 -9
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/dyntar/models.py +5 -14
- openenergyid-0.1.30/openenergyid/elia/__init__.py +4 -0
- openenergyid-0.1.30/openenergyid/elia/api.py +91 -0
- openenergyid-0.1.30/openenergyid/elia/const.py +18 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/energysharing/data_formatting.py +9 -1
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/energysharing/main.py +13 -2
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/energysharing/models.py +3 -2
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/models.py +15 -5
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/mvlr/models.py +2 -3
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/mvlr/mvlr.py +3 -3
- openenergyid-0.1.30/openenergyid/pvsim/__init__.py +8 -0
- openenergyid-0.1.30/openenergyid/pvsim/abstract.py +60 -0
- openenergyid-0.1.30/openenergyid/pvsim/elia/__init__.py +3 -0
- openenergyid-0.1.30/openenergyid/pvsim/elia/main.py +89 -0
- openenergyid-0.1.30/openenergyid/pvsim/main.py +49 -0
- openenergyid-0.1.30/openenergyid/pvsim/pvlib/__init__.py +11 -0
- openenergyid-0.1.30/openenergyid/pvsim/pvlib/main.py +115 -0
- openenergyid-0.1.30/openenergyid/pvsim/pvlib/models.py +235 -0
- openenergyid-0.1.30/openenergyid/pvsim/pvlib/quickscan.py +99 -0
- openenergyid-0.1.30/openenergyid/pvsim/pvlib/weather.py +91 -0
- openenergyid-0.1.30/openenergyid/sim/__init__.py +5 -0
- openenergyid-0.1.30/openenergyid/sim/main.py +67 -0
- openenergyid-0.1.30/openenergyid/simeval/__init__.py +6 -0
- openenergyid-0.1.30/openenergyid/simeval/main.py +148 -0
- openenergyid-0.1.30/openenergyid/simeval/models.py +162 -0
- openenergyid-0.1.30/openenergyid.egg-info/PKG-INFO +32 -0
- openenergyid-0.1.30/openenergyid.egg-info/SOURCES.txt +53 -0
- openenergyid-0.1.30/openenergyid.egg-info/dependency_links.txt +1 -0
- openenergyid-0.1.30/openenergyid.egg-info/requires.txt +5 -0
- openenergyid-0.1.30/openenergyid.egg-info/top_level.txt +1 -0
- openenergyid-0.1.30/pyproject.toml +60 -0
- openenergyid-0.1.30/setup.cfg +4 -0
- openenergyid-0.1.28/PKG-INFO +0 -41
- openenergyid-0.1.28/pyproject.toml +0 -65
- {openenergyid-0.1.28 → openenergyid-0.1.30}/LICENSE +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/README.md +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/baseload/exceptions.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/baseload/models.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/capacity/__init__.py +1 -1
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/dyntar/__init__.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/dyntar/const.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/energysharing/__init__.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/energysharing/const.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/enums.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/mvlr/__init__.py +1 -1
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/mvlr/helpers.py +0 -0
- {openenergyid-0.1.28 → openenergyid-0.1.30}/openenergyid/mvlr/main.py +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openenergyid
|
|
3
|
+
Version: 0.1.30
|
|
4
|
+
Summary: Open Source Python library for energy analytics and simulations
|
|
5
|
+
Author-email: Jan Pecinovsky <jan@energieid.be>, Max Helskens <max@energieid.be>, Oscar Swyns <oscar@energieid.be>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: energy,analytics,simulation
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Natural Language :: English
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: aiohttp>=3.12.15
|
|
22
|
+
Requires-Dist: pandera[polars]>=0.22.1
|
|
23
|
+
Requires-Dist: pvlib>=0.13.0
|
|
24
|
+
Requires-Dist: pydantic>=2.8.2
|
|
25
|
+
Requires-Dist: statsmodels>=0.14.2
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# OpenEnergyID
|
|
29
|
+
|
|
30
|
+
Open Source Python library for energy data analytics and simulations
|
|
31
|
+
|
|
32
|
+
[*more info for developers*](DEVELOPERS.md)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Annotated, Self
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from aiohttp import ClientSession
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from ..simeval.models import ComparisonPayload, EvalPayload, EvaluationOutput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SimulationInputAbstract(BaseModel):
|
|
12
|
+
"""Abstract input parameters for any Simulation"""
|
|
13
|
+
|
|
14
|
+
type: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SimulationSummary(BaseModel):
|
|
18
|
+
"""Summary of a simulation including ex-ante, simulation results, ex-post, and comparisons."""
|
|
19
|
+
|
|
20
|
+
ex_ante: Annotated[EvalPayload, Field(description="Ex-ante evaluation results.")]
|
|
21
|
+
simulation_result: Annotated[EvalPayload, Field(description="Simulation results.")]
|
|
22
|
+
ex_post: Annotated[EvalPayload, Field(description="Ex-post evaluation results.")]
|
|
23
|
+
comparison: Annotated[
|
|
24
|
+
ComparisonPayload, Field(description="Comparison between ex-ante and ex-post results.")
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def from_simulation(
|
|
29
|
+
cls,
|
|
30
|
+
ex_ante: dict[str, pd.DataFrame | pd.Series],
|
|
31
|
+
simulation_result: dict[str, pd.DataFrame | pd.Series],
|
|
32
|
+
ex_post: dict[str, pd.DataFrame | pd.Series],
|
|
33
|
+
comparison: dict[str, dict[str, pd.DataFrame | pd.Series]],
|
|
34
|
+
) -> Self:
|
|
35
|
+
"""Create a SimulationSummary from simulation data."""
|
|
36
|
+
ea = {
|
|
37
|
+
k: EvaluationOutput.from_pandas(v) if isinstance(v, pd.DataFrame) else v.to_dict()
|
|
38
|
+
for k, v in ex_ante.items()
|
|
39
|
+
}
|
|
40
|
+
sr = {
|
|
41
|
+
k: EvaluationOutput.from_pandas(v) if isinstance(v, pd.DataFrame) else v.to_dict()
|
|
42
|
+
for k, v in simulation_result.items()
|
|
43
|
+
}
|
|
44
|
+
ep = {
|
|
45
|
+
k: EvaluationOutput.from_pandas(v) if isinstance(v, pd.DataFrame) else v.to_dict()
|
|
46
|
+
for k, v in ex_post.items()
|
|
47
|
+
}
|
|
48
|
+
c = {
|
|
49
|
+
k: {
|
|
50
|
+
kk: EvaluationOutput.from_pandas(vv)
|
|
51
|
+
if isinstance(vv, pd.DataFrame)
|
|
52
|
+
else vv.to_dict()
|
|
53
|
+
for kk, vv in v.items()
|
|
54
|
+
}
|
|
55
|
+
for k, v in comparison.items()
|
|
56
|
+
}
|
|
57
|
+
return cls(
|
|
58
|
+
ex_ante=ea, # type: ignore
|
|
59
|
+
simulation_result=sr, # type: ignore
|
|
60
|
+
ex_post=ep, # type: ignore
|
|
61
|
+
comparison=c, # type: ignore
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Simulator(ABC):
|
|
66
|
+
"""
|
|
67
|
+
An abstract base class simulators.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def simulation_results(self):
|
|
73
|
+
"""The results of the simulation."""
|
|
74
|
+
raise NotImplementedError()
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def simulate(self, **kwargs):
|
|
78
|
+
"""
|
|
79
|
+
Run the simulation and return the results.
|
|
80
|
+
"""
|
|
81
|
+
raise NotImplementedError()
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def result_as_frame(self) -> pd.DataFrame:
|
|
85
|
+
"""
|
|
86
|
+
Convert the simulation results to a DataFrame.
|
|
87
|
+
"""
|
|
88
|
+
raise NotImplementedError()
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_pydantic(cls, input_: SimulationInputAbstract) -> Self:
|
|
92
|
+
"""
|
|
93
|
+
Create an instance of the simulator from Pydantic input data.
|
|
94
|
+
"""
|
|
95
|
+
return cls(**input_.model_dump())
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
async def load_resources(self, session: ClientSession) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Asynchronously load any required resources using the provided session.
|
|
101
|
+
"""
|
|
102
|
+
raise NotImplementedError()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Baseload analysis package for power consumption data."""
|
|
2
2
|
|
|
3
|
-
from .models import PowerReadingSchema, PowerSeriesSchema, BaseloadResultSchema
|
|
4
3
|
from .analysis import BaseloadAnalyzer
|
|
5
4
|
from .exceptions import InsufficientDataError, InvalidDataError
|
|
5
|
+
from .models import BaseloadResultSchema, PowerReadingSchema, PowerSeriesSchema
|
|
6
6
|
|
|
7
7
|
__version__ = "0.1.0"
|
|
8
8
|
__all__ = [
|
|
@@ -163,11 +163,13 @@ class BaseloadAnalyzer:
|
|
|
163
163
|
pl.col("total_consumption_in_kilowatthour")
|
|
164
164
|
- pl.col("consumption_due_to_baseload_in_kilowatthour")
|
|
165
165
|
).alias("consumption_not_due_to_baseload_in_kilowatthour"),
|
|
166
|
-
|
|
167
|
-
(
|
|
166
|
+
pl.when(pl.col("total_consumption_in_kilowatthour") != 0)
|
|
167
|
+
.then(
|
|
168
168
|
pl.col("consumption_due_to_baseload_in_kilowatthour")
|
|
169
169
|
/ pl.col("total_consumption_in_kilowatthour")
|
|
170
|
-
)
|
|
170
|
+
)
|
|
171
|
+
.otherwise(None)
|
|
172
|
+
.alias("baseload_ratio"),
|
|
171
173
|
]
|
|
172
174
|
)
|
|
173
175
|
)
|
|
@@ -7,6 +7,8 @@ from typing import Literal
|
|
|
7
7
|
ELECTRICITY_DELIVERED: Literal["electricity_delivered"] = "electricity_delivered"
|
|
8
8
|
ELECTRICITY_EXPORTED: Literal["electricity_exported"] = "electricity_exported"
|
|
9
9
|
ELECTRICITY_PRODUCED: Literal["electricity_produced"] = "electricity_produced"
|
|
10
|
+
ELECTRICITY_CONSUMED: Literal["electricity_consumed"] = "electricity_consumed"
|
|
11
|
+
ELECTRICITY_SELF_CONSUMED: Literal["electricity_self_consumed"] = "electricity_self_consumed"
|
|
10
12
|
|
|
11
13
|
PRICE_DAY_AHEAD: Literal["price_day_ahead"] = "price_day_ahead"
|
|
12
14
|
PRICE_IMBALANCE_UPWARD: Literal["price_imbalance_upward"] = "price_imbalance_upward"
|
|
@@ -16,3 +18,12 @@ PRICE_ELECTRICITY_EXPORTED: Literal["price_electricity_exported"] = "price_elect
|
|
|
16
18
|
|
|
17
19
|
RLP: Literal["RLP"] = "RLP"
|
|
18
20
|
SPP: Literal["SPP"] = "SPP"
|
|
21
|
+
|
|
22
|
+
COST_ELECTRICITY_DELIVERED: Literal["cost_electricity_delivered"] = "cost_electricity_delivered"
|
|
23
|
+
EARNINGS_ELECTRICITY_EXPORTED: Literal["earnings_electricity_exported"] = (
|
|
24
|
+
"earnings_electricity_exported"
|
|
25
|
+
)
|
|
26
|
+
COST_ELECTRICITY_NET: Literal["cost_electricity_net"] = "cost_electricity_net"
|
|
27
|
+
|
|
28
|
+
RATIO_SELF_CONSUMPTION: Literal["ratio_self_consumption"] = "ratio_self_consumption"
|
|
29
|
+
RATIO_SELF_SUFFICIENCY: Literal["ratio_self_sufficiency"] = "ratio_self_sufficiency"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Main module of the DynTar package."""
|
|
2
2
|
|
|
3
3
|
from typing import cast
|
|
4
|
+
|
|
4
5
|
import pandas as pd
|
|
5
6
|
|
|
6
7
|
from openenergyid.const import (
|
|
@@ -13,22 +14,22 @@ from openenergyid.const import (
|
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
from .const import (
|
|
16
|
-
ELECTRICITY_DELIVERED_SMR3,
|
|
17
|
-
ELECTRICITY_EXPORTED_SMR3,
|
|
18
|
-
ELECTRICITY_DELIVERED_SMR2,
|
|
19
|
-
ELECTRICITY_EXPORTED_SMR2,
|
|
20
17
|
COST_ELECTRICITY_DELIVERED_SMR2,
|
|
21
|
-
COST_ELECTRICITY_EXPORTED_SMR2,
|
|
22
18
|
COST_ELECTRICITY_DELIVERED_SMR3,
|
|
19
|
+
COST_ELECTRICITY_EXPORTED_SMR2,
|
|
23
20
|
COST_ELECTRICITY_EXPORTED_SMR3,
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
ELECTRICITY_DELIVERED_SMR2,
|
|
22
|
+
ELECTRICITY_DELIVERED_SMR3,
|
|
23
|
+
ELECTRICITY_EXPORTED_SMR2,
|
|
24
|
+
ELECTRICITY_EXPORTED_SMR3,
|
|
26
25
|
HEATMAP_DELIVERED,
|
|
27
|
-
HEATMAP_EXPORTED,
|
|
28
|
-
HEATMAP_TOTAL,
|
|
29
26
|
HEATMAP_DELIVERED_DESCRIPTION,
|
|
27
|
+
HEATMAP_EXPORTED,
|
|
30
28
|
HEATMAP_EXPORTED_DESCRIPTION,
|
|
29
|
+
HEATMAP_TOTAL,
|
|
31
30
|
HEATMAP_TOTAL_DESCRIPTION,
|
|
31
|
+
RLP_WEIGHTED_PRICE_DELIVERED,
|
|
32
|
+
SPP_WEIGHTED_PRICE_EXPORTED,
|
|
32
33
|
Register,
|
|
33
34
|
)
|
|
34
35
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Models for dynamic tariff analysis."""
|
|
2
2
|
|
|
3
3
|
from typing import Literal
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, confloat, conlist
|
|
5
6
|
|
|
6
7
|
from openenergyid.models import TimeDataFrame
|
|
7
|
-
from .const import Register
|
|
8
8
|
|
|
9
|
+
from .const import Register
|
|
9
10
|
|
|
10
11
|
RequiredColumns = Literal[
|
|
11
12
|
"electricity_delivered",
|
|
@@ -95,16 +96,6 @@ class DynamicTariffAnalysisOutputSummary(BaseModel):
|
|
|
95
96
|
class DynamicTariffAnalysisOutput(TimeDataFrame):
|
|
96
97
|
"""Output frame for dynamic tariff analysis."""
|
|
97
98
|
|
|
98
|
-
columns: list[
|
|
99
|
-
|
|
100
|
-
max_length=len(OutputColumns.__args__),
|
|
101
|
-
examples=[OutputColumns.__args__],
|
|
102
|
-
)
|
|
103
|
-
data: list[
|
|
104
|
-
conlist(
|
|
105
|
-
item_type=confloat(allow_inf_nan=True),
|
|
106
|
-
min_length=1,
|
|
107
|
-
max_length=len(OutputColumns.__args__),
|
|
108
|
-
) # type: ignore
|
|
109
|
-
] = Field(examples=[[0.0] * len(OutputColumns.__args__)])
|
|
99
|
+
columns: list[str]
|
|
100
|
+
data: list[list[float | None]]
|
|
110
101
|
summary: DynamicTariffAnalysisOutputSummary | None = None
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the functions to interact with the Elia API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .const import Region
|
|
11
|
+
|
|
12
|
+
DATE_FORMAT = "%Y-%m-%d"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def get_dataset(
|
|
16
|
+
dataset: str,
|
|
17
|
+
start: dt.date,
|
|
18
|
+
end: dt.date,
|
|
19
|
+
region: Region,
|
|
20
|
+
select: set[str],
|
|
21
|
+
session: aiohttp.ClientSession,
|
|
22
|
+
timezone: str = "Europe/Brussels",
|
|
23
|
+
) -> dict:
|
|
24
|
+
"""
|
|
25
|
+
Fetches a dataset from the Elia open data API within a specified date range and region.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
dataset (str): The name of the dataset to fetch.
|
|
29
|
+
start (dt.date): The start date for the data range.
|
|
30
|
+
end (dt.date): The end date for the data range.
|
|
31
|
+
region (Region): The region for which to fetch the data.
|
|
32
|
+
select (set[str]): A set of fields to select in the dataset.
|
|
33
|
+
session (aiohttp.ClientSession): The aiohttp session to use for making the request.
|
|
34
|
+
timezone (str, optional): The timezone to use for the data. Defaults to "Europe/Brussels".
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
dict: The dataset fetched from the API.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
aiohttp.ClientError: If there is an error making the request.
|
|
41
|
+
"""
|
|
42
|
+
url = f"https://opendata.elia.be/api/explore/v2.1/catalog/datasets/{dataset}/exports/json"
|
|
43
|
+
|
|
44
|
+
if "datetime" not in select:
|
|
45
|
+
select.add("datetime")
|
|
46
|
+
select_str = ",".join(select)
|
|
47
|
+
|
|
48
|
+
params = {
|
|
49
|
+
"where": (
|
|
50
|
+
f"datetime IN [date'{start.strftime(DATE_FORMAT)}'..date'{end.strftime(DATE_FORMAT)}'] "
|
|
51
|
+
f"AND region='{region.value}'"
|
|
52
|
+
),
|
|
53
|
+
"timezone": timezone,
|
|
54
|
+
"select": select_str,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async with session.get(url, params=params) as response:
|
|
58
|
+
data = await response.json()
|
|
59
|
+
|
|
60
|
+
return data
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_response(
|
|
64
|
+
data: dict, index: str, columns: list[str], timezone: str = "Europe/Brussels"
|
|
65
|
+
) -> pd.DataFrame:
|
|
66
|
+
"""
|
|
67
|
+
Parses a response dictionary into a pandas DataFrame.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
data (dict): The input data where each key is a column name
|
|
71
|
+
and each value is a list of column values.
|
|
72
|
+
index (str): The key in the data dictionary to be used as the index for the DataFrame.
|
|
73
|
+
columns (list[str]): The list of column names for the DataFrame.
|
|
74
|
+
timezone (str, optional): The timezone to convert the DataFrame index to.
|
|
75
|
+
Defaults to "Europe/Brussels".
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
pd.DataFrame: A pandas DataFrame with the specified columns and index,
|
|
79
|
+
converted to the specified timezone.
|
|
80
|
+
"""
|
|
81
|
+
df = pd.DataFrame(
|
|
82
|
+
data,
|
|
83
|
+
index=pd.to_datetime([x[index] for x in data], utc=True),
|
|
84
|
+
columns=columns,
|
|
85
|
+
)
|
|
86
|
+
df.index = pd.DatetimeIndex(df.index)
|
|
87
|
+
df = df.tz_convert(timezone)
|
|
88
|
+
df.sort_index(inplace=True)
|
|
89
|
+
df.dropna(inplace=True)
|
|
90
|
+
|
|
91
|
+
return df
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Region(Enum):
|
|
5
|
+
Belgium = "Belgium"
|
|
6
|
+
Brussels = "Brussels"
|
|
7
|
+
Flanders = "Flanders"
|
|
8
|
+
Wallonia = "Wallonia"
|
|
9
|
+
Antwerp = "Antwerp"
|
|
10
|
+
East_Flanders = "East-Flanders"
|
|
11
|
+
Flemish_Brabant = "Flemish-Brabant"
|
|
12
|
+
Limburg = "Limburg"
|
|
13
|
+
West_Flanders = "West-Flanders"
|
|
14
|
+
Hainaut = "Hainaut"
|
|
15
|
+
Liège = "Liège"
|
|
16
|
+
Luxembourg = "Luxembourg"
|
|
17
|
+
Namur = "Namur"
|
|
18
|
+
Walloon_Brabant = "Walloon-Brabant"
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
"""Functions to create multi-indexed DataFrames for input and output data for energy sharing."""
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
from .const import (
|
|
6
|
+
GROSS_INJECTION,
|
|
7
|
+
GROSS_OFFTAKE,
|
|
8
|
+
KEY,
|
|
9
|
+
NET_INJECTION,
|
|
10
|
+
NET_OFFTAKE,
|
|
11
|
+
SHARED_ENERGY,
|
|
12
|
+
)
|
|
5
13
|
|
|
6
14
|
|
|
7
15
|
def create_multi_index_input_frame(
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"""Main Calcuation Module for Energy Sharing."""
|
|
2
2
|
|
|
3
3
|
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from .const import (
|
|
6
|
+
GROSS_INJECTION,
|
|
7
|
+
GROSS_OFFTAKE,
|
|
8
|
+
KEY,
|
|
9
|
+
NET_INJECTION,
|
|
10
|
+
NET_OFFTAKE,
|
|
11
|
+
SHARED_ENERGY,
|
|
12
|
+
)
|
|
13
|
+
from .data_formatting import (
|
|
14
|
+
create_multi_index_output_frame,
|
|
15
|
+
result_to_input_for_reiteration,
|
|
16
|
+
)
|
|
4
17
|
from .models import CalculationMethod
|
|
5
|
-
from .const import GROSS_INJECTION, GROSS_OFFTAKE, KEY, NET_INJECTION, NET_OFFTAKE, SHARED_ENERGY
|
|
6
|
-
from .data_formatting import create_multi_index_output_frame, result_to_input_for_reiteration
|
|
7
18
|
|
|
8
19
|
|
|
9
20
|
def _calculate(df: pd.DataFrame, method: CalculationMethod) -> pd.DataFrame:
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Annotated, Any
|
|
5
5
|
|
|
6
|
-
from pydantic import BaseModel, Field, confloat
|
|
7
6
|
import pandas as pd
|
|
7
|
+
from pydantic import BaseModel, Field, confloat
|
|
8
8
|
|
|
9
9
|
from openenergyid import TimeDataFrame
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
from .const import NET_INJECTION, NET_OFFTAKE, SHARED_ENERGY
|
|
12
|
+
from .data_formatting import create_multi_index_input_frame
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class CalculationMethod(Enum):
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""Data models for the Open Energy ID."""
|
|
2
2
|
|
|
3
3
|
import datetime as dt
|
|
4
|
-
from typing import overload
|
|
5
|
-
|
|
6
|
-
from typing import Self
|
|
4
|
+
from typing import Self, overload
|
|
7
5
|
|
|
8
6
|
import pandas as pd
|
|
9
|
-
from pydantic import BaseModel
|
|
10
7
|
import polars as pl
|
|
8
|
+
from pydantic import BaseModel
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
class TimeSeriesBase(BaseModel):
|
|
@@ -62,6 +60,14 @@ class TimeSeriesBase(BaseModel):
|
|
|
62
60
|
return cls.model_validate_json(file.read(), **kwargs)
|
|
63
61
|
raise ValueError("Either string or path must be provided.")
|
|
64
62
|
|
|
63
|
+
def first_timestamp(self) -> dt.datetime:
|
|
64
|
+
"""Get the first timestamp in the index."""
|
|
65
|
+
return min(self.index)
|
|
66
|
+
|
|
67
|
+
def last_timestamp(self) -> dt.datetime:
|
|
68
|
+
"""Get the last timestamp in the index."""
|
|
69
|
+
return max(self.index)
|
|
70
|
+
|
|
65
71
|
|
|
66
72
|
class TimeSeries(TimeSeriesBase):
|
|
67
73
|
"""
|
|
@@ -88,7 +94,11 @@ class TimeSeries(TimeSeriesBase):
|
|
|
88
94
|
@classmethod
|
|
89
95
|
def from_pandas(cls, data: pd.Series) -> Self:
|
|
90
96
|
"""Create from a Pandas Series."""
|
|
91
|
-
|
|
97
|
+
if not hasattr(data, "name") or data.name is None:
|
|
98
|
+
name = None
|
|
99
|
+
else:
|
|
100
|
+
name = str(data.name)
|
|
101
|
+
return cls(name=name, data=data.tolist(), index=data.index.tolist())
|
|
92
102
|
|
|
93
103
|
def to_pandas(self, timezone: str = "UTC") -> pd.Series:
|
|
94
104
|
"""Convert to a Pandas Series."""
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Models for multivariable linear regression."""
|
|
2
2
|
|
|
3
3
|
from typing import Any
|
|
4
|
-
import pandas as pd
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
import pandas as pd
|
|
7
6
|
import statsmodels.formula.api as fm
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
8
8
|
|
|
9
9
|
from openenergyid.enums import Granularity
|
|
10
10
|
from openenergyid.models import TimeDataFrame
|
|
11
11
|
|
|
12
12
|
from .mvlr import MultiVariableLinearRegression
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
COLUMN_TEMPERATUREEQUIVALENT = "temperatureEquivalent"
|
|
16
15
|
|
|
17
16
|
|
|
@@ -206,9 +206,9 @@ class MultiVariableLinearRegression:
|
|
|
206
206
|
|
|
207
207
|
def _do_analysis_cross_validation(self):
|
|
208
208
|
"""Find the best model (fit) based on cross-valiation (leave one out)"""
|
|
209
|
-
assert (
|
|
210
|
-
|
|
211
|
-
)
|
|
209
|
+
assert len(self.data) < 15, (
|
|
210
|
+
"Cross-validation is not implemented if your sample contains more than 15 datapoints"
|
|
211
|
+
)
|
|
212
212
|
|
|
213
213
|
# initialization: first model is the mean, but compute cv correctly.
|
|
214
214
|
errors = []
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the abstract base class for PVSimulator.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
|
|
12
|
+
from openenergyid.models import TimeSeries
|
|
13
|
+
|
|
14
|
+
from ..abstractsim import SimulationInputAbstract, Simulator
|
|
15
|
+
from ..const import ELECTRICITY_PRODUCED
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PVSimulationInputAbstract(SimulationInputAbstract):
|
|
19
|
+
"""
|
|
20
|
+
Input parameters for the PV simulation.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
start: dt.date
|
|
24
|
+
end: dt.date
|
|
25
|
+
result_resolution: str = Field(
|
|
26
|
+
"15min",
|
|
27
|
+
description="Resolution of the simulation results",
|
|
28
|
+
examples=["15min", "1h", "D", "MS"],
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PVSimulator(Simulator, ABC):
|
|
33
|
+
"""
|
|
34
|
+
An abstract base class for PV simulators.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, result_resolution: str = "15min", **kwargs) -> None:
|
|
38
|
+
self._simulation_results: pd.Series | None = None
|
|
39
|
+
self.result_resolution = result_resolution
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def simulation_results(self) -> pd.Series:
|
|
43
|
+
"""The results of the simulation."""
|
|
44
|
+
if self._simulation_results is None:
|
|
45
|
+
results = self.simulate()
|
|
46
|
+
self._simulation_results = cast(pd.Series, results)
|
|
47
|
+
return self._simulation_results
|
|
48
|
+
|
|
49
|
+
def result_to_timeseries(self):
|
|
50
|
+
"""
|
|
51
|
+
Convert the simulation results to a TimeSeries object.
|
|
52
|
+
"""
|
|
53
|
+
result = self.simulation_results.resample(self.result_resolution).sum()
|
|
54
|
+
return TimeSeries.from_pandas(result)
|
|
55
|
+
|
|
56
|
+
def result_as_frame(self) -> pd.DataFrame:
|
|
57
|
+
"""
|
|
58
|
+
Convert the simulation results to a DataFrame.
|
|
59
|
+
"""
|
|
60
|
+
return self.simulation_results.rename(ELECTRICITY_PRODUCED).to_frame()
|