Skip to content
20 changes: 14 additions & 6 deletions modules/vaos/app/models/vaos/v2/unified/va_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@
module VAOS
module V2
module Unified
# Represents a single VA schedulable clinic (clinic IEN) at a Lighthouse facility.
# +id+ is the clinic IEN. +location_id+ is the parent facility +unique_id+ used with VAOS
# clinics and slots APIs.
class VAProvider < BaseProvider
attr_accessor :location_id, :facility_type, :scheduling_config
attr_accessor :location_id, :facility_name, :facility_type, :scheduling_config

def initialize(attrs = {})
super
self.provider_type = 'va'
end

# Builds a VAProvider from a Lighthouse Facility object
# (FacilitiesApi::V2::Lighthouse::Facility)
def self.from_lighthouse_facility(facility)
##
# @param facility [FacilitiesApi::V2::Lighthouse::Facility] parent facility (address, geo, phone)
# @param clinic [OpenStruct, Hash] VAOS clinic payload from SystemsService#get_facility_clinics
#
def self.from_facility_and_clinic(facility, clinic)
clinic = clinic.to_h if clinic.is_a?(OpenStruct)

new(
id: facility.unique_id,
id: clinic[:id],
location_id: facility.unique_id,
name: facility.name,
name: clinic[:service_name],
facility_name: facility.name,
Comment thread
JunTaoLuo marked this conversation as resolved.
address: parse_lighthouse_address(facility.address),
phone: facility.phone&.dig('main'),
latitude: facility.lat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def initialize(current_user)
end

##
# Searches for both VA facilities and EPS CC providers near the user's address,
# filtered by the referral's category of care. Pins the referral's matched CC
# provider at the top, then sorts remaining results by distance.
# Searches for VA clinics (via Lighthouse facilities + VAOS clinics) and EPS CC providers
# near the user's address, filtered by the referral's category of care. Pins the referral's
# matched CC provider at the top, then sorts remaining results by distance.
#
# @param referral [Object] A CCRA referral object with category_of_care, provider NPI, etc.
# @param radius [Integer] Search radius in miles (default: 25)
Expand Down Expand Up @@ -69,9 +69,7 @@ def fetch_va_providers(user_address, referral, radius, lh_client:)
per_page: 50
)

providers = facilities.map { |f| VAOS::V2::Unified::VAProvider.from_lighthouse_facility(f) }
providers.each { |p| assign_distance(p, user_address) }
filter_va_by_category_of_care(providers, referral.category_of_care)
fetch_providers_for_facilities(facilities, referral, user_address)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the UX flow and guessing at how this service is to be used, we'll want to produce a list of VA providers which are clinics not facilities. There really isn't any use for raw VA facilities so I'm updating the logic here to:

  1. check eligibility at each facility
  2. fetch clinics at each eligible facility

