Alarm Calc - Threshold¶
The AlarmCalcThreshold class is a subclass of AlarmCalc and is used to calculate alarms based on feature thresholds. This means that an alarm will be created if the value of a feature is above or below a certain threshold.
This type of alarm calculation requires the following keys in the trigger column of the alarm definition in the database:
trigger_type: Must bethreshold.threshold_type: One of "high" or "low".- "high": the alarm is triggered when the value is above the threshold.
- "low": the alarm is triggered when the value is below the threshold.
feature_name: Name of the feature in the performance_db that will be used to calculate the alarm.threshold_value: Value of the threshold that will trigger the alarm.
Class Definition¶
AlarmCalcThreshold(object_name, alarm_id)
¶
Alarm calculator that calculates alarms based on thresholds.
It expects the following settings in the trigger columns of the alarm in performance_db:
- trigger_type: "threshold"
- threshold_type: One of "high" or "low":
- "high": the alarm is triggered when the value is above the threshold.
- "low": the alarm is triggered when the value is below the threshold.
- feature_name: Name of the feature in the performance_db that will be used to calculate the alarm.
- threshold_value: Value of the threshold that will trigger the alarm.
If non_overlapping_alarms is defined in the alarm settings, this alarm will not overlap with the alarms defined in the list, being the ones in this list considered as more important.
```
Parameters¶
object_name : str Name of the object for which the alarm is calculated. It must exist in performance_db. alarm_id : int ID of the alarm (manufacturer id) for which the alarm calculator is being created. It must exist in performance_db for the model of the object.
Source code in echo_energycalc/alarm_calc_threshold.py
def __init__(
self,
object_name: str,
alarm_id: int,
) -> None:
"""Alarm calculator that calculates alarms based on thresholds.
It expects the following settings in the trigger columns of the alarm in performance_db:
- **trigger_type**: "threshold"
- **threshold_type**: One of "high" or "low":
- "high": the alarm is triggered when the value is above the threshold.
- "low": the alarm is triggered when the value is below the threshold.
- **feature_name**: Name of the feature in the performance_db that will be used to calculate the alarm.
- **threshold_value**: Value of the threshold that will trigger the alarm.
If `non_overlapping_alarms` is defined in the alarm settings, this alarm will not overlap with the alarms defined in the list, being the ones in this list considered as more important.
```
Parameters
----------
object_name : str
Name of the object for which the alarm is calculated. It must exist in performance_db.
alarm_id : int
ID of the alarm (manufacturer id) for which the alarm calculator is being created. It must exist in performance_db for the model of the object.
"""
super().__init__(object_name, alarm_id)
# validating if all the required columns are present in the alarm settings
required_settings = {
"threshold_type": {"type": str, "values": ["high", "low"]},
"feature_name": {"type": str},
"threshold_value": {"type": (int, float)},
}
for setting, setting_info in required_settings.items():
if setting not in self.alarm_settings["trigger"]:
raise ValueError(
f"Setting '{setting}' is missing in the alarm settings of alarm {alarm_id} and object {object_name}.",
)
if not isinstance(
self.alarm_settings["trigger"][setting],
setting_info["type"],
):
raise TypeError(
f"Setting '{setting}' must be of type {setting_info['type']}, not {type(self.alarm_settings['trigger'][setting])}.",
)
if "values" in setting_info and self.alarm_settings["trigger"][setting] not in setting_info["values"]:
raise ValueError(
f"Setting '{setting}' must be one of {setting_info['values']}, not {self.alarm_settings['trigger'][setting]}.",
)
alarm_db_id
property
¶
ID of the alarm in the database. This is used to get the alarm settings.
Returns:
-
int–ID of the alarm in the database.
alarm_id
property
¶
ID of the alarm that is calculated (manufacturer_id). This will be defined in the constructor and cannot be changed.
Returns:
-
int–ID of the alarm that is calculated (manufacturer_id).
alarm_settings
property
¶
Settings of the alarm. This is a dictionary with the settings of the alarm that is being calculated.
Returns:
-
dict[str, Any]–Settings of the alarm.
alarm_type
property
¶
Type of the alarm that is calculated. This will be defined in the constructor and cannot be changed.
Returns:
-
str–Type of the alarm that is calculated.
name
property
¶
Name of the alarm calculator. Is defined in child classes of AlarmCalculator.
This must be equal to the "server_calc_type" attribute of the alarm in performance_db.
Returns:
-
str–Name of the alarm calculator.
object
property
¶
Object for which the alarm is calculated. This will be defined in the constructor and cannot be changed.
Returns:
-
str–Object name for which the alarm is calculated.
result
property
¶
Result of the calculation. This is None until the method "calculate" is called.
Returns:
-
pl.DataFrame | None:–Result of the calculation if the method "calculate" was called. None otherwise.
calculate(period, cached_data=None, save=True)
¶
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 alarm will be calculated.
-
(cached_data¶dict[str, DataFrame] | None, default:None) –Dict with Polars DataFrames containing cached data, keyed by data type (e.g.
"features"). Avoids re-querying the database across multiple alarm calculations in the same job. The default is None. -
(save¶bool, default:True) –If True, the result of the alarms will be saved in the database. The default is True.
Returns:
-
DataFrame–Polars DataFrame with the calculated alarm.
Source code in echo_energycalc/alarm_calc_core.py
def calculate(
self,
period: DateTimeRange,
cached_data: dict[str, pl.DataFrame | None] | None = None,
save: bool = True,
) -> 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 alarm will be calculated.
cached_data : dict[str, pl.DataFrame] | None, optional
Dict with Polars DataFrames containing cached data, keyed by data type
(e.g. ``"features"``). Avoids re-querying the database across multiple
alarm calculations in the same job. The default is None.
save : bool, optional
If True, the result of the alarms will be saved in the database. The default is True.
Returns
-------
pl.DataFrame
Polars DataFrame with the calculated alarm.
"""
result = self._compute(period, cached_data=cached_data)
self._result = result
if save:
self.save()
return result
save()
¶
Save the result of the calculation in the database.
If the method "calculate" was not called before, this method will raise an error.
Source code in echo_energycalc/alarm_calc_core.py
def save(self) -> None:
"""
Save the result of the calculation in the database.
If the method "calculate" was not called before, this method will raise an error.
"""
# checking if the result was calculated
if self._result is None:
raise ValueError("The method 'calculate' must be called before saving the result.")
# checking if the period was evaluated
if self._evaluated_period is None:
raise ValueError("Evaluated period was not set during calculation.")
result = self._result
if not result.is_empty():
equal_count = result.filter(pl.col("start") == pl.col("end")).height
if equal_count > 0:
logger.warning(f"Found {equal_count} rows with start equal to end in the result. These rows will be removed.")
result = result.filter(pl.col("start") != pl.col("end"))
end_before_start_count = result.filter(pl.col("end") < pl.col("start")).height
if end_before_start_count > 0:
logger.warning(f"Found {end_before_start_count} rows with end before start in the result. These rows will be removed.")
result = result.filter(pl.col("end") >= pl.col("start"))
result = result.rename({"alarm_id": "manufacturer_id"})
# dropping existing alarms within the period
logger.info(
f"Deleting existing alarms for object {self.object} and alarm {self.alarm_id} within the period {self._evaluated_period}.",
)
self._perfdb.alarms.history.delete(
object_names=[self.object],
period=self._evaluated_period,
alarm_ids=[self.alarm_db_id],
)
if not result.is_empty():
# saving the result in the database
logger.info(f"Saving alarms for object {self.object} and alarm {self.alarm_id} within the period {self._evaluated_period}.")
self._perfdb.alarms.history.insert(
df=result,
on_conflict="update",
)
else:
logger.info(f"No alarms to save for object {self.object} and alarm {self.alarm_id} within the period {self._evaluated_period}.")