Solar Active Power Unclipped¶
Overview¶
FeatureCalcSolarUnclippedPower estimates the theoretical AC power that a solar inverter would produce without AC-side clipping limitations. It uses a simple linear regression between plane-of-array irradiance and power, whose coefficient is stored as an object attribute. The result is a 5-minute time series.
This feature is used in combination with ClippingState to quantify the energy lost due to inverter clipping.
Calculation Logic¶
Formula¶
unclipped_power = irradiance_coefficient × IrradiancePOACommOk_5min.AVG
Where irradiance_coefficient is a fitted scalar regression parameter (units: kW / (W/m²)) stored in the object attribute unclipped_pwr_regression.
Steps¶
-
Read regression parameter: Retrieves
irradiance_coefficientfromunclipped_pwr_regression["attribute_value"]["params"]["IrradiancePOACommOk_5min.AVG"]. -
Fetch irradiance: Retrieves
IrradiancePOACommOk_5min.AVGfrom the associated simple weather station (reference_weather_stations["simple_ws"]), rounded to 5-minute timestamps within ±2 minutes tolerance. -
Fill missing irradiance: Applies forward-fill then backward-fill to minimize gaps in the irradiance series.
-
Compute power: Multiplies irradiance by the coefficient and clips to
≥ 0.0(negative irradiance readings produce zero power). -
Align to period: Trims the result to the originally requested period.
Database Requirements¶
Feature Attribute¶
| Attribute | Value |
|---|---|
server_calc_type |
solar_unclipped_power |
Object Attributes¶
| Attribute | Required | Description |
|---|---|---|
unclipped_pwr_regression |
Yes | Nested dict containing the fitted regression coefficient. Expected structure: {"attribute_value": {"params": {"IrradiancePOACommOk_5min.AVG": <float>}}} |
reference_weather_stations |
Yes | Dict with a "simple_ws" key naming the weather station object that provides irradiance. |
Example unclipped_pwr_regression value:
{
"attribute_value": {
"params": {
"IrradiancePOACommOk_5min.AVG": 0.285
}
}
}
Features (simple weather station — from Bazefield)¶
| Feature | Description |
|---|---|
IrradiancePOACommOk_5min.AVG |
Plane-of-array irradiance (W/m²). Fetched with _b# suffix from Bazefield. |
Class Definition¶
FeatureCalcSolarUnclippedPower(object_name, feature)
¶
Base class for solar energy unclipped active power. Basing the result on the linear regression parameters saved on unclipped_pwr_regression object attribute.
This class uses linear regression parameters stored in the object's 'unclipped_pwr_regression' attribute and requires reference weather station data (irradiation). It sets up the necessary requirements for the calculation.
Parameters:
-
(object_name¶str) –Name of the object for which the feature is calculated.
-
(feature¶str) –Name of the feature to be calculated.
Source code in echo_energycalc/feature_calc_solar_unclipped_pwr.py
def __init__(
self,
object_name: str,
feature: str,
) -> None:
"""
Initializes the FeatureCalcSolarUnclippedPower class for calculating the solar unclipped active power feature.
This class uses linear regression parameters stored in the object's 'unclipped_pwr_regression' attribute
and requires reference weather station data (irradiation). It sets up the necessary requirements for the calculation.
Parameters
----------
object_name : str
Name of the object for which the feature is calculated.
feature : str
Name of the feature to be calculated.
"""
# initialize parent class
super().__init__(object_name, feature)
# Defining which object attributes are required for the calculation.
self._add_requirement(
RequiredObjectAttributes(
{
self.object: [
"unclipped_pwr_regression",
"reference_weather_stations",
],
},
),
)
self._fetch_requirements()
# Getting the complete weather station name for the specif object.
simple_ws = self._requirement_data("RequiredObjectAttributes")[self.object]["reference_weather_stations"]["simple_ws"]
# Defining the features that will be required for the calculation.
features = {
simple_ws: ["IrradiancePOACommOk_5min.AVG_b#"],
}
self._add_requirement(RequiredFeatures(features=features))
feature
property
¶
Feature that is calculated. This will be defined in the constructor and cannot be changed.
Returns:
-
str–Name of the feature that is calculated.
name
property
¶
Name of the feature calculator. Is defined in child classes of FeatureCalculator.
This must be equal to the "server_calc_type" attribute of the feature in performance_db.
Returns:
-
str–Name of the feature calculator.
object
property
¶
Object for which the feature is calculated. This will be defined in the constructor and cannot be changed.
Returns:
-
str–Object name for which the feature is calculated.
requirements
property
¶
List of requirements of the feature calculator. Is defined in child classes of FeatureCalculator.
Returns:
-
dict[str, list[CalculationRequirement]]–Dict of requirements.
The keys are the names of the classes of the requirements and the values are lists of requirements of that class.
For example:
{"RequiredFeatures": [RequiredFeatures(...), RequiredFeatures(...)], "RequiredObjects": [RequiredObjects(...)]}
result
property
¶
Result of the calculation. This is None until the method "calculate" is called.
Returns:
-
DataFrame | None–Polars DataFrame with a
"timestamp"column and one or more feature value columns. None untilcalculateis called.
calculate(period, save_into=None, cached_data=None, **kwargs)
¶
Run the calculation for the given period and optionally save the result.
Calls :meth:_compute to get the result, stores it in :attr:result,
then calls :meth:save. Subclasses should implement :meth:_compute instead
of overriding this method.
Parameters:
-
(period¶DateTimeRange) –Period for which the feature will be calculated.
-
(save_into¶Literal['all', 'performance_db'] | None, default:None) –"all": save in performance_db and bazefield."performance_db": save only in performance_db.None: do not save.
By default None.
-
(cached_data¶DataFrame | None, default:None) –Polars DataFrame with features already fetched/calculated. Passed to
_computeto enable chained calculations without re-querying performance_db. By default None. -
–**kwargs¶Forwarded to :meth:
save.
Returns:
-
DataFrame–Polars DataFrame with a
"timestamp"column and one or more feature value columns.
Source code in echo_energycalc/feature_calc_core.py
def calculate(
self,
period: DateTimeRange,
save_into: Literal["all", "performance_db"] | None = None,
cached_data: pl.DataFrame | None = None,
**kwargs,
) -> pl.DataFrame:
"""
Run the calculation for the given period and optionally save the result.
Calls :meth:`_compute` to get the result, stores it in :attr:`result`,
then calls :meth:`save`. Subclasses should implement :meth:`_compute` instead
of overriding this method.
Parameters
----------
period : DateTimeRange
Period for which the feature will be calculated.
save_into : Literal["all", "performance_db"] | None, optional
- ``"all"``: save in performance_db and bazefield.
- ``"performance_db"``: save only in performance_db.
- ``None``: do not save.
By default None.
cached_data : pl.DataFrame | None, optional
Polars DataFrame with features already fetched/calculated. Passed to
``_compute`` to enable chained calculations without re-querying
performance_db. By default None.
**kwargs
Forwarded to :meth:`save`.
Returns
-------
pl.DataFrame
Polars DataFrame with a ``"timestamp"`` column and one or more feature value columns.
"""
result = self._compute(period, cached_data=cached_data)
self._result = result
self.save(save_into=save_into, **kwargs)
return result
save(save_into=None, **kwargs)
¶
Method to save the calculated feature values in performance_db.
Parameters:
-
(save_into¶Literal['all', 'performance_db'] | None, default:None) –Argument that will be passed to the method "save". The options are: - "all": The feature will be saved in performance_db and bazefield. - "performance_db": the feature will be saved only in performance_db. - None: The feature will not be saved.
By default None.
-
(**kwargs¶dict, default:{}) –Not being used at the moment. Here only for compatibility.
Source code in echo_energycalc/feature_calc_core.py
def save(
self,
save_into: Literal["all", "performance_db"] | None = None,
**kwargs, # noqa: ARG002
) -> None:
"""
Method to save the calculated feature values in performance_db.
Parameters
----------
save_into : Literal["all", "performance_db"] | None, optional
Argument that will be passed to the method "save". The options are:
- "all": The feature will be saved in performance_db and bazefield.
- "performance_db": the feature will be saved only in performance_db.
- None: The feature will not be saved.
By default None.
**kwargs : dict, optional
Not being used at the moment. Here only for compatibility.
"""
# checking arguments
if not isinstance(save_into, str | type(None)):
raise TypeError(f"save_into must be a string or None, not {type(save_into)}")
if isinstance(save_into, str) and save_into not in ["all", "performance_db"]:
raise ValueError(f"save_into must be 'all', 'performance_db' or None, not {save_into}")
# checking if calculation was done
if self.result is None:
raise ValueError(
"The calculation was not done. Please call 'calculate' before calling 'save'.",
)
if save_into is None:
return
upload_to_bazefield = save_into == "all"
if not isinstance(self.result, pl.DataFrame):
raise TypeError(f"result must be a polars DataFrame, not {type(self.result)}.")
if "timestamp" not in self.result.columns:
raise ValueError("result DataFrame must contain a 'timestamp' column.")
# rename feature columns to "object@feature" format expected by perfdb polars insert
feat_cols = [c for c in self.result.columns if c != "timestamp"]
result_pl = self.result.rename({col: f"{self.object}@{col}" for col in feat_cols})
self._perfdb.features.values.series.insert(
df=result_pl,
on_conflict="update",
bazefield_upload=upload_to_bazefield,
)