detquantlib 5.1.1__tar.gz → 5.2.0__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.
Files changed (33) hide show
  1. {detquantlib-5.1.1 → detquantlib-5.2.0}/PKG-INFO +1 -1
  2. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/assets/wind_turbine.py +70 -32
  3. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/data/databases/detdatabase.py +32 -17
  4. {detquantlib-5.1.1 → detquantlib-5.2.0}/pyproject.toml +1 -1
  5. {detquantlib-5.1.1 → detquantlib-5.2.0}/LICENSE.txt +0 -0
  6. {detquantlib-5.1.1 → detquantlib-5.2.0}/README.md +0 -0
  7. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/__init__.py +0 -0
  8. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/assets/__init__.py +0 -0
  9. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/assets/battery.py +0 -0
  10. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/converters/__init__.py +0 -0
  11. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/converters/definitions.py +0 -0
  12. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/converters/energy.py +0 -0
  13. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/converters/helpers.py +0 -0
  14. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/converters/price.py +0 -0
  15. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/data/__init__.py +0 -0
  16. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/data/databases/helpers.py +0 -0
  17. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/data/entsoe/entsoe.py +0 -0
  18. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/data/sftp/sftp.py +0 -0
  19. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/dates/__init__.py +0 -0
  20. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/dates/dates.py +0 -0
  21. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/figures/__init__.py +0 -0
  22. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/figures/plotly_figures.py +0 -0
  23. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/forecasting/__init__.py +0 -0
  24. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/forecasting/forecasting.py +0 -0
  25. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/outputs/__init__.py +0 -0
  26. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/outputs/outputs_interface.py +0 -0
  27. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/stats/__init__.py +0 -0
  28. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/stats/data_analysis.py +0 -0
  29. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/tradable_products/__init__.py +0 -0
  30. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/tradable_products/tradable_products.py +0 -0
  31. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/utils/__init__.py +0 -0
  32. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/utils/logging.py +0 -0
  33. {detquantlib-5.1.1 → detquantlib-5.2.0}/detquantlib/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detquantlib
3
- Version: 5.1.1
3
+ Version: 5.2.0
4
4
  Summary: An internal library containing functions and classes that can be used across Quant models.
5
5
  License-File: LICENSE.txt
6
6
  Author: Dynamic Energy Trading
@@ -5,6 +5,7 @@ from scipy.interpolate import CubicSpline
5
5
 
6
6
  # Internal modules
7
7
  from detquantlib.converters import power_to_energy
8
+ from detquantlib.utils import list_to_str
8
9
 
9
10
 
10
11
  class WindTurbine:
@@ -35,6 +36,73 @@ class WindTurbine:
35
36
  self.nr_turbines = nr_turbines
36
37
  self.power_curve = power_curve
37
38
 
