medicafe 0.250813.2__py3-none-any.whl → 0.250814.3__py3-none-any.whl

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.
@@ -1,910 +0,0 @@
1
- # api_core.py
2
- import time, requests, yaml, json, os, traceback
3
-
4
- try:
5
- from MediLink import MediLink_ConfigLoader
6
- from MediLink import MediLink_GraphQL
7
- except ImportError:
8
- import MediLink_ConfigLoader
9
- import MediLink_GraphQL
10
-
11
- """
12
- TODO At some point it might make sense to test their acknoledgment endpoint. body is transactionId.
13
- This API is used to extract the claim acknowledgement details for the given transactionid which was
14
- generated for 837 requests in claim submission process. Claims Acknowledgement (277CA) will provide
15
- a status of claim-level acknowledgement of all claims received in the front-end processing system and
16
- adjudication system.
17
- """
18
-
19
- class ConfigLoader:
20
- @staticmethod
21
- def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
22
- crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
23
- return MediLink_ConfigLoader.load_configuration(config_path, crosswalk_path)
24
-
25
- @staticmethod
26
- def load_swagger_file(swagger_path):
27
- try:
28
- print("Attempting to load Swagger file: {}".format(swagger_path))
29
- with open(swagger_path, 'r') as swagger_file:
30
- if swagger_path.endswith('.yaml') or swagger_path.endswith('.yml'):
31
- print("Parsing YAML file: {}".format(swagger_path))
32
- swagger_data = yaml.safe_load(swagger_file)
33
- elif swagger_path.endswith('.json'):
34
- print("Parsing JSON file: {}".format(swagger_path))
35
- swagger_data = json.load(swagger_file)
36
- else:
37
- raise ValueError("Unsupported Swagger file format.")
38
- print("Successfully loaded Swagger file: {}".format(swagger_path))
39
- return swagger_data
40
- except ValueError as e:
41
- print("Error parsing Swagger file {}: {}".format(swagger_path, e))
42
- MediLink_ConfigLoader.log("Error parsing Swagger file {}: {}".format(swagger_path, e), level="ERROR")
43
- except FileNotFoundError:
44
- print("Swagger file not found: {}".format(swagger_path))
45
- MediLink_ConfigLoader.log("Swagger file not found: {}".format(swagger_path), level="ERROR")
46
- except Exception as e:
47
- print("Unexpected error loading Swagger file {}: {}".format(swagger_path, e))
48
- MediLink_ConfigLoader.log("Unexpected error loading Swagger file {}: {}".format(swagger_path, e), level="ERROR")
49
- return None
50
-
51
- # Function to ensure numeric type
52
- def ensure_numeric(value):
53
- if isinstance(value, str):
54
- try:
55
- value = float(value)
56
- except ValueError:
57
- raise ValueError("Cannot convert {} to a numeric type".format(value))
58
- return value
59
-
60
- class TokenCache:
61
- def __init__(self):
62
- self.tokens = {}
63
-
64
- def get(self, endpoint_name, current_time):
65
- token_info = self.tokens.get(endpoint_name, {})
66
- if token_info:
67
- expires_at = token_info['expires_at']
68
- # Log cache hit and expiration time
69
- log_message = "Token for {} expires at {}. Current time: {}".format(endpoint_name, expires_at, current_time)
70
- MediLink_ConfigLoader.log(log_message, level="DEBUG")
71
-
72
- if expires_at > current_time:
73
- return token_info['access_token']
74
-
75
- # Log cache miss
76
- # Token refresh flow validation has been implemented in get_access_token() to prevent unnecessary token pickup
77
- log_message = "No valid token found for {}".format(endpoint_name)
78
- MediLink_ConfigLoader.log(log_message, level="INFO")
79
-
80
- return None
81
-
82
- def set(self, endpoint_name, access_token, expires_in, current_time):
83
- current_time = ensure_numeric(current_time)
84
- expires_in = ensure_numeric(expires_in)
85
-
86
- # Log the expires_in value to debug
87
- log_message = "Token expires in: {} seconds for {}".format(expires_in, endpoint_name)
88
- MediLink_ConfigLoader.log(log_message, level="INFO")
89
-
90
- # Adjust expiration time by subtracting a buffer of 120 seconds
91
- expires_at = current_time + expires_in - 120
92
- log_message = "Setting token for {}. Expires at: {}".format(endpoint_name, expires_at)
93
- MediLink_ConfigLoader.log(log_message, level="INFO")
94
-
95
- self.tokens[endpoint_name] = {
96
- 'access_token': access_token,
97
- 'expires_at': expires_at
98
- }
99
-
100
- class BaseAPIClient:
101
- def __init__(self, config):
102
- self.config = config
103
- self.token_cache = TokenCache()
104
-
105
- def get_access_token(self, endpoint_name):
106
- raise NotImplementedError("Subclasses should implement this!")
107
-
108
- def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
109
- raise NotImplementedError("Subclasses should implement this!")
110
-
111
- class APIClient(BaseAPIClient):
112
- def __init__(self):
113
- config, _ = MediLink_ConfigLoader.load_configuration()
114
- super().__init__(config)
115
-
116
- # Add enhanced features if available
117
- try:
118
- from MediLink_api_utils import APICircuitBreaker, APICache, APIRateLimiter
119
- from MediLink_insurance_utils import get_feature_flag
120
-
121
- # Initialize enhancements if enabled
122
- enable_circuit_breaker = get_feature_flag('api_circuit_breaker', default=False)
123
- enable_caching = get_feature_flag('api_caching', default=False)
124
- enable_rate_limiting = get_feature_flag('api_rate_limiting', default=False)
125
-
126
- self.circuit_breaker = APICircuitBreaker() if enable_circuit_breaker else None
127
- self.api_cache = APICache() if enable_caching else None
128
- self.rate_limiter = APIRateLimiter() if enable_rate_limiting else None
129
-
130
- if any([enable_circuit_breaker, enable_caching, enable_rate_limiting]):
131
- MediLink_ConfigLoader.log("Enhanced API client initialized with circuit_breaker={}, caching={}, rate_limiting={}".format(
132
- enable_circuit_breaker, enable_caching, enable_rate_limiting), level="INFO")
133
- except ImportError:
134
- MediLink_ConfigLoader.log("API enhancements not available, using standard client", level="DEBUG")
135
- self.circuit_breaker = None
136
- self.api_cache = None
137
- self.rate_limiter = None
138
-
139
- def get_access_token(self, endpoint_name):
140
- MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
141
- current_time = time.time()
142
- cached_token = self.token_cache.get(endpoint_name, current_time)
143
-
144
- if cached_token:
145
- expires_at = self.token_cache.tokens[endpoint_name]['expires_at']
146
- MediLink_ConfigLoader.log("Cached token expires at {}".format(expires_at), level="DEBUG")
147
- return cached_token
148
-
149
- # Validate that we actually need a token before fetching
150
- # Check if the endpoint configuration exists and is valid
151
- try:
152
- endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
153
- if not endpoint_config:
154
- MediLink_ConfigLoader.log("No configuration found for endpoint: {}".format(endpoint_name), level="ERROR")
155
- return None
156
-
157
- # Validate required configuration fields
158
- required_fields = ['token_url', 'client_id', 'client_secret']
159
- missing_fields = [field for field in required_fields if field not in endpoint_config]
160
- if missing_fields:
161
- MediLink_ConfigLoader.log("Missing required configuration fields for {}: {}".format(endpoint_name, missing_fields), level="ERROR")
162
- return None
163
-
164
- except KeyError:
165
- MediLink_ConfigLoader.log("Endpoint {} not found in configuration".format(endpoint_name), level="ERROR")
166
- return None
167
- except Exception as e:
168
- MediLink_ConfigLoader.log("Error validating endpoint configuration for {}: {}".format(endpoint_name, str(e)), level="ERROR")
169
- return None
170
-
171
- # If no valid token, fetch a new one
172
- token_url = endpoint_config['token_url']
173
- data = {
174
- 'grant_type': 'client_credentials',
175
- 'client_id': endpoint_config['client_id'],
176
- 'client_secret': endpoint_config['client_secret']
177
- }
178
-
179
- # Add scope if specified in the configuration
180
- if 'scope' in endpoint_config:
181
- data['scope'] = endpoint_config['scope']
182
-
183
- headers = {'Content-Type': 'application/x-www-form-urlencoded'}
184
-
185
- try:
186
- response = requests.post(token_url, headers=headers, data=data)
187
- response.raise_for_status()
188
- token_data = response.json()
189
- access_token = token_data['access_token']
190
- expires_in = token_data.get('expires_in', 3600)
191
-
192
- self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
193
- MediLink_ConfigLoader.log("Obtained NEW token for endpoint: {}".format(endpoint_name), level="INFO")
194
- return access_token
195
- except requests.exceptions.RequestException as e:
196
- MediLink_ConfigLoader.log("Failed to obtain token for {}: {}".format(endpoint_name, str(e)), level="ERROR")
197
- return None
198
- except (KeyError, ValueError) as e:
199
- MediLink_ConfigLoader.log("Invalid token response for {}: {}".format(endpoint_name, str(e)), level="ERROR")
200
- return None
201
-
202
- def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
203
- # Try enhanced API call if available
204
- if hasattr(self, 'circuit_breaker') and self.circuit_breaker:
205
- try:
206
- return self._make_enhanced_api_call(endpoint_name, call_type, url_extension, params, data, headers)
207
- except Exception as e:
208
- MediLink_ConfigLoader.log("Enhanced API call failed, falling back to standard: {}".format(str(e)), level="WARNING")
209
-
210
- # Standard API call logic
211
- return self._make_standard_api_call(endpoint_name, call_type, url_extension, params, data, headers)
212
-
213
- def _make_enhanced_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
214
- """Enhanced API call with circuit breaker, caching, and rate limiting"""
215
- # Check cache first (for GET requests)
216
- if self.api_cache and call_type == 'GET':
217
- cached_result = self.api_cache.get(endpoint_name, call_type, url_extension, params)
218
- if cached_result is not None:
219
- MediLink_ConfigLoader.log("Cache hit for {} {} {}".format(call_type, endpoint_name, url_extension), level="DEBUG")
220
- return cached_result
221
-
222
- # Check rate limits
223
- if self.rate_limiter:
224
- self.rate_limiter.wait_if_needed()
225
-
226
- # Make call with circuit breaker protection
227
- result = self.circuit_breaker.call_with_breaker(
228
- self._make_standard_api_call, endpoint_name, call_type, url_extension, params, data, headers)
229
-
230
- # Record rate limit call
231
- if self.rate_limiter:
232
- self.rate_limiter.record_call()
233
-
234
- # Cache result (for GET requests)
235
- if self.api_cache and call_type == 'GET':
236
- self.api_cache.set(result, endpoint_name, call_type, url_extension, params)
237
-
238
- return result
239
-
240
- def _make_standard_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
241
- """Standard API call logic preserved for compatibility"""
242
- token = self.get_access_token(endpoint_name)
243
- if token:
244
- MediLink_ConfigLoader.log("[Make API Call] Token found for {}".format(endpoint_name), level="DEBUG")
245
- else:
246
- MediLink_ConfigLoader.log("[Make API Call] No token obtained for {}".format(endpoint_name), level="ERROR")
247
- raise ValueError("No access token available for endpoint: {}".format(endpoint_name))
248
-
249
- if headers is None:
250
- headers = {}
251
- headers.update({'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'})
252
- base_url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url']
253
- url = base_url + url_extension
254
-
255
- try:
256
- masked_headers = headers.copy()
257
- if 'Authorization' in masked_headers:
258
- masked_headers['Authorization'] = 'Bearer ***'
259
-
260
- def make_request():
261
- if call_type == 'GET':
262
- return requests.get(url, headers=headers, params=params)
263
- elif call_type == 'POST':
264
- # Check if there are custom headers (any headers beyond Authorization and Accept)
265
- custom_headers = {k: v for k, v in headers.items() if k not in ['Authorization', 'Accept']}
266
-
267
- if custom_headers:
268
- # Log that custom headers were detected
269
- MediLink_ConfigLoader.log("Custom headers detected: {}".format(custom_headers), level="DEBUG")
270
- else:
271
- # Set default Content-Type if no custom headers
272
- headers['Content-Type'] = 'application/json'
273
- return requests.post(url, headers=headers, json=data)
274
- elif call_type == 'DELETE':
275
- return requests.delete(url, headers=headers)
276
- else:
277
- raise ValueError("Unsupported call type: {}".format(call_type))
278
-
279
- # Make initial request
280
- response = make_request()
281
-
282
- # If we get a 5xx error, wait and retry once
283
- if 500 <= response.status_code < 600:
284
- error_msg = "Received {} error from server for {} request to {}. Waiting 1 second before retry...".format(
285
- response.status_code, call_type, url
286
- )
287
- MediLink_ConfigLoader.log(error_msg, level="WARNING")
288
-
289
- # Add more verbose logging for 504 errors specifically
290
- if response.status_code == 504:
291
- MediLink_ConfigLoader.log(
292
- "504 Gateway Timeout detected. This usually indicates the server is overloaded or taking too long to respond. "
293
- "Retrying after 1 second delay...",
294
- level="WARNING"
295
- )
296
-
297
- time.sleep(1)
298
- response = make_request()
299
-
300
- # Log the retry result
301
- if response.status_code == 200:
302
- MediLink_ConfigLoader.log(
303
- "Retry successful! Request to {} now returned 200 status code.".format(url),
304
- level="INFO"
305
- )
306
- else:
307
- MediLink_ConfigLoader.log(
308
- "Retry failed. Request to {} still returned {} status code.".format(url, response.status_code),
309
- level="ERROR"
310
- )
311
-
312
- # Raise an HTTPError if the response was unsuccessful
313
- response.raise_for_status()
314
-
315
- return response.json()
316
-
317
- except requests.exceptions.HTTPError as http_err:
318
- # If http_err.response is None, handle it separately
319
- if http_err.response is None:
320
- log_message = (
321
- "HTTPError with no response. "
322
- "URL: {url}, "
323
- "Method: {method}, "
324
- "Params: {params}, "
325
- "Data: {data}, "
326
- "Headers: {masked_headers}, "
327
- "Error: {error}"
328
- ).format(
329
- url=url,
330
- method=call_type,
331
- params=params,
332
- data=data,
333
- masked_headers=masked_headers,
334
- error=str(http_err)
335
- )
336
- MediLink_ConfigLoader.log(log_message, level="ERROR")
337
- else:
338
- # Extract status code and full response content
339
- status_code = http_err.response.status_code
340
- try:
341
- # Try to log the JSON content if available
342
- response_content = http_err.response.json()
343
- except ValueError:
344
- # Fallback to raw text if JSON decoding fails
345
- response_content = http_err.response.text
346
-
347
- log_message = (
348
- "HTTPError: Status Code: {status}, "
349
- "URL: {url}, "
350
- "Method: {method}, "
351
- "Params: {params}, "
352
- "Data: {data}, "
353
- "Headers: {masked_headers}, "
354
- "Response Content: {content}"
355
- ).format(
356
- status=status_code,
357
- url=url,
358
- method=call_type,
359
- params=params,
360
- data=data,
361
- masked_headers=masked_headers,
362
- content=response_content
363
- )
364
- MediLink_ConfigLoader.log(log_message, level="ERROR")
365
- raise
366
-
367
- except requests.exceptions.RequestException as req_err:
368
- # Log connection-related issues or other request exceptions
369
- log_message = (
370
- "RequestException: No response received. "
371
- "URL: {url}, "
372
- "Method: {method}, "
373
- "Params: {params}, "
374
- "Data: {data}, "
375
- "Headers: {masked_headers}, "
376
- "Error: {error}"
377
- ).format(
378
- url=url,
379
- method=call_type,
380
- params=params,
381
- data=data,
382
- masked_headers=masked_headers,
383
- error=str(req_err)
384
- )
385
- MediLink_ConfigLoader.log(log_message, level="ERROR")
386
- raise
387
-
388
- except Exception as e:
389
- # Capture traceback for unexpected exceptions
390
- tb = traceback.format_exc()
391
- log_message = (
392
- "Unexpected error: {error}. "
393
- "URL: {url}, "
394
- "Method: {method}, "
395
- "Params: {params}, "
396
- "Data: {data}, "
397
- "Headers: {masked_headers}. "
398
- "Traceback: {traceback}"
399
- ).format(
400
- error=str(e),
401
- url=url,
402
- method=call_type,
403
- params=params,
404
- data=data,
405
- masked_headers=masked_headers,
406
- traceback=tb
407
- )
408
- MediLink_ConfigLoader.log(log_message, level="ERROR")
409
- raise
410
-
411
- def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
412
- """
413
- Fetches the payer name using the provided APIClient instance.
414
-
415
- :param client: An instance of APIClient
416
- :param payer_id: The payer ID to fetch
417
- :param primary_endpoint: The primary endpoint to use
418
- :return: The payer name if found
419
- """
420
- # Ensure client is an instance of APIClient
421
- if not isinstance(client, APIClient):
422
- error_message = "Invalid client provided. Expected an instance of APIClient."
423
- print(error_message)
424
- MediLink_ConfigLoader.log(error_message, level="ERROR")
425
- exit(1) # Exit the script
426
-
427
- # TODO: FUTURE IMPLEMENTATION - Remove AVAILITY default when other endpoints have payer-list APIs
428
- # Currently defaulting to AVAILITY as it's the only endpoint with confirmed payer-list functionality
429
- # In the future, this should be removed and the system should dynamically detect which endpoints
430
- # have payer-list capabilities and use them accordingly.
431
- if primary_endpoint != 'AVAILITY':
432
- MediLink_ConfigLoader.log("[Fetch payer name from API] Overriding {} with AVAILITY (default until multi-endpoint payer-list support is implemented).".format(primary_endpoint), level="DEBUG")
433
- primary_endpoint = 'AVAILITY'
434
-
435
- try:
436
- endpoints = config['MediLink_Config']['endpoints']
437
- except KeyError as e:
438
- error_message = "Configuration loading error in fetch_payer_name_from_api: Missing key {0}... Attempting to reload configuration.".format(e)
439
- # print(error_message)
440
- MediLink_ConfigLoader.log(error_message, level="ERROR")
441
- # Attempt to reload configuration if key is missing
442
- config, _ = MediLink_ConfigLoader.load_configuration()
443
- endpoints = config['MediLink_Config']['endpoints'] # Re-attempt to access endpoints
444
- MediLink_ConfigLoader.log("Re-loaded configuration successfully.", level="INFO")
445
-
446
- # Sanitize and validate payer_id
447
- if not isinstance(payer_id, str):
448
- payer_id = str(payer_id)
449
-
450
- payer_id = ''.join(char for char in payer_id if char.isalnum())
451
-
452
- if not payer_id:
453
- error_message = "Invalid payer_id in API v3: {}. Must contain a string of alphanumeric characters.".format(payer_id)
454
- MediLink_ConfigLoader.log(error_message, level="ERROR")
455
- print(error_message)
456
-
457
- # FUTURE IMPLEMENTATION: Dynamic endpoint selection based on payer-list availability
458
- # This will replace the hardcoded AVAILITY default when other endpoints have payer-list APIs
459
- # The logic should:
460
- # 1. Check all endpoints for 'payer_list_endpoint' configuration
461
- # 2. Prioritize endpoints that have confirmed payer-list functionality
462
- # 3. Fall back to endpoints with basic payer lookup if available
463
- # 4. Use AVAILITY as final fallback
464
-
465
- # Define endpoint rotation logic with payer-list capability detection
466
- available_endpoints = []
467
-
468
- # Check which endpoints have payer-list functionality configured
469
- for endpoint_name, endpoint_config in endpoints.items():
470
- if 'payer_list_endpoint' in endpoint_config:
471
- available_endpoints.append(endpoint_name)
472
- MediLink_ConfigLoader.log("Found payer-list endpoint for {}: {}".format(endpoint_name, endpoint_config['payer_list_endpoint']), level="DEBUG")
473
-
474
- # If no endpoints have payer-list configured, fall back to AVAILITY
475
- if not available_endpoints:
476
- MediLink_ConfigLoader.log("No endpoints with payer-list configuration found, using AVAILITY as fallback", level="INFO")
477
- available_endpoints = ['AVAILITY']
478
-
479
- # Prioritize the primary endpoint if it has payer-list capability
480
- if primary_endpoint in available_endpoints:
481
- endpoint_order = [primary_endpoint] + [ep for ep in available_endpoints if ep != primary_endpoint]
482
- else:
483
- # If primary endpoint doesn't have payer-list, use available endpoints in order
484
- endpoint_order = available_endpoints
485
-
486
- MediLink_ConfigLoader.log("Endpoint order for payer lookup: {}".format(endpoint_order), level="DEBUG")
487
-
488
- for endpoint_name in endpoint_order:
489
- try:
490
- endpoint_url = endpoints[endpoint_name].get('payer_list_endpoint', '/availity-payer-list')
491
- response = client.make_api_call(endpoint_name, 'GET', endpoint_url, {'payerId': payer_id})
492
-
493
- # Check if response exists
494
- if not response:
495
- log_message = "No response from {0} for Payer ID {1}".format(endpoint_name, payer_id)
496
- print(log_message)
497
- MediLink_ConfigLoader.log(log_message, level="ERROR")
498
- continue
499
-
500
- # Check if the status code is not 200
501
- status_code = response.get('statuscode', 200)
502
- if status_code != 200:
503
- log_message = "Invalid response status code {0} from {1} for Payer ID {2}. Message: {3}".format(
504
- status_code, endpoint_name, payer_id, response.get('message', 'No message'))
505
- print(log_message)
506
- MediLink_ConfigLoader.log(log_message, level="ERROR")
507
- continue
508
-
509
- # Extract payers and validate the response structure
510
- payers = response.get('payers', [])
511
- if not payers:
512
- log_message = "No payer found at {0} for ID {1}. Response: {2}".format(endpoint_name, payer_id, response)
513
- print(log_message)
514
- MediLink_ConfigLoader.log(log_message, level="INFO")
515
- continue
516
-
517
- # Extract the payer name from the first payer in the list
518
- payer_name = payers[0].get('displayName') or payers[0].get('name')
519
- if not payer_name:
520
- log_message = "Payer name not found in the response from {0} for ID {1}. Response: {2}".format(
521
- endpoint_name, payer_id, response)
522
- print(log_message)
523
- MediLink_ConfigLoader.log(log_message, level="ERROR")
524
- continue
525
-
526
- # Log successful payer retrieval
527
- log_message = "Found payer at {0} for ID {1}: {2}".format(endpoint_name, payer_id, payer_name)
528
- MediLink_ConfigLoader.log(log_message, level="INFO")
529
- return payer_name
530
-
531
- except Exception as e:
532
- error_message = "Error calling {0} for Payer ID {1}. Exception: {2}".format(endpoint_name, payer_id, e)
533
- MediLink_ConfigLoader.log(error_message, level="INFO")
534
-
535
- # If all endpoints fail
536
- final_error_message = "All endpoints exhausted for Payer ID {0}.".format(payer_id)
537
- print(final_error_message)
538
- MediLink_ConfigLoader.log(final_error_message, level="CRITICAL")
539
- raise ValueError(final_error_message)
540
-
541
- def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false'):
542
- endpoint_name = 'UHCAPI'
543
- url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_summary_by_provider']
544
- headers = {
545
- 'tin': tin,
546
- 'firstServiceDt': first_service_date,
547
- 'lastServiceDt': last_service_date,
548
- 'payerId': payer_id,
549
- 'getStandardError': get_standard_error,
550
- 'Accept': 'application/json'
551
- }
552
- return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
553
-
554
- def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
555
- endpoint_name = 'UHCAPI'
556
- url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility']
557
- url_extension = url_extension + '?payerID={}&providerLastName={}&searchOption={}&dateOfBirth={}&memberId={}&npi={}'.format(
558
- payer_id, provider_last_name, search_option, date_of_birth, member_id, npi)
559
- return client.make_api_call(endpoint_name, 'GET', url_extension)
560
-
561
- def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
562
- first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
563
- middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
564
- service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
565
- corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
566
- organization_id=None, identify_service_level_deductible=True):
567
-
568
- # Ensure all required parameters have values
569
- if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
570
- raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
571
-
572
- # Validate payer_id
573
- valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
574
- if payer_id not in valid_payer_ids:
575
- raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
576
-
577
- endpoint_name = 'UHCAPI'
578
- url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_v3']
579
-
580
- # Construct request body
581
- body = {
582
- "memberId": member_id,
583
- "lastName": last_name,
584
- "firstName": first_name,
585
- "dateOfBirth": date_of_birth,
586
- "payerID": payer_id,
587
- "payerLabel": payer_label,
588
- "payerName": payer_name,
589
- "serviceStart": service_start,
590
- "serviceEnd": service_end,
591
- "middleName": middle_name,
592
- "gender": gender,
593
- "ssn": ssn,
594
- "city": city,
595
- "state": state,
596
- "zip": zip,
597
- "groupNumber": group_number,
598
- "serviceTypeCode": service_type_code,
599
- "providerLastName": provider_last_name,
600
- "providerFirstName": provider_first_name,
601
- "taxIdNumber": tax_id_number,
602
- "providerNameID": provider_name_id,
603
- "npi": npi,
604
- "corporateTaxOwnerID": corporate_tax_owner_id,
605
- "corporateTaxOwnerName": corporate_tax_owner_name,
606
- "organizationName": organization_name,
607
- "organizationID": organization_id,
608
- "searchOption": search_option,
609
- "identifyServiceLevelDeductible": identify_service_level_deductible
610
- }
611
-
612
- # Remove None values from the body
613
- body = {k: v for k, v in body.items() if v is not None}
614
-
615
- # Log the request body
616
- MediLink_ConfigLoader.log("Request body: {}".format(json.dumps(body, indent=4)), level="DEBUG")
617
-
618
- return client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=body)
619
-
620
- def get_eligibility_super_connector(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
621
- first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
622
- middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
623
- service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
624
- corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
625
- organization_id=None, identify_service_level_deductible=True):
626
- """
627
- GraphQL Super Connector version of eligibility check that maps to the same interface as get_eligibility_v3.
628
- This function provides a drop-in replacement for the REST API with identical input/output behavior.
629
- """
630
- # Ensure all required parameters have values
631
- if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
632
- raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
633
-
634
- # Validate payer_id
635
- valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
636
- if payer_id not in valid_payer_ids:
637
- raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
638
-
639
- endpoint_name = 'UHCAPI'
640
- url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_super_connector']
641
-
642
- # Get provider TIN from config (using existing billing_provider_tin)
643
- provider_tin = client.config['MediLink_Config'].get('billing_provider_tin')
644
- if not provider_tin:
645
- raise ValueError("Provider TIN not found in configuration")
646
-
647
- # Construct GraphQL query variables using the consolidated module
648
- graphql_variables = MediLink_GraphQL.build_eligibility_variables(
649
- member_id=member_id,
650
- date_of_birth=date_of_birth,
651
- payer_id=payer_id,
652
- provider_last_name=provider_last_name,
653
- provider_npi=npi
654
- )
655
-
656
- # Validate NPI format (should be 10 digits)
657
- if 'providerNPI' in graphql_variables:
658
- npi_value = graphql_variables['providerNPI']
659
- if not npi_value.isdigit() or len(npi_value) != 10:
660
- MediLink_ConfigLoader.log("Warning: NPI '{}' is not 10 digits, but continuing anyway".format(npi_value), level="WARNING")
661
-
662
- # Build GraphQL request using the consolidated module
663
- # Hardcoded switch to use sample data for testing
664
- USE_SAMPLE_DATA = False # Set to False to use constructed data
665
-
666
- if USE_SAMPLE_DATA:
667
- # Use the sample data from swagger documentation
668
- graphql_body = MediLink_GraphQL.get_sample_eligibility_request()
669
- MediLink_ConfigLoader.log("Using SAMPLE DATA from swagger documentation", level="INFO")
670
- else:
671
- # Build GraphQL request with actual data using consolidated module
672
- graphql_body = MediLink_GraphQL.build_eligibility_request(graphql_variables)
673
- MediLink_ConfigLoader.log("Using CONSTRUCTED DATA with consolidated GraphQL module", level="INFO")
674
-
675
- # Compare with sample data for debugging
676
- sample_data = MediLink_GraphQL.get_sample_eligibility_request()
677
- MediLink_ConfigLoader.log("Sample data structure: {}".format(json.dumps(sample_data, indent=2)), level="DEBUG")
678
- MediLink_ConfigLoader.log("Constructed data structure: {}".format(json.dumps(graphql_body, indent=2)), level="DEBUG")
679
-
680
- # Compare key differences
681
- sample_vars = sample_data['variables']['input']
682
- constructed_vars = graphql_body['variables']['input']
683
-
684
- # Log differences in variables
685
- for key in set(sample_vars.keys()) | set(constructed_vars.keys()):
686
- sample_val = sample_vars.get(key)
687
- constructed_val = constructed_vars.get(key)
688
- if sample_val != constructed_val:
689
- MediLink_ConfigLoader.log("Variable difference - {}: sample='{}', constructed='{}'".format(
690
- key, sample_val, constructed_val), level="DEBUG")
691
-
692
- # Log the GraphQL request
693
- MediLink_ConfigLoader.log("GraphQL request body: {}".format(json.dumps(graphql_body, indent=2)), level="DEBUG")
694
- MediLink_ConfigLoader.log("GraphQL variables: {}".format(json.dumps(graphql_variables, indent=2)), level="DEBUG")
695
-
696
- # Add required headers for Super Connector
697
- headers = {
698
- 'Content-Type': 'application/json',
699
- 'Accept': 'application/json',
700
- 'tin': str(provider_tin) # Ensure TIN is a string
701
- }
702
-
703
- # Only add env header when using sample data
704
- if USE_SAMPLE_DATA:
705
- headers['env'] = 'sandbox'
706
-
707
- # Remove None values from headers
708
- headers = {k: v for k, v in headers.items() if v is not None}
709
-
710
- # Log the final headers being sent
711
- MediLink_ConfigLoader.log("Final headers being sent: {}".format(json.dumps(headers, indent=2)), level="DEBUG")
712
-
713
- # Make the GraphQL API call
714
- response = client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=graphql_body, headers=headers)
715
-
716
- # Transform GraphQL response to match REST API format
717
- # This ensures the calling code doesn't know the difference
718
- transformed_response = MediLink_GraphQL.transform_eligibility_response(response)
719
-
720
- return transformed_response
721
-
722
- def is_test_mode(client, body, endpoint_type):
723
- """
724
- Checks if Test Mode is enabled in the client's configuration and simulates the response if it is.
725
-
726
- :param client: An instance of APIClient
727
- :param body: The intended request body
728
- :param endpoint_type: The type of endpoint being accessed ('claim_submission' or 'claim_details')
729
- :return: A dummy response simulating the real API call if Test Mode is enabled, otherwise None
730
- """
731
- if client.config.get("MediLink_Config", {}).get("TestMode", True):
732
- print("Test Mode is enabled! API Call not executed.")
733
- print("\nIntended request body:", body)
734
- MediLink_ConfigLoader.log("Test Mode is enabled! Simulating 1 second delay for API response for {}.".format(endpoint_type), level="INFO")
735
- time.sleep(1)
736
- MediLink_ConfigLoader.log("Intended request body: {}".format(body), level="INFO")
737
-
738
- if endpoint_type == 'claim_submission':
739
- dummy_response = {
740
- "transactionId": "CS07180420240328013411240", # This is the tID for the sandbox Claim Acknowledgement endpoint.
741
- "x12ResponseData": "ISA*00* *00* *ZZ*TEST1234567890 *33*TEST *210101*0101*^*00501*000000001*0*P*:~GS*HC*TEST1234567890*TEST*20210101*0101*1*X*005010X222A1~ST*837*000000001*005010X222A1~BHT*0019*00*00001*20210101*0101*CH~NM1*41*2*TEST SUBMITTER*****46*TEST~PER*IC*TEST CONTACT*TE*1234567890~NM1*40*2*TEST RECEIVER*****46*TEST~HL*1**20*1~NM1*85*2*TEST PROVIDER*****XX*1234567890~N3*TEST ADDRESS~N4*TEST CITY*TEST STATE*12345~REF*EI*123456789~PER*IC*TEST PROVIDER*TE*1234567890~NM1*87*2~N3*TEST ADDRESS~N4*TEST CITY*TEST STATE*12345~HL*2*1*22*0~SBR*P*18*TEST GROUP******CI~NM1*IL*1*TEST PATIENT****MI*123456789~N3*TEST ADDRESS~N4*TEST CITY*TEST STATE*12345~DMG*D8*19800101*M~NM1*PR*2*TEST INSURANCE*****PI*12345~CLM*TESTCLAIM*100***12:B:1*Y*A*Y*Y*P~REF*D9*TESTREFERENCE~HI*ABK:TEST~NM1*DN*1*TEST DOCTOR****XX*1234567890~LX*1~SV1*HC:TEST*100*UN*1***1~DTP*472*RD8*20210101-20210101~REF*6R*TESTREFERENCE~SE*30*000000001~GE*1*1~IEA*1*000000001~",
742
- "responseType": "dummy_response_837999",
743
- "message": "Test Mode: Claim validated and sent for further processing"
744
- }
745
- elif endpoint_type == 'claim_details':
746
- dummy_response = {
747
- "responseType": "dummy_response_277CA-CH",
748
- "x12ResponseData": "ISA*00* *00* *ZZ*841162764 *ZZ*UB920086 *240318*0921*^*00501*000165687*0*T*:~GS*HN*841162764*UB920086*20240318*0921*0165687*X*005010X214~ST*277*000000006*005010X214~…………….. SE*116*000000006~GE*1*0165687~IEA*1*000165687~",
749
- "statuscode": "000",
750
- "message:": ""
751
- }
752
- return dummy_response
753
- return None
754
-
755
- def submit_uhc_claim(client, x12_request_data):
756
- """
757
- Submits a UHC claim and retrieves the claim acknowledgement details.
758
-
759
- This function first submits the claim using the provided x12 837p data. If the client is in Test Mode,
760
- it returns a simulated response. If Test Mode is not enabled, it submits the claim and then retrieves
761
- the claim acknowledgement details using the transaction ID from the initial response.
762
-
763
- NOTE: This function uses endpoints that may not be available in the new swagger version:
764
- - /Claims/api/claim-submission/v1 (claim submission)
765
- - /Claims/api/claim-details/v1 (claim acknowledgement)
766
-
767
- If these endpoints are deprecated in the new swagger, this function will need to be updated
768
- to use the new available endpoints.
769
-
770
- :param client: An instance of APIClient
771
- :param x12_request_data: The x12 837p data as a string
772
- :return: The final response containing the claim acknowledgement details or a dummy response if in Test Mode
773
- """
774
- endpoint_name = 'UHCAPI'
775
- claim_submission_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_submission']
776
- claim_details_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_details']
777
-
778
- # Headers for the request
779
- headers = {'Content-Type': 'application/json'}
780
-
781
- # Request body for claim submission
782
- claim_body = {'x12RequestData': x12_request_data}
783
-
784
- # Check if Test Mode is enabled and return simulated response if so
785
- test_mode_response = is_test_mode(client, claim_body, 'claim_submission')
786
- if test_mode_response:
787
- return test_mode_response
788
-
789
- # Make the API call to submit the claim
790
- try:
791
- submission_response = client.make_api_call(endpoint_name, 'POST', claim_submission_url, data=claim_body, headers=headers)
792
-
793
- # Extract the transaction ID from the submission response
794
- transaction_id = submission_response.get('transactionId')
795
- if not transaction_id:
796
- raise ValueError("transactionId not found in the submission response")
797
-
798
- # Log the transaction ID for traceability
799
- MediLink_ConfigLoader.log("UHCAPI claim submission transactionId: {}".format(transaction_id), level="INFO")
800
-
801
- # Prepare the request body for the claim acknowledgement retrieval
802
- acknowledgement_body = {'transactionId': transaction_id}
803
-
804
- # Check if Test Mode is enabled and return simulated response if so
805
- test_mode_response = is_test_mode(client, acknowledgement_body, 'claim_details')
806
- if test_mode_response:
807
- return test_mode_response
808
-
809
- # Make the API call to retrieve the claim acknowledgement details
810
- acknowledgement_response = client.make_api_call(endpoint_name, 'POST', claim_details_url, data=acknowledgement_body, headers=headers)
811
- return acknowledgement_response
812
-
813
- except Exception as e:
814
- print("Error during claim processing: {}".format(e))
815
- raise
816
-
817
- if __name__ == "__main__":
818
- client = APIClient()
819
-
820
- # Define a configuration to enable or disable tests
821
- test_config = {
822
- 'test_fetch_payer_name': False,
823
- 'test_claim_summary': False,
824
- 'test_eligibility': False,
825
- 'test_eligibility_v3': False,
826
- 'test_eligibility_super_connector': False,
827
- 'test_claim_submission': False,
828
- }
829
-
830
- try:
831
- api_test_cases = client.config['MediLink_Config']['API Test Case']
832
-
833
- # Test 1: Fetch Payer Name
834
- if test_config.get('test_fetch_payer_name', False):
835
- try:
836
- for case in api_test_cases:
837
- payer_name = fetch_payer_name_from_api(client, case['payer_id'], client.config)
838
- print("*** TEST API: Payer Name: {}".format(payer_name))
839
- except Exception as e:
840
- print("*** TEST API: Error in Fetch Payer Name Test: {}".format(e))
841
-
842
- # Test 2: Get Claim Summary
843
- if test_config.get('test_claim_summary', False):
844
- try:
845
- for case in api_test_cases:
846
- claim_summary = get_claim_summary_by_provider(client, case['provider_tin'], '05/01/2024', '06/23/2024', case['payer_id'])
847
- print("TEST API: Claim Summary: {}".format(claim_summary))
848
- except Exception as e:
849
- print("TEST API: Error in Claim Summary Test: {}".format(e))
850
-
851
- # Test 3: Get Eligibility
852
- if test_config.get('test_eligibility', False):
853
- try:
854
- for case in api_test_cases:
855
- eligibility = get_eligibility(client, case['payer_id'], case['provider_last_name'], case['search_option'],
856
- case['date_of_birth'], case['member_id'], case['npi'])
857
- print("TEST API: Eligibility: {}".format(eligibility))
858
- except Exception as e:
859
- print("TEST API: Error in Eligibility Test: {}".format(e))
860
-
861
- # Test 4: Get Eligibility v3
862
- if test_config.get('test_eligibility_v3', False):
863
- try:
864
- for case in api_test_cases:
865
- eligibility_v3 = get_eligibility_v3(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
866
- search_option=case['search_option'], date_of_birth=case['date_of_birth'],
867
- member_id=case['member_id'], npi=case['npi'])
868
- print("TEST API: Eligibility v3: {}".format(eligibility_v3))
869
- except Exception as e:
870
- print("TEST API: Error in Eligibility v3 Test: {}".format(e))
871
-
872
- # Test 5: Get Eligibility Super Connector (GraphQL)
873
- if test_config.get('test_eligibility_super_connector', False):
874
- try:
875
- for case in api_test_cases:
876
- eligibility_super_connector = get_eligibility_super_connector(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
877
- search_option=case['search_option'], date_of_birth=case['date_of_birth'],
878
- member_id=case['member_id'], npi=case['npi'])
879
- print("TEST API: Eligibility Super Connector: {}".format(eligibility_super_connector))
880
- except Exception as e:
881
- print("TEST API: Error in Eligibility Super Connector Test: {}".format(e))
882
-
883
- """
884
- # Example of iterating over multiple patients (if needed)
885
- patients = [
886
- {'payer_id': '87726', 'provider_last_name': 'VIDA', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1980-01-01', 'member_id': '123456789', 'npi': '9876543210'},
887
- {'payer_id': '87726', 'provider_last_name': 'SMITH', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1970-02-02', 'member_id': '987654321', 'npi': '1234567890'},
888
- # Add more patients as needed
889
- ]
890
-
891
- for patient in patients:
892
- try:
893
- eligibility = get_eligibility(client, patient['payer_id'], patient['provider_last_name'], patient['search_option'], patient['date_of_birth'], patient['member_id'], patient['npi'])
894
- print("Eligibility for {}: {}".format(patient['provider_last_name'], eligibility))
895
- except Exception as e:
896
- print("Error in getting eligibility for {}: {}".format(patient['provider_last_name'], e))
897
- """
898
- # Test 6: UHC Claim Submission
899
- if test_config.get('test_claim_submission', False):
900
- try:
901
- x12_request_data = (
902
- "ISA*00* *00* *ZZ*BRT219991205 *33*87726 *170417*1344*^*00501*019160001*0*P*:~GS*HC*BRT219991205*B2BRTA*20170417*134455*19160001*X*005010X222A1~ST*837*000000001*005010X222A1~BHT*0019*00*00001*20170417*134455*CH~NM1*41*2*B00099999819*****46*BB2B~PER*IC*NO NAME*TE*1234567890~NM1*40*2*TIGER*****46*87726~HL*1**20*1~NM1*85*2*XYZ ADDRESS*****XX*1073511762~N3*123 CITY#680~N4*STATE*TG*98765~REF*EI*943319804~PER*IC*XYZ ADDRESS*TE*8008738385*TE*9142862043*FX*1234567890~NM1*87*2~N3*PO BOX 277500~N4*STATE*TS*303847000~HL*2*1*22*0~SBR*P*18*701648******CI~NM1*IL*1*FNAME*LNAME****MI*00123456789~N3*2020 CITY~N4*STATE*TG*80001~DMG*D8*19820220*M~NM1*PR*2*PROVIDER XYZ*****PI*87726~\nCLM*TOSRTA-SPL1*471***12:B:1*Y*A*Y*Y*P~REF*D9*H4HZMH0R4P0104~HI*ABK:Z12~NM1*DN*1*DN*SKO****XX*1255589300~LX*1~SV1*HC:73525*471*UN*1***1~DTP*472*RD8*0190701-20190701~REF*6R*2190476543Z1~SE*30*000000001~GE*1*19160001~IEA*1*019160001~"
903
- )
904
- response = submit_uhc_claim(client, x12_request_data)
905
- print("\nTEST API: Claim Submission Response:\n", response)
906
- except Exception as e:
907
- print("\nTEST API: Error in Claim Submission Test:\n", e)
908
-
909
- except Exception as e:
910
- print("TEST API: Unexpected Error: {}".format(e))