rescue => e
Rails.logger.error("#{log_prefix}: VA facility search failed",
{
Expand All @@ -84,6 +82,19 @@ def fetch_va_providers(user_address, referral, radius, lh_client:)
[]
end

def fetch_providers_for_facilities(facilities, referral, user_address)
matching_facilities = filter_supported_facilities(facilities, referral.category_of_care)
clinical_service = ServiceTypeMapper.to_vaos(referral.category_of_care)

matching_facilities.flat_map do |facility|
fetch_clinics_for_facility(facility, clinical_service).map do |clinic|
provider = VAProvider.from_facility_and_clinic(facility, clinic)
assign_distance(provider, user_address)
provider
end
end
Comment thread
JunTaoLuo marked this conversation as resolved.
end

def fetch_eps_providers(user_address, referral, radius, eps_client:)
providers = eps_client.search_by_location(
latitude: user_address.latitude,
Expand Down Expand Up @@ -127,16 +138,33 @@ def assign_distance(provider, user_address)
)
end

def filter_va_by_category_of_care(providers, category_of_care)
return providers if category_of_care.blank?
def filter_supported_facilities(facilities, category_of_care)
return facilities if category_of_care.blank?

normalized = category_of_care.to_s.downcase.gsub(/[\s_-]+/, '')
# We'll need to filter based on category of care and eligibility

providers.select do |provider|
provider.schedulable_services.any? do |svc|
svc.to_s.downcase.gsub(/[\s_-]+/, '') == normalized
end
end
facilities
end

def fetch_clinics_for_facility(facility, clinical_service)
systems_service.get_facility_clinics(
location_id: facility.unique_id,
clinical_service:
)
rescue => e
Rails.logger.warn(
"#{log_prefix}: Clinic fetch failed for facility #{facility.unique_id}",
{
error_class: e.class.name,
clinical_service:,
user_uuid: @cached_user_uuid
}.compact
)
[]
end

def systems_service
@systems_service ||= VAOS::V2::SystemsService.new(current_user)
end

def combine_and_sort(va_providers, eps_providers, referral)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@

let(:va_provider) do
VAOS::V2::Unified::VAProvider.new(
id: '983',
id: '983:455',
location_id: '983',
name: 'Cheyenne VA Medical Center',
clinic_id: '455',
facility_name: 'Cheyenne VA Medical Center',
name: 'CHY UROLOGY',
address: { street1: '2360 E Pershing Blvd', city: 'Cheyenne', state: 'WY', zip: '82001' },
phone: '307-778-7550',
latitude: 41.1456,
Expand Down
74 changes: 45 additions & 29 deletions modules/vaos/spec/models/vaos/v2/unified/va_provider_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,15 @@
end
end

describe '.from_lighthouse_facility' do
describe '.from_facility_and_clinic' do
let(:facility) do
double(
'FacilitiesApi::V2::Lighthouse::Facility',
id: 'vha_983',
unique_id: '983',
name: 'Cheyenne VA Medical Center',
address: {
'physical' => {
'address1' => '2360 East Pershing Boulevard',
'address2' => 'Suite 100',
'address3' => 'Building A',
'city' => 'Cheyenne',
'state' => 'WY',
'zip' => '82001'
Expand All @@ -40,41 +37,60 @@
)
end

it 'maps Lighthouse facility fields to VaProvider' do
provider = described_class.from_lighthouse_facility(facility)
let(:clinic) do
{
id: '1014',
station_id: '983',
service_name: 'CHY AUDIOLOGY',
physical_location: 'Main building'
}
end

it 'sets id and name from the clinic payload and location_id from the facility' do
provider = described_class.from_facility_and_clinic(facility, clinic)

expect(provider.id).to eq('983')
expect(provider.id).to eq('1014')
expect(provider.location_id).to eq('983')
expect(provider.name).to eq('Cheyenne VA Medical Center')
expect(provider.facility_name).to eq('Cheyenne VA Medical Center')
expect(provider.name).to eq('CHY AUDIOLOGY')
expect(provider.provider_type).to eq('va')
expect(provider.latitude).to eq(41.1456)
expect(provider.longitude).to eq(-104.7892)
expect(provider.phone).to eq('307-778-7550')
expect(provider.distance_from_user).to be_nil
expect(provider.facility_type).to eq('va_health_facility')
expect(provider.schedulable_services).to eq(%w[primaryCare audiology])
expect(provider.address).to eq({
street1: '2360 East Pershing Boulevard',
street2: 'Suite 100',
street3: 'Building A',
city: 'Cheyenne',
state: 'WY',
zip: '82001'
})
end

it 'handles nil services gracefully' do
allow(facility).to receive(:services).and_return(nil)
provider = described_class.from_lighthouse_facility(facility)
it 'uses service_name for name' do
provider = described_class.from_facility_and_clinic(
facility,
clinic.merge(service_name: 'PODIATRY CLINIC')
)

expect(provider.schedulable_services).to eq([])
expect(provider.name).to eq('PODIATRY CLINIC')
end

it 'handles nil address gracefully' do
allow(facility).to receive(:address).and_return(nil)
provider = described_class.from_lighthouse_facility(facility)
it 'uses facility unique_id as location_id regardless of clinic station_id' do
satellite_clinic = clinic.merge(station_id: '983GC', id: '945')

provider = described_class.from_facility_and_clinic(facility, satellite_clinic)

expect(provider.location_id).to eq('983')
expect(provider.id).to eq('945')
end

expect(provider.address).to be_nil
it 'accepts OpenStruct clinic payloads' do
provider = described_class.from_facility_and_clinic(
facility,
OpenStruct.new(clinic)
)

expect(provider.id).to eq('1014')
expect(provider.name).to eq('CHY AUDIOLOGY')
end

it 'handles nil facility services gracefully' do
allow(facility).to receive(:services).and_return(nil)

provider = described_class.from_facility_and_clinic(facility, clinic)

expect(provider.schedulable_services).to eq([])
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

let(:va_provider) do
VAOS::V2::Unified::VAProvider.new(
id: '983',
id: '1081',
location_id: '983',
name: 'Cheyenne VA Medical Center',
address: { street1: '2360 E Pershing Blvd', city: 'Cheyenne', state: 'WY', zip: '82001' },
phone: '307-778-7550',
Expand Down Expand Up @@ -44,7 +45,7 @@
it 'serializes VA provider attributes' do
result = serializer.serialize([va_provider]).first

expect(result[:id]).to eq('983')
expect(result[:id]).to eq('1081')
expect(result[:attributes][:name]).to eq('Cheyenne VA Medical Center')
expect(result[:attributes][:providerType]).to eq('va')
expect(result[:attributes][:distanceInMiles]).to eq(3.2)
Expand All @@ -57,6 +58,8 @@
expect(result[:id]).to eq('9mN718pH')
expect(result[:attributes][:name]).to eq('Dr. Bones @ Melbourne Medical')
expect(result[:attributes][:providerType]).to eq('community_care')
expect(result[:attributes]).not_to have_key(:locationId)
expect(result[:attributes]).not_to have_key(:clinicId)
end

it 'marks the referral provider correctly' do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@
}
end

let(:urology_clinic) do
OpenStruct.new(
id: '455',
station_id: '983',
service_name: 'CHY UROLOGY',
physical_location: nil
)
end

let(:systems_service) { instance_double(VAOS::V2::SystemsService) }

before do
allow(user).to receive(:vet360_contact_info).and_return(vet360_contact_info)
end
Expand All @@ -63,17 +74,22 @@
before do
allow(FacilitiesApi::V2::Lighthouse::Client).to receive(:new).and_return(lighthouse_client)
allow(Eps::ProviderService).to receive(:new).and_return(eps_provider_service)
allow(VAOS::V2::SystemsService).to receive(:new).with(user).and_return(systems_service)

allow(lighthouse_client).to receive(:get_facilities).and_return([lighthouse_facility])
allow(eps_provider_service).to receive(:search_by_location).and_return([eps_provider_hash])
allow(systems_service).to receive(:get_facility_clinics).and_return([urology_clinic])
end

it 'returns a combined list of VA and EPS providers' do
it 'returns a combined list of VA clinics and EPS providers' do
results = service.search(referral:)

expect(results.size).to eq(2)
provider_types = results.map(&:provider_type)
expect(provider_types).to include('va', 'community_care')
va = results.find { |p| p.provider_type == 'va' }
expect(va.id).to eq('455')
expect(va.location_id).to eq('983')
end

it 'pins the referral matched provider at the top' do
Expand Down Expand Up @@ -132,6 +148,27 @@
)
end

it 'fetches VAOS clinics using ServiceTypeMapper.to_vaos(category_of_care)' do
service.search(referral:)

# UROLOGY is not in LIGHTHOUSE_TO_VAOS; mapper returns nil
expect(systems_service).to have_received(:get_facility_clinics).with(
location_id: '983',
clinical_service: nil
)
end

it 'passes mapped clinical service when category_of_care maps to VAOS' do
audio_referral = double('Referral', category_of_care: 'audiology', provider_npi: '91560381x')

service.search(referral: audio_referral)

expect(systems_service).to have_received(:get_facility_clinics).with(
location_id: '983',
clinical_service: 'audiology'
)
end

it 'raises error when user has no residential address' do
allow(vet360_contact_info).to receive(:residential_address).and_return(nil)

Expand Down Expand Up @@ -162,7 +199,7 @@
expect(results.first.provider_type).to eq('va')
end

it 'filters VA facilities by category of care' do
it 'requests clinics for each Lighthouse facility (filter_supported_facilities is not yet applied)' do
non_matching_facility = double(
'Facility',
id: 'vha_984', unique_id: '984', name: 'Other VA',
Expand All @@ -177,8 +214,17 @@
results = service.search(referral:)

va_providers = results.select { |p| p.provider_type == 'va' }
expect(va_providers.size).to eq(1)
expect(va_providers.first.id).to eq('983')
expect(va_providers.size).to eq(2)
expect(va_providers.map(&:location_id).sort).to eq(%w[983 984])
expect(systems_service).to have_received(:get_facility_clinics).twice
end

it 'returns no VA providers when get_facility_clinics returns no clinics' do
allow(systems_service).to receive(:get_facility_clinics).and_return([])

results = service.search(referral:)

expect(results.map(&:provider_type)).to eq(['community_care'])
end

it 'uses the default 25-mile radius' do
Expand Down
Loading