Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile: Remove duplicate appointments reason_code logic #18288

Merged
merged 6 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def build_appointment_model
appointment_type:,
appointment_ien: appointment[:ien],
cancel_id:,
comment:,
comment: appointment[:patient_comments],
facility_id:,
sta6aid: facility_id,
healthcare_provider:,
Expand All @@ -106,7 +106,7 @@ def build_appointment_model
status_detail: cancellation_reason(appointment[:cancelation_reason]),
time_zone: timezone,
vetext_id:,
reason:,
reason: appointment[:reason_for_appointment],
is_covid_vaccine: appointment[:service_type] == COVID_SERVICE,
is_pending: appointment_request?,
proposed_times:,
Expand All @@ -122,12 +122,13 @@ def build_appointment_model

Mobile::V0::Appointment.new(adapted_appointment)
end

# rubocop:enable Metrics/MethodLength

private

def appointment_request?
requested_periods.present?
appointment[:requested_periods].present?
end

# to match web behavior, prefer the value found in the practitioners list over the preferred_provider_name.
Expand Down Expand Up @@ -159,11 +160,7 @@ def friendly_location_name
end

def patient_phone_number
phone_number = if reason_code_contains_embedded_data?
embedded_data[:phone]
else
contact(appointment.dig(:contact, :telecom), CONTACT_TYPE[:phone])
end
phone_number = contact(appointment.dig(:contact, :telecom), CONTACT_TYPE[:phone])

return nil unless phone_number

Expand Down Expand Up @@ -230,49 +227,29 @@ def cancellation_reason(cancellation_reason)
def contact(telecom, type)
return nil if telecom.blank?

telecom.select { |contact| contact[:type] == type }&.dig(0, :value)
telecom.select { |contact| contact&.try(:dig, :type) == type }&.dig(0, :value)
end

def proposed_times
return nil if requested_periods.nil?

requested_periods.map do |period|
date, time = if reason_code_contains_embedded_data?
period.split(' ')
else
start_date = time_to_datetime(period[:start])
date = start_date.strftime('%m/%d/%Y')
time = start_date.hour.zero? ? 'AM' : 'PM'
[date, time]
end

{
date:,
time:
}
return nil unless appointment[:requested_periods]

appointment[:requested_periods].map do |period|
start_date = time_to_datetime(period[:start])
date = start_date.strftime('%m/%d/%Y')
time = start_date.hour.zero? ? 'AM' : 'PM'
{ date:, time: }
end
end

def status
STATUSES[appointment[:status].to_sym]
end

def requested_periods
@requested_periods ||= begin
if reason_code_contains_embedded_data?
date_string = embedded_data[:preferred_dates]
return date_string&.split(',')
end

appointment[:requested_periods]
end
end

def start_date_utc
@start_date_utc ||= begin
start = appointment[:start]
if start.nil?
sorted_dates = requested_periods.map do |period|
sorted_dates = appointment[:requested_periods].map do |period|
time_to_datetime(period[:start])
end.sort
future_dates = sorted_dates.select { |period| period > DateTime.now }
Expand Down Expand Up @@ -449,51 +426,10 @@ def va_appointment?
APPOINTMENT_TYPES[:va_video_connect_onsite]].include?(appointment_type)
end

def comment
return embedded_data[:comment] if reason_code_contains_embedded_data?

appointment[:comment] || appointment.dig(:reason_code, :text)
end

def reason
return REASONS[embedded_data[:reason_code]] if reason_code_contains_embedded_data?

appointment.dig(:reason_code, :coding, 0, :code)
end

def patient_email
return embedded_data[:email] if reason_code_contains_embedded_data?

contact(appointment.dig(:contact, :telecom), CONTACT_TYPE[:email])
end

# the upstream server that hosts VA appointment requests (acheron) does not support some fields
# so the front end puts all of that data into a comment, which is returned from upstream
# as reason_code text. We must parse out some parts of that data. If any of those values is
# present, we assume it's an acheron appointment and use only acheron values for relevant attributes.
def reason_code_contains_embedded_data?
@reason_code_contains_embedded_data ||= embedded_data.values.any?
end

def embedded_data
@embedded_data ||= {
phone: embedded_data_match('phone number'),
email: embedded_data_match('email'),
preferred_dates: embedded_data_match('preferred dates'),
reason_code: embedded_data_match('reason code'),
comment: embedded_data_match('comments')
}
end

def embedded_data_match(key)
camelized_key = key.gsub(' ', '_').camelize(:lower)
match = reason_code_match(key) || reason_code_match(camelized_key)

return nil unless match

match[2].strip.presence
end

