<commit_before>require_relative 'binary_operator_operator'
require_relative '../date_adjuster'

module ConceptQL
  module Operators
    # Base class for all temporal operators
    #
    # Subclasses must implement the where_clause method which should probably return
    # a Sequel expression to use for filtering.
    class TemporalOperator < BinaryOperatorOperator
      reset_categories
      category "Filter by Comparing"
      default_query_columns

      option :within, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
      option :occurrences, type: :integer, desc: "Number of occurrences that must precede the event of interest, e.g. if you'd like the 4th event in a set of events, set occurrences to 3"

      validate_option DateAdjuster::VALID_INPUT, :within, :at_least
      validate_option /\A\d+\z/, :occurrences

      class << self
        def allows_at_least_option
          option :at_least, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
          define_method(:"at_least_check?") { true }
        end
      end

      def self.within_skip(type)
        define_method(:"within_check_#{type}?"){false}
      end

      def query(db)
        ds = db.from(left_stream(db))
               .join(right_stream(db), person_id: :person_id)
               .where(where_clause)
               .select_all(:l)

        ds = add_option_conditions(ds)
        ds.from_self
      end

      def add_option_conditions(ds)
        if within = options[:within]
          ds = add_within_condition(ds, within)
        end

        if (at_least = options[:at_least]) && at_least_check?
          ds = add_within_condition(ds, at_least, :exclude)
        end

        if occurrences = options[:occurrences]
          ds = add_occurrences_condition(ds, occurrences)
        end

        ds
      end

      def add_within_condition(ds, within, meth=:where)
        within = DateAdjuster.new(self, within)
        after = within.adjust(Sequel[:r][:start_date], true)
        before = within.adjust(Sequel[:r][:end_date])
        within_col = Sequel.expr(within_column)
        ds = ds.send(meth){within_col >= after} if within_check_after?
        ds = ds.send(meth){within_col <= before} if within_check_before?
        ds.distinct
      end

      def add_occurrences_condition(ds, occurrences)
        occurrences_col = occurrences_column
        ds.distinct.from_self
          .select_append{row_number.function.over(:partition => :person_id, :order => occurrences_col).as(:occurrence)}
          .from_self
          .select(*dm.columns)
          .where{occurrence > occurrences.to_i}
      end

      def within_column
        Sequel[:l][:start_date]
      end

      def occurrences_column
        :start_date
      end

      def within_check_after?
        true
      end

      def within_check_before?
        true
      end

      def at_least_check?
        false
      end

      def inclusive?
        options[:inclusive]
      end

      def left_stream(db)
        Sequel.expr(left.evaluate(db).from_self).as(:l)<commit_msg>Refactor TemporalOperator's within_check<commit_after>require_relative 'binary_operator_operator'
require_relative '../date_adjuster'

module ConceptQL
  module Operators
    # Base class for all temporal operators
    #
    # Subclasses must implement the where_clause method which should probably return
    # a Sequel expression to use for filtering.
    class TemporalOperator < BinaryOperatorOperator
      reset_categories
      category "Filter by Comparing"
      default_query_columns

      option :within, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
      option :occurrences, type: :integer, desc: "Number of occurrences that must precede the event of interest, e.g. if you'd like the 4th event in a set of events, set occurrences to 3"

      validate_option DateAdjuster::VALID_INPUT, :within, :at_least
      validate_option /\A\d+\z/, :occurrences

      class << self
        def allows_at_least_option
          option :at_least, type: :string, instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years". Negative numbers change dates prior to the existing date. Example: -30d = 30 days before the existing date.'
          define_method(:"at_least_check?") { true }
        end
      end

      def self.within_skip(type)
        define_method(:"within_check_#{type}?"){false}
      end

      def query(db)
        ds = db.from(left_stream(db))
               .join(right_stream(db), person_id: :person_id)
               .where(where_clause)
               .select_all(:l)

        ds = add_option_conditions(ds)
        ds.from_self
      end

      def add_option_conditions(ds)
        if within = options[:within]
          ds = add_within_condition(ds, within)
        end

        if (at_least = options[:at_least]) && at_least_check?
          ds = add_within_condition(ds, at_least, :exclude)
        end

        if occurrences = options[:occurrences]
          ds = add_occurrences_condition(ds, occurrences)
        end

        ds
      end

      def add_within_condition(ds, within, meth=:where)
        within = DateAdjuster.new(self, within)
        after = within.adjust(within_after_column, true)
        before = within.adjust(within_before_column)
        within_col_start = Sequel.expr(within_column_start)
        within_col_end = Sequel.expr(within_column_end)
        ds = ds.send(meth){ within_col_start >= after } if within_check_after?
        ds = ds.send(meth){ within_col_end <= before } if within_check_before?
        ds.distinct
      end

      def add_occurrences_condition(ds, occurrences)
        occurrences_col = occurrences_column
        ds.distinct.from_self
          .select_append{row_number.function.over(:partition => :person_id, :order => occurrences_col).as(:occurrence)}
          .from_self
          .select(*dm.columns)
          .where{occurrence > occurrences.to_i}
      end

      def within_column
        Sequel[:l][:start_date]
      end

      def within_column_start
        within_column
      end

      def within_column_end
        within_column
      end

      def within_after_column
        Sequel[:r][:start_date]
      end

      def within_before_column
        Sequel[:r][:end_date]
      end

      def occurrences_column
        :start_date
      end

      def within_check_after?
        true
      end

      def within_check_before?
        true
      end

      def at_least_check?
        false
      end

      def inclusive?
        options[:inclusive]
      end

      def left_stream(db)
        Sequel.expr(left.evaluate(db).from_self).as(:l)
{'fraction_non_alphanumeric': 0.08636807590344635, 'fraction_numerical': 0.0027905678805636948, 'mean_word_length': 2.5502724120851905, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7239466', 'n_tokens_mistral': 2223, 'n_tokens_neox': 2018, 'n_words': 565}