module Abstractor::Abstractable::InstanceMethods

Public Instance Methods

abstract(options = {}) click to toggle source

The method for generating abstractions from the abstractable entity.

The generation of abstactions is based on the setup of Abstactor::AbstractorAbstactionSchema, Abstractor::AbstractorSubject, Abstractor::AbstractorSubjectGroup and Abstractor::AbstractorAbstractionSource associated to the abstractable entity.

Namespacing allows for different sets data points to be associated to the same abstractable entity.

Namespacing is achieved by setting the Abstractor::AbstractorSubject#namespace_type and Abstractor::AbstractorSubject#namespace_id attributes.

Passing a namespace to this method will restrict the generation of abstractions to the given namespace. Otherwise, all configured abstractions associated to the abstractable entity will be generated.

A practical example of the use of a namespace would be two different clincal departments wanting to chart abstract two distinct sets of datapoints for progress notes extracted from an electronic medical record system. @param [Hash] options the options to filter the generation of abstractions to a namespace. @option options [String] :namespace_type The type parameter of the namespace. @option options [Integer] :namespace_id The instance parameter of the namespace. @option options [List of integers] :abstractor_abstraction_schema_ids List of abstractor_abstraction_schema_ids to limit abstraction. @return [void]

# File lib/abstractor/abstractable.rb, line 89
def abstract(options = {})
  options = { namespace_type: nil, namespace_id: nil, abstractor_abstraction_schema_ids: [] }.merge(options)
  sentinental_groups = []
  self.class.abstractor_subjects(options).each do |abstractor_subject|
    abstractor_subject.abstract(self)
    sentinental_groups << abstractor_subject.abstractor_subject_group if abstractor_subject.abstractor_subject_group && abstractor_subject.abstractor_subject_group.has_subtype?(Abstractor::Enum::ABSTRACTOR_GROUP_SENTINENTAL_SUBTYPE)
  end
  sentinental_groups.uniq.map{|sentinental_group| regroup_sentinental_suggestions(sentinental_group, options)}
end
abstractor_abstraction_groups_by_namespace(options = {}) click to toggle source

Returns all abstraction groups for the abstractable entity by a namespace.

@param [Hash] options the options to filter the list of abstraction groups to a namespace. @option options [String] :namespace_type The type parameter of the namespace. @option options [Integer] :namespace_id The instance parameter of the namespace. @return [ActiveRecord::Relation] List of [Abstractor::AbstractorAbstractionGroup].

# File lib/abstractor/abstractable.rb, line 57
def abstractor_abstraction_groups_by_namespace(options = {})
  options = { namespace_type: nil, namespace_id: nil }.merge(options)
  if options[:namespace_type] || options[:namespace_id]
    groups = abstractor_abstraction_groups.find(abstractor_abstractions_by_namespace(options).joins(:abstractor_abstraction_group).includes(:abstractor_abstraction_group).map{|s| s.abstractor_abstraction_group.id})
  else
    groups = abstractor_abstraction_groups.not_deleted
  end
  if options[:abstractor_subject_group_id]
    groups.select{|g| g.abstractor_subject_group_id == options[:abstractor_subject_group_id]}
  else
    groups
  end
end
abstractor_abstractions_by_abstraction_schemas(options = {}) click to toggle source

Returns all abstractions for the abstractable entity by abstraction options.

@param [Hash] options the options to filter the list of abstractions to a namespace. @option options [Array] :abstractor_abstraction_schema_ids List of [Abstractor::AbstractorAbstractionSchema] ids @option options [ActiveRecord::Relation] List of [Abstractor::AbstractorAbstraction]. @return [ActiveRecord::Relation] List of [Abstractor::AbstractorAbstraction].

# File lib/abstractor/abstractable.rb, line 41
def abstractor_abstractions_by_abstraction_schemas(options = {})
  options = { abstractor_abstraction_schema_ids: [], abstractor_abstractions: abstractor_abstractions.not_deleted }.merge(options)
  if options[:abstractor_abstraction_schema_ids].any?
    options[:abstractor_abstractions].joins(:abstractor_subject).where(abstractor_subjects: { abstractor_abstraction_schema_id: options[:abstractor_abstraction_schema_ids]})
  else
    options[:abstractor_abstractions]
  end
end
abstractor_abstractions_by_abstractor_abstraction_status(abstractor_abstraction_status, options = {}) click to toggle source