def reason_code_match(key)
appointment.dig(:reason_code, :text)&.match(/(^|\|)#{key}:?(.*?)(\||$)/)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -859,85 +859,6 @@ def parse_appointment(appt)
}
)
end

it 'handles empty fields safely' do
appointment = appointment_data[12]
appointment[:reason_code][:text] = 'phone number:|email:hello|preferred dates:|reason code:|comments:'
result = subject.parse([appointment]).first
# at least one of these has to be set to be inferred to be an acheron appointment
expect(result.patient_email).to eq('hello')
expect(result.patient_phone_number).to be_nil
expect(result.proposed_times).to be_nil
expect(result.comment).to be_nil
expect(result.reason).to be_nil
end

it 'handles fields at the beginning and end of the reason code text' do
appointment = appointment_data[12]
appointment[:reason_code][:text] = 'email:[email protected]|comments:at the end'
result = subject.parse([appointment]).first

expect(result.patient_email).to eq('[email protected]')
expect(result.comment).to eq('at the end')
end

it 'handles order changes and extra fields' do
appointment = appointment_data[12]
appointment[:reason_code][:text] = "fax number: 8675309|comments:look i'm not at the end for once|\
reason code:OTHER_REASON|work email:[email protected]|email:[email protected]|phone number:1112223333|\
preferred dates:12/13/2022 PM|pager number:8675309"
result = subject.parse([appointment]).first

expect(result.patient_email).to eq('[email protected]')
expect(result.patient_phone_number).to eq('111-222-3333')
expect(result.proposed_times).to eq([{ date: '12/13/2022', time: 'PM' }])
expect(result.comment).to eq("look i'm not at the end for once")
expect(result.reason).to eq('My reason isn’t listed')
end

it 'disambiguates similarly named fields' do
appointment = appointment_data[12]
appointment[:reason_code][:text] = "work email:[email protected]|spouse email:[email protected]\
|email:[email protected]"
result = subject.parse([appointment]).first

expect(result.patient_email).to eq('[email protected]')
end

context 'when non-acheron values and any acheron values are present' do
it 'uses only acheron values' do
appointment = appointment_data[12]
appointment[:reason_code][:coding] = [{ code: 'will not be used' }]
appointment[:contact] = { telecom: { email: 'will not be used', phone: '1112223333' } }
appointment[:comment] = 'will not be used'
# setting preferred dates to indicate that this is acheron
appointment[:reason_code][:text] = 'phone number:|email:|preferred dates:12/13/2022 AM|reason code:|comments:'
result = subject.parse([appointment]).first

expect(result.patient_email).to be_nil
expect(result.patient_phone_number).to be_nil
expect(result.proposed_times).to eq([{ date: '12/13/2022', time: 'AM' }])
expect(result.comment).to be_nil
expect(result.reason).to be_nil
end
end

context 'when some acheron field keys are camel case' do
it 'parses both camel case and non camel case fields' do
appointment = appointment_data[12]
appointment[:reason_code][:coding] = [{ code: 'will not be used' }]
appointment[:contact] = { telecom: { email: 'will not be used', phone: '1112223333' } }
appointment[:comment] = 'will not be used'
appointment[:reason_code][:text] =
'email:[email protected]|preferred dates:12/13/2022 AM|reasonCode:ROUTINEVISIT|comments:My leg!'

result = subject.parse([appointment]).first
expect(result.patient_email).to eq('[email protected]')
expect(result.proposed_times).to eq([{ date: '12/13/2022', time: 'AM' }])
expect(result.comment).to eq('My leg!')
expect(result.reason).to eq('Routine Follow-up')
end
end
end

describe 'healthcare provider' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
expect(response).to have_http_status(:ok)
location = response.parsed_body.dig('data', 0, 'attributes', 'location')
physical_location = response.parsed_body.dig('data', 0, 'attributes', 'physicalLocation')
comments = response.parsed_body.dig('data', 0, 'attributes', 'comment')
reason = response.parsed_body.dig('data', 0, 'attributes', 'reason')
expect(response.body).to match_json_schema('VAOS_v2_appointments')
expect(location).to eq({ 'id' => '983',
'name' => 'Cheyenne VA Medical Center',
Expand All @@ -93,6 +95,8 @@
'url' => nil,
'code' => nil })
expect(physical_location).to eq('MTZ OPC, LAB')
expect(comments).to eq('My leg!')
expect(reason).to eq('Routine/Follow-up')
expect(response.parsed_body['meta']).to eq({
'pagination' => { 'currentPage' => 1,
'perPage' => 10,
Expand Down
Loading
Loading