Class: InaturalistImportJob

Inherits:
ApplicationJob show all
Defined in:
app/jobs/inaturalist_import_job.rb

Instance Method Summary collapse

Instance Method Details

#import_observation(result, project_id:, match_otu_by_name:, use_community_taxon:, import_images:, import_sounds:) ⇒ Object (private)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'app/jobs/inaturalist_import_job.rb', line 42

def import_observation(result, project_id:, match_otu_by_name:, use_community_taxon:, import_images:, import_sounds:)
  ApplicationRecord.transaction do
    # Save the OTU first so otu.id is available for the TaxonDetermination nested
    # attributes — reject_taxon_determinations rejects entries with a blank otu_id
    # and a blank otu.id, which is the case for any new (unsaved) OTU object.
    taxon_determination_attributes =
      if @taxon_determination
        @taxon_determination
      else
        otu = ::Vendor::Nasturtium.stub_otu(
          result, project_id:, match_by_name: match_otu_by_name,
          use_community_taxon:
        )
        unless otu
          Rails.logger.warn(
            "InaturalistImportJob: skipping observation #{result['id']} " \
            '— no taxon name'
          )
          return
        end
        otu.save! if otu.new_record?

        d = result['observed_on_details']
        {
          otu_id: otu.id,
          year_made: d['year'],
          month_made: d['month'],
          day_made: d['day'],
        }
      end

    # Save the CE (and its nested georeference) before the FO so that
    # collecting_event_id is set when the FO is created.
    ce = ::Vendor::Nasturtium.stub_collecting_event(result, person_cache: @person_cache)
    ce.save!

    if (georef = ce.georeferences.first)
      georef.georeferencer_roles.create!(person: observer_person(result))
    end

    fo = FieldOccurrence.new(
      total: 1,
      collecting_event: ce,
      taxon_determinations_attributes: [taxon_determination_attributes],
      identifiers: [::Vendor::Nasturtium.stub_identifier(result)].compact,
    )
    fo.save!

    ::Vendor::Nasturtium.stub_biocuration_classes(result, project_id:).each do |biocuration_class|
      BiocurationClassification.create!(biocuration_class:, biocuration_classification_object: fo)
    end

    # The observer's iNat identification isn't the determination when one
    # was given for all results.
    if !use_community_taxon && @taxon_determination.nil?
      td = fo.taxon_determinations.first
      td.determiner_roles.create!(person: observer_person(result))

      if (ident_uuid = ::Vendor::Nasturtium.observer_identification_uuid(result))
        Identifier::Global::Uuid::InaturalistIdentification.create!(
          identifier_object: td,
          identifier: ident_uuid
        )
      end
    end

    if result['description'].present?
      Note.create!(note_object: fo, text: result['description'])
    end

    import_photos(result, fo:) if import_images
    import_sounds(result, fo:) if import_sounds
  end
rescue => e
  # Failures are logged but do not interrupt the remaining imports
  Rails.logger.error(
    "InaturalistImportJob: failed to import observation #{result['id']}: " \
    "#{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}"
  )
end

#import_photos(result, fo:) ⇒ Object (private)



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/jobs/inaturalist_import_job.rb', line 142

def import_photos(result, fo:)
  observed_year = result.dig('observed_on_details', 'year')

  ::Vendor::Nasturtium.permitted_photos(result).each do |obs_photo|
    ApplicationRecord.transaction do
      image = ::Vendor::Nasturtium.build_image!(obs_photo, result:, observed_year:, person_cache: @person_cache)
      Depiction.create!(image:, depiction_object: fo)
    end
  rescue => e
    Rails.logger.error(
      "InaturalistImportJob: failed to import photo #{obs_photo['uuid']} " \
      "for observation #{result['uuid']}: #{e.class}: #{e.message}\n" \
      "#{e.backtrace&.first(5)&.join("\n")}"
    )
  end
end

#import_sounds(result, fo:) ⇒ Object (private)

For each CC/PD-licensed sound on the observation, create a Sound with full licensing metadata and attach it as a Conveyance on the FO.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/jobs/inaturalist_import_job.rb', line 125

def import_sounds(result, fo:)
  observed_year = result.dig('observed_on_details', 'year')

  ::Vendor::Nasturtium.permitted_sounds(result).each do |obs_sound|
    ApplicationRecord.transaction do
      sound = ::Vendor::Nasturtium.build_sound!(obs_sound, result:, observed_year:, person_cache: @person_cache)
      Conveyance.create!(sound:, conveyance_object: fo)
    end
  rescue => e
    Rails.logger.error(
      "InaturalistImportJob: failed to import sound #{obs_sound['uuid']} " \
      "for observation #{result['uuid']}: #{e.class}: #{e.message}\n" \
      "#{e.backtrace&.first(5)&.join("\n")}"
    )
  end
end

#observer_person(result) ⇒ Object (private)

The observation's observer as a saved Person, deduplicated across the run.



36
37
38
39
40
# File 'app/jobs/inaturalist_import_job.rb', line 36

def observer_person(result)
  person = ::Vendor::Nasturtium.stub_observer_person(result, person_cache: @person_cache)
  person.save! if person.new_record?
  person
end

#perform(results:, project_id:, user_id:, match_otu_by_name: false, use_community_taxon: true, import_images: false, import_sounds: false, taxon_determination: nil) ⇒ Object

Parameters:

  • results (Array<Hash>) —

    pre-fetched Nasturtium observation results

  • project_id (Integer)
  • user_id (Integer)
  • match_otu_by_name (Boolean) (defaults to: false)
  • use_community_taxon (Boolean) (defaults to: true)
  • import_images (Boolean) (defaults to: false)
  • import_sounds (Boolean) (defaults to: false)
  • taxon_determination (Hash, nil) (defaults to: nil) —

    TaxonDetermination attributes to determine every result with, in place of matching/creating an OTU from its iNat taxon



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'app/jobs/inaturalist_import_job.rb', line 14

def perform(
  results:, project_id:, user_id:, match_otu_by_name: false,
  use_community_taxon: true, import_images: false, import_sounds: false,
  taxon_determination: nil
)
  Current.project_id = project_id
  Current.user_id = user_id

  @taxon_determination = taxon_determination

  # Reuse one Person across this run for an observer/copyright holder/etc.
  # that would otherwise be built repeatedly.
  @person_cache = {}

  results.each do |result|
    import_observation(result, project_id:, match_otu_by_name:, use_community_taxon:, import_images:, import_sounds:)
  end
end