Skip to content
This repository is currently being migrated. It's locked while the migration is in progress.

Commit 762942d

Browse files
committed
VAOS: add AvailabilityAdapter
Add AvailabilityAdapter for VA clinic and EPS slot fetches.
1 parent 9bbc0cd commit 762942d

3 files changed

Lines changed: 220 additions & 2 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
module VAOS
4+
module V2
5+
module Unified
6+
# Fetches appointment availability (slots) for unified scheduling: VA clinic providers via
7+
# VAOS SystemsService, community care via EPS ProviderService. VA providers must include
8+
# +location_id+ and +id+ (clinic IEN; see VAProvider).
9+
class SlotAdapter
10+
def initialize(user)
11+
@user = user
12+
end
13+
14+
##
15+
# @param provider [BaseProvider] VAProvider or EpsProvider
16+
# @param start_dt [String] ISO8601 start of search window
17+
# @param end_dt [String] ISO8601 end of search window
18+
# @param clinical_service [String, nil] VAOS clinical service (required for VA)
19+
# @param eps_opts [Hash] options for Eps::ProviderService#get_provider_slots
20+
# @return [Array<BaseSlot>]
21+
#
22+
def slots_for(provider:, start_dt:, end_dt:, clinical_service: nil, eps_opts: {})
23+
case provider
24+
when VAProvider
25+
fetch_va_slots(provider, start_dt, end_dt, clinical_service)
26+
when EpsProvider
27+
fetch_eps_slots(provider, eps_opts)
28+
else
29+
raise ArgumentError, "Unsupported provider type: #{provider.class.name}"
30+
end
31+
end
32+
33+
private
34+
35+
attr_reader :user
36+
37+
def fetch_va_slots(provider, start_dt, end_dt, clinical_service)
38+
raise Common::Exceptions::ParameterMissing, 'service_type' if clinical_service.blank?
39+
40+
if provider.location_id.blank? || provider.id.blank?
41+
raise Common::Exceptions::UnprocessableEntity.new(
42+
detail: 'VA provider requires location_id and id (clinic IEN) for slot lookup'
43+
)
44+
end
45+
46+
raw = systems_service.get_available_slots(
47+
location_id: provider.location_id,
48+
clinic_id: provider.id,
49+
clinical_service:,
50+
provider_id: nil,
51+
start_dt:,
52+
end_dt:
53+
)
54+
Array(raw).map { |slot| VASlot.from_vaos_slot(slot, location_id: provider.location_id) }
55+
end
56+
57+
def fetch_eps_slots(provider, eps_opts)
58+
provider_id = provider.provider_service_id.presence || provider.id
59+
response = eps_provider_service.get_provider_slots(provider_id, eps_opts)
60+
slots = response&.slots
61+
Array(slots).map { |slot| EpsSlot.from_eps_slot(slot) }
62+
end
63+
64+
def systems_service
65+
@systems_service ||= VAOS::V2::SystemsService.new(user)
66+
end
67+
68+
def eps_provider_service
69+
@eps_provider_service ||= Eps::ProviderService.new(user)
70+
end
71+
end
72+
end
73+
end
74+
end

modules/vaos/spec/controllers/v2/providers_controller_index_spec.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818