39
+ @staticmethod
40
+ def list_models() -> list[str]:
41
+ """Gets the list of available wind turbine models."""
42
+ return [d for d in WindTurbineModels.__dict__ if not d.startswith("__")]
43
+
44
+ @staticmethod
45
+ def get_model(model_name: str) -> dict:
46
+ """Gets the model information of a given wind turbine."""
47
+ try:
48
+ return getattr(WindTurbineModels, model_name)
49
+ except AttributeError as e:
50
+ raise ValueError(
51
+ f"Invalid model name '{model_name}'. Available models: "
52
+ f"{list_to_str(WindTurbine.list_models())}."
53
+ ) from e
54
+
55
+ @staticmethod
56
+ def build_from_model(model_name: str = None, model_info: dict = None, nr_turbines: int = 1):
57
+ """
58
+ Builds a WindTurbine object from a known wind turbine model.
59
+
60
+ Args:
61
+ model_name: Wind turbine model name, as given in the WindTurbineModels class. If not
62
+ provided, argument 'model_info' should be provided.
63
+ model_info: Wind turbine model information. The dictionary can be taken from the
64
+ list of models in the WindTurbineModels class. Otherwise, the dictionary should
65
+ have the same structure as those in the WindTurbineModels class. If not provided,
66
+ argument 'model_name' should be provided.
67
+ nr_turbines: Number of turbines in the wind farm
68
+
69
+ Returns:
70
+ WindTurbine object
71
+
72
+ Raises:
73
+ ValueError: Raises an error if both 'model_name' and 'model_info' are missing.
74
+ ValueError: Raises an error if both 'model_name' and 'model_info' are provided.
75
+ """
76
+ # Inputs validation
77
+ if model_name is None and model_info is None:
78
+ raise ValueError(
79
+ "Cannot determine turbine model. Please provide either argument 'model_name' "
80
+ "or 'model_info'."
81
+ )
82
+ elif model_name is not None and model_info is not None:
83
+ raise ValueError(
84
+ "Cannot determine turbine model. Please provide either argument 'model_name' "
85
+ "or 'model_info', but not both."
86
+ )
87
+ elif model_name is not None:
88
+ model_info = WindTurbine.get_model(model_name)
89
+
90
+ # Create object
91
+ obj = WindTurbine(
92
+ rated_power_mw=model_info["rated_power_mw"],
93
+ cut_in=model_info["cut_in"],
94
+ cut_out=model_info["cut_out"],
95
+ nr_turbines=nr_turbines,
96
+ )
97
+
98
+ # Set power_curve attribute
99
+ obj.fit_power_curve(
100
+ wind_speed=[d["wind_speed_(m/s)"] for d in model_info["power_curve"]],
101
+ power_mw=[d["power(mw)"] for d in model_info["power_curve"]],
102
+ )
103
+
104
+ return obj
105
+
38
106
  def fit_power_curve(
39
107
  self, wind_speed: list | np.ndarray | pd.Series, power_mw: list | np.ndarray | pd.Series
40
108
  ):
@@ -92,8 +160,8 @@ class WindTurbine:
92
160
  # Cubic spline interpolation
93
161
  power_mw = self.power_curve(wind_speed)
94
162
 
95
- # Cap to rated power
96
- power_mw = np.minimum(power_mw, self.rated_power_mw)
163
+ # Bound between 0 and rated power
164
+ power_mw = np.clip(a=power_mw, a_min=0.0, a_max=self.rated_power_mw)
97
165
 
98
166
  # Adjust values outside of cut-in - cut-out bounds
99
167
  idx = (wind_speed < self.cut_in) | (wind_speed > self.cut_out)
@@ -121,36 +189,6 @@ class WindTurbine:
121
189
  )
122
190
  return energy_mwh
123
191
 
124
- @staticmethod
125
- def build_from_model(model: dict, nr_turbines: int):
126
- """
127
- Builds a WindTurbine object from a known wind turbine model.
128
-
129
- Args:
130
- model: Wind turbine model. The dictionary can be taken from the list of models in
131
- the WindTurbineModels class. Otherwise, the dictionary should have the same
132
- structure as those in the WindTurbineModels class.
133
- nr_turbines: Number of wind turbines in the wind farm
134
-
135
- Returns:
136
- WindTurbine object
137
- """
138
- # Create object
139
- obj = WindTurbine(
140
- rated_power_mw=model["rated_power_mw"],
141
- cut_in=model["cut_in"],
142
- cut_out=model["cut_out"],
143
- nr_turbines=nr_turbines,
144
- )
145
-
146
- # Set power_curve attribute
147
- obj.fit_power_curve(
148
- wind_speed=[d["wind_speed_(m/s)"] for d in model["power_curve"]],
149
- power_mw=[d["power(mw)"] for d in model["power_curve"]],
150
- )
151
-
152
- return obj
153
-
154
192
 
155
193
  class WindTurbineModels:
