This is an automated email from the ASF dual-hosted git repository.

jihao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new aa6d48f  [TE] add threshold-based anomaly labeler (#5972)
aa6d48f is described below

commit aa6d48f183a6e7fd7c3c09e71bd165926da561aa
Author: Vincent Chen <jianc...@linkedin.com>
AuthorDate: Tue Sep 8 17:05:16 2020 -0700

    [TE] add threshold-based anomaly labeler (#5972)
    
    This PR is the first part of adding an anomaly labeler for better alerting 
ThirdEye users. It adds a labeling phase and a threshold-based labeler. The 
next PR is to add the logic of translating YAML to JSON config.
---
 .../AnomalySeverity.java}                          |  28 +++-
 .../datalayer/dto/MergedAnomalyResultDTO.java      |  11 ++
 .../datalayer/pojo/MergedAnomalyResultBean.java    |  15 ++
 .../thirdeye/detection/DetectionPipeline.java      |  22 +++
 .../detection/algorithm/DimensionWrapper.java      |  22 +--
 .../algorithm/LegacyAlertFilterWrapper.java        |  16 +-
 .../algorithm/LegacyDimensionWrapper.java          |  24 +--
 .../detection/algorithm/LegacyMergeWrapper.java    |  17 +-
 .../thirdeye/detection/algorithm/MergeWrapper.java |  15 +-
 .../detection/annotation/DetectionTag.java         |   3 +-
 .../components/ThresholdSeverityLabeler.java       |  88 ++++++++++
 .../SeverityThresholdLabelerSpec.java}             |  40 ++++-
 .../components/Labeler.java}                       |  19 ++-
 .../detection/wrapper/AnomalyFilterWrapper.java    |  17 +-
 ...lterWrapper.java => AnomalyLabelerWrapper.java} |  79 +++------
 .../thirdeye/detection/wrapper/GrouperWrapper.java |  15 +-
 .../components/ThresholdRuleAnomalyFilterTest.java |   2 -
 .../components/ThresholdSeverityLabelerTest.java   | 181 +++++++++++++++++++++
 18 files changed, 426 insertions(+), 188 deletions(-)

diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java
similarity index 64%
copy from 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
copy to 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java
index c30120d..16b545a 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/anomaly/AnomalySeverity.java
@@ -17,12 +17,26 @@
  * under the License.
  */
 
-package org.apache.pinot.thirdeye.detection.annotation;
+package org.apache.pinot.thirdeye.anomaly;
 
