Watcher: How can I send email alert only when the difference of actual and typical is greater than X

How can I send email alert only when the difference of actual and typical is greater than X?

this is my watch:

{
    "trigger": {
      "schedule": {
        "interval": "5m"
      }
    },
    "input": {
      "search": {
        "request": {
          "search_type": "query_then_fetch",
          "indices": [
            ".ml-anomalies-*"
          ],
          "rest_total_hits_as_int": true,
          "body": {
            "query": {
              "bool": {
                "filter": [
                  {
                    "range": {
                      "timestamp": {
                        "gte": "now-35m"
                      }
                    }
                  },
                  {
                    "term": {
                      "result_type": "record"
                    }
                  },
                  {
                    "term": {
                      "job_id": "{{ctx.metadata.job_id}}"
                    }
                  },
                  {
                    "range": {
                      "record_score": {
                        "gte": "{{ctx.metadata.min_record_score}}"
                      }
                    }
                  }
                ]
              }
            },
            "script_fields": {
              "start": {
                "script": {
                  "lang": "painless",
                  "source": """
LocalDateTime.ofEpochSecond((doc["timestamp"].value.getMillis()-((doc["bucket_span"].value * 1000) * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+":00.000Z"
""",
                  "params": {
                    "padding": 10
                  }
                }
              },
              "end": {
                "script": {
                  "lang": "painless",
                  "source": """
LocalDateTime.ofEpochSecond((doc["timestamp"].value.getMillis()+((doc["bucket_span"].value * 1000) * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+":00.000Z"
""",
                  "params": {
                    "padding": 10
                  }
                }
              },
              "timestamp_epoch": {
                "script": {
                  "lang": "painless",
                  "source": """doc["timestamp"].value.getMillis()/1000"""
                }
              },
              "timestamp_iso8601": {
                "script": {
                  "lang": "painless",
                  "source": """doc["timestamp"].value"""
                }
              },
              "split": {
                "script": {
                  "lang": "painless",
                  "source": """doc["partition_field_value"]"""
                }
              },
              "by_field": {
                "script": {
                  "lang": "painless",
                  "source": """doc["by_field_value"]"""
                }
              },
              "actual": {
                "script": {
                  "lang": "painless",
                  "source": """Math.round(doc["actual"].value)"""
                }
              },
              "typical": {
                "script": {
                  "lang": "painless",
                  "source": """Math.round(doc["typical"].value)"""
                }
              },
              "score": {
                "script": {
                  "lang": "painless",
                  "source": """Math.round(doc["record_score"].value)"""
                }
              }
            }
          }
        }
      }
    },
    "condition": {
      "compare": {
        "ctx.payload.hits.total": {
          "gt": 0
        }
      }
    },
    "actions": {
      "send_email": {
        "throttle_period_in_millis": 900000,
        "transform": {
          "script": {
            "source": """
          return ctx.payload.hits.hits.stream().map(p -> [
            'source':p.fields.split.0,
            'disco':p.fields.by_field.0,
            'score':p.fields.score.0,
            'actual':p.fields.actual.0,
            'typical':p.fields.typical.0,
            'timestamp':p.fields.timestamp_iso8601.0,
            'start':p.fields.start.0,
            'end':p.fields.end.0
            ])
            .collect(Collectors.toList());
""",
            "lang": "painless"
          }
        },
        "email": {
          "profile": "standard",
          "from": "noreply-ml@mymail.cl",
          "to": [
            "dsto@mail.com"
          ],
          "subject": "ML Watcher Alert  (record score alert)",
          "body": {
            "html": """<html>
  <body>
    <strong>
      Machine Learning Alert     </strong>
    <br />
    <br />

  {{#ctx.payload._value}}  <strong>
      Job
    </strong>: {{ctx.metadata.job_id}}
    <br />

    <strong>
      Time
    </strong>: {{timestamp}}
    <br />
    
    <strong>
      Record score
    </strong>: {{score}}
    <br />
  <strong>
      Host
    </strong>: {{source}}
    <br />
  <strong>
      Source
    </strong>: {{disco}}
    <br />

 <strong>
      actual
    </strong>: {{actual}}
    <br />

  <strong>
      typical
    </strong>: {{typical}}
    <br />

 <br />

     <a href="https://mykibana.com/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!({{ctx.metadata.job_id}})),refreshInterval:(pause:!t,value:0),time:(from:'{{start}}',to:'{{end}}'))&_a=(mlSelectInterval:(display:Auto,val:auto),mlSelectSeverity:(color:%23d2e9f7,display:warning,val:0),mlTimeSeriesExplorer:(entities:(disco.keyword:{{disco}},hostname.keyword:{{source}})))">              
        >>> Abrir en Anomaly Explorer.
         </a>
    <br />
    <br />

  {{/ctx.payload._value}}

  </body>
</html>
"""
          }
        }
      }
    },
    "metadata": {
      "min_record_score": 75,
      "job_id": "datastore-util-per"
    }
  }

See this example: https://gist.github.com/richcollier/e03a012b3ebc426e0720f238374ea3ad

1 Like

Thanks again Rich, what you do on your watcher is similar to apply this rule in ML?

image

in the end, the outcome is similar. Obviously if you use a rule in ML, the anomaly record never gets created in the first place. Otherwise, you can use the Watcher logic to do the "filtering".

1 Like