openenergyid 0.1.28__py3-none-any.whl → 0.1.30__py3-none-any.whl

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.

Files changed (44) hide show
  1. openenergyid/__init__.py +1 -1
  2. openenergyid/abstractsim/__init__.py +5 -0
  3. openenergyid/abstractsim/abstract.py +102 -0
  4. openenergyid/baseload/__init__.py +1 -1
  5. openenergyid/baseload/analysis.py +5 -3
  6. openenergyid/capacity/__init__.py +1 -1
  7. openenergyid/capacity/main.py +1 -0
  8. openenergyid/capacity/models.py +2 -0
  9. openenergyid/const.py +11 -0
  10. openenergyid/dyntar/main.py +10 -9
  11. openenergyid/dyntar/models.py +5 -14
  12. openenergyid/elia/__init__.py +4 -0
  13. openenergyid/elia/api.py +91 -0
  14. openenergyid/elia/const.py +18 -0
  15. openenergyid/energysharing/data_formatting.py +9 -1
  16. openenergyid/energysharing/main.py +13 -2
  17. openenergyid/energysharing/models.py +3 -2
  18. openenergyid/models.py +15 -5
  19. openenergyid/mvlr/__init__.py +1 -1
  20. openenergyid/mvlr/main.py +1 -1
  21. openenergyid/mvlr/models.py +2 -3
  22. openenergyid/mvlr/mvlr.py +3 -3
  23. openenergyid/pvsim/__init__.py +8 -0
  24. openenergyid/pvsim/abstract.py +60 -0
  25. openenergyid/pvsim/elia/__init__.py +3 -0
  26. openenergyid/pvsim/elia/main.py +89 -0
  27. openenergyid/pvsim/main.py +49 -0
  28. openenergyid/pvsim/pvlib/__init__.py +11 -0
  29. openenergyid/pvsim/pvlib/main.py +115 -0
  30. openenergyid/pvsim/pvlib/models.py +235 -0
  31. openenergyid/pvsim/pvlib/quickscan.py +99 -0
  32. openenergyid/pvsim/pvlib/weather.py +91 -0
  33. openenergyid/sim/__init__.py +5 -0
  34. openenergyid/sim/main.py +67 -0
  35. openenergyid/simeval/__init__.py +6 -0
  36. openenergyid/simeval/main.py +148 -0
  37. openenergyid/simeval/models.py +162 -0
  38. openenergyid-0.1.30.dist-info/METADATA +32 -0
  39. openenergyid-0.1.30.dist-info/RECORD +50 -0
  40. {openenergyid-0.1.28.dist-info → openenergyid-0.1.30.dist-info}/WHEEL +2 -1
  41. openenergyid-0.1.30.dist-info/top_level.txt +1 -0
  42. openenergyid-0.1.28.dist-info/METADATA +0 -41
  43. openenergyid-0.1.28.dist-info/RECORD +0 -29
  44. {openenergyid-0.1.28.dist-info → openenergyid-0.1.30.dist-info/licenses}/LICENSE +0 -0
openenergyid/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Open Energy ID Python SDK."""
2
2
 
3
- __version__ = "0.1.28"
3
+ __version__ = "0.1.30"
4
4
 
5
5
  from .enums import Granularity
6
6
  from .models import TimeDataFrame, TimeSeries
@@ -0,0 +1,5 @@
1
+ """Module with abstract definitions that ALL SIMULATIONS should adhere to."""
2
+
3
+ from .abstract import SimulationInputAbstract, SimulationSummary, Simulator
4
+
5
+ __all__ = ["SimulationInputAbstract", "SimulationSummary", "Simulator"]
@@ -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
- # What fraction of total energy was from baseload
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
- ).alias("baseload_ratio"),
170
+ )
171
+ .otherwise(None)
172
+ .alias("baseload_ratio"),
171
173
  ]
172
174
  )
173
175
  )
@@ -1,6 +1,6 @@
1
1
  """Power Offtake peak analysis module."""
2
2
 
3
- from .models import CapacityInput, CapacityOutput, PeakDetail
4
3
  from .main import CapacityAnalysis
4
+ from .models import CapacityInput, CapacityOutput, PeakDetail
5
5
 
6
6
  __all__ = ["CapacityInput", "CapacityAnalysis", "CapacityOutput", "PeakDetail"]
@@ -2,6 +2,7 @@
2
2
 
3
3
  import datetime as dt
4
4
  import typing
5
+
5
6
  import pandas as pd
6
7
  import pandera.typing as pdt
7
8
 
@@ -1,7 +1,9 @@
1
1
  """Model for Capacity Analysis."""
2
2
 
3
3
  import datetime as dt
4
+
4
5
  from pydantic import BaseModel, ConfigDict, Field
6
+
5
7
  from openenergyid.models import TimeSeries
6
8
 
7
9
 
openenergyid/const.py CHANGED
@@ -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
- RLP_WEIGHTED_PRICE_DELIVERED,
25
- SPP_WEIGHTED_PRICE_EXPORTED,
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
- from pydantic import Field, conlist, confloat, BaseModel
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[OutputColumns] = Field(
99
- min_length=1,
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,4 @@
1
+ from .api import get_dataset, parse_response
2
+ from .const import Region
3
+
4
+ __all__ = ["get_dataset", "parse_response", "Region"]
@@ -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
- from .const import GROSS_INJECTION, GROSS_OFFTAKE, KEY, NET_INJECTION, NET_OFFTAKE, SHARED_ENERGY
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
- from .data_formatting import create_multi_index_input_frame
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):
openenergyid/models.py CHANGED
@@ -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
- return cls(name=str(data.name), data=data.tolist(), index=data.index.tolist())
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."""
@@ -3,10 +3,10 @@
3
3
  from .main import find_best_mvlr
4
4
  from .models import (
5
5
  IndependentVariableInput,
6
+ IndependentVariableResult,
6
7
  MultiVariableRegressionInput,
7
8
  MultiVariableRegressionResult,
8
9
  ValidationParameters,
9
- IndependentVariableResult,
10
10
  )
11
11
 
12
12
  __all__ = [
openenergyid/mvlr/main.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Main module for the MultiVariableLinearRegression class."""
2
2
 
3
- from .models import MultiVariableRegressionInput, MultiVariableRegressionResult
4
3
  from .helpers import resample_input_data
4
+ from .models import MultiVariableRegressionInput, MultiVariableRegressionResult
5
5
  from .mvlr import MultiVariableLinearRegression
6
6
 
7
7
 
@@ -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
- from pydantic import BaseModel, Field, ConfigDict
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
 
openenergyid/mvlr/mvlr.py CHANGED
@@ -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
- len(self.data) < 15
211
- ), "Cross-validation is not implemented if your sample contains more than 15 datapoints"
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,8 @@
1
+ from .main import (
2
+ PVSimulationInput,
3
+ PVSimulationSummary,
4
+ apply_simulation,
5
+ get_simulator,
6
+ )
7
+
8
+ __all__ = ["PVSimulationInput", "get_simulator", "apply_simulation", "PVSimulationSummary"]
@@ -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()
@@ -0,0 +1,3 @@
1
+ from .main import EliaPVSimulationInput, EliaPVSimulator
2
+
3
+ __all__ = ["EliaPVSimulationInput", "EliaPVSimulator"]