Class: FormProfile

Inherits:
Object
  • Object
show all
Includes:
SentryLogging
Defined in:
app/models/form_profile.rb

Constant Summary collapse

MAPPINGS =
Rails.root.glob('config/form_profile_mappings/*.yml').map { |f| File.basename(f, '.*') }
ALL_FORMS =
{
  edu: %w[22-1990 22-1990N 22-1990E 22-1990EMEB 22-1995 22-5490 22-5490E
          22-5495 22-0993 22-0994 FEEDBACK-TOOL 22-10203 22-1990S 22-1990EZ],
  evss: ['21-526EZ'],
  hca: %w[1010ez 10-10EZR],
  pension_burial: %w[21P-530 21P-527EZ 21P-530V2],
  dependents: ['686C-674'],
  decision_review: %w[20-0995 20-0996 10182],
  mdot: ['MDOT'],
  fsr: ['5655'],
  vre_counseling: ['28-8832'],
  vre_readiness: ['28-1900'],
  coe: ['26-1880'],
  adapted_housing: ['26-4555'],
  intent_to_file: ['21-0966'],
  ivc_champva: ['10-7959C'],
  form_upload_flow: ['FORM-UPLOAD-FLOW'],
  acc_rep_management: %w[21-22 21-22A],
  form_mock_ae_design_patterns: ['FORM-MOCK-AE-DESIGN-PATTERNS']
}.freeze
FORM_ID_TO_CLASS =
{
  '0873' => ::FormProfiles::VA0873,
  '1010EZ' => ::FormProfiles::VA1010ez,
  '10-10EZR' => ::FormProfiles::VA1010ezr,
  '10-7959C' => ::FormProfiles::VHA107959c,
  '10182' => ::FormProfiles::VA10182,
  '20-0995' => ::FormProfiles::VA0995,
  '20-0996' => ::FormProfiles::VA0996,
  '21-526EZ' => ::FormProfiles::VA526ez,
  '22-1990' => ::FormProfiles::VA1990,
  '22-1990N' => ::FormProfiles::VA1990n,
  '22-1990E' => ::FormProfiles::VA1990e,
  '22-1990EMEB' => ::FormProfiles::VA1990emeb,
  '22-1995' => ::FormProfiles::VA1995,
  '22-5490' => ::FormProfiles::VA5490,
  '22-5490E' => ::FormProfiles::VA5490e,
  '22-5495' => ::FormProfiles::VA5495,
  '21P-530' => ::FormProfiles::VA21p530,
  '21P-530V2' => ::FormProfiles::VA21p530v2,
  '21-686C' => ::FormProfiles::VA21686c,
  '686C-674' => ::FormProfiles::VA686c674,
  '40-10007' => ::FormProfiles::VA4010007,
  '21P-527EZ' => ::FormProfiles::VA21p527ez,
  '22-0993' => ::FormProfiles::VA0993,
  '22-0994' => ::FormProfiles::VA0994,
  'FEEDBACK-TOOL' => ::FormProfiles::FeedbackTool,
  'MDOT' => ::FormProfiles::MDOT,
  '22-10203' => ::FormProfiles::VA10203,
  '22-1990S' => ::FormProfiles::VA1990s,
  '5655' => ::FormProfiles::VA5655,
  '28-8832' => ::FormProfiles::VA288832,
  '28-1900' => ::FormProfiles::VA281900,
  '22-1990EZ' => ::FormProfiles::VA1990ez,
  '26-1880' => ::FormProfiles::VA261880,
  '26-4555' => ::FormProfiles::VA264555,
  '21-0966' => ::FormProfiles::VA210966,
  'FORM-UPLOAD-FLOW' => ::FormProfiles::FormUploadFlow,
  '21-22' => ::FormProfiles::VA2122,
  '21-22A' => ::FormProfiles::VA2122a,
  'FORM-MOCK-AE-DESIGN-PATTERNS' => ::FormProfiles::FormMockAeDesignPatterns
}.freeze
APT_REGEX =
/\S\s+((apt|apartment|unit|ste|suite).+)/i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SentryLogging

