# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""The database model for an "Anomaly", which represents a step up or down."""

import sys

from google.appengine.ext import ndb

from dashboard.models import alert

# A string to describe the magnitude of a change from zero to non-zero.
FREAKIN_HUGE = 'zero-to-nonzero'

# Possible improvement directions for a change. An Anomaly will always have a
# direction of UP or DOWN, but a test's improvement direction can be UNKNOWN.
UP, DOWN, UNKNOWN = (0, 1, 4)


class Anomaly(alert.Alert):
  """Represents a change-point or step found in the data series for a test.

  An Anomaly can be an upward or downward change, and can represent an
  improvement or a regression.
  """
  # The number of points before and after this anomaly that were looked at
  # when finding this anomaly.
  segment_size_before = ndb.IntegerProperty(indexed=False)
  segment_size_after = ndb.IntegerProperty(indexed=False)

  # The medians of the segments before and after the anomaly.
  median_before_anomaly = ndb.FloatProperty(indexed=False)
  median_after_anomaly = ndb.FloatProperty(indexed=False)

  # The standard deviation of the segments before the anomaly.
  std_dev_before_anomaly = ndb.FloatProperty(indexed=False)

  # The number of points that were used in the before/after segments.
  # This is also  returned by FindAnomalies
  window_end_revision = ndb.IntegerProperty(indexed=False)

  # In order to estimate how likely it is that this anomaly is due to noise,
  # t-test may be performed on the points before and after. The t-statistic,
  # degrees of freedom, and p-value are potentially-useful intermediary results.
  t_statistic = ndb.FloatProperty(indexed=False)
  degrees_of_freedom = ndb.FloatProperty(indexed=False)
  p_value = ndb.FloatProperty(indexed=False)

  # Whether this anomaly represents an improvement; if false, this anomaly is
  # considered to be a regression.
  is_improvement = ndb.BooleanProperty(indexed=True, default=False)

  # Whether this anomaly recovered (i.e. if this is a step down, whether there
  # is a corresponding step up later on, or vice versa.)
  recovered = ndb.BooleanProperty(indexed=True, default=False)

  @property
  def percent_changed(self):
    """The percent change from before the anomaly to after."""
    if self.median_before_anomaly == 0.0:
      return sys.float_info.max
    difference = self.median_after_anomaly - self.median_before_anomaly
    return 100 * difference / self.median_before_anomaly

  @property
  def direction(self):
    """Whether the change is numerically an increase or decrease."""
    if self.median_before_anomaly < self.median_after_anomaly:
      return UP
    return DOWN

  def GetDisplayPercentChanged(self):
    """Gets a string showing the percent change."""
    if abs(self.percent_changed) == sys.float_info.max:
      return FREAKIN_HUGE
    else:
      return str('%.1f%%' % abs(self.percent_changed))

  def SetIsImprovement(self, test=None):
    """Sets whether the alert is an improvement for the given test."""
    if not test:
      test = self.GetTestMetadataKey().get()
    # |self.direction| is never equal to |UNKNOWN| (see the definition above)
    # so when the test improvement direction is |UNKNOWN|, |self.is_improvement|
    # will be False.
    self.is_improvement = (self.direction == test.improvement_direction)

# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""The database model for an "Anomaly", which represents a step up or down."""

import sys

from google.appengine.ext import ndb

from dashboard.models import alert

# A string to describe the magnitude of a change from zero to non-zero.
FREAKIN_HUGE = 'zero-to-nonzero'

# Possible improvement directions for a change. An Anomaly will always have a
# direction of UP or DOWN, but a test's improvement direction can be UNKNOWN.
UP, DOWN, UNKNOWN = (0, 1, 4)


class Anomaly(alert.Alert):
  """Represents a change-point or step found in the data series for a test.

  An Anomaly can be an upward or downward change, and can represent an
  improvement or a regression.
  """
  # The number of points before and after this anomaly that were looked at
  # when finding this anomaly.
  segment_size_before = ndb.IntegerProperty(indexed=False)
  segment_size_after = ndb.IntegerProperty(indexed=False)

  # The medians of the segments before and after the anomaly.
  median_before_anomaly = ndb.FloatProperty(indexed=False)
  median_after_anomaly = ndb.FloatProperty(indexed=False)

  # The standard deviation of the segments before the anomaly.
  std_dev_before_anomaly = ndb.FloatProperty(indexed=False)

  # The number of points that were used in the before/after segments.
  # This is also  returned by FindAnomalies
  window_end_revision = ndb.IntegerProperty(indexed=False)

  # In order to estimate how likely it is that this anomaly is due to noise,
  # t-test may be performed on the points before and after. The t-statistic,
  # degrees of freedom, and p-value are potentially-useful intermediary results.
  t_statistic = ndb.FloatProperty(indexed=False)
  degrees_of_freedom = ndb.FloatProperty(indexed=False)
  p_value = ndb.FloatProperty(indexed=False)

  # Whether this anomaly represents an improvement; if false, this anomaly is
  # considered to be a regression.
  is_improvement = ndb.BooleanProperty(indexed=True, default=False)

  # Whether this anomaly recovered (i.e. if this is a step down, whether there
  # is a corresponding step up later on, or vice versa.)
  recovered = ndb.BooleanProperty(indexed=True, default=False)

  @property
  def percent_changed(self):
    """The percent change from before the anomaly to after."""
    if self.median_before_anomaly == 0.0:
      return sys.float_info.max
    difference = self.median_after_anomaly - self.median_before_anomaly
    return 100 * difference / self.median_before_anomaly

  @property
  def direction(self):
    """Whether the change is numerically an increase or decrease."""
    if self.median_before_anomaly < self.median_after_anomaly:
      return UP
    return DOWN

  def GetDisplayPercentChanged(self):
    """Gets a string showing the percent change."""
    if abs(self.percent_changed) == sys.float_info.max:
      return FREAKIN_HUGE
    else:
      return str('%.1f%%' % abs(self.percent_changed))

  def SetIsImprovement(self, test=None):
    """Sets whether the alert is an improvement for the given test."""
    if not test:
      test = self.GetTestMetadataKey().get()
    # |self.direction| is never equal to |UNKNOWN| (see the definition above)
    # so when the test improvement direction is |UNKNOWN|, |self.is_improvement|
    # will be False.
    self.is_improvement = (self.direction == test.improvement_direction)

# Copyright 2015 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. The database model for an "Anomaly", which represents a step up or down. # A string to describe the magnitude of a change from zero to non-zero. # Possible improvement directions for a change. An Anomaly will always have a # direction of UP or DOWN, but a test's improvement direction can be UNKNOWN. Represents a change-point or step found in the data series for a test.    An Anomaly can be an upward or downward change, and can represent an   improvement or a regression. # The number of points before and after this anomaly that were looked at # when finding this anomaly. # The medians of the segments before and after the anomaly. # The standard deviation of the segments before the anomaly. # The number of points that were used in the before/after segments. # This is also  returned by FindAnomalies # In order to estimate how likely it is that this anomaly is due to noise, # t-test may be performed on the points before and after. The t-statistic, # degrees of freedom, and p-value are potentially-useful intermediary results. # Whether this anomaly represents an improvement; if false, this anomaly is # considered to be a regression. # Whether this anomaly recovered (i.e. if this is a step down, whether there # is a corresponding step up later on, or vice versa.) The percent change from before the anomaly to after. Whether the change is numerically an increase or decrease. Gets a string showing the percent change. Sets whether the alert is an improvement for the given test. # |self.direction| is never equal to |UNKNOWN| (see the definition above) # so when the test improvement direction is |UNKNOWN|, |self.is_improvement| # will be False.