156
194
  """
@@ -878,15 +878,20 @@ class DetDatabase:
878
878
  Args:
879
879
  profile: Customer/profile name.
880
880
  Note on the 'Portfolio' and 'Portfolioweekend' profiles:
881
- - Profile names 'Portfolio' and 'Portfolioweekend' refer to the same set of
882
- connection points.
883
- - In the database, the profile name is set to 'Portfolio' when the forecast date
884
- is between Monday and Friday.
885
- - In the database, the profile name is set to 'Portfolioweekend' when the forecast
886
- date is Saturday or Sunday.
887
- - When calling this method, the input argument 'profile' can be set to
888
- 'PortfolioAll' to automatically let the method switch between 'Portfolio' and
889
- 'Portfolioweekend', based on the used-input forecast date.
881
+ - [Legacy data until March 2026]:
882
+ - Profile names 'Portfolio' and 'Portfolioweekend' refer to the same set
883
+ of connection points.
884
+ - In the database, the profile name is set to 'Portfolio' when the
885
+ forecast date is between Monday and Friday.
886
+ - In the database, the profile name is set to 'Portfolioweekend' when
887
+ the forecast date is Saturday or Sunday.
888
+ - [Data since March 2026]:
889
+ - The 'Portfolioweekend' name is deprecated. Day-ahead forecasts are
890
+ now stored under the 'Portfolio' profile only.
891
+ - When calling this method, set the input argument 'profile' to
892
+ 'PortfolioAll' to automatically switch between 'Portfolio' and
893
+ 'Portfolioweekend', based on the used-input forecast date.
894
+ 'PortfolioAll' supports both the legacy and new data structure.
890
895
  forecast_date: Date on which customer volume forecast is generated.
891
896
  start_delivery_date: First delivery date included.
892
897
  end_delivery_date: Last delivery date included.
@@ -902,13 +907,6 @@ class DetDatabase:
902
907
  Raises:
903
908
  ValueError: Raises an error if no volume forecast data is found for user inputs.
904
909
  """
905
- # Convert profile if set to "PortfolioAll"
906
- if profile == "PortfolioAll":
907
- if forecast_date.weekday() > 4:
908
- profile = "Portfolioweekend"
909
- else:
910
- profile = "Portfolio"
911
-
912
910
  # Convert start delivery date from local timezone to UTC and string
913
911
  start_delivery_date = start_delivery_date.replace(tzinfo=ZoneInfo(local_timezone))
914
912
  start_delivery_date = start_delivery_date.astimezone(ZoneInfo("UTC"))
@@ -930,17 +928,34 @@ class DetDatabase:
930
928
  # Convert dates from datetime to string
931
929
  forecast_date_str = forecast_date.strftime("%Y-%m-%d")
932
930
 
931
+ # Define query profile name. If 'PortfolioAll', first try with 'Portfolio' (compatibility
932
+ # with new data).
933
+ query_profile = "Portfolio" if profile == "PortfolioAll" else profile
934
+
933
935
  # Query db
934
936
  table = DetDatabase.get_table_name("client_volume_forecast")
935
937
  query = (
936
938
  f"SELECT {columns_str} FROM {table} "
937
- f"WHERE Profile='{profile}' "
939
+ f"WHERE Profile='{query_profile}' "
938
940
  f"AND ForecastDate='{forecast_date_str}' "
939
941
  f"AND Datetime>='{start_date_str}' "
940
942
  f"AND Datetime<'{end_date_str}'"
941
943
  )
942
944
  df = self.query_db(query)
943
945
 
946
+ if profile == "PortfolioAll" and df.empty and forecast_date.weekday() > 4:
947
+ # If 'Portfolio' not found, try with 'Portfolioweekend' (backward compatibility with
948
+ # legacy data)
949
+ query_profile = "Portfolioweekend"
950
+ query = (
951
+ f"SELECT {columns_str} FROM {table} "
952
+ f"WHERE Profile='{query_profile}' "
953
+ f"AND ForecastDate='{forecast_date_str}' "
954
+ f"AND Datetime>='{start_date_str}' "
955
+ f"AND Datetime<'{end_date_str}'"
956
+ )
957
+ df = self.query_db(query)
958
+
944
959
  # Assert data
945
960
  if df.empty:
946
961
  raise ValueError("No volume forecast data found for user-defined inputs.")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "detquantlib"
3
- version = "5.1.1"
3
+ version = "5.2.0"
4
4
  description = "An internal library containing functions and classes that can be used across Quant models."
5
5
  authors = ["Dynamic Energy Trading"]
6
6
  readme = "README.md"
File without changes
File without changes