#log_exception_to_sentry, #log_message_to_sentry, #non_nil_hash?, #normalize_level, #rails_logger, #set_sentry_metadata

Constructor Details

#initialize(form_id:, user:) ⇒ FormProfile

Returns a new instance of FormProfile.



173
174
175
176
# File 'app/models/form_profile.rb', line 173

def initialize(form_id:, user:)
  @form_id = form_id
  @user = user
end

Instance Attribute Details

#form_idObject (readonly)

Returns the value of attribute form_id.



155
156
157
# File 'app/models/form_profile.rb', line 155

def form_id
  @form_id
end

#userObject (readonly)

Returns the value of attribute user.



155
156
157
# File 'app/models/form_profile.rb', line 155

def user
  @user
end

Class Method Details

.for(form_id:, user:) ⇒ Object

lookup FormProfile subclass by form_id and initialize (or use FormProfile if lookup fails)



168
169
170
171
# File 'app/models/form_profile.rb', line 168

def self.for(form_id:, user:)
  form_id = form_id.upcase
  FORM_ID_TO_CLASS.fetch(form_id, self).new(form_id:, user:)
end

.load_form_mapping(form_id) ⇒ Object

Raises:

  • (IOError)


187
188
189
190
191
192
193
# File 'app/models/form_profile.rb', line 187

def self.load_form_mapping(form_id)
  form_id = form_id.downcase if form_id == '1010EZ' # our first form. lessons learned.
  file = Rails.root.join('config', 'form_profile_mappings', "#{form_id}.yml")
  raise IOError, "Form profile mapping file is missing for form id #{form_id}" unless File.exist?(file)

  YAML.load_file(file)
end

.mappings_for_form(form_id) ⇒ Object



182
183
184
185
# File 'app/models/form_profile.rb', line 182

def self.mappings_for_form(form_id)
  @mappings ||= {}
  @mappings[form_id] || (@mappings[form_id] = load_form_mapping(form_id))
end

.prefill_enabled_formsObject



161
162
163
164
165
# File 'app/models/form_profile.rb', line 161

def self.prefill_enabled_forms
  forms = %w[21-686C 40-10007 0873]
  ALL_FORMS.each { |type, form_list| forms += form_list if Settings[type].prefill }
  forms
end

Instance Method Details

#call_methods(methods) ⇒ Object (private)



408
409
410
411
412
# File 'app/models/form_profile.rb', line 408

def call_methods(methods)
  methods.inject(self) { |a, e| a.send e }.as_json
rescue NoMethodError
  nil
end

#clean!(value) ⇒ Object (private)



414
415
416
417
418
419
420
421
422
423
# File 'app/models/form_profile.rb', line 414

def clean!(value)
  case value
  when Hash
    clean_hash!(value)
  when Array
    value.map { |v| clean!(v) }.delete_if(&:blank?)
  else
    value
  end
end

#clean_hash!(hash) ⇒ Object (private)



425
426
427
428
429
# File 'app/models/form_profile.rb', line 425

def clean_hash!(hash)
  hash.deep_transform_keys! { |k| k.camelize(:lower) }
  hash.each { |k, v| hash[k] = clean!(v) }
  hash.delete_if { |_k, v| v.blank? }
end

#convert_mapping(hash) ⇒ Object (private)



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'app/models/form_profile.rb', line 375

def convert_mapping(hash)
  prefilled = {}

  hash.each do |k, v|
    if v.is_a?(Array) && v.any?(Hash)
      prefilled[k.camelize(:lower)] = []

      v.each do |h|
        nested_prefill = {}

        h.each do |key, val|
          nested_prefill[key.camelize(:lower)] = convert_value(val)
        end

        prefilled[k.camelize(:lower)] << nested_prefill
      end
    else
      prefilled[k.camelize(:lower)] = convert_value(v)
    end
  end

  prefilled
end

#convert_value(val) ⇒ Object (private)