-public enum DetectionTag {
-  ALGORITHM_DETECTION,
-  RULE_DETECTION,
-  ALGORITHM_FILTER,
-  RULE_FILTER,
-  GROUPER
+/**
+ * The severity of anomaly.
+ */
+public enum AnomalySeverity {
+  // the order of definition follows the severity from highest to lowest
+  CRITICAL ("critical"),
+  HIGH ("high"),
+  MEDIUM ("medium"),
+  LOW ("low"),
+  DEFAULT ("default");
+
+  private String severity;
+
+  AnomalySeverity(String severity) {
+    this.severity = severity;
+  }
+
+  public String getLabel() {
+    return severity;
+  }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java
index 415a252..69b08c5 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/dto/MergedAnomalyResultDTO.java
@@ -44,6 +44,9 @@ public class MergedAnomalyResultDTO extends 
MergedAnomalyResultBean implements A
 
   private Set<MergedAnomalyResultDTO> children = new HashSet<>();
 
+  // flag to be set when severity changes but not to be persisted
+  private boolean renotify = false;
+
   public MergedAnomalyResultDTO() {
     setCreatedTime(System.currentTimeMillis());
   }
@@ -107,6 +110,14 @@ public class MergedAnomalyResultDTO extends 
MergedAnomalyResultBean implements A
     this.children = children;
   }
 
+  public boolean isRenotify() {
+    return renotify;
+  }
+
+  public void setRenotify(boolean renotify) {
+    this.renotify = renotify;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java
index 834356d..cf9236c 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/datalayer/pojo/MergedAnomalyResultBean.java
@@ -20,6 +20,7 @@
 package org.apache.pinot.thirdeye.datalayer.pojo;
 
 import java.io.Serializable;
+import org.apache.pinot.thirdeye.anomaly.AnomalySeverity;
 import org.apache.pinot.thirdeye.anomaly.AnomalyType;
 import org.apache.pinot.thirdeye.common.dimension.DimensionMap;
 import org.apache.pinot.thirdeye.constant.AnomalyResultSource;
@@ -60,6 +61,8 @@ public class MergedAnomalyResultBean extends AbstractBean 
implements Comparable<
   private Set<Long> childIds; // ids of the anomalies this anomaly merged from
   private boolean isChild;
   private AnomalyType type;
+  private AnomalySeverity severityLabel;
+
 
   public Set<Long> getChildIds() {
     return childIds;
@@ -264,6 +267,18 @@ public class MergedAnomalyResultBean extends AbstractBean 
implements Comparable<
     this.type = type;
   }
 
+  public void setSeverityLabel(AnomalySeverity severityLabel) {
+    this.severityLabel = severityLabel;
+  }
+
+  public AnomalySeverity getSeverityLabel() {
+    // default severity level is debug
+    if (severityLabel == null) {
+      return AnomalySeverity.DEFAULT;
+    }
+    return severityLabel;
+  }
+
   @Override
   public int hashCode() {
     return Objects.hash(getId(), startTime, endTime, collection, metric, 
dimensions, score, impactToGlobal, avgBaselineVal, avgCurrentVal, 
anomalyResultSource, metricUrn, detectionConfigId, childIds, isChild);
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java
index d10a7f4..b6521da 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionPipeline.java
@@ -19,7 +19,9 @@
 
 package org.apache.pinot.thirdeye.detection;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Multimap;
+import java.util.HashMap;
 import org.apache.pinot.thirdeye.common.dimension.DimensionMap;
 import org.apache.pinot.thirdeye.dataframe.BooleanSeries;
 import org.apache.pinot.thirdeye.dataframe.DataFrame;
@@ -244,6 +246,26 @@ public abstract class DetectionPipeline {
     return anomalies;
   }
 
+  /**
+   * Helper to initialize and run the next level wrapper
+   * @param nestedProps nested properties
+   * @return intermediate result of a detection pipeline
+   * @throws Exception
+   */
+  protected DetectionPipelineResult runNested(
+      Map<String, Object> nestedProps, final long startTime, final long 
endTime) throws Exception {
+    Preconditions.checkArgument(nestedProps.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
+    Map<String, Object> properties = new HashMap<>(nestedProps);
+    DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
+    nestedConfig.setId(this.config.getId());
+    nestedConfig.setName(this.config.getName());
+    nestedConfig.setDescription(this.config.getDescription());
+    nestedConfig.setComponents(this.config.getComponents());
+    nestedConfig.setProperties(properties);
+    DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
startTime, endTime);
+    return pipeline.run();
+  }
+
   // TODO anomaly should support multimap
   private DimensionMap toFilterMap(Multimap<String, String> filters) {
     DimensionMap map = new DimensionMap();
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java
index a798cdb..77bdaa4 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/DimensionWrapper.java
@@ -332,7 +332,8 @@ public class DimensionWrapper extends DetectionPipeline {
       for (Map<String, Object> properties : this.nestedProperties) {
         DetectionPipelineResult intermediate;
         try {
-          intermediate = this.runNested(metric, properties);
+          properties.put(this.nestedMetricUrnKey, metric.getUrn());
+          intermediate = this.runNested(properties, this.startTime, 
this.endTime);
         } catch (Exception e) {
           LOG.warn("[DetectionConfigID{}] detecting anomalies for window {} to 
{} failed for metric urn {}.",
               this.config.getId(), this.start, this.end, metric.getUrn(), e);
@@ -446,23 +447,4 @@ public class DimensionWrapper extends DetectionPipeline {
     }
     return false;
   }
-
-  protected DetectionPipelineResult runNested(MetricEntity metric, Map<String, 
Object> template) throws Exception {
-    Preconditions.checkArgument(template.containsKey(PROP_CLASS_NAME), "Nested 
missing " + PROP_CLASS_NAME);
-
-    Map<String, Object> properties = new HashMap<>(template);
-
-    properties.put(this.nestedMetricUrnKey, metric.getUrn());
-
-    DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-    nestedConfig.setId(this.config.getId());
-    nestedConfig.setName(this.config.getName());
-    nestedConfig.setDescription(this.config.getDescription());
-    nestedConfig.setProperties(properties);
-    nestedConfig.setComponents(this.config.getComponents());
-
-    DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-    return pipeline.run();
-  }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java
index c100306..776271c 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyAlertFilterWrapper.java
@@ -95,26 +95,14 @@ public class LegacyAlertFilterWrapper extends 
DetectionPipeline {
   @Override
   public DetectionPipelineResult run() throws Exception {
     List<MergedAnomalyResultDTO> candidates = new ArrayList<>();
-    for (Map<String, Object> propertiesRaw : this.nestedProperties) {
-      Map<String, Object> properties = new HashMap<>(propertiesRaw);
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-
+    for (Map<String, Object> properties : this.nestedProperties) {
       if (!properties.containsKey(PROP_SPEC)) {
         properties.put(PROP_SPEC, this.anomalyFunctionSpecs);
       }
       if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) {
         properties.put(PROP_ANOMALY_FUNCTION_CLASS, 
this.config.getProperties().get(PROP_ANOMALY_FUNCTION_CLASS));
       }
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(properties);
-
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime - this.alertFilterLookBack, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
+      DetectionPipelineResult intermediate = this.runNested(properties, 
this.startTime - this.alertFilterLookBack, this.endTime);
       candidates.addAll(intermediate.getAnomalies());
     }
 
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java
index 6ba74bb..31b75f7 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyDimensionWrapper.java
@@ -83,25 +83,15 @@ public class LegacyDimensionWrapper extends 
DimensionWrapper {
   }
 
   @Override
-  protected DetectionPipelineResult runNested(MetricEntity metric, Map<String, 
Object> template) throws Exception {
-    Map<String, Object> properties = new HashMap<>(template);
-
-    properties.put(this.nestedMetricUrnKey, metric.getUrn());
-    if (!properties.containsKey(PROP_SPEC)) {
-      properties.put(PROP_SPEC, this.anomalyFunctionSpecs);
+  protected DetectionPipelineResult runNested(
+      Map<String, Object> nestedProps, final long startTime, final long 
endTime) throws Exception {
+    if (!nestedProps.containsKey(PROP_SPEC)) {
+      nestedProps.put(PROP_SPEC, this.anomalyFunctionSpecs);
     }
-    if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) {
-      properties.put(PROP_ANOMALY_FUNCTION_CLASS, 
this.anomalyFunctionClassName);
+    if (!nestedProps.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) {
+      nestedProps.put(PROP_ANOMALY_FUNCTION_CLASS, 
this.anomalyFunctionClassName);
     }
-    DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-    nestedConfig.setId(this.config.getId());
-    nestedConfig.setName(this.config.getName());
-    nestedConfig.setDescription(this.config.getDescription());
-    nestedConfig.setProperties(properties);
-
-    DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-    return pipeline.run();
+    return super.runNested(nestedProps, startTime, endTime);
   }
 
   private static DetectionConfigDTO augmentConfig(DetectionConfigDTO config) {
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java
index 7d3435e..51ea009 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/LegacyMergeWrapper.java
@@ -143,27 +143,14 @@ public class LegacyMergeWrapper extends DetectionPipeline 
{
     // generate anomalies
     List<MergedAnomalyResultDTO> generated = new ArrayList<>();
 
-    for (Map<String, Object> propertiesRaw : this.nestedProperties) {
-      Map<String, Object> properties = new HashMap<>(propertiesRaw);
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-
+    for (Map<String, Object> properties : this.nestedProperties) {
       if (!properties.containsKey(PROP_SPEC)) {
         properties.put(PROP_SPEC, this.anomalyFunctionSpecs);
       }
       if (!properties.containsKey(PROP_ANOMALY_FUNCTION_CLASS)) {
         properties.put(PROP_ANOMALY_FUNCTION_CLASS, 
this.anomalyFunctionClassName);
       }
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(properties);
-
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
-
+      DetectionPipelineResult intermediate = this.runNested(properties, 
startTime, endTime);
       generated.addAll(intermediate.getAnomalies());
     }
 
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java
index f9a55f3..ee67927 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/algorithm/MergeWrapper.java
@@ -123,20 +123,8 @@ public class MergeWrapper extends DetectionPipeline {
     int i = 0;
     Set<Long> lastTimeStamps = new HashSet<>();
     for (Map<String, Object> properties : this.nestedProperties) {
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(properties);
-      nestedConfig.setComponents(this.config.getComponents());
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
+      DetectionPipelineResult intermediate = this.runNested(properties, 
this.startTime, this.endTime);
       lastTimeStamps.add(intermediate.getLastTimestamp());
-
       generated.addAll(intermediate.getAnomalies());
       predictionResults.addAll(intermediate.getPredictions());
       evaluations.addAll(intermediate.getEvaluations());
@@ -376,6 +364,7 @@ public class MergeWrapper extends DetectionPipeline {
     to.setWeight(from.getWeight());
     to.setProperties(from.getProperties());
     to.setType(from.getType());
+    to.setSeverityLabel(from.getSeverityLabel());
     return to;
   }
 
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
index c30120d..cef608a 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
@@ -24,5 +24,6 @@ public enum DetectionTag {
   RULE_DETECTION,
   ALGORITHM_FILTER,
   RULE_FILTER,
-  GROUPER
+  GROUPER,
+  LABELER
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java
new file mode 100644
index 0000000..ff04015
--- /dev/null
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabeler.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.pinot.thirdeye.detection.components;
+
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.pinot.thirdeye.anomaly.AnomalySeverity;
+import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
+import org.apache.pinot.thirdeye.detection.InputDataFetcher;
+import org.apache.pinot.thirdeye.detection.annotation.Components;
+import org.apache.pinot.thirdeye.detection.annotation.DetectionTag;
+import org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec;
+import org.apache.pinot.thirdeye.detection.spi.components.Labeler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static 
org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec.Threshold;
+
+/**
+ * Threshold-based severity labeler, which labels anomalies with severity 
based on deviation from baseline and duration
+ * of the anomalies. It tries to label anomalies from highest to lowest if 
deviation or duration exceeds the threshold
+ */
+@Components(title = "ThresholdSeverityLabeler", type = 
"THRESHOLD_SEVERITY_LABELER",
+    tags = {DetectionTag.LABELER}, description = "An threshold-based labeler 
for anomaly severity")
+public class ThresholdSeverityLabeler implements 
Labeler<SeverityThresholdLabelerSpec> {
+  private final static Logger LOG = 
LoggerFactory.getLogger(ThresholdSeverityLabeler.class);
+  // severity map ordered by priority from top to bottom
+  private TreeMap<AnomalySeverity, Threshold> severityMap;
+
+  @Override
+  public void label(List<MergedAnomalyResultDTO> anomalies) {
+    for (MergedAnomalyResultDTO anomaly : anomalies) {
+      double currVal = anomaly.getAvgCurrentVal();
+      double baseVal = anomaly.getAvgBaselineVal();
+      if (Double.isNaN(currVal) || Double.isNaN(baseVal)) {
+        LOG.warn("Unable to label anomaly for detection {} from {} to {}, so 
skipping labeling...",
+            anomaly.getDetectionConfigId(), anomaly.getStartTime(), 
anomaly.getEndTime());
+        continue;
+      }
+      double deviation = Math.abs(currVal - baseVal) / baseVal;
+      long duration = anomaly.getEndTime() - anomaly.getStartTime();
+      for (Map.Entry<AnomalySeverity, Threshold> entry : 
severityMap.entrySet()) {
+        if (deviation >= entry.getValue().change || duration >= 
entry.getValue().duration) {
+          if (anomaly.getSeverityLabel() != entry.getKey()) {
+            // find the severity from highest to lowest
+            if (anomaly.getId() != null && 
anomaly.getSeverityLabel().compareTo(entry.getKey()) > 0) {
+              // only set renotify if the anomaly exists and its severity gets 
higher
+              anomaly.setRenotify(true);
+            }
+            anomaly.setSeverityLabel(entry.getKey());
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public void init(SeverityThresholdLabelerSpec spec, InputDataFetcher 
dataFetcher) {
+    this.severityMap = new TreeMap<>();
+    for (String key : spec.getSeverity().keySet()) {
+      try {
+        AnomalySeverity severity = AnomalySeverity.valueOf(key);
+        this.severityMap.put(severity, spec.getSeverity().get(key));
+      } catch (IllegalArgumentException e) {
+        LOG.error("Cannot find valid anomaly severity, so ignoring...", e);
+      }
+    }
+  }
+}
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java
similarity index 50%
copy from 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
copy to 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java
index c30120d..8390580 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spec/SeverityThresholdLabelerSpec.java
@@ -17,12 +17,36 @@
  * under the License.
  */
 
-package org.apache.pinot.thirdeye.detection.annotation;
-
-public enum DetectionTag {
-  ALGORITHM_DETECTION,
-  RULE_DETECTION,
-  ALGORITHM_FILTER,
-  RULE_FILTER,
-  GROUPER
+package org.apache.pinot.thirdeye.detection.spec;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import java.util.Map;
+
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class SeverityThresholdLabelerSpec  extends AbstractSpec{
+  private Map<String, Threshold> severity;
+
+  @JsonIgnoreProperties(ignoreUnknown = true)
+  public static class Threshold {
+    public double change = Double.MAX_VALUE;
+    public long duration = Long.MAX_VALUE;
+
+    public Threshold() {
+
+    }
+
+    public Threshold(double change, long duration) {
+      this.change = change;
+      this.duration = duration;
+    }
+  }
+
+  public Map<String, Threshold> getSeverity() {
+    return severity;
+  }
+
+  public void setSeverity(Map<String, Threshold> severity) {
+    this.severity = severity;
+  }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java
similarity index 65%
copy from 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
copy to 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java
index c30120d..61541a4 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/annotation/DetectionTag.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/spi/components/Labeler.java
@@ -17,12 +17,17 @@
  * under the License.
  */
 
-package org.apache.pinot.thirdeye.detection.annotation;
+package org.apache.pinot.thirdeye.detection.spi.components;
 
-public enum DetectionTag {
-  ALGORITHM_DETECTION,
-  RULE_DETECTION,
-  ALGORITHM_FILTER,
-  RULE_FILTER,
-  GROUPER
+import java.util.List;
+import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
+import org.apache.pinot.thirdeye.detection.spec.AbstractSpec;
+
+
+public interface Labeler <T extends AbstractSpec> extends BaseComponent<T> {
+  /**
+   * add or modify labels of anomalies in place
+   * @param anomalies
+   */
+  void label(List<MergedAnomalyResultDTO> anomalies);
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
index 2b8eb1f..565d277 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
@@ -46,7 +46,6 @@ import org.apache.commons.collections4.MapUtils;
  */
 public class AnomalyFilterWrapper extends DetectionPipeline {
   private static final String PROP_NESTED = "nested";
-  private static final String PROP_CLASS_NAME = "className";
   private static final String PROP_METRIC_URN = "metricUrn";
   private static final String PROP_FILTER = "filter";
 
@@ -81,21 +80,7 @@ public class AnomalyFilterWrapper extends DetectionPipeline {
 
     Set<Long> lastTimeStamps = new HashSet<>();
     for (Map<String, Object> properties : this.nestedProperties) {
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-      HashMap<String, Object> nestedProp = new HashMap<>(properties);
-      if (this.metricUrn != null){
-        nestedProp.put(PROP_METRIC_URN, this.metricUrn);
-      }
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(nestedProp);
-      nestedConfig.setComponents(this.config.getComponents());
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
+      DetectionPipelineResult intermediate = this.runNested(properties, 
this.startTime, this.endTime);
       lastTimeStamps.add(intermediate.getLastTimestamp());
       diagnostics.putAll(intermediate.getDiagnostics());
       evaluations.addAll(intermediate.getEvaluations());
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java
similarity index 51%
copy from 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
copy to 
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java
index 2b8eb1f..5d59e9b 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyFilterWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/AnomalyLabelerWrapper.java
@@ -20,10 +20,13 @@
 package org.apache.pinot.thirdeye.detection.wrapper;
 
 import com.google.common.base.Preconditions;
-import com.google.common.collect.Collections2;
-import java.util.Collection;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import org.apache.commons.collections4.MapUtils;
 import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.EvaluationDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
@@ -33,80 +36,48 @@ import 
org.apache.pinot.thirdeye.detection.DetectionPipeline;
 import org.apache.pinot.thirdeye.detection.DetectionPipelineResult;
 import org.apache.pinot.thirdeye.detection.DetectionUtils;
 import org.apache.pinot.thirdeye.detection.PredictionResult;
-import org.apache.pinot.thirdeye.detection.spi.components.AnomalyFilter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.collections4.MapUtils;
-
+import org.apache.pinot.thirdeye.detection.spi.components.Labeler;
 
 /**
- * This anomaly filter wrapper runs the anomaly filter component to filter 
anomalies generated by detector based on the filter implementation.
+ * This anomaly labeler wrapper runs the anomaly labeler component to label 
anomalies generated by detector based on the labeler implementation.
  */
-public class AnomalyFilterWrapper extends DetectionPipeline {
+public class AnomalyLabelerWrapper extends DetectionPipeline {
   private static final String PROP_NESTED = "nested";
-  private static final String PROP_CLASS_NAME = "className";
-  private static final String PROP_METRIC_URN = "metricUrn";
-  private static final String PROP_FILTER = "filter";
+  private static final String PROP_LABELER = "labeler";
 
   private final List<Map<String, Object>> nestedProperties;
-  private final AnomalyFilter anomalyFilter;
-  private String metricUrn;
+  private String labelerName;
+  private Labeler labeler;
 
-  public AnomalyFilterWrapper(DataProvider provider, DetectionConfigDTO 
config, long startTime, long endTime) {
+  public AnomalyLabelerWrapper(DataProvider provider, DetectionConfigDTO 
config, long startTime, long endTime) {
     super(provider, config, startTime, endTime);
     Map<String, Object> properties = config.getProperties();
     this.nestedProperties = ConfigUtils.getList(properties.get(PROP_NESTED));
 
-    
Preconditions.checkArgument(this.config.getProperties().containsKey(PROP_FILTER));
-    String detectorReferenceKey = 
DetectionUtils.getComponentKey(MapUtils.getString(config.getProperties(), 
PROP_FILTER));
-    
Preconditions.checkArgument(this.config.getComponents().containsKey(detectorReferenceKey));
-    this.anomalyFilter = (AnomalyFilter) 
this.config.getComponents().get(detectorReferenceKey);
-
-    this.metricUrn = MapUtils.getString(properties, PROP_METRIC_URN);
+    
Preconditions.checkArgument(this.config.getProperties().containsKey(PROP_LABELER));
+    this.labelerName = 
DetectionUtils.getComponentKey(MapUtils.getString(config.getProperties(), 
PROP_LABELER));
+    
Preconditions.checkArgument(this.config.getComponents().containsKey(this.labelerName));
+    this.labeler = (Labeler) this.config.getComponents().get(this.labelerName);
   }
 
-  /**
-   * Runs the nested pipelines and calls the isQualified method in the anomaly 
filter stage to check if an anomaly passes the filter.
-   * @return the detection pipeline result
-   * @throws Exception
-   */
   @Override
-  public final DetectionPipelineResult run() throws Exception {
-    List<MergedAnomalyResultDTO> candidates = new ArrayList<>();
-    List<PredictionResult> predictionResults = new ArrayList<>();
+  public DetectionPipelineResult run() throws Exception {
+    List<MergedAnomalyResultDTO> anomalies = new ArrayList<>();
     Map<String, Object> diagnostics = new HashMap<>();
+    List<PredictionResult> predictionResults = new ArrayList<>();
     List<EvaluationDTO> evaluations = new ArrayList<>();
 
     Set<Long> lastTimeStamps = new HashSet<>();
     for (Map<String, Object> properties : this.nestedProperties) {
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-      HashMap<String, Object> nestedProp = new HashMap<>(properties);
-      if (this.metricUrn != null){
-        nestedProp.put(PROP_METRIC_URN, this.metricUrn);
-      }
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(nestedProp);
-      nestedConfig.setComponents(this.config.getComponents());
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
+      DetectionPipelineResult intermediate = this.runNested(properties, 
this.startTime, this.endTime);
       lastTimeStamps.add(intermediate.getLastTimestamp());
-      diagnostics.putAll(intermediate.getDiagnostics());
-      evaluations.addAll(intermediate.getEvaluations());
       predictionResults.addAll(intermediate.getPredictions());
-      candidates.addAll(intermediate.getAnomalies());
+      evaluations.addAll(intermediate.getEvaluations());
+      diagnostics.putAll(intermediate.getDiagnostics());
+      anomalies.addAll(intermediate.getAnomalies());
     }
-
-    Collection<MergedAnomalyResultDTO> anomalies =
-        Collections2.filter(candidates, mergedAnomaly -> mergedAnomaly != null 
&& !mergedAnomaly.isChild() && anomalyFilter.isQualified(mergedAnomaly));
-
-    return new DetectionPipelineResult(new ArrayList<>(anomalies), 
DetectionUtils.consolidateNestedLastTimeStamps(lastTimeStamps),
+    this.labeler.label(anomalies);
+    return new DetectionPipelineResult(anomalies, 
DetectionUtils.consolidateNestedLastTimeStamps(lastTimeStamps),
         predictionResults, evaluations).setDiagnostics(diagnostics);
   }
 }
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java
index b786cfa..6a13bbf 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/wrapper/GrouperWrapper.java
@@ -49,7 +49,6 @@ import static 
org.apache.pinot.thirdeye.detection.yaml.translator.DetectionConfi
  */
 public class GrouperWrapper extends DetectionPipeline {
   private static final String PROP_NESTED = "nested";
-  private static final String PROP_CLASS_NAME = "className";
   private static final String PROP_GROUPER = "grouper";
   public static final String PROP_DETECTOR_COMPONENT_NAME = 
"detectorComponentName";
 
@@ -88,20 +87,8 @@ public class GrouperWrapper extends DetectionPipeline {
 
     Set<Long> lastTimeStamps = new HashSet<>();
     for (Map<String, Object> properties : this.nestedProperties) {
-      DetectionConfigDTO nestedConfig = new DetectionConfigDTO();
-
-      Preconditions.checkArgument(properties.containsKey(PROP_CLASS_NAME), 
"Nested missing " + PROP_CLASS_NAME);
-
-      nestedConfig.setId(this.config.getId());
-      nestedConfig.setName(this.config.getName());
-      nestedConfig.setDescription(this.config.getDescription());
-      nestedConfig.setProperties(properties);
-      nestedConfig.setComponents(this.config.getComponents());
-      DetectionPipeline pipeline = this.provider.loadPipeline(nestedConfig, 
this.startTime, this.endTime);
-
-      DetectionPipelineResult intermediate = pipeline.run();
+      DetectionPipelineResult intermediate = this.runNested(properties, 
this.startTime, this.endTime);
       lastTimeStamps.add(intermediate.getLastTimestamp());
-
       predictionResults.addAll(intermediate.getPredictions());
       evaluations.addAll(intermediate.getEvaluations());
       diagnostics.putAll(intermediate.getDiagnostics());
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java
index ebee72f..63e244e 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdRuleAnomalyFilterTest.java
@@ -24,10 +24,8 @@ import 
org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.MetricConfigDTO;
 import org.apache.pinot.thirdeye.detection.DataProvider;
-import org.apache.pinot.thirdeye.detection.DefaultInputDataFetcher;
 import org.apache.pinot.thirdeye.detection.DetectionPipelineResult;
 import org.apache.pinot.thirdeye.detection.DetectionTestUtils;
-import org.apache.pinot.thirdeye.detection.InputDataFetcher;
 import org.apache.pinot.thirdeye.detection.MockDataProvider;
 import org.apache.pinot.thirdeye.detection.MockPipeline;
 import org.apache.pinot.thirdeye.detection.MockPipelineLoader;
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java
new file mode 100644
index 0000000..56c13d4
--- /dev/null
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/components/ThresholdSeverityLabelerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2014-2018 LinkedIn Corp. (pinot-c...@linkedin.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pinot.thirdeye.detection.components;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.apache.pinot.thirdeye.anomaly.AnomalySeverity;
+import org.apache.pinot.thirdeye.dataframe.DataFrame;
+import org.apache.pinot.thirdeye.dataframe.util.MetricSlice;
+import org.apache.pinot.thirdeye.datalayer.dto.DatasetConfigDTO;
+import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
+import org.apache.pinot.thirdeye.datalayer.dto.MergedAnomalyResultDTO;
+import org.apache.pinot.thirdeye.datalayer.dto.MetricConfigDTO;
+import org.apache.pinot.thirdeye.detection.DataProvider;
+import org.apache.pinot.thirdeye.detection.DetectionPipelineResult;
+import org.apache.pinot.thirdeye.detection.DetectionTestUtils;
+import org.apache.pinot.thirdeye.detection.MockDataProvider;
+import org.apache.pinot.thirdeye.detection.MockPipeline;
+import org.apache.pinot.thirdeye.detection.MockPipelineLoader;
+import org.apache.pinot.thirdeye.detection.MockPipelineOutput;
+import org.apache.pinot.thirdeye.detection.wrapper.AnomalyLabelerWrapper;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.apache.pinot.thirdeye.dataframe.util.DataFrameUtils.*;
+import static 
org.apache.pinot.thirdeye.detection.spec.SeverityThresholdLabelerSpec.Threshold;
+
+public class ThresholdSeverityLabelerTest {
+  private static final String METRIC_URN = "thirdeye:metric:123";
+  private static final long CONFIG_ID = 125L;
+
+  private List<MergedAnomalyResultDTO> anomalies;
+  private MockPipelineLoader loader;
+  private List<MockPipeline> runs;
+  private DataProvider testDataProvider;
+  private Map<String, Object> properties;
+  private DetectionConfigDTO config;
+  private Map<String, Object> specs;
+  private AnomalyLabelerWrapper thresholdSeverityLabeler;
+
+  @BeforeMethod
+  public void beforeMethod(){
+    Map<MetricSlice, DataFrame> aggregates = new HashMap<>();
+    aggregates.put(MetricSlice.from(123L, 1000L, 2000L), new 
DataFrame().addSeries(COL_VALUE, 1200));
+    aggregates.put(MetricSlice.from(123L, 2000L, 3000L), new 
DataFrame().addSeries(COL_VALUE, 1600));
+    aggregates.put(MetricSlice.from(123L, 3000L, 4000L), new 
DataFrame().addSeries(COL_VALUE, 4800));
+    aggregates.put(MetricSlice.from(123L, 4000L, 6000L), new 
DataFrame().addSeries(COL_VALUE, 2500));
+
+    MetricConfigDTO metricConfigDTO = new MetricConfigDTO();
+    metricConfigDTO.setId(123L);
+    metricConfigDTO.setName("thirdeye-test");
+    metricConfigDTO.setDataset("thirdeye-test-dataset");
+
+    DatasetConfigDTO datasetConfigDTO = new DatasetConfigDTO();
+    datasetConfigDTO.setId(124L);
+    datasetConfigDTO.setDataset("thirdeye-test-dataset");
+    datasetConfigDTO.setTimeDuration(2);
+    datasetConfigDTO.setTimeUnit(TimeUnit.MILLISECONDS);
+    datasetConfigDTO.setTimezone("UTC");
+
+    this.config = new DetectionConfigDTO();
+    this.config.setId(CONFIG_ID);
+    this.properties = new HashMap<>();
+    this.properties.put("nested", 
Collections.singletonList(Collections.singletonMap("className", "dummy")));
+    this.properties.put("labeler", "$test_labeler");
+    this.specs = new HashMap<>();
+    this.specs.put("className", ThresholdSeverityLabeler.class.getName());
+
+    this.config.setComponentSpecs(ImmutableMap.of("test_labeler", this.specs));
+    this.config.setProperties(this.properties);
+
+    this.anomalies =
+        Arrays.asList(DetectionTestUtils.makeAnomaly(1000L, 2000L, METRIC_URN, 
1200, 1000),
+            DetectionTestUtils.makeAnomaly(2000L, 3000, METRIC_URN, 1700, 
2000),
+            DetectionTestUtils.makeAnomaly(3000L, 4000L, METRIC_URN, 4800, 
5000),
+            DetectionTestUtils.makeAnomaly(4000L, 6000L, METRIC_URN, 2500, 
3000));
+    this.runs = new ArrayList<>();
+    this.loader = new MockPipelineLoader(this.runs,
+        Collections.singletonList(new MockPipelineOutput(this.anomalies, 
6000L)));
+    this.testDataProvider = new MockDataProvider().setLoader(this.loader)
+        .setMetrics(Collections.singletonList(metricConfigDTO))
+        .setDatasets(Collections.singletonList(datasetConfigDTO))
+        .setAggregates(aggregates);
+  }
+
+
+  @Test
+  public void testLabeling() throws Exception {
+    Map<String, Object> severityMap = new HashMap<>();
+    severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 
3000));
+    severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 
2000));
+    severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.12, 
1500));
+    this.specs.put("severity", severityMap);
+    this.thresholdSeverityLabeler = new 
AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L);
+    DetectionPipelineResult result = this.thresholdSeverityLabeler.run();
+    List<MergedAnomalyResultDTO> anomalies = result.getAnomalies();
+    Assert.assertEquals(anomalies.size(), 4);
+    Assert.assertEquals(anomalies.get(0).getSeverityLabel(), 
AnomalySeverity.CRITICAL);
+    Assert.assertEquals(anomalies.get(1).getSeverityLabel(), 
AnomalySeverity.HIGH);
+    Assert.assertEquals(anomalies.get(2).getSeverityLabel(), 
AnomalySeverity.DEFAULT);
+    Assert.assertEquals(anomalies.get(3).getSeverityLabel(), 
AnomalySeverity.HIGH);
+  }
+
+  @Test
+  public void testLabelingSingleThreshold() throws Exception {
+    Map<String, Object> severityMap = new HashMap<>();
+    Threshold singleThreshold = new Threshold();
+    singleThreshold.duration = 2000L;
+    severityMap.put(AnomalySeverity.CRITICAL.toString(), singleThreshold);
+    severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 
2000));
+    this.specs.put("severity", severityMap);
+    this.thresholdSeverityLabeler = new 
AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L);
+    DetectionPipelineResult result = this.thresholdSeverityLabeler.run();
+    List<MergedAnomalyResultDTO> anomalies = result.getAnomalies();
+    Assert.assertEquals(anomalies.size(), 4);
+    Assert.assertEquals(anomalies.get(0).getSeverityLabel(), 
AnomalySeverity.HIGH);
+    Assert.assertEquals(anomalies.get(1).getSeverityLabel(), 
AnomalySeverity.HIGH);
+    Assert.assertEquals(anomalies.get(2).getSeverityLabel(), 
AnomalySeverity.DEFAULT);
+    Assert.assertEquals(anomalies.get(3).getSeverityLabel(), 
AnomalySeverity.CRITICAL);
+  }
+
+  @Test
+  public void testLabelingHigherSeverity() throws Exception {
+    Map<String, Object> severityMap = new HashMap<>();
+    severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 
3000));
+    severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 
2000));
+    severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.12, 
1500));
+    this.specs.put("severity", severityMap);
+    MergedAnomalyResultDTO anomaly = DetectionTestUtils.makeAnomaly(4000L, 
6000L, METRIC_URN, 2400, 3000);
+    anomaly.setSeverityLabel(AnomalySeverity.HIGH);
+    anomaly.setId(125L);
+    this.anomalies.set(this.anomalies.size() - 1, anomaly);
+    this.thresholdSeverityLabeler = new 
AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L);
+    DetectionPipelineResult result = this.thresholdSeverityLabeler.run();
+    List<MergedAnomalyResultDTO> anomalies = result.getAnomalies();
+    Assert.assertEquals(anomalies.size(), 4);
+    Assert.assertEquals(anomalies.get(3).getSeverityLabel(), 
AnomalySeverity.CRITICAL);
+    Assert.assertTrue(anomalies.get(3).isRenotify());
+
+  }
+
+  @Test
+  public void testLabelingLowerSeverity() throws Exception {
+    Map<String, Object> severityMap = new HashMap<>();
+    severityMap.put(AnomalySeverity.CRITICAL.toString(), new Threshold(0.2, 
3000));
+    severityMap.put(AnomalySeverity.HIGH.toString(), new Threshold(0.15, 
2000));
+    severityMap.put(AnomalySeverity.MEDIUM.toString(), new Threshold(0.10, 
1500));
+    this.specs.put("severity", severityMap);
+    MergedAnomalyResultDTO anomaly = DetectionTestUtils.makeAnomaly(4000L, 
6000L, METRIC_URN, 2700, 3000);
+    anomaly.setSeverityLabel(AnomalySeverity.HIGH);
+    anomaly.setId(125L);
+    this.anomalies.set(this.anomalies.size() - 1, anomaly);
+    this.thresholdSeverityLabeler = new 
AnomalyLabelerWrapper(this.testDataProvider, this.config, 1000L, 6000L);
+    DetectionPipelineResult result = this.thresholdSeverityLabeler.run();
+    List<MergedAnomalyResultDTO> anomalies = result.getAnomalies();
+    Assert.assertEquals(anomalies.size(), 4);
+    Assert.assertEquals(anomalies.get(3).getSeverityLabel(), 
AnomalySeverity.MEDIUM);
+    Assert.assertFalse(anomalies.get(3).isRenotify());
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to