multiwoven-integrations 0.40.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e35c4cdc96ef297f6f538368d5c14c5fb20f26e81137801049350d9c0ccc51aa
4
- data.tar.gz: 17aa07d139d7338a09ff7e20529245e310f6ce18b448e7f8812837df413b3f78
3
+ metadata.gz: d02ac40ce23d00833bf9387f1363d3f871c0abdfb500c235e3d44a37110c9d8f
4
+ data.tar.gz: e76e2c81bf09fc64962b2639addb8d11ff7d9ad37a38c88b1267e0778d77bd95
5
5
  SHA512:
6
- metadata.gz: 2f77986008b3370e138389a8918e4f1250781efc130299bbc078c8fa38a08f26b0487f1247208ac759c7c02e87bd6fcc9dd953eb7be2acf43983478ad0f23d39
7
- data.tar.gz: a4e749c3b6d1fc27e9a8eb08d6659f3185f40f4c5d7154bee3392a666c3e83058453380fc0ecf5c7424e869c4ba623f4e69b8b1088f78e823c8ede48668920e6
6
+ metadata.gz: 2bf4882833f8c7e2f823b0077b3a4402d78f5c6acbdee0dc068063dd0be1310b0005c3f73305f26389e4bbf34d17ed6011aa6251852dfc0b17a7956a2928bba2
7
+ data.tar.gz: d460e9bd2b1e2b26fff0c1dd33148952c5f8f1d241cb208bc4da5c4cefbdd9e33b16e6e90ecc449c8e1ddd047b2a40c718dcb51f20aa0b7a9e4d61e21f301229
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.40.0"
5
+ VERSION = "0.40.1"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -8,6 +8,7 @@ module Multiwoven::Integrations::Source
8
8
  NDJSON_ACCEPT = "application/fhir+ndjson"
9
9
  DEFAULT_EXPORT_POLL_INTERVAL = 5
10
10
  DEFAULT_EXPORT_TIMEOUT = 120
11
+ CONCURRENT_EXPORT_MESSAGE = "Another request for this same Client and Group is in progress."
11
12
  REQUIRED_CONFIG_KEYS = %w[fhir_base_url token_url client_id private_key kid export_group_id].freeze
12
13
  DEFAULT_RESOURCES = %w[
13
14
  AllergyIntolerance
@@ -203,36 +204,68 @@ module Multiwoven::Integrations::Source
203
204
  end
204
205
 
205
206
  def run_bulk_export(connection_config, resource_type)
206
- status_url = kickoff_bulk_export(connection_config, resource_type)
207
- poll_bulk_export(connection_config, status_url)
207
+ deadline = Time.now + export_timeout(connection_config)
208
+ status_url = kickoff_bulk_export(connection_config, resource_type, deadline: deadline)
209
+ poll_bulk_export(connection_config, status_url, deadline: deadline)
208
210
  end
209
211
 
210
- def kickoff_bulk_export(connection_config, resource_type)
212
+ def kickoff_bulk_export(connection_config, resource_type, deadline:)
211
213
  url = bulk_export_url(connection_config, resource_type)
212
- response = fhir_get(connection_config, url, extra_headers: { "Prefer" => "respond-async" })
213
- raise fhir_api_error(response, url) unless response.code.to_s == "202"
214
214
 
215
- status_url = response["content-location"].presence
216
- raise StandardError, "Epic FHIR 202 #{url} missing Content-Location header" if status_url.nil?
215
+ loop do
216
+ response = fhir_get(connection_config, url, extra_headers: { "Prefer" => "respond-async" })
217
+ if response.code.to_s == "202"
218
+ status_url = response["content-location"].presence
219
+ raise StandardError, "Epic FHIR 202 #{url} missing Content-Location header" if status_url.nil?
217
220
 
218
- status_url
219
- end
221
+ return status_url
222
+ end
220
223
 
221
- def poll_bulk_export(connection_config, status_url)
222
- timeout = export_timeout(connection_config)
223
- deadline = Time.now + timeout
224
+ raise fhir_api_error(response, url) unless concurrent_export?(response)
225
+ raise concurrent_export_timeout_error unless wait_for_retry?(connection_config, response, deadline)
226
+ end
227
+ end
224
228
 
229
+ def poll_bulk_export(connection_config, status_url, deadline:)
225
230
  loop do
226
231
  response = fhir_get(connection_config, status_url)
227
232
  code = response.code.to_s
228
233
  return JSON.parse(response.body) if code == "200"
229
234
  raise fhir_api_error(response, status_url) unless code == "202"
230
- raise StandardError, "Epic FHIR 202 #{status_url} still in progress after #{timeout}s" if Time.now >= deadline
231
235
 
232
- sleep(retry_after(response) || export_poll_interval(connection_config))
236
+ unless wait_for_retry?(connection_config, response, deadline)
237
+ raise StandardError,
238
+ "Epic FHIR 202 #{status_url} still in progress after #{export_timeout(connection_config)}s"
239
+ end
233
240
  end
234
241
  end
235
242
 
243
+ # Waits between attempts without ever waiting past the shared export
244
+ # deadline: sleeping longer and then retrying would issue a request the
245
+ # caller has already given up on, and Epic counts it as one more
246
+ # concurrent export for this client and group.
247
+ def wait_for_retry?(connection_config, response, deadline)
248
+ remaining = deadline - Time.now
249
+ return false unless remaining.positive?
250
+
251
+ delay = retry_after(response) || export_poll_interval(connection_config)
252
+ return false if delay > remaining
253
+
254
+ sleep(delay)
255
+ true
256
+ end
257
+
258
+ def concurrent_export?(response)
259
+ response.body.to_s.downcase.include?(CONCURRENT_EXPORT_MESSAGE.downcase)
260
+ end
261
+
262
+ def concurrent_export_timeout_error
263
+ StandardError.new(
264
+ "Epic is still preparing another bulk export for this Client and Group. " \
265
+ "Retry after the existing export completes."
266
+ )
267
+ end
268
+
236
269
  def download_bulk_output(connection_config, manifest, resource_type, limit:, offset:)
237
270
  return [] if limit&.zero?
238
271
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multiwoven-integrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.40.0
4
+ version: 0.40.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subin T P
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-17 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport