Class: EducationForm::CreateDailySpoolFiles
- Inherits:
-
Object
- Object
- EducationForm::CreateDailySpoolFiles
- Includes:
- SentryLogging, Sidekiq::Job
- Defined in:
- app/sidekiq/education_form/create_daily_spool_files.rb
Constant Summary collapse
- MAX_RETRIES =
5
- WINDOWS_NOTEPAD_LINEBREAK =
"\r\n"
- STATSD_KEY =
'worker.education_benefits_claim'
- STATSD_FAILURE_METRIC =
"#{STATSD_KEY}.failed_spool_file".freeze
- LIVE_FORM_TYPES =
%w[1990 1995 1990e 5490 1990n 5495 0993 0994 10203 1990S].map { |t| "22-#{t.upcase}" }.freeze
- AUTOMATED_DECISIONS_STATES =
[nil, 'denied', 'processed'].freeze
Instance Method Summary collapse
-
#format_application(data, rpo: 0) ⇒ Object
Previously there was data.saved_claim.valid? check but this was causing issues for forms when 1.
-
#format_records(grouped_data) ⇒ Object
Convert the records into instances of their form representation.
- #group_submissions_by_region(records) ⇒ Object
- #inform_on_error(claim, region, error = nil) ⇒ Object
-
#perform ⇒ Object
Setting the default value to the ‘unprocessed` scope is safe because the execution of the query itself is deferred until the data is accessed by the code inside of the method.
-
#write_files(writer, structured_data:) ⇒ Object
Write out the combined spool files for each region along with recording and tracking successful transfers.
Methods included from SentryLogging
#log_exception_to_sentry, #log_message_to_sentry, #non_nil_hash?, #normalize_level, #rails_logger
Instance Method Details
#format_application(data, rpo: 0) ⇒ Object
Previously there was data.saved_claim.valid? check but this was causing issues for forms when
-
on submission the submission data is validated against vets-json-schema
-
vets-json-schema is updated in vets-api
-
during spool file creation the schema is then again validated against vets-json-schema
-
submission is no longer valid due to changes in step 2
159 160 161 162 163 164 165 166 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 159 def format_application(data, rpo: 0) form = EducationForm::Forms::Base.build(data) track_form_type("22-#{data.form_type}", rpo) form rescue => e inform_on_error(data, rpo, e) nil end |
#format_records(grouped_data) ⇒ Object
Convert the records into instances of their form representation. The conversion into ‘spool file format’ takes place here, rather than when we’re writing the files so we can hold the connection open for a shorter period of time.
79 80 81 82 83 84 85 86 87 88 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 79 def format_records(grouped_data) raw_groups = grouped_data.each do |region, v| region_id = EducationFacility.facility_for(region:) grouped_data[region] = v.map do |record| format_application(record, rpo: region_id) end.compact end # delete any regions that only had malformed claims before returning raw_groups.delete_if { |_, v| v.empty? } end |
#group_submissions_by_region(records) ⇒ Object
71 72 73 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 71 def group_submissions_by_region(records) records.group_by { |r| r.regional_processing_office.to_sym } end |
#inform_on_error(claim, region, error = nil) ⇒ Object
168 169 170 171 172 173 174 175 176 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 168 def inform_on_error(claim, region, error = nil) StatsD.increment("#{STATSD_KEY}.failed_formatting.#{region}.22-#{claim.form_type}") exception = if error.present? FormattingError.new("Could not format #{claim.confirmation_number}.\n\n#{error}") else FormattingError.new("Could not format #{claim.confirmation_number}") end log_exception(exception, nil, send_email: false) end |
#perform ⇒ Object
Setting the default value to the ‘unprocessed` scope is safe because the execution of the query itself is deferred until the data is accessed by the code inside of the method.
30 31 32 33 34 35 36 37 38 39 40 41 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 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 30 def perform retry_count = 0 begin records = EducationBenefitsClaim .unprocessed.joins(:saved_claim).includes(:education_stem_automated_decision).where( saved_claims: { form_id: LIVE_FORM_TYPES }, education_stem_automated_decisions: { automated_decision_state: AUTOMATED_DECISIONS_STATES } ) return false if federal_holiday? # Group the formatted records into different regions if records.count.zero? log_info('No records to process.') return true else log_info("Processing #{records.count} application(s)") end regional_data = group_submissions_by_region(records) formatted_records = format_records(regional_data) # Create a remote file for each region, and write the records into them writer = SFTPWriter::Factory.get_writer(Settings.edu.sftp).new(Settings.edu.sftp, logger:) write_files(writer, structured_data: formatted_records) rescue => e StatsD.increment("#{STATSD_FAILURE_METRIC}.general") if retry_count < MAX_RETRIES log_exception(DailySpoolFileError.new("Error creating spool files.\n\n#{e} Retry count: #{retry_count}. Retrying..... ")) retry_count += 1 sleep(10 * retry_count) # exponential backoff for retries retry else log_exception(DailySpoolFileError.new("Error creating spool files. Job failed after #{MAX_RETRIES} retries \n\n#{e}")) end end true end |
#write_files(writer, structured_data:) ⇒ Object
Write out the combined spool files for each region along with recording and tracking successful transfers. Creates or updates an SpoolFileEvent for tracking and to prevent multiple files per RPO per date during retries
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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'app/sidekiq/education_form/create_daily_spool_files.rb', line 93 def write_files(writer, structured_data:) structured_data.each do |region, records| region_id = EducationFacility.facility_for(region:) filename = "#{region_id}_#{Time.zone.now.strftime('%m%d%Y_%H%M%S')}_vetsgov.spl" spool_file_event = SpoolFileEvent.build_event(region_id, filename) if spool_file_event.successful_at.present? log_info("A spool file for #{region_id} on #{Time.zone.now.strftime('%m%d%Y')} was already created") else log_submissions(records, filename) # create the single textual spool file contents = records.map(&:text).join(EducationForm::CreateDailySpoolFiles::WINDOWS_NOTEPAD_LINEBREAK) begin writer.write(contents, filename) ## Testing to see if writer is the cause for retry attempt failures ## If we get to this message, it's not the writer object log_info("Successfully wrote to filename: #{filename}") # send copy of staging spool files to testers # This mailer is intended to only work for development, staging and NOT production # Rails.env will return 'production' on the development & staging servers and which # will trip the unwary. To be safe, use ENV['HOSTNAME'] email_staging_spool_files(contents) if # local developer development Rails.env.eql?('development') || # VA Staging environment where we really want this to work. ENV['HOSTNAME'].eql?('staging-api.va.gov') # track and update the records as processed once the file has been successfully written track_submissions(region_id) records.each { |r| r.record.update(processed_at: Time.zone.now) } spool_file_event.update(number_of_submissions: records.count, successful_at: Time.zone.now) rescue => e StatsD.increment("#{STATSD_FAILURE_METRIC}.#{region_id}") attempt_msg = if spool_file_event.retry_attempt.zero? 'initial attempt' else "attempt #{spool_file_event.retry_attempt}" end exception = DailySpoolFileError.new("Error creating #{filename} during #{attempt_msg}.\n\n#{e}") log_exception(exception, region) if spool_file_event.retry_attempt < MAX_RETRIES spool_file_event.update(retry_attempt: spool_file_event.retry_attempt + 1) # Reinstantiate the writer before retrying writer = SFTPWriter::Factory.get_writer(Settings.edu.sftp).new(Settings.edu.sftp, logger:) retry else next end end end end ensure writer.close end |