diff --git a/chaos_genius/alerts/anomaly_alerts.py b/chaos_genius/alerts/anomaly_alerts.py
index cd28ef7a7..53f03946e 100644
--- a/chaos_genius/alerts/anomaly_alerts.py
+++ b/chaos_genius/alerts/anomaly_alerts.py
@@ -43,7 +43,6 @@
from chaos_genius.alerts.slack import anomaly_alert_slack
from chaos_genius.alerts.utils import (
AlertException,
- change_message_from_percent,
find_percentage_change,
human_readable,
send_email_using_template,
@@ -79,6 +78,8 @@ class AnomalyPointOriginal(BaseModel):
# y-value of point
y: float
+ # model prediction (expected value)
+ yhat: Optional[float]
# lower bound of expected value
yhat_lower: float
# upper bound of expected value
@@ -102,8 +103,8 @@ class AnomalyPointOriginal(BaseModel):
data_datetime: datetime.datetime
@property
- def expected_value(self) -> str:
- """Expected values represented in a string."""
+ def expected_range(self) -> str:
+ """Expected values range represented in a string."""
return f"{self.yhat_lower} to {self.yhat_upper}"
@property
@@ -202,10 +203,6 @@ class AnomalyPoint(AnomalyPointOriginal):
severity: int
# previous data point (y-value)
previous_value: Optional[float]
- # percentage change from previous point
- percent_change: Union[StrictFloat, StrictInt, str]
- # human readable message describing the percent_change
- change_message: str
relevant_subdims_: Optional[List]
@@ -232,17 +229,13 @@ def _from_original_single(
relevant_subdims: Optional[List["AnomalyPoint"]],
) -> "AnomalyPoint":
y = round(point.y, 2)
+ yhat = round(point.yhat, 2) if point.yhat is not None else None
yhat_lower = round(point.yhat_lower, 2)
yhat_upper = round(point.yhat_upper, 2)
severity = round(point.severity)
series_type = point.series_type
- percent_change = find_percentage_change(
- point.y, previous_anomaly_point.y if previous_anomaly_point else None
- )
- change_message = change_message_from_percent(percent_change)
-
previous_value = (
round(previous_anomaly_point.y, 2)
if previous_anomaly_point is not None
@@ -251,6 +244,7 @@ def _from_original_single(
return AnomalyPoint(
y=y,
+ yhat=yhat,
yhat_lower=yhat_lower,
yhat_upper=yhat_upper,
severity=severity,
@@ -260,8 +254,6 @@ def _from_original_single(
created_at=point.created_at,
data_datetime=point.data_datetime,
previous_value=previous_value,
- percent_change=percent_change,
- change_message=change_message,
relevant_subdims_=relevant_subdims,
)
@@ -375,7 +367,8 @@ class AnomalyPointFormatted(AnomalyPoint):
alert_channel_conf: Any
formatted_date: str
- formatted_change_percent: str
+ percent_change: Union[StrictFloat, StrictInt, str]
+ percent_change_formatted: str
is_hourly: bool
@@ -395,24 +388,25 @@ def _from_point_single(
dt_format = ALERT_READABLE_DATE_FORMAT
formatted_date = point.data_datetime.strftime(dt_format)
- formatted_change_percent = point.percent_change
- if isinstance(point.percent_change, (int, float)):
+ percent_change = find_percentage_change(point.y, point.yhat)
+ percent_change_formatted = percent_change
+ if isinstance(percent_change, (int, float)):
# TODO: decide on this and simplify
change_percent = (
- f"{point.percent_change:.0f}"
- if abs(point.percent_change) < 10
- else f"{point.percent_change:.0f}"
+ f"{percent_change:.0f}"
+ if abs(percent_change) < 10
+ else f"{percent_change:.0f}"
)
- if point.percent_change > 0:
- formatted_change_percent = f"{change_percent}%"
+ if percent_change > 0:
+ percent_change_formatted = f"{change_percent}%"
else:
- formatted_change_percent = f"{change_percent[1:]}%"
+ percent_change_formatted = f"{change_percent[1:]}%"
if (
- isinstance(point.percent_change, str)
- and point.percent_change.endswith("inf")
- and point.previous_value is not None
+ isinstance(percent_change, str)
+ and percent_change.endswith("inf")
+ and point.yhat is not None
):
- formatted_change_percent = f"{point.percent_change}%"
+ percent_change_formatted = f"{percent_change}%"
is_hourly = time_series_frequency is not None and time_series_frequency == "H"
@@ -442,7 +436,8 @@ def _from_point_single(
alert_channel=alert_channel,
alert_channel_conf=alert_channel_conf,
formatted_date=formatted_date,
- formatted_change_percent=str(formatted_change_percent),
+ percent_change=percent_change,
+ percent_change_formatted=str(percent_change_formatted),
is_hourly=is_hourly,
)
@@ -1120,8 +1115,8 @@ def make_anomaly_data_csv(
anomaly_df.sort_values(by="severity", inplace=True, ascending=False)
# this is a property that is calculated, so it needs to be assigned separately
- anomaly_df["expected_value"] = [
- point.expected_value for point in all_anomaly_points
+ anomaly_df["expected_range"] = [
+ point.expected_range for point in all_anomaly_points
]
# this is a property that is calculated, so it needs to be assigned separately
anomaly_df["series_type"] = [point.series_type_name for point in all_anomaly_points]
@@ -1161,6 +1156,7 @@ def _format_anomaly_point_for_template(
def top_anomalies(points: Iterable[TAnomalyPointOrig], n=10) -> List[TAnomalyPointOrig]:
"""Returns top n anomalies according to severity."""
+ # TODO: how to incorporate impact here?
return heapq.nlargest(n, points, key=lambda point: point.severity)
diff --git a/chaos_genius/alerts/constants.py b/chaos_genius/alerts/constants.py
index 74e9b7e33..189f099c6 100644
--- a/chaos_genius/alerts/constants.py
+++ b/chaos_genius/alerts/constants.py
@@ -24,10 +24,10 @@
ANOMALY_TABLE_COLUMN_NAMES_MAPPER = {
"series_type": "Dimension",
"data_datetime": "Time of Occurrence",
- "y": "Value",
+ "y": "Actual Value",
+ "yhat": "Expected Value",
"severity": "Severity Score",
- "change_message": "Change",
- "expected_value": "Expected Value",
+ "expected_range": "Expected Range",
"previous_value": "Previous Value",
}
ANOMALY_REPORT_COLUMN_NAMES_MAPPER = dict(
@@ -36,11 +36,11 @@
ANOMALY_TABLE_COLUMN_NAMES_ORDERED = [
"series_type",
"data_datetime",
- "y",
"previous_value",
- "change_message",
+ "y",
+ "yhat",
"severity",
- "expected_value",
+ "expected_range",
]
ANOMALY_REPORT_COLUMN_NAMES_ORDERED = ["kpi_name"] + ANOMALY_TABLE_COLUMN_NAMES_ORDERED
diff --git a/chaos_genius/alerts/email_templates/common.html b/chaos_genius/alerts/email_templates/common.html
index 98402fc16..c1304551f 100644
--- a/chaos_genius/alerts/email_templates/common.html
+++ b/chaos_genius/alerts/email_templates/common.html
@@ -54,169 +54,42 @@
{% macro anomaly_point_formatting(point, kpi_link_prefix=none) -%}
{% set include_kpi_name = kpi_link_prefix is not none %}
- {% if point.previous_value is none or point.y == point.previous_value -%}
-
- ∿ Anomalous behavior
-
+ {% if point.percent_change is string %}
+ ∿ Anomaly detected
+ {% elif point.percent_change >= 0 %}
+ ↑ {{ point.percent_change_formatted }} higher than expected
+ {% else %}
+ ↓ {{ point.percent_change_formatted }} lower than expected
+ {% endif %}
- {% if include_kpi_name -%}
-
+ -
- in
+ {% if include_kpi_name -%}
+ {{ kpi_name_link(kpi_link_prefix, point) }}
+ {% endif %}
-
+ {% if point.is_subdim %}
+ {{ subdim_name_link(point) }}
+ {% endif %}
- {{ kpi_name_link(kpi_link_prefix, point) }}
+
+ changed to
+
- {% if point.is_subdim %}
- {{ subdim_name_link(point) }}
- {% endif %}
- {% else -%}
-
+ {{point.y_readable}}{% if point.is_hourly -%}
+ at
+ {{ point.anomaly_time_only }}{%- endif %}{% if point.is_hourly -%}
-
- from
-
- {{ point.previous_point_time_only }}
-
-
- to
-
- {{ point.anomaly_time_only }}
- {%- endif %}
- {%- endif %}. (Expected range: {{ point.yhat_lower_readable }} - {{ point.yhat_upper_readable }}).
+
- -->{% if point.relevant_subdims -%}
+ {% if point.relevant_subdims -%}
Reasons for change:
diff --git a/chaos_genius/alerts/slack.py b/chaos_genius/alerts/slack.py
index 150a93f35..00b7b4ce6 100644
--- a/chaos_genius/alerts/slack.py
+++ b/chaos_genius/alerts/slack.py
@@ -268,57 +268,27 @@ def anomaly_point_formatting(
include_kpi_name = kpi_link_prefix is not None
- if point.previous_value is None or point.y == point.previous_value:
+ if isinstance(point.percent_change, str):
out += "- :black_circle_for_record: Anomalous behavior"
-
- if include_kpi_name:
- out += f" in *{kpi_name_link(kpi_link_prefix, point)}* "
- if point.is_subdim:
- out += f"{subdim_name_link(point)} "
- else:
- out += " detected "
- if point.is_subdim:
- out += f"in {subdim_name_link(point)} "
-
- if point.previous_value is None:
- out += f"- changed to *{point.y_readable}*"
- if point.is_hourly:
- out += f" at {point.anomaly_time_only}"
- else:
- if point.is_hourly:
- out += (
- f"- with constant value *{point.y_readable}*"
- + f" from {point.previous_point_time_only}"
- + f" to {point.anomaly_time_only}"
- )
- else:
- out += f"- with same value *{point.y_readable}* as previous day"
-
+ elif point.percent_change >= 0:
+ out += f"- :arrow_up: {point.percent_change_formatted} higher than expected"
else:
- if point.y > point.previous_value:
- out += f"- :arrow_up: {point.formatted_change_percent} Spike"
- elif point.y < point.previous_value:
- out += f"- :arrow_down_small: {point.formatted_change_percent} Drop"
-
- if include_kpi_name:
- out += f" in *{kpi_name_link(kpi_link_prefix, point)}* "
- if point.is_subdim:
- out += f"{subdim_name_link(point)} "
- else:
- out += " detected "
- if point.is_subdim:
- out += f"in {subdim_name_link(point)} "
+ out += (
+ f"- :arrow_down_small: {point.percent_change_formatted} lower than expected"
+ )
+
+ out += " - "
- out += f"- changed to *{point.y_readable}*"
+ if include_kpi_name:
+ out += f"*{kpi_name_link(kpi_link_prefix, point)}* "
- if point.previous_value_readable is not None:
- out += f" from {point.previous_value_readable}"
+ if point.is_subdim:
+ out += f"{subdim_name_link(point)} "
- if point.is_hourly:
- out += (
- f" from {point.previous_point_time_only}"
- + f" to {point.anomaly_time_only}"
- )
+ out += f"changed to *{point.y_readable}*"
+ if point.is_hourly:
+ out += f" at {point.anomaly_time_only}"
+ out += f". (Expected range: {point.yhat_lower_readable} - {point.yhat_upper_readable})."
if point.relevant_subdims:
out += "\n - Reasons for change: "
diff --git a/chaos_genius/alerts/utils.py b/chaos_genius/alerts/utils.py
index 2d56de045..84ea39055 100644
--- a/chaos_genius/alerts/utils.py
+++ b/chaos_genius/alerts/utils.py
@@ -47,42 +47,23 @@ def webapp_url_prefix():
return f"{CHAOSGENIUS_WEBAPP_URL}{forward_slash}"
-def change_message_from_percent(percent_change: Union[str, int, float]) -> str:
- """Creates a change message from given percentage change.
-
- percent_change will be:
- - "–" in case the last data point was missing or both the points had values 0
- - 0 (int) in case there was no change
- - positive value (int/float) in case there was an increase
- - negative value (int/float) in case there was a decrease
- """
- if isinstance(percent_change, str):
- return percent_change
- elif percent_change == 0:
- return "No change (–)"
- elif percent_change > 0:
- return f"Increased by ({percent_change}%)"
- else:
- return f"Decreased by ({percent_change}%)"
-
-
def find_percentage_change(
- curr_val: Union[int, float], prev_val: Optional[Union[int, float]]
+ actual_val: Union[int, float], expected_val: Optional[Union[int, float]]
) -> Union[int, float, str]:
- """Calculates percentage change between previous and current value."""
- if prev_val is None:
- # previous point wasn't found
- return "–"
- elif curr_val == 0 and prev_val == curr_val:
- # both current and previous value are 0
- return "–"
- elif prev_val == 0:
+ """Calculates percentage change between expected and actual value."""
+ if expected_val is None:
+ # expected value has not been calculated for this point
+ return "N/A"
+ elif actual_val == 0 and expected_val == actual_val:
+ # both current and expected value are 0
+ return 0
+ elif expected_val == 0:
# previous value is 0, but current value isn't
- sign_ = "+" if curr_val > 0 else "-"
+ sign_ = "+" if actual_val > 0 else "-"
return sign_ + "inf"
else:
- change = curr_val - prev_val
- percentage_change = (change / prev_val) * 100
+ change = actual_val - expected_val
+ percentage_change = (change / expected_val) * 100
return round_number(percentage_change)
diff --git a/chaos_genius/templates/alert_dashboard.html b/chaos_genius/templates/alert_dashboard.html
index d754c03bf..146dd9bd1 100644
--- a/chaos_genius/templates/alert_dashboard.html
+++ b/chaos_genius/templates/alert_dashboard.html
@@ -89,11 +89,14 @@