Returns all abstraction for the abstractable entity by abstractor_abstraction_status:

  • ‘needs_review’: Filter abstractions without a determined value (value, unknown or not_applicable).

  • ‘reviewed’: Filter abstractions having a determined value (value, unknown or not_applicable).

@param [String] abstractor_abstraction_status Filter abstractions that need review or are reviews. @param [Hash] options the options to filter abstractions to a namespace. @option options [String] :namespace_type the type parameter of the namespace. @option options [Integer] :namespace_id the instance parameter of the namespace. @return [ActiveRecord::Relation] list of [Abstractor::AbstractorAbstraction].

# File lib/abstractor/abstractable.rb, line 162
def abstractor_abstractions_by_abstractor_abstraction_status(abstractor_abstraction_status, options = {})
  options = { namespace_type: nil, namespace_id: nil }.merge(options)
  case abstractor_abstraction_status
  when Abstractor::Enum::ABSTRACTION_STATUS_NEEDS_REVIEW
    abstractor_abstractions_by_namespace(options).select { |abstractor_abstraction| abstractor_abstraction.value.blank? && abstractor_abstraction.unknown.blank? && abstractor_abstraction.not_applicable.blank? }
  when Abstractor::Enum::ABSTRACTION_STATUS_REVIEWED
    abstractor_abstractions_by_namespace(options).select { |abstractor_abstraction| !abstractor_abstraction.value.blank? || !abstractor_abstraction.unknown.blank? || !abstractor_abstraction.not_applicable.blank? }
  end
end
abstractor_abstractions_by_namespace(options = {}) click to toggle source

Returns all abstractions for the abstractable entity by a namespace.

@param [Hash] options the options to filter the list of abstractions to a namespace. @option options [String] :namespace_type The type parameter of the namespace. @option options [Integer] :namespace_id The instance parameter of the namespace. @return [ActiveRecord::Relation] List of [Abstractor::AbstractorAbstraction].

# File lib/abstractor/abstractable.rb, line 25
def abstractor_abstractions_by_namespace(options = {})
  options = { namespace_type: nil, namespace_id: nil }.merge(options)
  abstractions = abstractor_abstractions.not_deleted
  if options[:namespace_type] || options[:namespace_id]
    abstractions = abstractions.where(abstractor_subject_id: self.class.abstractor_subjects(options).map(&:id))
  end
  abstractions
end
abstractor_subject_group_complete?(abstractor_subject_group_id, options = {}) click to toggle source

Determines if provided abstractor_subject_group reached number of abstractor_abstraction_groups defined by abstractor_subject_group cardinality

@param [Integer] abstractor_subject_group_id the id of abstractor_subject_group of interest. @option options [String] :namespace_type the type parameter of the namespace. @option options [Integer] :namespace_id the instance parameter of the namespace. @return [boolean]

# File lib/abstractor/abstractable.rb, line 140
def abstractor_subject_group_complete?(abstractor_subject_group_id, options = {})
  abstractor_subject_group = Abstractor::AbstractorSubjectGroup.find(abstractor_subject_group_id)
  if abstractor_subject_group.cardinality.blank?
    false
  else
    options = { namespace_type: nil, namespace_id: nil, abstractor_subject_group_id: abstractor_subject_group_id }.merge(options)
    abstractor_abstraction_groups = abstractor_abstraction_groups_by_namespace(options)
    abstractor_abstraction_groups.length == abstractor_subject_group.cardinality
  end
end
detect_abstractor_abstraction(abstractor_subject) click to toggle source
# File lib/abstractor/abstractable.rb, line 99
def detect_abstractor_abstraction(abstractor_subject)
  abstractor_abstractions(true).not_deleted.detect { |abstractor_abstraction| abstractor_abstraction.abstractor_subject_id == abstractor_subject.id }
end
detect_abstractor_abstraction_group(abstractor_subject_group, options) click to toggle source
# File lib/abstractor/abstractable.rb, line 118
def detect_abstractor_abstraction_group(abstractor_subject_group, options)
  abstractor_abstraction_groups(true).
    select { |abstractor_abstraction_group| abstractor_abstraction_group.abstractor_subject_group_id ==  abstractor_subject_group.id }.
    select { |abstractor_abstraction_group| abstractor_abstraction_group.abstractor_abstractions.joins(:abstractor_subject).where(abstractor_subjects: { namespace_type: options[:namespace_type], namespace_id: options[:namespace_id]}).any?}.
    first
