Module: Abstractor::Abstractable::InstanceMethods

Included in:
Abstractor::Abstractable
Defined in:
lib/abstractor/abstractable.rb

Instance Method Summary collapse

Instance Method Details

#abstract(options = {}) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • options (Hash) (defaults to: {})

    the options to filter the generation of abstractions to a namespace.

Options Hash (options):

  • :namespace_type (String)

    The type parameter of the namespace.

  • :namespace_id (Integer)

    The instance parameter of the namespace.

  • :abstractor_abstraction_schema_ids (List of integers)

    List of abstractor_abstraction_schema_ids to limit abstraction.



89
90
91
92
93
94
95
96
97
# 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 = {}) ⇒ ActiveRecord::Relation

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

Parameters:

  • options (Hash) (defaults to: {})

    the options to filter the list of abstraction groups to a namespace.

Options Hash (options):

  • :namespace_type (String)

    The type parameter of the namespace.

  • :namespace_id (Integer)

    The instance parameter of the namespace.

Returns:

  • (ActiveRecord::Relation)

    List of [Abstractor::AbstractorAbstractionGroup].



57
58
59
60
61
62
63
64
65
66
67
68
69
# 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 = {}) ⇒ ActiveRecord::Relation

Returns all abstractions for the abstractable entity by abstraction options.

Parameters:

  • options (Hash) (defaults to: {})

    the options to filter the list of abstractions to a namespace.

Options Hash (options):

  • :abstractor_abstraction_schema_ids (Array)

    List of [Abstractor::AbstractorAbstractionSchema] ids

  • List (ActiveRecord::Relation)

    of [Abstractor::AbstractorAbstraction].

Returns:

  • (ActiveRecord::Relation)

    List of [Abstractor::AbstractorAbstraction].



41
42
43
44
45
46
47
48
# 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 = {}) ⇒ ActiveRecord::Relation

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).

Parameters:

  • abstractor_abstraction_status (String)

    Filter abstractions that need review or are reviews.

  • options (Hash) (defaults to: {})

    the options to filter abstractions to a namespace.

Options Hash (options):

  • :namespace_type (String)

    the type parameter of the namespace.

  • :namespace_id (Integer)

    the instance parameter of the namespace.

Returns:

  • (ActiveRecord::Relation)

    list of [Abstractor::AbstractorAbstraction].



162
163
164
165
166
167
168
169
170
# 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 = {}) ⇒ ActiveRecord::Relation

Returns all abstractions for the abstractable entity by a namespace.

Parameters:

  • options (Hash) (defaults to: {})

    the options to filter the list of abstractions to a namespace.

Options Hash (options):

  • :namespace_type (String)

    The type parameter of the namespace.

  • :namespace_id (Integer)

    The instance parameter of the namespace.

Returns:

  • (ActiveRecord::Relation)

    List of [Abstractor::AbstractorAbstraction].



25
26
27
28
29
30
31
32
# 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 = {}) ⇒ boolean

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

Parameters:

  • abstractor_subject_group_id (Integer)

    the id of abstractor_subject_group of interest.

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :namespace_type (String)

    the type parameter of the namespace.

  • :namespace_id (Integer)

    the instance parameter of the namespace.

Returns:

  • (boolean)


140
141
142
143
144
145
146
147
148
149
# 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) ⇒ Object



99
100
101
# 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) ⇒ Object



118
119
120
121
122
123
# 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) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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) ⇒ Object



125
126
127
128
129
130
131
# 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) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • sentinental_group (ActiveRecord::Relation)

    sentinental group to process



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# 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 = {}) ⇒ void

This method returns an undefined value.

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

Parameters:

  • options (Hash) (defaults to: {})

    the options to filter the removal of abstractions.

Options Hash (options):

  • :only_unreviewed (Booelan)

    Instructs whether to confine removal to only ‘unreviewed’ abstractions.

  • :namespace_type (String)

    The type parameter of the namespace to remove.

  • :namespace_id (Integer)

    The instance parameter of the namespace to remove.

  • :abstractor_abstraction_schema_ids (List of integers)

    List of abstractor_abstraction_schema_ids to limit abstraction removal.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# 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