399
400
401
# File 'app/models/form_profile.rb', line 399

def convert_value(val)
  val.is_a?(Hash) ? convert_mapping(val) : call_methods(val)
end

#extract_pciu_data(method) ⇒ Object (private)



340
341
342
343
344
# File 'app/models/form_profile.rb', line 340

def extract_pciu_data(method)
  user&.send(method)
rescue Common::Exceptions::Forbidden, Common::Exceptions::BackendServiceException, EVSS::ErrorMiddleware::EVSSError
  ''
end

#format_for_schema_compatibility(opt) ⇒ Object (private)



327
328
329
330
331
332
333
334
335
336
337
338
# File 'app/models/form_profile.rb', line 327

def format_for_schema_compatibility(opt)
  if opt.dig(:address, :street) && opt[:address][:street2].blank? && (apt = opt[:address][:street].match(APT_REGEX))
    opt[:address][:street2] = apt[1]
    opt[:address][:street] = opt[:address][:street].gsub(/\W?\s+#{apt[1]}/, '').strip
  end

  %i[home_phone us_phone mobile_phone].each do |phone|
    opt[phone] = opt[phone].gsub(/\D/, '') if opt[phone]
  end

  opt[:address][:postal_code] = opt[:address][:postal_code][0..4] if opt.dig(:address, :postal_code)
end

#generate_prefill(mappings) ⇒ Object (private)



403
404
405
406
# File 'app/models/form_profile.rb', line 403

def generate_prefill(mappings)
  result = convert_mapping(mappings)
  clean!(result)
end

#initialize_contact_informationObject (private)



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'app/models/form_profile.rb', line 281

def initialize_contact_information
  opt = {}
  opt.merge!(vets360_contact_info_hash) if vet360_contact_info
  Rails.logger.info("User Vet360 Contact Info, Address? #{opt[:address].present?}
    Email? #{opt[:email].present?}, Phone? #{opt[:home_phone].present?}")

  opt[:address] ||= user_address_hash
  opt[:email] ||= extract_pciu_data(:pciu_email)
  if opt[:home_phone].nil?
    opt[:home_phone] = pciu_primary_phone
    opt[:us_phone] = pciu_us_phone
  end

  format_for_schema_compatibility(opt)

  FormContactInformation.new(opt)
end

#initialize_identity_informationObject (private)



245
246
247
248
249
250
251
252
# File 'app/models/form_profile.rb', line 245

def initialize_identity_information
  FormIdentityInformation.new(
    full_name: user.full_name_normalized,
    date_of_birth: user.birth_date,
    gender: user.gender,
    ssn: user.ssn_normalized
  )
end

#initialize_military_informationObject



218
219
220
221
222
223
224
225
226
# File 'app/models/form_profile.rb', line 218

def initialize_military_information
  return {} unless user.authorize :va_profile, :access?

  military_information_data = {}
  military_information_data.merge!(initialize_va_profile_prefill_military_information)
  military_information_data[:vic_verified] = user.can_access_id_card?

  FormMilitaryInformation.new(military_information_data)
end

#initialize_va_profile_prefill_military_informationObject (private)



230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'app/models/form_profile.rb', line 230

def initialize_va_profile_prefill_military_information
  military_information_data = {}
  military_information = VAProfile::Prefill::MilitaryInformation.new(user)

  VAProfile::Prefill::MilitaryInformation::PREFILL_METHODS.each do |attr|
    military_information_data[attr] = military_information.public_send(attr)
  end

  military_information_data
rescue => e
  log_exception_to_sentry(e, {}, prefill: :va_profile_prefill_military_information)

  {}
end

#metadataObject



178
179
180
# File 'app/models/form_profile.rb', line 178

def 
  {}
end

#pciu_primary_phoneObject (private)



371
372
373
# File 'app/models/form_profile.rb', line 371

def pciu_primary_phone
  @pciu_primary_phone ||= extract_pciu_data(:pciu_primary_phone)
end

#pciu_us_phoneObject (private)



346
347
348
349
350
351
352
353
# File 'app/models/form_profile.rb', line 346

def pciu_us_phone
  return '' if pciu_primary_phone.blank?
  return pciu_primary_phone if pciu_primary_phone.size == 10

  return pciu_primary_phone[1..] if pciu_primary_phone.size == 11 && pciu_primary_phone[0] == '1'

  ''
end

#phone_objectObject (private)

returns the veteran’s phone number as an object preference: vet360 mobile -> vet360 home -> pciu



357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'app/models/form_profile.rb', line 357

def phone_object
  mobile = vet360_contact_info&.mobile_phone
  return mobile if mobile&.area_code && mobile.phone_number

  home = vet360_contact_info&.home_phone
  return home if home&.area_code && home.phone_number

  phone_struct = Struct.new(:area_code, :phone_number)

  return phone_struct.new(pciu_us_phone.first(3), pciu_us_phone.last(7)) if pciu_us_phone&.length == 10

  phone_struct.new
end

#prefillObject

Collects data the VA has on hand for a user. The data may come from many databases/services. In case of collisions, preference is given in this order:

  • The form profile cache (the record for this class)

  • ID.me

  • MVI

  • TODO(AJD): MIS (military history)



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'app/models/form_profile.rb', line 202

def prefill
  @identity_information = initialize_identity_information
  @contact_information = initialize_contact_information
  @military_information = initialize_military_information
  form = form_id == '1010EZ' ? '1010ez' : form_id
  if FormProfile.prefill_enabled_forms.include?(form)
    mappings = self.class.mappings_for_form(form_id)

    form_data = generate_prefill(mappings)

    { form_data:, metadata: }
  else
    { metadata: }
  end
end

#user_address_hashObject (private)



316
317
318
319
320
321
322
323
324
325
# File 'app/models/form_profile.rb', line 316

def user_address_hash
  {
    street: user.address[:street],
    street2: user.address[:street2],
    city: user.address[:city],
    state: user.address[:state],
    country: user.address[:country],
    postal_code: user.address[:postal_code]
  }
end

#vet360_contact_infoObject (private)

doing this (below) instead of ‘@vet360_contact_info ||= Settings…` to cache nil too



300
301
302
303
304
305
306
307
308
309
310
# File 'app/models/form_profile.rb', line 300

def vet360_contact_info
  return @vet360_contact_info if @vet360_contact_info_retrieved

  @vet360_contact_info_retrieved = true
  if VAProfile::Configuration::SETTINGS.prefill && user.vet360_id.present?
    @vet360_contact_info = VAProfileRedis::ContactInformation.for_user(user)
  else
    Rails.logger.info('Vet360 Contact Info Null')
  end
  @vet360_contact_info
end

#vet360_mailing_addressObject (private)



312
313
314
# File 'app/models/form_profile.rb', line 312

def vet360_mailing_address
  vet360_contact_info&.mailing_address
end

#vet360_mailing_address_hashObject (private)



254
255
256
257
258
259
260
261
262
263
264
265
# File 'app/models/form_profile.rb', line 254

def vet360_mailing_address_hash
  address = vet360_mailing_address
  {
    street: address.address_line1,
    street2: address.address_line2,
    city: address.city,
    state: address.state_code || address.province,
    country: address.country_code_iso3,
    postal_code: address.zip_plus_four || address.international_postal_code,
    zip_code: address.zip_code
  }.compact
end

#vets360_contact_info_hashObject (private)



267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'app/models/form_profile.rb', line 267

def vets360_contact_info_hash
  return_val = {}
  return_val[:email] = vet360_contact_info&.email&.email_address

  return_val[:address] = vet360_mailing_address_hash if vet360_mailing_address.present?

  phone = vet360_contact_info&.home_phone&.formatted_phone
  return_val[:us_phone] = phone
  return_val[:home_phone] = phone
  return_val[:mobile_phone] = vet360_contact_info&.mobile_phone&.formatted_phone

  return_val
end