end
find_or_create_abstractor_abstraction(abstractor_abstraction_schema, abstractor_subject) click to toggle source
# File lib/abstractor/abstractable.rb, line 103
def find_or_create_abstractor_abstraction(abstractor_abstraction_schema, abstractor_subject)
  options = { namespace_type: abstractor_subject.namespace_type, namespace_id: abstractor_subject.namespace_id }
  if abstractor_abstraction = detect_abstractor_abstraction(abstractor_subject)
  else
    abstractor_abstraction = Abstractor::AbstractorAbstraction.create!(abstractor_subject: abstractor_subject, about: self)

    if abstractor_subject.groupable?
      abstractor_abstraction_group = find_or_initialize_abstractor_abstraction_group(abstractor_subject.abstractor_subject_group, options)
      abstractor_abstraction_group.abstractor_abstractions << abstractor_abstraction
      abstractor_abstraction_group.save!
    end
  end
  abstractor_abstraction
end
find_or_initialize_abstractor_abstraction_group(abstractor_subject_group, options) click to toggle source
# File lib/abstractor/abstractable.rb, line 125
def find_or_initialize_abstractor_abstraction_group(abstractor_subject_group, options)
  if abstractor_abstraction_group = detect_abstractor_abstraction_group(abstractor_subject_group, options)
  else
    abstractor_abstraction_group = Abstractor::AbstractorAbstractionGroup.new(abstractor_subject_group: abstractor_subject_group, about: self, system_generated: true)
  end
  abstractor_abstraction_group
end
regroup_sentinental_suggestions(sentinental_group, options) click to toggle source

Regroups suggestions for subjects grouped marked with ‘sentinental’ subtype. Does not affect abstraction groups with curated values. Creates an abstraction group for each combination of suggestions that came from the same sentence. Creates groups only if there are enough abstractions.

@param [ActiveRecord::Relation] sentinental_group sentinental group to process @return [void]

