From 8ce8abf85c0ebe56f9d5c9a755c53aeb83e650a7 Mon Sep 17 00:00:00 2001 From: Dimitris Athanasiou Date: Tue, 12 Jun 2018 14:25:52 +0100 Subject: [PATCH] [ML] Implement new rules design (#119) The ml-cpp side of the new rules implementation. --- docs/CHANGELOG.asciidoc | 3 + include/api/CDetectionRulesJsonParser.h | 16 +- include/model/CDetectionRule.h | 61 +- include/model/CRuleCondition.h | 78 +- include/model/CRuleScope.h | 65 ++ lib/api/CDetectionRulesJsonParser.cc | 258 +++--- lib/api/unittest/CConfigUpdaterTest.cc | 41 +- .../unittest/CDetectionRulesJsonParserTest.cc | 244 +++--- .../unittest/CDetectionRulesJsonParserTest.h | 17 +- lib/api/unittest/CFieldConfigTest.cc | 4 +- .../unittest/testfiles/scheduled_events.conf | 4 +- lib/model/CAnomalyDetectorModel.cc | 8 +- lib/model/CCountingModel.cc | 2 +- lib/model/CDetectionRule.cc | 105 +-- lib/model/CRuleCondition.cc | 172 +--- lib/model/CRuleScope.cc | 69 ++ lib/model/Makefile | 1 + lib/model/unittest/CCountingModelTest.cc | 15 +- lib/model/unittest/CDetectionRuleTest.cc | 771 ++++++------------ lib/model/unittest/CDetectionRuleTest.h | 7 +- lib/model/unittest/CEventRateModelTest.cc | 14 +- .../unittest/CEventRatePopulationModelTest.cc | 8 +- lib/model/unittest/CMetricModelTest.cc | 8 +- .../unittest/CMetricPopulationModelTest.cc | 8 +- lib/model/unittest/CRuleConditionTest.cc | 30 +- 25 files changed, 802 insertions(+), 1207 deletions(-) create mode 100644 include/model/CRuleScope.h create mode 100644 lib/model/CRuleScope.cc diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 28426be96f..762eb2ae25 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -44,6 +44,9 @@ new processes being created and macOS uses the sandbox functionality ({pull}98[# Fix a bug causing us to under estimate the memory used by shared pointers and reduce the memory consumed by unnecessary reference counting ({pull}108[#108]) +Detectors now support rules that allow the user to improve the results by providing some domain specific +knowledge in the form of rule. ({pull}119[#119]) + === Bug Fixes Age seasonal components in proportion to the fraction of values with which they're updated ({pull}88[#88]) diff --git a/include/api/CDetectionRulesJsonParser.h b/include/api/CDetectionRulesJsonParser.h index b518e42227..3455796be5 100644 --- a/include/api/CDetectionRulesJsonParser.h +++ b/include/api/CDetectionRulesJsonParser.h @@ -38,24 +38,22 @@ class API_EXPORT CDetectionRulesJsonParser { bool parseRules(const std::string& json, TDetectionRuleVec& rules); private: + bool parseRuleScope(const rapidjson::Value& ruleObject, model::CDetectionRule& rule); bool parseRuleConditions(const rapidjson::Value& ruleObject, model::CDetectionRule& rule); - bool parseFilterId(const rapidjson::Value& conditionObject, - model::CRuleCondition& ruleCondition); static bool hasStringMember(const rapidjson::Value& object, const std::string& name); static bool hasArrayMember(const rapidjson::Value& object, const std::string& name); + static bool hasDoubleMember(const rapidjson::Value& object, const std::string& name); static bool parseRuleActions(const rapidjson::Value& ruleObject, model::CDetectionRule& rule); static bool parseConditionsConnective(const rapidjson::Value& ruleObject, model::CDetectionRule& rule); - static bool parseRuleConditionType(const rapidjson::Value& ruleConditionObject, - model::CRuleCondition& ruleCondition); - static bool parseCondition(const rapidjson::Value& ruleConditionObject, - model::CRuleCondition& ruleCondition); - static bool parseConditionOperator(const rapidjson::Value& conditionObject, - model::CRuleCondition& ruleCondition); - static bool parseConditionThreshold(const rapidjson::Value& conditionObject, + static bool parseConditionAppliesTo(const rapidjson::Value& ruleConditionObject, model::CRuleCondition& ruleCondition); + static bool parseConditionOperator(const rapidjson::Value& conditionObject, + model::CRuleCondition& condition); + static bool parseConditionValue(const rapidjson::Value& conditionObject, + model::CRuleCondition& condition); private: //! The filters per id used by categorical rule conditions. diff --git a/include/model/CDetectionRule.h b/include/model/CDetectionRule.h index e58ea6ba85..9502af3c61 100644 --- a/include/model/CDetectionRule.h +++ b/include/model/CDetectionRule.h @@ -8,6 +8,7 @@ #define INCLUDED_ml_model_CDetectionRule_h #include +#include #include #include @@ -21,48 +22,33 @@ class CAnomalyDetectorModel; //! \brief A rule that dictates an action to be taken when certain conditions occur. //! //! DESCRIPTION:\n -//! A rule describes an action to be taken and the conditions under which -//! the action should be taken. A rule has an action and one or more conditions. -//! The conditions are combined according to the rule's connective which can -//! be either OR or AND. A rule can optionally have a target field specified. -//! When such target is not specified, the rule applies to the series that is -//! checked against the rule. When a target is specified, the rule applies to -//! all series that are contained within the target. For example, if the target -//! is the partition field and no targetFieldValue is specified, then if the -//! conditions trigger the rule, the rule will apply to all series within the -//! partition. However, when no target is specified, the rule will trigger only -//! for series that are described in the conditions themselves. +//! A rule describes actions to be taken when the scope and conditions are met. +//! A rule has one or more actions, a scope and zero or more conditions. +//! The scope dictates to which series the rule applies. +//! When conditions are present, they dictate to which results the rule applies +//! depending the result's values. Multiple conditions are combined with a logical AND. class MODEL_EXPORT CDetectionRule { + public: using TRuleConditionVec = std::vector; - using TDouble1Vec = core::CSmallVector; - //! Rule actions can apply to filtering results, skipping sampling or both. + //! Rule actions can apply to skip results, skip model updates, or both. //! This is meant to work as a bit mask so added values should be powers of 2. - enum ERuleAction { E_FilterResults = 1, E_SkipSampling = 2 }; - - enum EConditionsConnective { E_Or, E_And }; + enum ERuleAction { E_SkipResult = 1, E_SkipModelUpdate = 2 }; public: - //! Default constructor. - //! The rule's action defaults to FILTER_RESULTS and the connective to OR. - CDetectionRule(); - //! Set the rule's action. void action(int ruleAction); - //! Set the conditions' connective. - void conditionsConnective(EConditionsConnective connective); + //! Adds a requirement for \p field not to be in \p filter for the rule to apply + void includeScope(const std::string& field, const core::CPatternSet& filter); + + //! Adds a requirement for \p field not to be in \p filter for the rule to apply + void excludeScope(const std::string& field, const core::CPatternSet& filter); //! Add a condition. void addCondition(const CRuleCondition& condition); - //! Set the target field name. - void targetFieldName(const std::string& targetFieldName); - - //! Set the target field value. - void targetFieldValue(const std::string& targetFieldValue); - //! Check whether the rule applies on a series. //! \p action is bitwise and'ed with the m_Action member bool apply(ERuleAction action, @@ -77,30 +63,19 @@ class MODEL_EXPORT CDetectionRule { std::string print() const; private: - //! Check whether the given series is in the scope - //! of the rule's target. - bool isInScope(const CAnomalyDetectorModel& model, std::size_t pid, std::size_t cid) const; - std::string printAction() const; - std::string printConditionsConnective() const; private: //! The rule action. It works as a bit mask so its value //! may not match any of the declared enum values but the //! corresponding bit will be 1 when an action is enabled. - int m_Action; + int m_Action{E_SkipResult}; + + //! The rule scope. + CRuleScope m_Scope; //! The conditions that trigger the rule. TRuleConditionVec m_Conditions; - - //! The way the rule's conditions are logically connected (i.e. OR, AND). - EConditionsConnective m_ConditionsConnective; - - //! The optional target field name. Empty when not specified. - std::string m_TargetFieldName; - - //! The optional target field value. Empty when not specified. - std::string m_TargetFieldValue; }; } } diff --git a/include/model/CRuleCondition.h b/include/model/CRuleCondition.h index d76b4db800..1bc4dee40b 100644 --- a/include/model/CRuleCondition.h +++ b/include/model/CRuleCondition.h @@ -21,7 +21,7 @@ class CPatternSet; namespace model { class CAnomalyDetectorModel; -//! \brief A condition that may trigger a rule. +//! \brief A numeric condition that may trigger a rule. //! //! DESCRIPTION:\n //! A condition has a type that determines the calculation @@ -34,52 +34,27 @@ class MODEL_EXPORT CRuleCondition { using TPatternSetCRef = boost::reference_wrapper; public: - enum ERuleConditionType { - E_CategoricalMatch, - E_CategoricalComplement, - E_NumericalActual, - E_NumericalTypical, - E_NumericalDiffAbs, + enum ERuleConditionAppliesTo { + E_Actual, + E_Typical, + E_DiffFromTypical, E_Time }; - enum EConditionOperator { E_LT, E_LTE, E_GT, E_GTE }; - - struct SCondition { - SCondition(EConditionOperator op, double threshold); - - bool test(double value) const; - - EConditionOperator s_Op; - double s_Threshold; - }; + enum ERuleConditionOperator { E_LT, E_LTE, E_GT, E_GTE }; public: //! Default constructor. CRuleCondition(); - //! Set the condition type. - void type(ERuleConditionType ruleType); - - //! Set the field name. Empty means it is not specified. - void fieldName(const std::string& fieldName); + //! Set which value the condition applies to. + void appliesTo(ERuleConditionAppliesTo appliesTo); - //! Set the field value. Empty means it is not specified. - void fieldValue(const std::string& fieldValue); + //! Set the condition operator. + void op(ERuleConditionOperator op); - //! Get the numerical condition. - SCondition& condition(); - - //! Set the value filter (used for categorical only). - void valueFilter(const core::CPatternSet& valueFilter); - - //! Is the condition categorical? - //! Categorical conditions are pattern match conditions i.e. - //! E_CategoricalMatch and E_CategoricalComplement - bool isCategorical() const; - - //! Is the condition numerical? - bool isNumerical() const; + //! Set the condition value. + void value(double value); //! Pretty-print the condition. std::string print() const; @@ -88,35 +63,24 @@ class MODEL_EXPORT CRuleCondition { bool test(const CAnomalyDetectorModel& model, model_t::EFeature feature, const model_t::CResultType& resultType, - bool isScoped, std::size_t pid, std::size_t cid, core_t::TTime time) const; private: - bool checkCondition(const CAnomalyDetectorModel& model, - model_t::EFeature feature, - model_t::CResultType resultType, - std::size_t pid, - std::size_t cid, - core_t::TTime time) const; - std::string print(ERuleConditionType type) const; - std::string print(EConditionOperator op) const; + bool testValue(double value) const; + std::string print(ERuleConditionAppliesTo appliesTo) const; + std::string print(ERuleConditionOperator op) const; private: - //! The condition type. - ERuleConditionType m_Type; - - //! The numerical condition. - SCondition m_Condition; - - //! The field name. Empty when not specified. - std::string m_FieldName; + //! The value the condition applies to. + ERuleConditionAppliesTo m_AppliesTo; - //! The field value. Empty when not specified. - std::string m_FieldValue; + //! The condition operator. + ERuleConditionOperator m_Operator; - TPatternSetCRef m_ValueFilter; + //! The condition value. + double m_Value; }; } } diff --git a/include/model/CRuleScope.h b/include/model/CRuleScope.h new file mode 100644 index 0000000000..73ff014f97 --- /dev/null +++ b/include/model/CRuleScope.h @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +#ifndef INCLUDED_ml_model_CRuleScope_h +#define INCLUDED_ml_model_CRuleScope_h + +#include + +#include +#include + +#include + +#include +#include + +namespace ml { +namespace model { + +class CAnomalyDetectorModel; + +//! \brief The scope of the rule. It dictates the series where the rule applies. +//! +//! DESCRIPTION:\n +//! The rule scope allows to specify when a rule applies based on the series +//! split fields (partition, over, by). When the scope is empty, the rule +//! applies to all series. Fields can be specified to either be included in +//! the scope or excluded from it depending on whether they are contained +//! in a filter (i.e. a list of string patterns). Multiple fields are combined +//! with a logical AND. +class MODEL_EXPORT CRuleScope { +public: + enum ERuleScopeFilterType { E_Include, E_Exclude }; + + using TPatternSetCRef = boost::reference_wrapper; + using TStrPatternSetCRefFilterTypeTriple = + core::CTriple; + using TStrPatternSetCRefFilterTypeTripleVec = std::vector; + +public: + //! Default constructor. + CRuleScope() = default; + + //! Adds a requirement for \p field to be in \p filter for the rule to apply + void include(const std::string& field, const core::CPatternSet& filter); + + //! Adds a requirement for \p field not to be in \p filter for the rule to apply + void exclude(const std::string& field, const core::CPatternSet& filter); + + //! Check whether the given series is in the rule scope. + bool check(const CAnomalyDetectorModel& model, std::size_t pid, std::size_t cid) const; + + //! Pretty-print the scope. + std::string print() const; + +private: + //! A vector that holds the triple of the field, filter and its type. + TStrPatternSetCRefFilterTypeTripleVec m_Scope; +}; +} +} + +#endif // INCLUDED_ml_model_CRuleScope_h diff --git a/lib/api/CDetectionRulesJsonParser.cc b/lib/api/CDetectionRulesJsonParser.cc index 58c14de602..167f3791e6 100644 --- a/lib/api/CDetectionRulesJsonParser.cc +++ b/lib/api/CDetectionRulesJsonParser.cc @@ -12,33 +12,28 @@ namespace ml { namespace api { namespace { + const std::string ACTIONS("actions"); -const std::string FILTER_RESULTS("filter_results"); -const std::string SKIP_SAMPLING("skip_sampling"); -const std::string CONDITIONS_CONNECTIVE("conditions_connective"); -const std::string AND("and"); -const std::string OR("or"); -const std::string CONDITIONS("conditions"); -const std::string TARGET_FIELD_NAME("target_field_name"); -const std::string TARGET_FIELD_VALUE("target_field_value"); -const std::string TYPE("type"); -const std::string CATEGORICAL("categorical"); -const std::string CATEGORICAL_MATCH("categorical_match"); -const std::string CATEGORICAL_COMPLEMENT("categorical_complement"); -const std::string NUMERICAL_ACTUAL("numerical_actual"); -const std::string NUMERICAL_TYPICAL("numerical_typical"); -const std::string NUMERICAL_DIFF_ABS("numerical_diff_abs"); -const std::string TIME("time"); +const std::string ACTUAL("actual"); +const std::string APPLIES_TO("applies_to"); const std::string CONDITION("condition"); -const std::string OPERATOR("operator"); -const std::string LT("lt"); -const std::string LTE("lte"); +const std::string CONDITIONS("conditions"); +const std::string DIFF_FROM_TYPICAL("diff_from_typical"); +const std::string EXCLUDE("exclude"); +const std::string FILTER_ID("filter_id"); +const std::string FILTER_TYPE("filter_type"); const std::string GT("gt"); const std::string GTE("gte"); +const std::string INCLUDE("include"); +const std::string LT("lt"); +const std::string LTE("lte"); +const std::string OPERATOR("operator"); +const std::string SCOPE("scope"); +const std::string SKIP_RESULT("skip_result"); +const std::string SKIP_MODEL_UPDATE("skip_model_update"); +const std::string TIME("time"); +const std::string TYPICAL("typical"); const std::string VALUE("value"); -const std::string FIELD_NAME("field_name"); -const std::string FIELD_VALUE("field_value"); -const std::string FILTER_ID("filter_id"); } CDetectionRulesJsonParser::CDetectionRulesJsonParser(TStrPatternSetUMap& filtersByIdMap) @@ -79,26 +74,21 @@ bool CDetectionRulesJsonParser::parseRules(const std::string& json, TDetectionRu rapidjson::Value& ruleObject = doc[i]; - bool isValid = true; - - // Required fields - isValid &= parseRuleActions(ruleObject, rule); - isValid &= parseConditionsConnective(ruleObject, rule); - isValid &= parseRuleConditions(ruleObject, rule); + if (ruleObject.HasMember(SCOPE.c_str()) == false && + ruleObject.HasMember(CONDITIONS.c_str()) == false) { + LOG_ERROR(<< "At least one of 'scope' or 'conditions' must be specified"); + rules.clear(); + return false; + } + bool isValid = parseRuleActions(ruleObject, rule) && + parseRuleScope(ruleObject, rule) && + parseRuleConditions(ruleObject, rule); if (isValid == false) { LOG_ERROR(<< "Failed to parse detection rules from JSON: " << json); rules.clear(); return false; } - - // Optional fields - if (hasStringMember(ruleObject, TARGET_FIELD_NAME)) { - rule.targetFieldName(ruleObject[TARGET_FIELD_NAME.c_str()].GetString()); - } - if (hasStringMember(ruleObject, TARGET_FIELD_VALUE)) { - rule.targetFieldValue(ruleObject[TARGET_FIELD_VALUE.c_str()].GetString()); - } } return true; @@ -116,9 +106,15 @@ bool CDetectionRulesJsonParser::hasArrayMember(const rapidjson::Value& object, return object.HasMember(nameAsCStr) && object[nameAsCStr].IsArray(); } +bool CDetectionRulesJsonParser::hasDoubleMember(const rapidjson::Value& object, + const std::string& name) { + const char* nameAsCStr = name.c_str(); + return object.HasMember(nameAsCStr) && object[nameAsCStr].IsDouble(); +} + bool CDetectionRulesJsonParser::parseRuleActions(const rapidjson::Value& ruleObject, model::CDetectionRule& rule) { - if (!hasArrayMember(ruleObject, ACTIONS)) { + if (hasArrayMember(ruleObject, ACTIONS) == false) { LOG_ERROR(<< "Missing rule field: " << ACTIONS); return false; } @@ -131,12 +127,11 @@ bool CDetectionRulesJsonParser::parseRuleActions(const rapidjson::Value& ruleObj int action = 0; for (unsigned int i = 0; i < array.Size(); ++i) { - model::CRuleCondition ruleCondition; const std::string& parsedAction = array[i].GetString(); - if (parsedAction == FILTER_RESULTS) { - action |= model::CDetectionRule::E_FilterResults; - } else if (parsedAction == SKIP_SAMPLING) { - action |= model::CDetectionRule::E_SkipSampling; + if (parsedAction == SKIP_RESULT) { + action |= model::CDetectionRule::E_SkipResult; + } else if (parsedAction == SKIP_MODEL_UPDATE) { + action |= model::CDetectionRule::E_SkipModelUpdate; } else { LOG_ERROR(<< "Invalid rule action: " << parsedAction); return false; @@ -147,35 +142,74 @@ bool CDetectionRulesJsonParser::parseRuleActions(const rapidjson::Value& ruleObj return true; } -bool CDetectionRulesJsonParser::parseConditionsConnective(const rapidjson::Value& ruleObject, - model::CDetectionRule& rule) { - if (!hasStringMember(ruleObject, CONDITIONS_CONNECTIVE)) { - LOG_ERROR(<< "Missing rule field: " << CONDITIONS_CONNECTIVE); +bool CDetectionRulesJsonParser::parseRuleScope(const rapidjson::Value& ruleObject, + model::CDetectionRule& rule) { + + if (ruleObject.HasMember(SCOPE.c_str()) == false) { + return true; + } + + const rapidjson::Value& scopeObject = ruleObject[SCOPE.c_str()]; + if (scopeObject.IsObject() == false) { + LOG_ERROR(<< "Unexpected type for scope; object was expected"); return false; } - const std::string& connective = ruleObject[CONDITIONS_CONNECTIVE.c_str()].GetString(); - if (connective == OR) { - rule.conditionsConnective(model::CDetectionRule::E_Or); - } else if (connective == AND) { - rule.conditionsConnective(model::CDetectionRule::E_And); - } else { - LOG_ERROR(<< "Invalid conditionsConnective: " << connective); + if (scopeObject.Empty()) { + LOG_ERROR(<< "Scope must not be empty"); return false; } + + for (auto& member : scopeObject.GetObject()) { + if (member.value.IsObject() == false) { + LOG_ERROR(<< "Unexpected type for scope member; object was expected"); + return false; + } + + if (hasStringMember(member.value, FILTER_ID) == false) { + LOG_ERROR(<< "Scope member is missing field: " << FILTER_ID); + return false; + } + const std::string& filterId = member.value[FILTER_ID.c_str()].GetString(); + auto filterEntry = m_FiltersByIdMap.find(filterId); + if (filterEntry == m_FiltersByIdMap.end()) { + LOG_ERROR(<< "Filter with id [" << filterId << "] could not be found"); + return false; + } + + if (hasStringMember(member.value, FILTER_TYPE) == false) { + LOG_ERROR(<< "Scope member is missing field: " << FILTER_TYPE); + return false; + } + + const std::string& filterType = member.value[FILTER_TYPE.c_str()].GetString(); + if (filterType == INCLUDE) { + rule.includeScope(member.name.GetString(), filterEntry->second); + } else if (filterType == EXCLUDE) { + rule.excludeScope(member.name.GetString(), filterEntry->second); + } else { + LOG_ERROR(<< "Invalid filter_type [" << filterType << "]"); + return false; + } + } return true; } bool CDetectionRulesJsonParser::parseRuleConditions(const rapidjson::Value& ruleObject, model::CDetectionRule& rule) { - if (!hasArrayMember(ruleObject, CONDITIONS)) { - LOG_ERROR(<< "Missing rule field: " << CONDITIONS); + if (ruleObject.HasMember(CONDITIONS.c_str()) == false) { + return true; + } + + if (hasArrayMember(ruleObject, CONDITIONS) == false) { + LOG_ERROR(<< "Unexpected type for conditions; array was expected"); return false; } const rapidjson::Value& array = ruleObject[CONDITIONS.c_str()]; + if (array.Empty()) { - LOG_ERROR(<< "At least one condition is required"); + LOG_ERROR(<< "Conditions must not be empty"); return false; } @@ -183,113 +217,62 @@ bool CDetectionRulesJsonParser::parseRuleConditions(const rapidjson::Value& rule model::CRuleCondition condition; const rapidjson::Value& conditionObject = array[i]; - if (!conditionObject.IsObject()) { + if (conditionObject.IsObject() == false) { LOG_ERROR(<< "Unexpected condition type: array conditions is expected to contain objects"); return false; } - bool isValid = true; - - // Required fields - isValid &= parseRuleConditionType(conditionObject, condition); - if (condition.isNumerical()) { - isValid &= parseCondition(conditionObject, condition); - } else if (condition.isCategorical()) { - isValid &= this->parseFilterId(conditionObject, condition); - } - + bool isValid = parseConditionAppliesTo(conditionObject, condition) && + parseConditionOperator(conditionObject, condition) && + parseConditionValue(conditionObject, condition); if (isValid == false) { return false; } - // Optional fields - if (hasStringMember(conditionObject, FIELD_NAME)) { - condition.fieldName(conditionObject[FIELD_NAME.c_str()].GetString()); - } - if (hasStringMember(conditionObject, FIELD_VALUE)) { - condition.fieldValue(conditionObject[FIELD_VALUE.c_str()].GetString()); - } - rule.addCondition(condition); } return true; } -bool CDetectionRulesJsonParser::parseFilterId(const rapidjson::Value& conditionObject, - model::CRuleCondition& ruleCondition) { - if (!hasStringMember(conditionObject, FILTER_ID)) { - LOG_ERROR(<< "Missing condition field: " << FILTER_ID); - return false; - } - const std::string& filterId = conditionObject[FILTER_ID.c_str()].GetString(); - auto filterEntry = m_FiltersByIdMap.find(filterId); - if (filterEntry == m_FiltersByIdMap.end()) { - LOG_ERROR(<< "Filter with id [" << filterId << "] could not be found"); +bool CDetectionRulesJsonParser::parseConditionAppliesTo(const rapidjson::Value& conditionObject, + model::CRuleCondition& condition) { + if (hasStringMember(conditionObject, APPLIES_TO) == false) { + LOG_ERROR(<< "Missing rule condition field: " << APPLIES_TO); return false; } - ruleCondition.valueFilter(filterEntry->second); - return true; -} -bool CDetectionRulesJsonParser::parseRuleConditionType(const rapidjson::Value& ruleConditionObject, - model::CRuleCondition& ruleCondition) { - if (!hasStringMember(ruleConditionObject, TYPE)) { - LOG_ERROR(<< "Missing ruleCondition field: " << TYPE); - return false; - } - - const std::string& type = ruleConditionObject[TYPE.c_str()].GetString(); - if (type == CATEGORICAL_MATCH || type == CATEGORICAL) { - ruleCondition.type(model::CRuleCondition::E_CategoricalMatch); - } else if (type == CATEGORICAL_COMPLEMENT) { - ruleCondition.type(model::CRuleCondition::E_CategoricalComplement); - } else if (type == NUMERICAL_ACTUAL) { - ruleCondition.type(model::CRuleCondition::E_NumericalActual); - } else if (type == NUMERICAL_TYPICAL) { - ruleCondition.type(model::CRuleCondition::E_NumericalTypical); - } else if (type == NUMERICAL_DIFF_ABS) { - ruleCondition.type(model::CRuleCondition::E_NumericalDiffAbs); - } else if (type == TIME) { - ruleCondition.type(model::CRuleCondition::E_Time); + const std::string& appliesTo = conditionObject[APPLIES_TO.c_str()].GetString(); + if (appliesTo == ACTUAL) { + condition.appliesTo(model::CRuleCondition::E_Actual); + } else if (appliesTo == TYPICAL) { + condition.appliesTo(model::CRuleCondition::E_Typical); + } else if (appliesTo == DIFF_FROM_TYPICAL) { + condition.appliesTo(model::CRuleCondition::E_DiffFromTypical); + } else if (appliesTo == TIME) { + condition.appliesTo(model::CRuleCondition::E_Time); } else { - LOG_ERROR(<< "Invalid conditionType: " << type); + LOG_ERROR(<< "Invalid condition; unknown applies_to [" << appliesTo << "]"); return false; } return true; } -bool CDetectionRulesJsonParser::parseCondition(const rapidjson::Value& ruleConditionObject, - model::CRuleCondition& ruleCondition) { - if (!ruleConditionObject.HasMember(CONDITION.c_str())) { - LOG_ERROR(<< "Missing ruleCondition field: " << CONDITION); - return false; - } - const rapidjson::Value& conditionObject = ruleConditionObject[CONDITION.c_str()]; - if (!conditionObject.IsObject()) { - LOG_ERROR(<< "Unexpected type for condition; object was expected"); - return false; - } - - return parseConditionOperator(conditionObject, ruleCondition) && - parseConditionThreshold(conditionObject, ruleCondition); -} - bool CDetectionRulesJsonParser::parseConditionOperator(const rapidjson::Value& conditionObject, - model::CRuleCondition& ruleCondition) { - if (!hasStringMember(conditionObject, OPERATOR)) { + model::CRuleCondition& condition) { + if (hasStringMember(conditionObject, OPERATOR) == false) { LOG_ERROR(<< "Missing condition field: " << OPERATOR); return false; } const std::string& operatorString = conditionObject[OPERATOR.c_str()].GetString(); if (operatorString == LT) { - ruleCondition.condition().s_Op = model::CRuleCondition::E_LT; + condition.op(model::CRuleCondition::E_LT); } else if (operatorString == LTE) { - ruleCondition.condition().s_Op = model::CRuleCondition::E_LTE; + condition.op(model::CRuleCondition::E_LTE); } else if (operatorString == GT) { - ruleCondition.condition().s_Op = model::CRuleCondition::E_GT; + condition.op(model::CRuleCondition::E_GT); } else if (operatorString == GTE) { - ruleCondition.condition().s_Op = model::CRuleCondition::E_GTE; + condition.op(model::CRuleCondition::E_GTE); } else { LOG_ERROR(<< "Invalid operator value: " << operatorString); return false; @@ -297,19 +280,14 @@ bool CDetectionRulesJsonParser::parseConditionOperator(const rapidjson::Value& c return true; } -bool CDetectionRulesJsonParser::parseConditionThreshold(const rapidjson::Value& conditionObject, - model::CRuleCondition& ruleCondition) { - if (!hasStringMember(conditionObject, VALUE)) { +bool CDetectionRulesJsonParser::parseConditionValue(const rapidjson::Value& conditionObject, + model::CRuleCondition& condition) { + if (hasDoubleMember(conditionObject, VALUE) == false) { LOG_ERROR(<< "Missing condition field: " << VALUE); return false; } - const std::string valueString = conditionObject[VALUE.c_str()].GetString(); - if (core::CStringUtils::stringToType( - valueString, ruleCondition.condition().s_Threshold) == false) { - LOG_ERROR(<< "Invalid operator value: " << valueString); - return false; - } + condition.value(conditionObject[VALUE.c_str()].GetDouble()); return true; } } diff --git a/lib/api/unittest/CConfigUpdaterTest.cc b/lib/api/unittest/CConfigUpdaterTest.cc index 060ce9eb24..2a16337795 100644 --- a/lib/api/unittest/CConfigUpdaterTest.cc +++ b/lib/api/unittest/CConfigUpdaterTest.cc @@ -87,10 +87,10 @@ void CConfigUpdaterTest::testUpdateGivenModelPlotConfig() { void CConfigUpdaterTest::testUpdateGivenDetectorRules() { CFieldConfig fieldConfig; - std::string originalRules0("[{\"actions\":[\"filter_results\"],\"conditions_connective\":\"or\","); - originalRules0 += "\"conditions\":[{\"type\":\"numerical_actual\",\"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}]}]"; - std::string originalRules1("[{\"actions\":[\"filter_results\"],\"conditions_connective\":\"or\","); - originalRules1 += "\"conditions\":[{\"type\":\"numerical_actual\",\"condition\":{\"operator\":\"gt\",\"value\":\"5\"}}]}]"; + std::string originalRules0("[{\"actions\":[\"skip_result\"],"); + originalRules0 += "\"conditions\":[{\"applies_to\":\"actual\",\"operator\":\"lt\",\"value\": 5.0}]}]"; + std::string originalRules1("[{\"actions\":[\"skip_result\"],"); + originalRules1 += "\"conditions\":[{\"applies_to\":\"actual\",\"operator\":\"gt\",\"value\": 5.0}]}]"; fieldConfig.parseRules(0, originalRules0); fieldConfig.parseRules(1, originalRules1); @@ -98,10 +98,9 @@ void CConfigUpdaterTest::testUpdateGivenDetectorRules() { model::CAnomalyDetectorModelConfig::defaultConfig(); std::string configUpdate0("[detectorRules]\ndetectorIndex = 0\nrulesJson = []\n"); - std::string configUpdate1( - "[detectorRules]\ndetectorIndex = 1\nrulesJson = " - "[{\"actions\":[\"filter_results\"],\"conditions_connective\":\"or\",\"conditions\":[{\"type\":\"numerical_" - "typical\",\"condition\":{\"operator\":\"lt\",\"value\":\"15\"}}]}]"); + std::string configUpdate1("[detectorRules]\ndetectorIndex = 1\nrulesJson = " + "[{\"actions\":[\"skip_result\"],\"conditions\":[{\"applies_to\":\"typical\"," + "\"operator\":\"lt\",\"value\": 15.0}]}]"); CConfigUpdater configUpdater(fieldConfig, modelConfig); @@ -113,14 +112,14 @@ void CConfigUpdaterTest::testUpdateGivenDetectorRules() { CPPUNIT_ASSERT(itr->second.empty()); itr = fieldConfig.detectionRules().find(1); CPPUNIT_ASSERT_EQUAL(std::size_t(1), itr->second.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF TYPICAL < 15.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF TYPICAL < 15.000000"), itr->second[0].print()); } void CConfigUpdaterTest::testUpdateGivenRulesWithInvalidDetectorIndex() { CFieldConfig fieldConfig; - std::string originalRules("[{\"actions\":[\"filter_results\"],\"conditions_connective\":\"or\","); - originalRules += "\"conditions\":[{\"type\":\"numerical_actual\",\"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}]}]"; + std::string originalRules("[{\"actions\":[\"skip_result\"],"); + originalRules += "\"conditions\":[{\"applies_to\":\"actual\",\"operator\":\"lt\",\"value\": 5.0}]}]"; fieldConfig.parseRules(0, originalRules); model::CAnomalyDetectorModelConfig modelConfig = @@ -185,13 +184,13 @@ void CConfigUpdaterTest::testUpdateGivenFilters() { void CConfigUpdaterTest::testUpdateGivenScheduledEvents() { std::string validRule1 = - "[{\"actions\":[\"filter_results\",\"skip_sampling\"],\"conditions_connective\":\"and\"," - "\"conditions\":[{\"type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"1\"}}," - "{\"type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"2\"}}]}]"; + "[{\"actions\":[\"skip_result\",\"skip_model_update\"]," + "\"conditions\":[{\"applies_to\":\"time\",\"operator\":\"gte\",\"value\": 1.0}," + "{\"applies_to\":\"time\",\"operator\":\"lt\",\"value\": 2.0}]}]"; std::string validRule2 = - "[{\"actions\":[\"filter_results\",\"skip_sampling\"],\"conditions_connective\":\"and\"," - "\"conditions\":[{\"type\":\"time\",\"condition\":{\"operator\":\"gte\",\"value\":\"3\"}}," - "{\"type\":\"time\",\"condition\":{\"operator\":\"lt\",\"value\":\"4\"}}]}]"; + "[{\"actions\":[\"skip_result\",\"skip_model_update\"]," + "\"conditions\":[{\"applies_to\":\"time\",\"operator\":\"gte\",\"value\": 3.0}," + "{\"applies_to\":\"time\",\"operator\":\"lt\",\"value\": 4.0}]}]"; CFieldConfig fieldConfig; @@ -211,10 +210,10 @@ void CConfigUpdaterTest::testUpdateGivenScheduledEvents() { const auto& events = fieldConfig.scheduledEvents(); CPPUNIT_ASSERT_EQUAL(std::size_t(2), events.size()); CPPUNIT_ASSERT_EQUAL(std::string("old_event_1"), events[0].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 1.000000 AND TIME < 2.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 1.000000 AND TIME < 2.000000"), events[0].second.print()); CPPUNIT_ASSERT_EQUAL(std::string("old_event_2"), events[1].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 3.000000 AND TIME < 4.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 3.000000 AND TIME < 4.000000"), events[1].second.print()); } @@ -239,10 +238,10 @@ void CConfigUpdaterTest::testUpdateGivenScheduledEvents() { const auto& events = fieldConfig.scheduledEvents(); CPPUNIT_ASSERT_EQUAL(std::size_t(2), events.size()); CPPUNIT_ASSERT_EQUAL(std::string("new_event_1"), events[0].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 3.000000 AND TIME < 4.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 3.000000 AND TIME < 4.000000"), events[0].second.print()); CPPUNIT_ASSERT_EQUAL(std::string("new_event_2"), events[1].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 1.000000 AND TIME < 2.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 1.000000 AND TIME < 2.000000"), events[1].second.print()); } diff --git a/lib/api/unittest/CDetectionRulesJsonParserTest.cc b/lib/api/unittest/CDetectionRulesJsonParserTest.cc index 10f2654aab..552b8825d8 100644 --- a/lib/api/unittest/CDetectionRulesJsonParserTest.cc +++ b/lib/api/unittest/CDetectionRulesJsonParserTest.cc @@ -42,17 +42,14 @@ CppUnit::Test* CDetectionRulesJsonParserTest::suite() { "CDetectionRulesJsonParserTest::testParseRulesGivenInvalidRuleAction", &CDetectionRulesJsonParserTest::testParseRulesGivenInvalidRuleAction)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionsConnective", - &CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionsConnective)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionsConnective", - &CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionsConnective)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenMissingRuleConditions", - &CDetectionRulesJsonParserTest::testParseRulesGivenMissingRuleConditions)); + "CDetectionRulesJsonParserTest::testParseRulesGivenNeitherScopeNorConditions", + &CDetectionRulesJsonParserTest::testParseRulesGivenNeitherScopeNorConditions)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsNotArray", &CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsNotArray)); + suiteOfTests->addTest(new CppUnit::TestCaller( + "CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsEmptyArray", + &CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsEmptyArray)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionOperator", &CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionOperator)); @@ -60,29 +57,35 @@ CppUnit::Test* CDetectionRulesJsonParserTest::suite() { "CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionOperator", &CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionOperator)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenNumericalActualRuleWithConnectiveOr", - &CDetectionRulesJsonParserTest::testParseRulesGivenNumericalActualRuleWithConnectiveOr)); + "CDetectionRulesJsonParserTest::testParseRulesGivenConditionOnActual", + &CDetectionRulesJsonParserTest::testParseRulesGivenConditionOnActual)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenNumericalTypicalAndDiffAbsRuleWithConnectiveAnd", - &CDetectionRulesJsonParserTest::testParseRulesGivenNumericalTypicalAndDiffAbsRuleWithConnectiveAnd)); + "CDetectionRulesJsonParserTest::testParseRulesGivenConditionsOnTypicalAndDiffFromTypical", + &CDetectionRulesJsonParserTest::testParseRulesGivenConditionsOnTypicalAndDiffFromTypical)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRulesJsonParserTest::testParseRulesGivenMultipleRules", &CDetectionRulesJsonParserTest::testParseRulesGivenMultipleRules)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalMatchRule", - &CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalMatchRule)); + "CDetectionRulesJsonParserTest::testParseRulesGivenScopeIsEmpty", + &CDetectionRulesJsonParserTest::testParseRulesGivenScopeIsEmpty)); + suiteOfTests->addTest(new CppUnit::TestCaller( + "CDetectionRulesJsonParserTest::testParseRulesGivenIncludeScope", + &CDetectionRulesJsonParserTest::testParseRulesGivenIncludeScope)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalComplementRule", - &CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalComplementRule)); + "CDetectionRulesJsonParserTest::testParseRulesGivenExcludeScope", + &CDetectionRulesJsonParserTest::testParseRulesGivenExcludeScope)); + suiteOfTests->addTest(new CppUnit::TestCaller( + "CDetectionRulesJsonParserTest::testParseRulesGivenMultipleScopedFields", + &CDetectionRulesJsonParserTest::testParseRulesGivenMultipleScopedFields)); + suiteOfTests->addTest(new CppUnit::TestCaller( + "CDetectionRulesJsonParserTest::testParseRulesGivenScopeAndConditions", + &CDetectionRulesJsonParserTest::testParseRulesGivenScopeAndConditions)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRulesJsonParserTest::testParseRulesGivenTimeRule", &CDetectionRulesJsonParserTest::testParseRulesGivenTimeRule)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions", &CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRulesJsonParserTest::testParseRulesGivenOldStyleCategoricalRule", - &CDetectionRulesJsonParserTest::testParseRulesGivenOldStyleCategoricalRule)); return suiteOfTests; } @@ -121,9 +124,8 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMissingRuleAction() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"conditions_connective\":\"or\","; rulesJson += " \"conditions\": ["; - rulesJson += " {\"condition+type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -138,9 +140,8 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenRuleActionIsNotArray() { std::string rulesJson = "["; rulesJson += "{"; rulesJson += " \"actions\":\"not_array\","; - rulesJson += " \"conditions_connective\":\"or\","; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -155,9 +156,8 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenInvalidRuleAction() { std::string rulesJson = "["; rulesJson += "{"; rulesJson += " \"actions\":[\"something_invalid\"],"; - rulesJson += " \"conditions_connective\":\"or\","; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -166,14 +166,12 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenInvalidRuleAction() { CPPUNIT_ASSERT(rules.empty()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionsConnective() { +void CDetectionRulesJsonParserTest::testParseRulesGivenNeitherScopeNorConditions() { CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; - rulesJson += " ]"; + rulesJson += " \"actions\":[\"skip_result\"]"; rulesJson += "}"; rulesJson += "]"; @@ -181,28 +179,13 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionsConnecti CPPUNIT_ASSERT(rules.empty()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionsConnective() { - CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); - CDetectionRulesJsonParser::TDetectionRuleVec rules; - std::string rulesJson = "["; - rulesJson += "{"; - rulesJson += " \"conditions_connective\":\"XOR\","; - rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; - rulesJson += " ]"; - rulesJson += "}"; - rulesJson += "]"; - - CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules) == false); - CPPUNIT_ASSERT(rules.empty()); -} - -void CDetectionRulesJsonParserTest::testParseRulesGivenMissingRuleConditions() { +void CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsNotArray() { CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"conditions_connective\":\"or\","; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"conditions\": {}"; rulesJson += "}"; rulesJson += "]"; @@ -210,13 +193,13 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMissingRuleConditions() { CPPUNIT_ASSERT(rules.empty()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsNotArray() { +void CDetectionRulesJsonParserTest::testParseRulesGivenRuleConditionsIsEmptyArray() { CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"conditions_connective\":\"or\","; - rulesJson += " \"conditions\": {}"; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"conditions\": []"; rulesJson += "}"; rulesJson += "]"; @@ -229,8 +212,9 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMissingConditionOperator( CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -244,8 +228,9 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionOperator( CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"ha\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"ha\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -254,16 +239,15 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenInvalidConditionOperator( CPPUNIT_ASSERT(rules.empty()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenNumericalActualRuleWithConnectiveOr() { +void CDetectionRulesJsonParserTest::testParseRulesGivenConditionOnActual() { CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"or\","; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}},"; - rulesJson += " {\"type\":\"numerical_actual\", \"field_name\":\"metric\", \"condition\":{\"operator\":\"lte\",\"value\":\"2.3\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0},"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lte\",\"value\": 2.3}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -271,21 +255,19 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenNumericalActualRuleWithCo CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF ACTUAL < 5.000000 OR ACTUAL(metric) <= 2.300000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF ACTUAL < 5.000000 AND ACTUAL <= 2.300000"), rules[0].print()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenNumericalTypicalAndDiffAbsRuleWithConnectiveAnd() { +void CDetectionRulesJsonParserTest::testParseRulesGivenConditionsOnTypicalAndDiffFromTypical() { CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"and\","; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_typical\", \"condition\":{\"operator\":\"gt\",\"value\":\"5\"}},"; - rulesJson += " {\"type\":\"numerical_diff_abs\", \"field_name\":\"metric\", " - "\"field_value\":\"cpu\",\"condition\":{\"operator\":\"gte\",\"value\":\"2.3\"}}"; + rulesJson += " {\"applies_to\":\"typical\", \"operator\":\"gt\",\"value\": 5.0},"; + rulesJson += " {\"applies_to\":\"diff_from_typical\", \"operator\":\"gte\",\"value\": 2.3}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -293,7 +275,7 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenNumericalTypicalAndDiffAb CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF TYPICAL > 5.000000 AND DIFF_ABS(metric:cpu) >= 2.300000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF TYPICAL > 5.000000 AND DIFF_FROM_TYPICAL >= 2.300000"), rules[0].print()); } @@ -302,21 +284,15 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMultipleRules() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"or\","; - rulesJson += " \"target_field_name\":\"id\","; - rulesJson += " \"target_field_value\":\"foo\","; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"1\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 1.0}"; rulesJson += " ]"; rulesJson += "},"; rulesJson += "{"; - rulesJson += " \"actions\":[\"skip_sampling\"],"; - rulesJson += " \"conditions_connective\":\"and\","; - rulesJson += " \"target_field_name\":\"id\","; - rulesJson += " \"target_field_value\":\"42\","; + rulesJson += " \"actions\":[\"skip_model_update\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"2\"}}"; + rulesJson += " {\"applies_to\":\"typical\", \"operator\":\"lt\",\"value\": 2.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -324,13 +300,27 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenMultipleRules() { CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(2), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS (id:foo) IF ACTUAL < 1.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF ACTUAL < 1.000000"), rules[0].print()); - CPPUNIT_ASSERT_EQUAL(std::string("SKIP_SAMPLING (id:42) IF ACTUAL < 2.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_MODEL_UPDATE IF TYPICAL < 2.000000"), rules[1].print()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalMatchRule() { +void CDetectionRulesJsonParserTest::testParseRulesGivenScopeIsEmpty() { + CDetectionRulesJsonParser parser(EMPTY_VALUE_FILTER_MAP); + CDetectionRulesJsonParser::TDetectionRuleVec rules; + std::string rulesJson = "["; + rulesJson += "{"; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"scope\": {}"; + rulesJson += "}"; + rulesJson += "]"; + + CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules) == false); + CPPUNIT_ASSERT(rules.empty()); +} + +void CDetectionRulesJsonParserTest::testParseRulesGivenIncludeScope() { TStrPatternSetUMap filtersById; core::CPatternSet filter; filter.initFromJson("[\"b\", \"a\"]"); @@ -340,25 +330,20 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalMatchRule() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"or\","; - rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"categorical_match\", \"field_name\":\"foo\", \"filter_id\":\"filter1\"}"; - rulesJson += " ]"; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"scope\": {"; + rulesJson += " \"foo\": {\"filter_id\": \"filter1\", \"filter_type\": \"include\"}"; + rulesJson += " }"; rulesJson += "}"; rulesJson += "]"; CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF (foo) IN FILTER"), - rules[0].print()); + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF 'foo' IN FILTER"), rules[0].print()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenOldStyleCategoricalRule() { - // Tests that the rule type can be parsed as categorical_match - // when the type is categorical - +void CDetectionRulesJsonParserTest::testParseRulesGivenExcludeScope() { TStrPatternSetUMap filtersById; core::CPatternSet filter; filter.initFromJson("[\"b\", \"a\"]"); @@ -368,22 +353,49 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenOldStyleCategoricalRule() CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"or\","; - rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"categorical\", \"field_name\":\"foo\", \"filter_id\":\"filter1\"}"; - rulesJson += " ]"; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"scope\": {"; + rulesJson += " \"foo\": {\"filter_id\": \"filter1\", \"filter_type\": \"exclude\"}"; + rulesJson += " }"; + rulesJson += "}"; + rulesJson += "]"; + + CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); + + CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF 'foo' NOT IN FILTER"), + rules[0].print()); +} + +void CDetectionRulesJsonParserTest::testParseRulesGivenMultipleScopedFields() { + TStrPatternSetUMap filtersById; + core::CPatternSet filter1; + filter1.initFromJson("[\"b\", \"a\"]"); + filtersById["filter1"] = filter1; + core::CPatternSet filter2; + filter2.initFromJson("[\"c\"]"); + filtersById["filter2"] = filter2; + + CDetectionRulesJsonParser parser(filtersById); + CDetectionRulesJsonParser::TDetectionRuleVec rules; + std::string rulesJson = "["; + rulesJson += "{"; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"scope\": {"; + rulesJson += " \"foo\": {\"filter_id\": \"filter1\", \"filter_type\": \"include\"},"; + rulesJson += " \"bar\": {\"filter_id\": \"filter2\", \"filter_type\": \"exclude\"}"; + rulesJson += " }"; rulesJson += "}"; rulesJson += "]"; CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF (foo) IN FILTER"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF 'foo' IN FILTER AND 'bar' NOT IN FILTER"), rules[0].print()); } -void CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalComplementRule() { +void CDetectionRulesJsonParserTest::testParseRulesGivenScopeAndConditions() { TStrPatternSetUMap filtersById; core::CPatternSet filter; filter.initFromJson("[\"b\", \"a\"]"); @@ -393,10 +405,12 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalComplementRule CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"or\","; + rulesJson += " \"actions\":[\"skip_result\"],"; + rulesJson += " \"scope\": {"; + rulesJson += " \"foo\": {\"filter_id\": \"filter1\", \"filter_type\": \"include\"}"; + rulesJson += " },"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"categorical_complement\", \"field_name\":\"foo\", \"filter_id\":\"filter1\"}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 2.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -404,7 +418,7 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenCategoricalComplementRule CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF (foo) NOT IN FILTER"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF 'foo' IN FILTER AND ACTUAL < 2.000000"), rules[0].print()); } @@ -413,18 +427,17 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenTimeRule() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"and\","; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"time\", \"condition\":{\"operator\":\"gte\",\"value\":\"5000\"}}"; - rulesJson += " ,{\"type\":\"time\", \"condition\":{\"operator\":\"lt\",\"value\":\"10000\"}}"; + rulesJson += " {\"applies_to\":\"time\", \"operator\":\"gte\",\"value\": 5000.0},"; + rulesJson += " {\"applies_to\":\"time\", \"operator\":\"lt\",\"value\": 10000.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF TIME >= 5000.000000 AND TIME < 10000.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF TIME >= 5000.000000 AND TIME < 10000.000000"), rules[0].print()); } @@ -434,10 +447,9 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"and\","; + rulesJson += " \"actions\":[\"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -445,7 +457,7 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS IF ACTUAL < 5.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT IF ACTUAL < 5.000000"), rules[0].print()); } @@ -454,10 +466,9 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"skip_sampling\"],"; - rulesJson += " \"conditions_connective\":\"and\","; + rulesJson += " \"actions\":[\"skip_model_update\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -465,7 +476,7 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("SKIP_SAMPLING IF ACTUAL < 5.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_MODEL_UPDATE IF ACTUAL < 5.000000"), rules[0].print()); } @@ -474,10 +485,9 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CDetectionRulesJsonParser::TDetectionRuleVec rules; std::string rulesJson = "["; rulesJson += "{"; - rulesJson += " \"actions\":[\"skip_sampling\", \"filter_results\"],"; - rulesJson += " \"conditions_connective\":\"and\","; + rulesJson += " \"actions\":[\"skip_model_update\", \"skip_result\"],"; rulesJson += " \"conditions\": ["; - rulesJson += " {\"type\":\"numerical_actual\", \"condition\":{\"operator\":\"lt\",\"value\":\"5\"}}"; + rulesJson += " {\"applies_to\":\"actual\", \"operator\":\"lt\",\"value\": 5.0}"; rulesJson += " ]"; rulesJson += "}"; rulesJson += "]"; @@ -485,7 +495,7 @@ void CDetectionRulesJsonParserTest::testParseRulesGivenDifferentActions() { CPPUNIT_ASSERT(parser.parseRules(rulesJson, rules)); CPPUNIT_ASSERT_EQUAL(std::size_t(1), rules.size()); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF ACTUAL < 5.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF ACTUAL < 5.000000"), rules[0].print()); } } diff --git a/lib/api/unittest/CDetectionRulesJsonParserTest.h b/lib/api/unittest/CDetectionRulesJsonParserTest.h index 685b2642f3..8bafe3dca8 100644 --- a/lib/api/unittest/CDetectionRulesJsonParserTest.h +++ b/lib/api/unittest/CDetectionRulesJsonParserTest.h @@ -16,20 +16,21 @@ class CDetectionRulesJsonParserTest : public CppUnit::TestFixture { void testParseRulesGivenMissingRuleAction(); void testParseRulesGivenRuleActionIsNotArray(); void testParseRulesGivenInvalidRuleAction(); - void testParseRulesGivenMissingConditionsConnective(); - void testParseRulesGivenInvalidConditionsConnective(); - void testParseRulesGivenMissingRuleConditions(); + void testParseRulesGivenNeitherScopeNorConditions(); void testParseRulesGivenRuleConditionsIsNotArray(); + void testParseRulesGivenRuleConditionsIsEmptyArray(); void testParseRulesGivenMissingConditionOperator(); void testParseRulesGivenInvalidConditionOperator(); - void testParseRulesGivenNumericalActualRuleWithConnectiveOr(); - void testParseRulesGivenNumericalTypicalAndDiffAbsRuleWithConnectiveAnd(); + void testParseRulesGivenConditionOnActual(); + void testParseRulesGivenConditionsOnTypicalAndDiffFromTypical(); void testParseRulesGivenMultipleRules(); - void testParseRulesGivenCategoricalMatchRule(); - void testParseRulesGivenCategoricalComplementRule(); + void testParseRulesGivenScopeIsEmpty(); + void testParseRulesGivenIncludeScope(); + void testParseRulesGivenExcludeScope(); + void testParseRulesGivenMultipleScopedFields(); + void testParseRulesGivenScopeAndConditions(); void testParseRulesGivenTimeRule(); void testParseRulesGivenDifferentActions(); - void testParseRulesGivenOldStyleCategoricalRule(); static CppUnit::Test* suite(); }; diff --git a/lib/api/unittest/CFieldConfigTest.cc b/lib/api/unittest/CFieldConfigTest.cc index 4a5ec7e4af..3fd0183271 100644 --- a/lib/api/unittest/CFieldConfigTest.cc +++ b/lib/api/unittest/CFieldConfigTest.cc @@ -1840,9 +1840,9 @@ void CFieldConfigTest::testScheduledEvents() { ml::api::CFieldConfig::TStrDetectionRulePrVec events = config.scheduledEvents(); CPPUNIT_ASSERT_EQUAL(std::size_t{2}, events.size()); CPPUNIT_ASSERT_EQUAL(std::string("May Bank Holiday"), events[0].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 1525132800.000000 AND TIME < 1525219200.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 1525132800.000000 AND TIME < 1525219200.000000"), events[0].second.print()); CPPUNIT_ASSERT_EQUAL(std::string("New Years Day"), events[1].first); - CPPUNIT_ASSERT_EQUAL(std::string("FILTER_RESULTS AND SKIP_SAMPLING IF TIME >= 1514764800.000000 AND TIME < 1514851200.000000"), + CPPUNIT_ASSERT_EQUAL(std::string("SKIP_RESULT AND SKIP_MODEL_UPDATE IF TIME >= 1514764800.000000 AND TIME < 1514851200.000000"), events[1].second.print()); } diff --git a/lib/api/unittest/testfiles/scheduled_events.conf b/lib/api/unittest/testfiles/scheduled_events.conf index e15c40c8ca..79d0382e5e 100644 --- a/lib/api/unittest/testfiles/scheduled_events.conf +++ b/lib/api/unittest/testfiles/scheduled_events.conf @@ -1,5 +1,5 @@ detector.1.clause = count by mlcategory categorizationfield=_raw scheduledevent.1.description = May Bank Holiday -scheduledevent.1.rules = [{"actions":["filter_results","skip_sampling"],"conditions_connective":"and","conditions":[{"type":"time","condition":{"operator":"gte","value":"1525132800"}},{"type":"time","condition":{"operator":"lt","value":"1525219200"}}]}] +scheduledevent.1.rules = [{"actions":["skip_result","skip_model_update"],"conditions":[{"applies_to":"time","operator":"gte","value":1525132800.0},{"applies_to":"time","operator":"lt","value":1525219200.0}]}] scheduledevent.2.description = New Years Day -scheduledevent.2.rules = [{"actions":["filter_results","skip_sampling"],"conditions_connective":"and","conditions":[{"type":"time","condition":{"operator":"gte","value":"1514764800"}},{"type":"time","condition":{"operator":"lt","value":"1514851200"}}]}] \ No newline at end of file +scheduledevent.2.rules = [{"actions":["skip_result","skip_model_update"],"conditions":[{"applies_to":"time","operator":"gte","value":1514764800.0},{"applies_to":"time","operator":"lt","value":1514851200.0}]}] diff --git a/lib/model/CAnomalyDetectorModel.cc b/lib/model/CAnomalyDetectorModel.cc index 628adb6041..d06223f466 100644 --- a/lib/model/CAnomalyDetectorModel.cc +++ b/lib/model/CAnomalyDetectorModel.cc @@ -441,10 +441,10 @@ bool CAnomalyDetectorModel::shouldIgnoreResult(model_t::EFeature feature, core_t::TTime time) const { bool shouldIgnore = checkScheduledEvents(this->params().s_ScheduledEvents.get(), - boost::cref(*this), feature, CDetectionRule::E_FilterResults, + boost::cref(*this), feature, CDetectionRule::E_SkipResult, resultType, pid, cid, time) || checkRules(this->params().s_DetectionRules.get(), boost::cref(*this), feature, - CDetectionRule::E_FilterResults, resultType, pid, cid, time); + CDetectionRule::E_SkipResult, resultType, pid, cid, time); return shouldIgnore; } @@ -455,10 +455,10 @@ bool CAnomalyDetectorModel::shouldIgnoreSample(model_t::EFeature feature, core_t::TTime time) const { bool shouldIgnore = checkScheduledEvents(this->params().s_ScheduledEvents.get(), - boost::cref(*this), feature, CDetectionRule::E_SkipSampling, + boost::cref(*this), feature, CDetectionRule::E_SkipModelUpdate, SKIP_SAMPLING_RESULT_TYPE, pid, cid, time) || checkRules(this->params().s_DetectionRules.get(), boost::cref(*this), - feature, CDetectionRule::E_SkipSampling, + feature, CDetectionRule::E_SkipModelUpdate, SKIP_SAMPLING_RESULT_TYPE, pid, cid, time); return shouldIgnore; diff --git a/lib/model/CCountingModel.cc b/lib/model/CCountingModel.cc index a885d2b7f2..25ec6bec6c 100644 --- a/lib/model/CCountingModel.cc +++ b/lib/model/CCountingModel.cc @@ -261,7 +261,7 @@ CCountingModel::checkScheduledEvents(core_t::TTime sampleTime) const { for (auto& event : events) { // Note that as the counting model is not aware of partitions // scheduled events cannot support partitions as the code stands. - if (event.second.apply(CDetectionRule::E_SkipSampling, boost::cref(*this), + if (event.second.apply(CDetectionRule::E_SkipModelUpdate, boost::cref(*this), model_t::E_IndividualCountByBucketAndPerson, model_t::CResultType(), model_t::INDIVIDUAL_ANALYSIS_ATTRIBUTE_ID, model_t::INDIVIDUAL_ANALYSIS_ATTRIBUTE_ID, sampleTime)) { diff --git a/lib/model/CDetectionRule.cc b/lib/model/CDetectionRule.cc index 7cf6cf7836..fc8ec27d59 100644 --- a/lib/model/CDetectionRule.cc +++ b/lib/model/CDetectionRule.cc @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -#include - #include #include @@ -14,30 +12,20 @@ namespace ml { namespace model { -CDetectionRule::CDetectionRule() - : m_Action(E_FilterResults), m_Conditions(), m_ConditionsConnective(E_Or), - m_TargetFieldName(), m_TargetFieldValue() { - m_Conditions.reserve(1); -} - void CDetectionRule::action(int action) { m_Action = action; } -void CDetectionRule::conditionsConnective(EConditionsConnective connective) { - m_ConditionsConnective = connective; -} - -void CDetectionRule::addCondition(const CRuleCondition& condition) { - m_Conditions.push_back(condition); +void CDetectionRule::includeScope(const std::string& field, const core::CPatternSet& filter) { + m_Scope.include(field, filter); } -void CDetectionRule::targetFieldName(const std::string& targetFieldName) { - m_TargetFieldName = targetFieldName; +void CDetectionRule::excludeScope(const std::string& field, const core::CPatternSet& filter) { + m_Scope.exclude(field, filter); } -void CDetectionRule::targetFieldValue(const std::string& targetFieldValue) { - m_TargetFieldValue = targetFieldValue; +void CDetectionRule::addCondition(const CRuleCondition& condition) { + m_Conditions.push_back(condition); } bool CDetectionRule::apply(ERuleAction action, @@ -51,72 +39,31 @@ bool CDetectionRule::apply(ERuleAction action, return false; } - if (this->isInScope(model, pid, cid) == false) { + if (m_Scope.check(model, pid, cid) == false) { return false; } - for (std::size_t i = 0; i < m_Conditions.size(); ++i) { - bool conditionResult = m_Conditions[i].test( - model, feature, resultType, !m_TargetFieldName.empty(), pid, cid, time); - switch (m_ConditionsConnective) { - case E_Or: - if (conditionResult == true) { - return true; - } - break; - case E_And: - if (conditionResult == false) { - return false; - } - break; + for (const auto& condition : m_Conditions) { + if (condition.test(model, feature, resultType, pid, cid, time) == false) { + return false; } } - switch (m_ConditionsConnective) { - case E_Or: - return false; - case E_And: - return true; - } - return false; -} - -bool CDetectionRule::isInScope(const CAnomalyDetectorModel& model, - std::size_t pid, - std::size_t cid) const { - if (m_TargetFieldName.empty() || m_TargetFieldValue.empty()) { - return true; - } - - const CDataGatherer& gatherer = model.dataGatherer(); - if (m_TargetFieldName == gatherer.partitionFieldName()) { - return m_TargetFieldValue == gatherer.partitionFieldValue(); - } else if (m_TargetFieldName == gatherer.personFieldName()) { - return m_TargetFieldValue == gatherer.personName(pid); - } else if (m_TargetFieldName == gatherer.attributeFieldName()) { - return m_TargetFieldValue == gatherer.attributeName(cid); - } else { - LOG_ERROR(<< "Unexpected targetFieldName = " << m_TargetFieldName); - } - return false; + return true; } std::string CDetectionRule::print() const { std::string result = this->printAction(); - if (m_TargetFieldName.empty() == false) { - result += " (" + m_TargetFieldName; - if (m_TargetFieldValue.empty() == false) { - result += ":" + m_TargetFieldValue; - } - result += ")"; - } result += " IF "; + std::string scopeString = m_Scope.print(); + result += scopeString; + if (scopeString.empty() == false && m_Conditions.empty() == false) { + result += " AND "; + } for (std::size_t i = 0; i < m_Conditions.size(); ++i) { result += m_Conditions[i].print(); if (i < m_Conditions.size() - 1) { - result += " "; - result += this->printConditionsConnective(); - result += " "; + result += " AND "; } } return result; @@ -124,26 +71,16 @@ std::string CDetectionRule::print() const { std::string CDetectionRule::printAction() const { std::string result; - if (E_FilterResults & m_Action) { - result += "FILTER_RESULTS"; + if (E_SkipResult & m_Action) { + result += "SKIP_RESULT"; } - if (E_SkipSampling & m_Action) { + if (E_SkipModelUpdate & m_Action) { if (result.empty() == false) { result += " AND "; } - result += "SKIP_SAMPLING"; + result += "SKIP_MODEL_UPDATE"; } return result; } - -std::string CDetectionRule::printConditionsConnective() const { - switch (m_ConditionsConnective) { - case E_And: - return "AND"; - case E_Or: - return "OR"; - } - return std::string(); -} } } diff --git a/lib/model/CRuleCondition.cc b/lib/model/CRuleCondition.cc index 83904c02c5..1303007c9d 100644 --- a/lib/model/CRuleCondition.cc +++ b/lib/model/CRuleCondition.cc @@ -5,11 +5,9 @@ */ #include -#include #include #include -#include #include namespace ml { @@ -17,135 +15,40 @@ namespace model { namespace { const CAnomalyDetectorModel::TSizeDoublePr1Vec EMPTY_CORRELATED; -const core::CPatternSet EMPTY_FILTER; } using TDouble1Vec = CAnomalyDetectorModel::TDouble1Vec; -CRuleCondition::SCondition::SCondition(EConditionOperator op, double threshold) - : s_Op(op), s_Threshold(threshold) { -} - -bool CRuleCondition::SCondition::test(double value) const { - switch (s_Op) { - case E_LT: - return value < s_Threshold; - case E_LTE: - return value <= s_Threshold; - case E_GT: - return value > s_Threshold; - case E_GTE: - return value >= s_Threshold; - } - return false; -} - CRuleCondition::CRuleCondition() - : m_Type(E_NumericalActual), m_Condition(E_LT, 0.0), m_FieldName(), - m_FieldValue(), m_ValueFilter(EMPTY_FILTER) { + : m_AppliesTo(E_Actual), m_Operator(E_LT), m_Value(0.0) { } -void CRuleCondition::type(ERuleConditionType ruleType) { - m_Type = ruleType; +void CRuleCondition::appliesTo(ERuleConditionAppliesTo appliesTo) { + m_AppliesTo = appliesTo; } -void CRuleCondition::fieldName(const std::string& fieldName) { - m_FieldName = fieldName; +void CRuleCondition::op(ERuleConditionOperator op) { + m_Operator = op; } -void CRuleCondition::fieldValue(const std::string& fieldValue) { - m_FieldValue = fieldValue; -} - -CRuleCondition::SCondition& CRuleCondition::condition() { - return m_Condition; -} - -void CRuleCondition::valueFilter(const core::CPatternSet& valueFilter) { - m_ValueFilter = TPatternSetCRef(valueFilter); -} - -bool CRuleCondition::isCategorical() const { - return m_Type == E_CategoricalMatch || m_Type == E_CategoricalComplement; -} - -bool CRuleCondition::isNumerical() const { - return !this->isCategorical(); +void CRuleCondition::value(double value) { + m_Value = value; } bool CRuleCondition::test(const CAnomalyDetectorModel& model, model_t::EFeature feature, const model_t::CResultType& resultType, - bool isScoped, std::size_t pid, std::size_t cid, core_t::TTime time) const { - const CDataGatherer& gatherer = model.dataGatherer(); - - if (this->isCategorical()) { - bool containsValue{false}; - if (m_FieldName == gatherer.partitionFieldName()) { - containsValue = m_ValueFilter.get().contains(gatherer.partitionFieldValue()); - } else if (m_FieldName == gatherer.personFieldName()) { - containsValue = m_ValueFilter.get().contains(gatherer.personName(pid)); - } else if (m_FieldName == gatherer.attributeFieldName()) { - containsValue = m_ValueFilter.get().contains(gatherer.attributeName(cid)); - } else { - LOG_ERROR(<< "Unexpected fieldName = " << m_FieldName); - return false; - } - - return (m_Type == E_CategoricalComplement) ? !containsValue : containsValue; - } else { - if (m_FieldValue.empty() == false) { - if (isScoped) { - // When scoped we are checking if the rule condition applies to the entity - // identified by m_FieldName/m_FieldValue, and we do this for all time - // series which have resolved to check this condition. - // Thus we ignore the supplied pid/cid and instead look up - // the time series identifier that matches the condition's m_FieldValue. - bool successfullyResolvedId = - model.isPopulation() ? gatherer.attributeId(m_FieldValue, cid) - : gatherer.personId(m_FieldValue, pid); - if (successfullyResolvedId == false) { - return false; - } - } else { - // For numerical rules the field name may be: - // - empty - // - the person field name if the detector has only an over field or only a by field - // - the attribute field name if the detector has both over and by fields - const std::string& fieldValue = - model.isPopulation() && m_FieldName == gatherer.attributeFieldName() - ? gatherer.attributeName(cid) - : gatherer.personName(pid); - if (m_FieldValue != fieldValue) { - return false; - } - } - } - return this->checkCondition(model, feature, resultType, pid, cid, time); - } -} -bool CRuleCondition::checkCondition(const CAnomalyDetectorModel& model, - model_t::EFeature feature, - model_t::CResultType resultType, - std::size_t pid, - std::size_t cid, - core_t::TTime time) const { TDouble1Vec value; - switch (m_Type) { - case E_CategoricalMatch: - case E_CategoricalComplement: { - LOG_ERROR(<< "Should never check numerical condition for categorical rule condition"); - return false; - } - case E_NumericalActual: { + switch (m_AppliesTo) { + case E_Actual: { value = model.currentBucketValue(feature, pid, cid, time); break; } - case E_NumericalTypical: { + case E_Typical: { value = model.baselineBucketMean(feature, pid, cid, resultType, EMPTY_CORRELATED, time); if (value.empty()) { @@ -154,7 +57,7 @@ bool CRuleCondition::checkCondition(const CAnomalyDetectorModel& model, } break; } - case E_NumericalDiffAbs: { + case E_DiffFromTypical: { value = model.currentBucketValue(feature, pid, cid, time); TDouble1Vec typical = model.baselineBucketMean(feature, pid, cid, resultType, EMPTY_CORRELATED, time); @@ -186,50 +89,45 @@ bool CRuleCondition::checkCondition(const CAnomalyDetectorModel& model, return false; } - return m_Condition.test(value[0]); + return this->testValue(value[0]); } -std::string CRuleCondition::print() const { - std::string result = this->print(m_Type); - if (m_FieldName.empty() == false) { - result += "(" + m_FieldName; - if (m_FieldValue.empty() == false) { - result += ":" + m_FieldValue; - } - result += ")"; +bool CRuleCondition::testValue(double value) const { + switch (m_Operator) { + case E_LT: + return value < m_Value; + case E_LTE: + return value <= m_Value; + case E_GT: + return value > m_Value; + case E_GTE: + return value >= m_Value; } - result += " "; + return false; +} - if (this->isCategorical()) { - if (m_Type == E_CategoricalComplement) { - result += "NOT "; - } - result += "IN FILTER"; - } else { - result += this->print(m_Condition.s_Op) + " " + - core::CStringUtils::typeToString(m_Condition.s_Threshold); - } +std::string CRuleCondition::print() const { + std::string result = this->print(m_AppliesTo); + result += " " + this->print(m_Operator) + " " + + core::CStringUtils::typeToString(m_Value); return result; } -std::string CRuleCondition::print(ERuleConditionType type) const { - switch (type) { - case E_CategoricalMatch: - case E_CategoricalComplement: - return ""; - case E_NumericalActual: +std::string CRuleCondition::print(ERuleConditionAppliesTo appliesTo) const { + switch (appliesTo) { + case E_Actual: return "ACTUAL"; - case E_NumericalTypical: + case E_Typical: return "TYPICAL"; - case E_NumericalDiffAbs: - return "DIFF_ABS"; + case E_DiffFromTypical: + return "DIFF_FROM_TYPICAL"; case E_Time: return "TIME"; } return std::string(); } -std::string CRuleCondition::print(EConditionOperator op) const { +std::string CRuleCondition::print(ERuleConditionOperator op) const { switch (op) { case E_LT: return "<"; diff --git a/lib/model/CRuleScope.cc b/lib/model/CRuleScope.cc new file mode 100644 index 0000000000..e7b949f01c --- /dev/null +++ b/lib/model/CRuleScope.cc @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +#include + +#include + +#include +#include + +namespace ml { +namespace model { + +void CRuleScope::include(const std::string& field, const core::CPatternSet& filter) { + m_Scope.emplace_back(field, TPatternSetCRef(filter), E_Include); +} + +void CRuleScope::exclude(const std::string& field, const core::CPatternSet& filter) { + m_Scope.emplace_back(field, TPatternSetCRef(filter), E_Exclude); +} + +bool CRuleScope::check(const CAnomalyDetectorModel& model, std::size_t pid, std::size_t cid) const { + + const CDataGatherer& gatherer = model.dataGatherer(); + for (const auto& scopeField : m_Scope) { + bool containsValue{false}; + if (scopeField.first == gatherer.partitionFieldName()) { + containsValue = scopeField.second.get().contains(gatherer.partitionFieldValue()); + } else if (scopeField.first == gatherer.personFieldName()) { + containsValue = scopeField.second.get().contains(gatherer.personName(pid)); + } else if (scopeField.first == gatherer.attributeFieldName()) { + containsValue = scopeField.second.get().contains(gatherer.attributeName(cid)); + } else { + LOG_ERROR(<< "Unexpected scoped field = " << scopeField.first); + return false; + } + + if ((scopeField.third == E_Include && containsValue == false) || + (scopeField.third == E_Exclude && containsValue)) { + return false; + } + } + return true; +} + +std::string CRuleScope::print() const { + std::string result{""}; + auto itr = m_Scope.begin(); + while (itr != m_Scope.end()) { + result += "'" + itr->first + "'"; + if (itr->third == E_Include) { + result += " IN "; + } else { + result += " NOT IN "; + } + result += "FILTER"; + + ++itr; + if (itr != m_Scope.end()) { + result += " AND "; + } + } + return result; +} +} +} diff --git a/lib/model/Makefile b/lib/model/Makefile index 99a779ed15..6b097207c5 100644 --- a/lib/model/Makefile +++ b/lib/model/Makefile @@ -66,6 +66,7 @@ CProbabilityAndInfluenceCalculator.cc \ CResourceMonitor.cc \ CResultsQueue.cc \ CRuleCondition.cc \ +CRuleScope.cc \ CSample.cc \ CSampleCounts.cc \ CSearchKey.cc \ diff --git a/lib/model/unittest/CCountingModelTest.cc b/lib/model/unittest/CCountingModelTest.cc index f50955f446..3036c5035d 100644 --- a/lib/model/unittest/CCountingModelTest.cc +++ b/lib/model/unittest/CCountingModelTest.cc @@ -52,17 +52,16 @@ void addArrival(CDataGatherer& gatherer, SModelParams::TStrDetectionRulePr makeScheduledEvent(const std::string& description, double start, double end) { CRuleCondition conditionGte; - conditionGte.type(CRuleCondition::E_Time); - conditionGte.condition().s_Op = CRuleCondition::E_GTE; - conditionGte.condition().s_Threshold = start; + conditionGte.appliesTo(CRuleCondition::E_Time); + conditionGte.op(CRuleCondition::E_GTE); + conditionGte.value(start); CRuleCondition conditionLt; - conditionLt.type(CRuleCondition::E_Time); - conditionLt.condition().s_Op = CRuleCondition::E_LT; - conditionLt.condition().s_Threshold = end; + conditionLt.appliesTo(CRuleCondition::E_Time); + conditionLt.op(CRuleCondition::E_LT); + conditionLt.value(end); CDetectionRule rule; - rule.action(CDetectionRule::E_SkipSampling); - rule.conditionsConnective(CDetectionRule::E_And); + rule.action(CDetectionRule::E_SkipModelUpdate); rule.addCondition(conditionGte); rule.addCondition(conditionLt); diff --git a/lib/model/unittest/CDetectionRuleTest.cc b/lib/model/unittest/CDetectionRuleTest.cc index 4acaec6d72..06c9cd37b6 100644 --- a/lib/model/unittest/CDetectionRuleTest.cc +++ b/lib/model/unittest/CDetectionRuleTest.cc @@ -36,8 +36,7 @@ CppUnit::Test* CDetectionRuleTest::suite() { CppUnit::TestSuite* suiteOfTests = new CppUnit::TestSuite("CDetectionRuleTest"); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRuleTest::testApplyGivenCategoricalCondition", - &CDetectionRuleTest::testApplyGivenCategoricalCondition)); + "CDetectionRuleTest::testApplyGivenScope", &CDetectionRuleTest::testApplyGivenScope)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRuleTest::testApplyGivenNumericalActualCondition", &CDetectionRuleTest::testApplyGivenNumericalActualCondition)); @@ -47,9 +46,6 @@ CppUnit::Test* CDetectionRuleTest::suite() { suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRuleTest::testApplyGivenNumericalDiffAbsCondition", &CDetectionRuleTest::testApplyGivenNumericalDiffAbsCondition)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRuleTest::testApplyGivenSingleSeriesModelAndConditionWithField", - &CDetectionRuleTest::testApplyGivenSingleSeriesModelAndConditionWithField)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRuleTest::testApplyGivenNoActualValueAvailable", &CDetectionRuleTest::testApplyGivenNoActualValueAvailable)); @@ -60,14 +56,8 @@ CppUnit::Test* CDetectionRuleTest::suite() { "CDetectionRuleTest::testApplyGivenDifferentSeriesAndPopulationModel", &CDetectionRuleTest::testApplyGivenDifferentSeriesAndPopulationModel)); suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRuleTest::testApplyGivenMultipleConditionsWithOr", - &CDetectionRuleTest::testApplyGivenMultipleConditionsWithOr)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRuleTest::testApplyGivenMultipleConditionsWithAnd", - &CDetectionRuleTest::testApplyGivenMultipleConditionsWithAnd)); - suiteOfTests->addTest(new CppUnit::TestCaller( - "CDetectionRuleTest::testApplyGivenTargetFieldIsPartitionAndIndividualModel", - &CDetectionRuleTest::testApplyGivenTargetFieldIsPartitionAndIndividualModel)); + "CDetectionRuleTest::testApplyGivenMultipleConditions", + &CDetectionRuleTest::testApplyGivenMultipleConditions)); suiteOfTests->addTest(new CppUnit::TestCaller( "CDetectionRuleTest::testApplyGivenTimeCondition", &CDetectionRuleTest::testApplyGivenTimeCondition)); @@ -77,7 +67,7 @@ CppUnit::Test* CDetectionRuleTest::suite() { return suiteOfTests; } -void CDetectionRuleTest::testApplyGivenCategoricalCondition() { +void CDetectionRuleTest::testApplyGivenScope() { core_t::TTime bucketLength = 100; core_t::TTime startTime = 100; CSearchKey key; @@ -117,214 +107,207 @@ void CDetectionRuleTest::testApplyGivenCategoricalCondition() { model.mockAddBucketValue(model_t::E_PopulationMeanByPersonAndAttribute, 1, 2, 100, actual); model.mockAddBucketValue(model_t::E_PopulationMeanByPersonAndAttribute, 1, 3, 100, actual); - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"a1_1\",\"a2_2\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(attributeFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(attributeFieldName, valueFilter); + } else { + rule.excludeScope(attributeFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) == isCategoricalMatch); + resultType, 1, 3, 100) == isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"a1*\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(attributeFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(attributeFieldName, valueFilter); + } else { + rule.excludeScope(attributeFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) != isCategoricalMatch); + resultType, 1, 3, 100) != isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"*2\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(attributeFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(attributeFieldName, valueFilter); + } else { + rule.excludeScope(attributeFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) == isCategoricalMatch); + resultType, 1, 3, 100) == isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"*1*\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(attributeFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(attributeFieldName, valueFilter); + } else { + rule.excludeScope(attributeFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) != isCategoricalMatch); + resultType, 1, 3, 100) != isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"p2\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(personFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(personFieldName, valueFilter); + } else { + rule.excludeScope(personFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) == isCategoricalMatch); + resultType, 1, 3, 100) == isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"par_1\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(partitionFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(partitionFieldName, valueFilter); + } else { + rule.excludeScope(partitionFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) == isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) == isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) == isCategoricalMatch); + resultType, 1, 3, 100) == isInclude); } - for (auto conditionType : {CRuleCondition::E_CategoricalMatch, - CRuleCondition::E_CategoricalComplement}) { + for (auto filterType : {CRuleScope::E_Include, CRuleScope::E_Exclude}) { std::string filterJson("[\"par_2\"]"); core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(conditionType); - condition.fieldName(partitionFieldName); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.addCondition(condition); + if (filterType == CRuleScope::E_Include) { + rule.includeScope(partitionFieldName, valueFilter); + } else { + rule.excludeScope(partitionFieldName, valueFilter); + } - bool isCategoricalMatch = CRuleCondition::E_CategoricalMatch == conditionType; + bool isInclude = filterType == CRuleScope::E_Include; model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 0, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 0, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 0, 1, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 0, 1, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 2, 100) != isCategoricalMatch); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + resultType, 1, 2, 100) != isInclude); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, - resultType, 1, 3, 100) != isCategoricalMatch); + resultType, 1, 3, 100) != isInclude); } } @@ -358,19 +341,19 @@ void CDetectionRuleTest::testApplyGivenNumericalActualCondition() { // Test rule with condition with operator LT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 5.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LT); + condition.value(5.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300) == false); } @@ -378,57 +361,57 @@ void CDetectionRuleTest::testApplyGivenNumericalActualCondition() { // Test rule with condition with operator LTE CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_LTE; - condition.condition().s_Threshold = 5.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LTE); + condition.value(5.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300) == false); } { // Test rule with condition with operator GT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_GT; - condition.condition().s_Threshold = 5.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_GT); + condition.value(5.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300)); } { // Test rule with condition with operator GT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_GTE; - condition.condition().s_Threshold = 5.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_GTE); + condition.value(5.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300)); } } @@ -469,19 +452,19 @@ void CDetectionRuleTest::testApplyGivenNumericalTypicalCondition() { // Test rule with condition with operator LT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalTypical); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 45.0; + condition.appliesTo(CRuleCondition::E_Typical); + condition.op(CRuleCondition::E_LT); + condition.value(45.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300) == false); } @@ -489,19 +472,19 @@ void CDetectionRuleTest::testApplyGivenNumericalTypicalCondition() { // Test rule with condition with operator GT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalTypical); - condition.condition().s_Op = CRuleCondition::E_GT; - condition.condition().s_Threshold = 45.0; + condition.appliesTo(CRuleCondition::E_Typical); + condition.op(CRuleCondition::E_GT); + condition.value(45.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300)); } } @@ -554,25 +537,25 @@ void CDetectionRuleTest::testApplyGivenNumericalDiffAbsCondition() { // Test rule with condition with operator LT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalDiffAbs); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 1.0; + condition.appliesTo(CRuleCondition::E_DiffFromTypical); + condition.op(CRuleCondition::E_LT); + condition.value(1.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 400)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 500) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 600) == false); } @@ -580,76 +563,29 @@ void CDetectionRuleTest::testApplyGivenNumericalDiffAbsCondition() { // Test rule with condition with operator GT CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalDiffAbs); - condition.condition().s_Op = CRuleCondition::E_GT; - condition.condition().s_Threshold = 1.0; + condition.appliesTo(CRuleCondition::E_DiffFromTypical); + condition.op(CRuleCondition::E_GT); + condition.value(1.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 300) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 400) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 500) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 600)); } } -void CDetectionRuleTest::testApplyGivenSingleSeriesModelAndConditionWithField() { - core_t::TTime bucketLength = 100; - core_t::TTime startTime = 100; - CSearchKey key; - SModelParams params(bucketLength); - CAnomalyDetectorModel::TFeatureInfluenceCalculatorCPtrPrVecVec influenceCalculators; - - TFeatureVec features; - features.push_back(model_t::E_IndividualMeanByPerson); - CAnomalyDetectorModel::TDataGathererPtr gathererPtr(new CDataGatherer( - model_t::E_Metric, model_t::E_None, params, EMPTY_STRING, EMPTY_STRING, - EMPTY_STRING, EMPTY_STRING, EMPTY_STRING, EMPTY_STRING, TStrVec(), - false, key, features, startTime, 0)); - - std::string person1("p1"); - bool addedPerson = false; - gathererPtr->addPerson(person1, m_ResourceMonitor, addedPerson); - - CMockModel model(params, gathererPtr, influenceCalculators); - CAnomalyDetectorModel::TDouble1Vec actual100(1, 4.99); - CAnomalyDetectorModel::TDouble1Vec actual200(1, 5.00); - CAnomalyDetectorModel::TDouble1Vec actual300(1, 5.01); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 100, actual100); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 200, actual200); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 300, actual300); - - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - std::string fieldName("unknownField"); - std::string fieldValue("unknownValue"); - condition.fieldName(fieldName); - condition.fieldValue(fieldValue); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 5.0; - CDetectionRule rule; - rule.addCondition(condition); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 200) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 300) == false); -} - void CDetectionRuleTest::testApplyGivenNoActualValueAvailable() { core_t::TTime bucketLength = 100; core_t::TTime startTime = 100; @@ -677,15 +613,15 @@ void CDetectionRuleTest::testApplyGivenNoActualValueAvailable() { model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 300, actual300); CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 5.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LT); + condition.value(5.0); CDetectionRule rule; rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 400) == false); } @@ -716,20 +652,25 @@ void CDetectionRuleTest::testApplyGivenDifferentSeriesAndIndividualModel() { model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 100, p1Actual); model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 1, 0, 100, p2Actual); - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.fieldName(personFieldName); - condition.fieldValue(person1); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 5.0; CDetectionRule rule; + + std::string filterJson("[\"p1\"]"); + core::CPatternSet valueFilter; + valueFilter.initFromJson(filterJson); + rule.includeScope(personFieldName, valueFilter); + + CRuleCondition condition; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LT); + condition.value(5.0); + rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 1, 0, 100) == false); } @@ -771,32 +712,36 @@ void CDetectionRuleTest::testApplyGivenDifferentSeriesAndPopulationModel() { model.mockAddBucketValue(model_t::E_PopulationMeanByPersonAndAttribute, 1, 2, 100, actual); model.mockAddBucketValue(model_t::E_PopulationMeanByPersonAndAttribute, 1, 3, 100, actual); - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.fieldName(attributeFieldName); - condition.fieldValue(attr12); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 5.0; CDetectionRule rule; + + std::string filterJson("[\"" + attr12 + "\"]"); + core::CPatternSet valueFilter; + valueFilter.initFromJson(filterJson); + rule.includeScope(attributeFieldName, valueFilter); + + CRuleCondition condition; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LT); + condition.value(5.0); rule.addCondition(condition); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, resultType, 0, 1, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, resultType, 1, 2, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_PopulationMeanByPersonAndAttribute, resultType, 1, 3, 100) == false); } -void CDetectionRuleTest::testApplyGivenMultipleConditionsWithOr() { +void CDetectionRuleTest::testApplyGivenMultipleConditions() { core_t::TTime bucketLength = 100; core_t::TTime startTime = 100; CSearchKey key; @@ -822,309 +767,81 @@ void CDetectionRuleTest::testApplyGivenMultipleConditionsWithOr() { { // None applies CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 9.0; + condition1.appliesTo(CRuleCondition::E_Actual); + condition1.op(CRuleCondition::E_LT); + condition1.value(9.0); CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 9.5; + condition2.appliesTo(CRuleCondition::E_Actual); + condition2.op(CRuleCondition::E_LT); + condition2.value(9.5); CDetectionRule rule; rule.addCondition(condition1); rule.addCondition(condition2); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); } { // First applies only CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 11.0; + condition1.appliesTo(CRuleCondition::E_Actual); + condition1.op(CRuleCondition::E_LT); + condition1.value(11.0); CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 9.5; + condition2.appliesTo(CRuleCondition::E_Actual); + condition2.op(CRuleCondition::E_LT); + condition2.value(9.5); CDetectionRule rule; rule.addCondition(condition1); rule.addCondition(condition2); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100)); - } - { - // Second applies only - CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 9.0; - CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 10.5; - CDetectionRule rule; - rule.addCondition(condition1); - rule.addCondition(condition2); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100)); - } - { - // Both apply - CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 12.0; - CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 10.5; - CDetectionRule rule; - rule.addCondition(condition1); - rule.addCondition(condition2); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100)); - } -} - -void CDetectionRuleTest::testApplyGivenMultipleConditionsWithAnd() { - core_t::TTime bucketLength = 100; - core_t::TTime startTime = 100; - CSearchKey key; - SModelParams params(bucketLength); - CAnomalyDetectorModel::TFeatureInfluenceCalculatorCPtrPrVecVec influenceCalculators; - - TFeatureVec features; - features.push_back(model_t::E_IndividualMeanByPerson); - std::string personFieldName("series"); - CAnomalyDetectorModel::TDataGathererPtr gathererPtr(new CDataGatherer( - model_t::E_Metric, model_t::E_None, params, EMPTY_STRING, EMPTY_STRING, - EMPTY_STRING, personFieldName, EMPTY_STRING, EMPTY_STRING, TStrVec(), - false, key, features, startTime, 0)); - - std::string person1("p1"); - bool addedPerson = false; - gathererPtr->addPerson(person1, m_ResourceMonitor, addedPerson); - - CMockModel model(params, gathererPtr, influenceCalculators); - CAnomalyDetectorModel::TDouble1Vec p1Actual(1, 10.0); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 100, p1Actual); - - { - // None applies - CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 9.0; - CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 9.5; - CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); - rule.addCondition(condition1); - rule.addCondition(condition2); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100) == false); - } - { - // First applies only - CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 11.0; - CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 9.5; - CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); - rule.addCondition(condition1); - rule.addCondition(condition2); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); } { // Second applies only CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 9.0; + condition1.appliesTo(CRuleCondition::E_Actual); + condition1.op(CRuleCondition::E_LT); + condition1.value(9.0); CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 10.5; + condition2.appliesTo(CRuleCondition::E_Actual); + condition2.op(CRuleCondition::E_LT); + condition2.value(10.5); CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); rule.addCondition(condition1); rule.addCondition(condition2); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); } { // Both apply CRuleCondition condition1; - condition1.type(CRuleCondition::E_NumericalActual); - condition1.fieldName(personFieldName); - condition1.fieldValue(person1); - condition1.condition().s_Op = CRuleCondition::E_LT; - condition1.condition().s_Threshold = 12.0; + condition1.appliesTo(CRuleCondition::E_Actual); + condition1.op(CRuleCondition::E_LT); + condition1.value(12.0); CRuleCondition condition2; - condition2.type(CRuleCondition::E_NumericalActual); - condition2.fieldName(personFieldName); - condition2.fieldValue(person1); - condition2.condition().s_Op = CRuleCondition::E_LT; - condition2.condition().s_Threshold = 10.5; + condition2.appliesTo(CRuleCondition::E_Actual); + condition2.op(CRuleCondition::E_LT); + condition2.value(10.5); CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); rule.addCondition(condition1); rule.addCondition(condition2); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); } } -void CDetectionRuleTest::testApplyGivenTargetFieldIsPartitionAndIndividualModel() { - core_t::TTime bucketLength = 100; - core_t::TTime startTime = 100; - CSearchKey key; - SModelParams params(bucketLength); - CAnomalyDetectorModel::TFeatureInfluenceCalculatorCPtrPrVecVec influenceCalculators; - - TFeatureVec features; - features.push_back(model_t::E_IndividualMeanByPerson); - std::string partitionFieldName("partition"); - std::string partitionFieldValue("partition_1"); - std::string personFieldName("series"); - CAnomalyDetectorModel::TDataGathererPtr gathererPtr(new CDataGatherer( - model_t::E_Metric, model_t::E_None, params, EMPTY_STRING, - partitionFieldName, partitionFieldValue, personFieldName, EMPTY_STRING, - EMPTY_STRING, TStrVec(), false, key, features, startTime, 0)); - - std::string person1("p1"); - bool addedPerson = false; - gathererPtr->addPerson(person1, m_ResourceMonitor, addedPerson); - std::string person2("p2"); - gathererPtr->addPerson(person2, m_ResourceMonitor, addedPerson); - - CMockModel model(params, gathererPtr, influenceCalculators); - CAnomalyDetectorModel::TDouble1Vec p1Actual(1, 10.0); - CAnomalyDetectorModel::TDouble1Vec p2Actual(1, 20.0); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 0, 0, 100, p1Actual); - model.mockAddBucketValue(model_t::E_IndividualMeanByPerson, 1, 0, 100, p2Actual); - - { - // No targetFieldValue - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.fieldName(personFieldName); - condition.fieldValue(person1); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 15.0; - CDetectionRule rule; - rule.targetFieldName(partitionFieldName); - rule.addCondition(condition); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 1, 0, 100)); - } - { - // Matching targetFieldValue - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.fieldName(personFieldName); - condition.fieldValue(person1); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 15.0; - CDetectionRule rule; - rule.targetFieldName(partitionFieldName); - rule.targetFieldValue(partitionFieldValue); - rule.addCondition(condition); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 1, 0, 100)); - } - { - // Non-matching targetFieldValue - std::string partitionValue2("partition_2"); - CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.fieldName(personFieldName); - condition.fieldValue(person1); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 15.0; - CDetectionRule rule; - rule.targetFieldName(partitionFieldName); - rule.targetFieldValue(partitionValue2); - rule.addCondition(condition); - - model_t::CResultType resultType(model_t::CResultType::E_Final); - - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, - resultType, 1, 0, 100) == false); - } -} - void CDetectionRuleTest::testApplyGivenTimeCondition() { core_t::TTime bucketLength = 100; core_t::TTime startTime = 100; @@ -1143,28 +860,27 @@ void CDetectionRuleTest::testApplyGivenTimeCondition() { CMockModel model(params, gathererPtr, influenceCalculators); CRuleCondition conditionGte; - conditionGte.type(CRuleCondition::E_Time); - conditionGte.condition().s_Op = CRuleCondition::E_GTE; - conditionGte.condition().s_Threshold = 100; + conditionGte.appliesTo(CRuleCondition::E_Time); + conditionGte.op(CRuleCondition::E_GTE); + conditionGte.value(100); CRuleCondition conditionLt; - conditionLt.type(CRuleCondition::E_Time); - conditionLt.condition().s_Op = CRuleCondition::E_LT; - conditionLt.condition().s_Threshold = 200; + conditionLt.appliesTo(CRuleCondition::E_Time); + conditionLt.op(CRuleCondition::E_LT); + conditionLt.value(200); CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); rule.addCondition(conditionGte); rule.addCondition(conditionLt); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 99) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 150)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 200) == false); } @@ -1186,30 +902,29 @@ void CDetectionRuleTest::testRuleActions() { CMockModel model(params, gathererPtr, influenceCalculators); CRuleCondition conditionGte; - conditionGte.type(CRuleCondition::E_Time); - conditionGte.condition().s_Op = CRuleCondition::E_GTE; - conditionGte.condition().s_Threshold = 100; + conditionGte.appliesTo(CRuleCondition::E_Time); + conditionGte.op(CRuleCondition::E_GTE); + conditionGte.value(100); CDetectionRule rule; - rule.conditionsConnective(CDetectionRule::E_And); rule.addCondition(conditionGte); model_t::CResultType resultType(model_t::CResultType::E_Final); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipSampling, model, model_t::E_IndividualMeanByPerson, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipModelUpdate, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - rule.action(CDetectionRule::E_SkipSampling); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, model_t::E_IndividualMeanByPerson, + rule.action(CDetectionRule::E_SkipModelUpdate); + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100) == false); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipSampling, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipModelUpdate, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); rule.action(static_cast(3)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_FilterResults, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipResult, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); - CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipSampling, model, + CPPUNIT_ASSERT(rule.apply(CDetectionRule::E_SkipModelUpdate, model, model_t::E_IndividualMeanByPerson, resultType, 0, 0, 100)); } diff --git a/lib/model/unittest/CDetectionRuleTest.h b/lib/model/unittest/CDetectionRuleTest.h index e505993437..572859bc79 100644 --- a/lib/model/unittest/CDetectionRuleTest.h +++ b/lib/model/unittest/CDetectionRuleTest.h @@ -12,17 +12,14 @@ class CDetectionRuleTest : public CppUnit::TestFixture { public: - void testApplyGivenCategoricalCondition(); + void testApplyGivenScope(); void testApplyGivenNumericalActualCondition(); void testApplyGivenNumericalTypicalCondition(); void testApplyGivenNumericalDiffAbsCondition(); - void testApplyGivenSingleSeriesModelAndConditionWithField(); void testApplyGivenNoActualValueAvailable(); void testApplyGivenDifferentSeriesAndIndividualModel(); void testApplyGivenDifferentSeriesAndPopulationModel(); - void testApplyGivenMultipleConditionsWithOr(); - void testApplyGivenMultipleConditionsWithAnd(); - void testApplyGivenTargetFieldIsPartitionAndIndividualModel(); + void testApplyGivenMultipleConditions(); void testApplyGivenTimeCondition(); void testRuleActions(); diff --git a/lib/model/unittest/CEventRateModelTest.cc b/lib/model/unittest/CEventRateModelTest.cc index f4dae758a4..d247418e86 100644 --- a/lib/model/unittest/CEventRateModelTest.cc +++ b/lib/model/unittest/CEventRateModelTest.cc @@ -2467,9 +2467,9 @@ void CEventRateModelTest::testSummaryCountZeroRecordsAreIgnored() { void CEventRateModelTest::testComputeProbabilityGivenDetectionRule() { CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 100.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_LT); + condition.value(100.0); CDetectionRule rule; rule.addCondition(condition); @@ -2722,11 +2722,11 @@ void CEventRateModelTest::testIgnoreSamplingGivenDetectionRules() { // Create a rule to filter buckets where the count > 100 CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_GT; - condition.condition().s_Threshold = 100.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_GT); + condition.value(100.0); CDetectionRule rule; - rule.action(CDetectionRule::E_SkipSampling); + rule.action(CDetectionRule::E_SkipModelUpdate); rule.addCondition(condition); std::size_t bucketLength(100); diff --git a/lib/model/unittest/CEventRatePopulationModelTest.cc b/lib/model/unittest/CEventRatePopulationModelTest.cc index a1a436281d..946b567321 100644 --- a/lib/model/unittest/CEventRatePopulationModelTest.cc +++ b/lib/model/unittest/CEventRatePopulationModelTest.cc @@ -30,7 +30,6 @@ #include #include #include -#include #include @@ -1343,12 +1342,9 @@ void CEventRatePopulationModelTest::testIgnoreSamplingGivenDetectionRules() { core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(CRuleCondition::E_CategoricalMatch); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.action(CDetectionRule::E_SkipSampling); - rule.addCondition(condition); + rule.action(CDetectionRule::E_SkipModelUpdate); + rule.includeScope("", valueFilter); SModelParams paramsNoRules(bucketLength); auto interimBucketCorrector = std::make_shared(bucketLength); diff --git a/lib/model/unittest/CMetricModelTest.cc b/lib/model/unittest/CMetricModelTest.cc index d8e5f5c703..5ecb3fbcd4 100644 --- a/lib/model/unittest/CMetricModelTest.cc +++ b/lib/model/unittest/CMetricModelTest.cc @@ -2401,11 +2401,11 @@ void CMetricModelTest::testIgnoreSamplingGivenDetectionRules() { // Create a rule to filter buckets where the actual value > 100 CRuleCondition condition; - condition.type(CRuleCondition::E_NumericalActual); - condition.condition().s_Op = CRuleCondition::E_GT; - condition.condition().s_Threshold = 100.0; + condition.appliesTo(CRuleCondition::E_Actual); + condition.op(CRuleCondition::E_GT); + condition.value(100.0); CDetectionRule rule; - rule.action(CDetectionRule::E_SkipSampling); + rule.action(CDetectionRule::E_SkipModelUpdate); rule.addCondition(condition); std::size_t bucketLength(300); diff --git a/lib/model/unittest/CMetricPopulationModelTest.cc b/lib/model/unittest/CMetricPopulationModelTest.cc index 88a59ed932..80274c0060 100644 --- a/lib/model/unittest/CMetricPopulationModelTest.cc +++ b/lib/model/unittest/CMetricPopulationModelTest.cc @@ -33,7 +33,6 @@ #include #include #include -#include #include @@ -1429,12 +1428,9 @@ void CMetricPopulationModelTest::testIgnoreSamplingGivenDetectionRules() { core::CPatternSet valueFilter; valueFilter.initFromJson(filterJson); - CRuleCondition condition; - condition.type(CRuleCondition::E_CategoricalMatch); - condition.valueFilter(valueFilter); CDetectionRule rule; - rule.action(CDetectionRule::E_SkipSampling); - rule.addCondition(condition); + rule.action(CDetectionRule::E_SkipModelUpdate); + rule.includeScope("", valueFilter); model_t::TFeatureVec features{model_t::E_PopulationMeanByPersonAndAttribute}; diff --git a/lib/model/unittest/CRuleConditionTest.cc b/lib/model/unittest/CRuleConditionTest.cc index 0489763142..8b3a9b98b8 100644 --- a/lib/model/unittest/CRuleConditionTest.cc +++ b/lib/model/unittest/CRuleConditionTest.cc @@ -57,37 +57,31 @@ void CRuleConditionTest::testTimeContition() { { CRuleCondition condition; - condition.type(CRuleCondition::E_Time); - condition.condition().s_Op = CRuleCondition::E_GTE; - condition.condition().s_Threshold = 500; - - CPPUNIT_ASSERT(condition.isNumerical()); - CPPUNIT_ASSERT(condition.isCategorical() == false); + condition.appliesTo(CRuleCondition::E_Time); + condition.op(CRuleCondition::E_GTE); + condition.value(500); model_t::CResultType resultType(model_t::CResultType::E_Final); CPPUNIT_ASSERT(condition.test(model, model_t::E_IndividualCountByBucketAndPerson, - resultType, false, std::size_t(0), - std::size_t(1), core_t::TTime(450)) == false); + resultType, std::size_t(0), std::size_t(1), + core_t::TTime(450)) == false); CPPUNIT_ASSERT(condition.test(model, model_t::E_IndividualCountByBucketAndPerson, - resultType, false, std::size_t(0), + resultType, std::size_t(0), std::size_t(1), core_t::TTime(550))); } { CRuleCondition condition; - condition.type(CRuleCondition::E_Time); - condition.condition().s_Op = CRuleCondition::E_LT; - condition.condition().s_Threshold = 600; - - CPPUNIT_ASSERT(condition.isNumerical()); - CPPUNIT_ASSERT(condition.isCategorical() == false); + condition.appliesTo(CRuleCondition::E_Time); + condition.op(CRuleCondition::E_LT); + condition.value(600); model_t::CResultType resultType(model_t::CResultType::E_Final); CPPUNIT_ASSERT(condition.test(model, model_t::E_IndividualCountByBucketAndPerson, - resultType, false, std::size_t(0), - std::size_t(1), core_t::TTime(600)) == false); + resultType, std::size_t(0), std::size_t(1), + core_t::TTime(600)) == false); CPPUNIT_ASSERT(condition.test(model, model_t::E_IndividualCountByBucketAndPerson, - resultType, false, std::size_t(0), + resultType, std::size_t(0), std::size_t(1), core_t::TTime(599))); } }