1919
let(:va_provider) do
2020
VAOS::V2::Unified::VAProvider.new(
21-
id: '983:455',
21+
id: '455',
2222
location_id: '983',
23-
clinic_id: '455',
2423
facility_name: 'Cheyenne VA Medical Center',
2524
name: 'CHY UROLOGY',
2625
address: { street1: '2360 E Pershing Blvd', city: 'Cheyenne', state: 'WY', zip: '82001' },
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe VAOS::V2::Unified::SlotAdapter do
6+
let(:user) { build(:user, :vaos) }
7+
let(:adapter) { described_class.new(user) }
8+
9+
describe '#slots_for' do
10+
context 'with an unsupported provider' do
11+
let(:provider) { VAOS::V2::Unified::BaseProvider.new(id: 'x') }
12+
13+
it 'raises ArgumentError' do
14+
expect do
15+
adapter.slots_for(provider:, start_dt: '2025-01-01T00:00:00Z', end_dt: '2025-01-02T00:00:00Z')
16+
end.to raise_error(ArgumentError, /Unsupported provider type/)
17+
end
18+
end
19+
20+
context 'with VAProvider' do
21+
let(:provider) do
22+
VAOS::V2::Unified::VAProvider.new(
23+
id: '1081',
24+
location_id: '983'
25+
)
26+
end
27+
28+
let(:systems_service) { instance_double(VAOS::V2::SystemsService) }
29+
let(:raw_slot) do
30+
OpenStruct.new(
31+
id: 'slot-1',
32+
start: '2025-01-01T10:00:00Z',
33+
end: '2025-01-01T10:30:00Z',
34+
clinic: { clinic_ien: '1081' },
35+
location: { vha_facility_id: '983' }
36+
)
37+
end
38+
39+
before do
40+
allow(VAOS::V2::SystemsService).to receive(:new).with(user).and_return(systems_service)
41+
end
42+
43+
it 'returns normalized VASlot records' do
44+
allow(systems_service).to receive(:get_available_slots).and_return([raw_slot])
45+
46+
slots = adapter.slots_for(
47+
provider:,
48+
start_dt: '2025-01-01T00:00:00Z',
49+
end_dt: '2025-01-02T00:00:00Z',
50+
clinical_service: 'audiology'
51+
)
52+
53+
expect(slots.size).to eq(1)
54+
expect(slots.first).to be_a(VAOS::V2::Unified::VASlot)
55+
expect(slots.first.id).to eq('slot-1')
56+
expect(slots.first.location_id).to eq('983')
57+
end
58+
59+
it 'raises when clinical_service is blank' do
60+
expect do
61+
adapter.slots_for(
62+
provider:,
63+
start_dt: '2025-01-01T00:00:00Z',
64+
end_dt: '2025-01-02T00:00:00Z',
65+
clinical_service: nil
66+
)
67+
end.to raise_error(Common::Exceptions::ParameterMissing)
68+
end
69+
70+
it 'raises when id (clinic IEN) is blank' do
71+
provider.id = nil
72+
73+
expect do
74+
adapter.slots_for(
75+
provider:,
76+
start_dt: '2025-01-01T00:00:00Z',
77+
end_dt: '2025-01-02T00:00:00Z',
78+
clinical_service: 'audiology'
79+
)
80+
end.to raise_error(Common::Exceptions::UnprocessableEntity)
81+
end
82+
83+
it 'returns an empty array when upstream returns no slots' do
84+
allow(systems_service).to receive(:get_available_slots).and_return([])
85+
86+
slots = adapter.slots_for(
87+
provider:,
88+
start_dt: '2025-01-01T00:00:00Z',
89+
end_dt: '2025-01-02T00:00:00Z',
90+
clinical_service: 'audiology'
91+
)
92+
93+
expect(slots).to eq([])
94+
end
95+
end
96+
97+
context 'with EpsProvider' do
98+
let(:provider) do
99+
VAOS::V2::Unified::EpsProvider.new(
100+
id: '9mN718pH',
101+
provider_service_id: '9mN718pH'
102+
)
103+
end
104+
105+
let(:eps_service) { instance_double(Eps::ProviderService) }
106+
let(:eps_opts) do
107+
{
108+
appointmentTypeId: 'ov',
109+
startOnOrAfter: '2025-01-01T00:00:00Z',
110+
startBefore: '2025-01-02T00:00:00Z',
111+
appointmentId: 'draft-1'
112+
}
113+
end
114+
115+
before do
116+
allow(Eps::ProviderService).to receive(:new).with(user).and_return(eps_service)
117+
end
118+
119+
it 'returns normalized EpsSlot records' do
120+
allow(eps_service).to receive(:get_provider_slots).and_return(
121+
OpenStruct.new(slots: [{ id: 'eps-slot', start: '2025-01-01T12:00:00Z', provider_service_id: '9mN718pH' }])
122+
)
123+
124+
slots = adapter.slots_for(
125+
provider:,
126+
start_dt: 'ignored',
127+
end_dt: 'ignored',
128+
eps_opts:
129+
)
130+
131+
expect(slots.size).to eq(1)
132+
expect(slots.first).to be_a(VAOS::V2::Unified::EpsSlot)
133+
expect(slots.first.id).to eq('eps-slot')
134+
end
135+
136+
it 'returns an empty array when EPS returns no slots key' do
137+
allow(eps_service).to receive(:get_provider_slots).and_return(OpenStruct.new(slots: nil))
138+
139+
slots = adapter.slots_for(provider:, start_dt: 'a', end_dt: 'b', eps_opts:)
140+
141+
expect(slots).to eq([])
142+
end
143+
end
144+
end
145+
end

0 commit comments

Comments
 (0)