# File lib/abstractor/abstractable.rb, line 223
def regroup_sentinental_suggestions(sentinental_group, options)
  sentinental_group_abstractor_subjects           = sentinental_group.abstractor_subjects.not_deleted
  if options[:namespace_type] || options[:namespace_id]
    sentinental_group_abstractor_subjects           = sentinental_group_abstractor_subjects.where(namespace_type: options[:namespace_type], namespace_id: options[:namespace_id])
  end

  sentinental_group_abstractor_abstraction_groups = Abstractor::AbstractorAbstractionGroup.not_deleted.joins(:abstractor_subject_group, :abstractor_abstractions).where(abstractor_subject_group_id: sentinental_group.id, abstractor_abstractions: {about_id: self.id, abstractor_subject_id: sentinental_group_abstractor_subjects.map(&:id)}).distinct

  sentinental_group_abstractor_suggestion_sources = Abstractor::AbstractorSuggestionSource.joins(abstractor_suggestion: { abstractor_abstraction: :abstractor_abstraction_group})
    .where(abstractor_abstraction_groups: { id: sentinental_group_abstractor_abstraction_groups.map(&:id)}, abstractor_abstractions: { abstractor_subject_id: sentinental_group_abstractor_subjects.map(&:id), about_id: self.id})

  sentinental_abstractor_abstraction_groups = sentinental_group_abstractor_abstraction_groups.where(subtype: Abstractor::Enum::ABSTRACTOR_GROUP_SENTINENTAL_SUBTYPE)

  sentinental_group_abstractor_abstraction_groups.each do |abstractor_abstraction_group|
    unless abstractor_abstraction_group.abstractor_abstractions.not_deleted.where(about_id: self.id).where('value is not null').any? # skip abstraction groups with curated abstractions
      # get all suggestion sources
      abstractor_suggestion_sources = sentinental_group_abstractor_suggestion_sources.where(abstractor_abstraction_groups: { id: abstractor_abstraction_group.id})

      # get all matched sentences
      sentence_match_values = abstractor_suggestion_sources.select(:sentence_match_value).distinct.map(&:sentence_match_value).compact

      # skip groups where all abstractions come from the same sentence
      unless sentence_match_values.length == 1
        # create abstraction group for each sentence
        sentence_match_values.each do |sentence_match_value|
          # get all suggestion sources that reference the sentence
          abstractor_suggestion_sources_by_sentence = abstractor_suggestion_sources.where(sentence_match_value: sentence_match_value)

          abstractor_subjects = abstractor_suggestion_sources_by_sentence.
            map{|abstractor_suggestion_source| abstractor_suggestion_source.abstractor_suggestion.abstractor_abstraction.abstractor_subject }.
            reject{|abstractor_subject| abstractor_subject.abstractor_subject_group.blank? || abstractor_subject.abstractor_subject_group.id != sentinental_group.id}.uniq

          if abstractor_subjects.length == sentinental_group_abstractor_subjects.length
            matching_abstractor_suggestion_sources = sentinental_group_abstractor_suggestion_sources.where(sentence_match_value: sentence_match_value)

            existing_abstractor_abstraction_group = matching_abstractor_suggestion_sources.
              map{|abstractor_suggestion_source| abstractor_suggestion_source.abstractor_suggestion.abstractor_abstraction.abstractor_abstraction_group}.
              select{|aag| sentinental_abstractor_abstraction_groups.map(&:id).include? aag.id}.
              reject{|aag| aag.id == abstractor_abstraction_group.id}.uniq.first

            if existing_abstractor_abstraction_group
              new_abstractor_abstraction_group = existing_abstractor_abstraction_group
            else
              new_abstractor_abstraction_group  = Abstractor::AbstractorAbstractionGroup.new(abstractor_subject_group: abstractor_abstraction_group.abstractor_subject_group, about: self, system_generated: true, subtype: abstractor_abstraction_group.abstractor_subject_group.subtype)
            end

            abstractor_suggestion_sources_by_sentence.all.each do |abstractor_suggestion_source|
              abstractor_suggestion   = abstractor_suggestion_source.abstractor_suggestion
              abstractor_abstraction  = abstractor_suggestion.abstractor_abstraction

              # if corresponding abstraction has more than one suggestion and should not be moved
              # create a new abstraction if the new group does not yet have abstraction for the same subject
              abstractor_subject = abstractor_abstraction.abstractor_subject
              existing_new_abstractor_abstraction = new_abstractor_abstraction_group.abstractor_abstractions.select{|aa| aa.abstractor_subject_id == abstractor_subject.id}.first

              if existing_new_abstractor_abstraction
                new_abstractor_abstraction = existing_new_abstractor_abstraction
              else
                if abstractor_abstraction.abstractor_suggestions.length > 1
                  new_abstractor_abstraction  = Abstractor::AbstractorAbstraction.create!(abstractor_subject: abstractor_suggestion.abstractor_abstraction.abstractor_subject, about: self)
                else
                  new_abstractor_abstraction = abstractor_abstraction
                  new_abstractor_abstraction.abstractor_abstraction_group_member = nil
                  new_abstractor_abstraction.build_abstractor_abstraction_group_member(abstractor_abstraction_group: new_abstractor_abstraction_group)
                end
                unless new_abstractor_abstraction_group.abstractor_abstractions.include? new_abstractor_abstraction
                  new_abstractor_abstraction_group.abstractor_abstractions << new_abstractor_abstraction
                end
              end

              # if new abstraction already has matching suggestion, use it to map sources
              new_abstractor_suggestion = new_abstractor_abstraction.detect_abstractor_suggestion(abstractor_suggestion.suggested_value, abstractor_suggestion.unknown, abstractor_suggestion.not_applicable)

              # if matching suggestion does not exist, create a new suggestion if corresponding suggestion has multiple sources
              # and should not be moved of move existing one to the new abstraction
              # and map suggestion source to the new suggestion
              if new_abstractor_suggestion.blank?
                if abstractor_suggestion.abstractor_suggestion_sources.length > 1
                  new_abstractor_suggestion ||=  Abstractor::AbstractorSuggestion.create!(
                    abstractor_abstraction: new_abstractor_abstraction,
                    abstractor_suggestion_status: Abstractor::AbstractorSuggestionStatus.where(name: 'Needs review').first,
                    suggested_value: abstractor_suggestion.suggested_value,
                    unknown: abstractor_suggestion.unknown,
                    not_applicable: abstractor_suggestion.not_applicable
                  )
                else
                  new_abstractor_suggestion = abstractor_suggestion
                end
              end

              new_abstractor_suggestion.abstractor_abstraction  = new_abstractor_abstraction
              new_abstractor_suggestion.save!

              existing_abstractor_suggestion_source = abstractor_suggestion.detect_abstractor_suggestion_source(abstractor_suggestion_source.abstractor_abstraction_source, abstractor_suggestion_source.sentence_match_value, abstractor_suggestion_source.source_id, abstractor_suggestion_source.source_type, abstractor_suggestion_source.source_method, abstractor_suggestion_source.section_name)

              if existing_abstractor_suggestion_source && existing_abstractor_suggestion_source != abstractor_suggestion_source
                abstractor_suggestion_source.delete
              else
                abstractor_suggestion_source.abstractor_suggestion = new_abstractor_suggestion
                abstractor_suggestion_source.save!
              end
            end

            # do not save group if it does not have abstractions
            new_abstractor_abstraction_group.save! if new_abstractor_abstraction_group.abstractor_abstractions.any?
          end
        end

        abstractor_abstraction_group_siblings = AbstractorAbstractionGroup.not_deleted.joins(:abstractor_subject_group, :abstractor_abstractions).where(abstractor_subject_group_id: sentinental_group.id, abstractor_abstractions: {about_id: self.id, abstractor_subject_id: sentinental_group_abstractor_subjects.map(&:id)}).distinct

        if abstractor_abstraction_group_siblings.length > 1
          abstractor_abstraction_group.reload.abstractor_abstractions.each do |abstractor_abstraction|
            abstractor_abstraction.abstractor_suggestions.each do |abstractor_suggestion|
              abstractor_suggestion.abstractor_suggestion_sources.delete_all
              abstractor_suggestion.abstractor_suggestion_object_value.delete if abstractor_suggestion.abstractor_suggestion_object_value
              abstractor_suggestion.delete
            end
            abstractor_abstraction.abstractor_indirect_sources.each do |abstractor_indirect_source|
              abstractor_indirect_source.delete
            end
            abstractor_abstraction.delete
          end
          abstractor_abstraction_group.reload.abstractor_abstraction_group_members.map{|a| a.delete}
          abstractor_abstraction_group.delete
        end
      end
    end
  end
