evaluate (almanac) {
    const ruleResult = new RuleResult(
      this.conditions,
      this.ruleEvent,
      this.priority,
      this.name
    )

    /**
     * Evaluates the rule conditions
     * @param  {Condition} condition - condition to evaluate
     * @return {Promise(true|false)} - resolves with the result of the condition evaluation
     */
    const evaluateCondition = (condition) => {
      if (condition.isConditionReference()) {
        return realize(condition)
      } else if (condition.isBooleanOperator()) {
        const subConditions = condition[condition.operator]
        let comparisonPromise
        if (condition.operator === 'all') {
          comparisonPromise = all(subConditions)
        } else if (condition.operator === 'any') {
          comparisonPromise = any(subConditions)
        } else {
          comparisonPromise = not(subConditions)
        }
        // for booleans, rule passing is determined by the all/any/not result
        return comparisonPromise.then((comparisonValue) => {
          const passes = comparisonValue === true
          condition.result = passes
          return passes
        })
      } else {
        return condition
          .evaluate(almanac, this.engine.operators)
          .then((evaluationResult) => {
            const passes = evaluationResult.result
            condition.factResult = evaluationResult.leftHandSideValue
            condition.valueResult = evaluationResult.rightHandSideValue
            condition.result = passes
            return passes
          })
      }
    }

    /**
     * Evalutes an array of conditions, using an 'every' or 'some' array operation
     * @param  {Condition[]} conditions
     * @param  {string(every|some)} array method to call for determining result
     * @return {Promise(boolean)} whether conditions evaluated truthy or falsey based on condition evaluation + method
     */
    const evaluateConditions = (conditions, method) => {
      if (!Array.isArray(conditions)) conditions = [conditions]

      return Promise.all(
        conditions.map((condition) => evaluateCondition(condition))
      ).then((conditionResults) => {
        debug('rule::evaluateConditions', { results: conditionResults })
        return method.call(conditionResults, (result) => result === true)
      })
    }

    /**
     * Evaluates a set of conditions based on an 'all', 'any', or 'not' operator.
     *   First, orders the top level conditions based on priority
     *   Iterates over each priority set, evaluating each condition
     *   If any condition results in the rule to be guaranteed truthy or falsey,
     *   it will short-circuit and not bother evaluating any additional rules
     * @param  {Condition[]} conditions - conditions to be evaluated
     * @param  {string('all'|'any'|'not')} operator
     * @return {Promise(boolean)} rule evaluation result
     */
    const prioritizeAndRun = (conditions, operator) => {
      if (conditions.length === 0) {
        return Promise.resolve(true)
      }
      if (conditions.length === 1) {
        // no prioritizing is necessary, just evaluate the single condition
        // 'all' and 'any' will give the same results with a single condition so no method is necessary
        // this also covers the 'not' case which should only ever have a single condition
        return evaluateCondition(conditions[0])
      }
      const orderedSets = this.prioritizeConditions(conditions)
      let cursor = Promise.resolve(operator === 'all')
      // use for() loop over Array.forEach to support IE8 without polyfill
      for (let i = 0; i < orderedSets.length; i++) {
        const set = orderedSets[i]
        cursor = cursor.then((setResult) => {
          // rely on the short-circuiting behavior of || and && to avoid evaluating subsequent conditions
          return operator === 'any'
            ? (setResult || evaluateConditions(set, Array.prototype.some))
            : (setResult && evaluateConditions(set, Array.prototype.every))
        })
      }
      return cursor
    }

    /**
     * Runs an 'any' boolean operator on an array of conditions
     * @param  {Condition[]} conditions to be evaluated
     * @return {Promise(boolean)} condition evaluation result
     */
    const any = (conditions) => {
      return prioritizeAndRun(conditions, 'any')
    }

    /**
     * Runs an 'all' boolean operator on an array of conditions
     * @param  {Condition[]} conditions to be evaluated
     * @return {Promise(boolean)} condition evaluation result
     */
    const all = (conditions) => {
      return prioritizeAndRun(conditions, 'all')
    }

    /**
     * Runs a 'not' boolean operator on a single condition
     * @param  {Condition} condition to be evaluated
     * @return {Promise(boolean)} condition evaluation result
     */
    const not = (condition) => {
      return prioritizeAndRun([condition], 'not').then((result) => !result)
    }

    /**
     * Dereferences the condition reference and then evaluates it.
     * @param {Condition} conditionReference
     * @returns {Promise(boolean)} condition evaluation result
     */
    const realize = (conditionReference) => {
      const condition = this.engine.conditions.get(conditionReference.condition)
      if (!condition) {
        if (this.engine.allowUndefinedConditions) {
          // undefined conditions always fail
          conditionReference.result = false
          return Promise.resolve(false)
        } else {
          throw new Error(
            `No condition ${conditionReference.condition} exists`
          )
        }
      } else {
        // project the referenced condition onto reference object and evaluate it.
        delete conditionReference.condition
        Object.assign(conditionReference, deepClone(condition))
        return evaluateCondition(conditionReference)
      }
    }

    /**
     * Emits based on rule evaluation result, and decorates ruleResult with 'result' property
     * @param {RuleResult} ruleResult
     */
    const processResult = (result) => {
      ruleResult.setResult(result)
      let processEvent = Promise.resolve()
      if (this.engine.replaceFactsInEventParams) {
        processEvent = ruleResult.resolveEventParams(almanac)
      }
      const event = result ? 'success' : 'failure'
      return processEvent.then(() => this.emitAsync(event, ruleResult.event, almanac, ruleResult)).then(
        () => ruleResult
      )
    }

    if (ruleResult.conditions.any) {
      return any(ruleResult.conditions.any).then((result) =>
        processResult(result)
      )
    } else if (ruleResult.conditions.all) {
      return all(ruleResult.conditions.all).then((result) =>
        processResult(result)
      )
    } else if (ruleResult.conditions.not) {
      return not(ruleResult.conditions.not).then((result) =>
        processResult(result)
      )
    } else {
      return realize(
        ruleResult.conditions
      ).then((result) => processResult(result))
    }
  }
Evaluates the rule, starting with the root boolean operator and recursing down
All evaluation is done within the context of an almanac
@return {Promise(RuleResult)} rule evaluation result