end
remove_abstractions(options = {}) click to toggle source

Removes all abstractions, suggestions and indirect sources for the abstractable entity. Optionally filtred to only ‘unreviewed’ abstractions and to a given namespace.

@param [Hash] options the options to filter the removal of abstractions. @option options [Booelan] :only_unreviewed Instructs whether to confine removal to only ‘unreviewed’ abstractions. @option options [String] :namespace_type The type parameter of the namespace to remove. @option options [Integer] :namespace_id The instance parameter of the namespace to remove. @option options [List of integers] :abstractor_abstraction_schema_ids List of abstractor_abstraction_schema_ids to limit abstraction removal. @return [void]

# File lib/abstractor/abstractable.rb, line 181
def remove_abstractions(options = {})
  options = { only_unreviewed: true, namespace_type: nil, namespace_id: nil, abstractor_abstraction_schema_ids: [] }.merge(options)
  abstractor_abstractions = abstractor_abstractions_by_namespace(options)
  if options[:abstractor_abstraction_schema_ids].any?
    options = { abstractor_abstractions: abstractor_abstractions }.merge(options)
    abstractor_abstractions = abstractor_abstractions_by_abstraction_schemas(options)
  end
  abstractor_abstraction_groups = []
  abstractor_abstractions.each do |abstractor_abstraction|
    if abstractor_abstraction.abstractor_abstraction_group
      abstractor_abstraction_groups << abstractor_abstraction.abstractor_abstraction_group
    end
  end
  abstractor_abstraction_groups.uniq!
  abstractor_abstractions.each do |abstractor_abstraction|
    if !options[:only_unreviewed] || (options[:only_unreviewed] && abstractor_abstraction.unreviewed?)
      abstractor_abstraction.abstractor_suggestions.each do |abstractor_suggestion|
        abstractor_suggestion.abstractor_suggestion_sources.destroy_all
        abstractor_suggestion.abstractor_suggestion_object_value.destroy if abstractor_suggestion.abstractor_suggestion_object_value
        abstractor_suggestion.destroy
      end
      abstractor_abstraction.abstractor_indirect_sources.each do |abstractor_indirect_source|
        abstractor_indirect_source.destroy
      end

      abstractor_abstraction.destroy
    end
  end
  abstractor_abstraction_groups.each do |abstractor_abstraction_group|
    if abstractor_abstraction_group.reload.abstractor_abstraction_group_members.empty?
      abstractor_abstraction_group.destroy
    end
  end
end