medicafe 0.250728.9__py3-none-any.whl → 0.250805.0__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.

Potentially problematic release.


This version of medicafe might be problematic. Click here for more details.

Files changed (57) hide show
  1. MediBot/MediBot.bat +233 -19
  2. MediBot/MediBot.py +138 -46
  3. MediBot/MediBot_Crosswalk_Library.py +127 -623
  4. MediBot/MediBot_Crosswalk_Utils.py +618 -0
  5. MediBot/MediBot_Preprocessor.py +72 -17
  6. MediBot/MediBot_Preprocessor_lib.py +470 -76
  7. MediBot/MediBot_UI.py +32 -17
  8. MediBot/MediBot_dataformat_library.py +68 -20
  9. MediBot/MediBot_docx_decoder.py +120 -19
  10. MediBot/MediBot_smart_import.py +180 -0
  11. MediBot/__init__.py +89 -0
  12. MediBot/get_medicafe_version.py +25 -0
  13. MediBot/update_json.py +35 -6
  14. MediBot/update_medicafe.py +19 -1
  15. MediCafe/MediLink_ConfigLoader.py +160 -0
  16. MediCafe/__init__.py +171 -0
  17. MediCafe/__main__.py +222 -0
  18. MediCafe/api_core.py +1098 -0
  19. MediCafe/api_core_backup.py +427 -0
  20. MediCafe/api_factory.py +306 -0
  21. MediCafe/api_utils.py +356 -0
  22. MediCafe/core_utils.py +450 -0
  23. MediCafe/graphql_utils.py +445 -0
  24. MediCafe/logging_config.py +123 -0
  25. MediCafe/logging_demo.py +61 -0
  26. MediCafe/migration_helpers.py +463 -0
  27. MediCafe/smart_import.py +436 -0
  28. MediLink/MediLink_837p_cob_library.py +28 -28
  29. MediLink/MediLink_837p_encoder.py +33 -34
  30. MediLink/MediLink_837p_encoder_library.py +226 -150
  31. MediLink/MediLink_837p_utilities.py +129 -5
  32. MediLink/MediLink_API_Generator.py +83 -60
  33. MediLink/MediLink_API_v3.py +1 -1
  34. MediLink/MediLink_ClaimStatus.py +177 -31
  35. MediLink/MediLink_DataMgmt.py +378 -63
  36. MediLink/MediLink_Decoder.py +20 -1
  37. MediLink/MediLink_Deductible.py +155 -28
  38. MediLink/MediLink_Display_Utils.py +72 -0
  39. MediLink/MediLink_Down.py +127 -5
  40. MediLink/MediLink_Gmail.py +712 -653
  41. MediLink/MediLink_PatientProcessor.py +257 -0
  42. MediLink/MediLink_UI.py +85 -71
  43. MediLink/MediLink_Up.py +28 -4
  44. MediLink/MediLink_insurance_utils.py +227 -230
  45. MediLink/MediLink_main.py +248 -0
  46. MediLink/MediLink_smart_import.py +264 -0
  47. MediLink/__init__.py +93 -1
  48. MediLink/insurance_type_integration_test.py +13 -3
  49. MediLink/test.py +1 -1
  50. MediLink/test_timing.py +59 -0
  51. {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/METADATA +1 -1
  52. medicafe-0.250805.0.dist-info/RECORD +81 -0
  53. medicafe-0.250805.0.dist-info/entry_points.txt +2 -0
  54. {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/top_level.txt +1 -0
  55. medicafe-0.250728.9.dist-info/RECORD +0 -59
  56. {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/LICENSE +0 -0
  57. {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/WHEEL +0 -0
MediCafe/api_core.py ADDED
@@ -0,0 +1,1098 @@
1
+ # MediCafe/api_core.py
2
+ """
3
+ Core API functionality for MediCafe.
4
+ Moved from MediLink to centralize shared API operations.
5
+
6
+ COMPATIBILITY: Python 3.4.4 and Windows XP compatible
7
+ """
8
+
9
+ import time, json, os, traceback, sys, requests
10
+ from datetime import datetime, timedelta
11
+
12
+ # Import centralized logging configuration
13
+ try:
14
+ from MediCafe.logging_config import DEBUG, CONSOLE_LOGGING
15
+ except ImportError:
16
+ # Fallback to local flags if centralized config is not available
17
+ DEBUG = False
18
+ CONSOLE_LOGGING = False
19
+
20
+ # Set up project paths first
21
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
22
+ if project_dir not in sys.path:
23
+ sys.path.insert(0, project_dir)
24
+
25
+ try:
26
+ import yaml
27
+ except ImportError:
28
+ yaml = None
29
+ try:
30
+ import requests
31
+ except ImportError:
32
+ requests = None
33
+
34
+ # Use core utilities for standardized imports
35
+ try:
36
+ from MediCafe.core_utils import get_shared_config_loader
37
+ MediLink_ConfigLoader = get_shared_config_loader()
38
+ except ImportError:
39
+ try:
40
+ from .core_utils import get_shared_config_loader
41
+ MediLink_ConfigLoader = get_shared_config_loader()
42
+ except ImportError:
43
+ # Fallback to direct import
44
+ from MediCafe.MediLink_ConfigLoader import MediLink_ConfigLoader
45
+
46
+ try:
47
+ from MediCafe import graphql_utils as MediLink_GraphQL
48
+ except ImportError:
49
+ try:
50
+ from MediLink import MediLink_GraphQL
51
+ except ImportError:
52
+ try:
53
+ import graphql_utils as MediLink_GraphQL
54
+ except ImportError:
55
+ # Create a dummy module if graphql_utils is not available
56
+ class DummyGraphQL:
57
+ @staticmethod
58
+ def transform_eligibility_response(response):
59
+ return response
60
+ MediLink_GraphQL = DummyGraphQL()
61
+
62
+ """
63
+ TODO At some point it might make sense to test their acknoledgment endpoint. body is transactionId.
64
+ This API is used to extract the claim acknowledgement details for the given transactionid which was
65
+ generated for 837 requests in claim submission process. Claims Acknowledgement (277CA) will provide
66
+ a status of claim-level acknowledgement of all claims received in the front-end processing system and
67
+ adjudication system.
68
+ """
69
+
70
+ class ConfigLoader:
71
+ @staticmethod
72
+ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
73
+ crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
74
+ return MediLink_ConfigLoader.load_configuration(config_path, crosswalk_path)
75
+
76
+ @staticmethod
77
+ def load_swagger_file(swagger_path):
78
+ try:
79
+ print("Attempting to load Swagger file: {}".format(swagger_path))
80
+ with open(swagger_path, 'r') as swagger_file:
81
+ if swagger_path.endswith('.yaml') or swagger_path.endswith('.yml'):
82
+ print("Parsing YAML file: {}".format(swagger_path))
83
+ swagger_data = yaml.safe_load(swagger_file)
84
+ elif swagger_path.endswith('.json'):
85
+ print("Parsing JSON file: {}".format(swagger_path))
86
+ swagger_data = json.load(swagger_file)
87
+ else:
88
+ raise ValueError("Unsupported Swagger file format.")
89
+ print("Successfully loaded Swagger file: {}".format(swagger_path))
90
+ return swagger_data
91
+ except ValueError as e:
92
+ print("Error parsing Swagger file {}: {}".format(swagger_path, e))
93
+ MediLink_ConfigLoader.log("Error parsing Swagger file {}: {}".format(swagger_path, e), level="ERROR")
94
+ except FileNotFoundError:
95
+ print("Swagger file not found: {}".format(swagger_path))
96
+ MediLink_ConfigLoader.log("Swagger file not found: {}".format(swagger_path), level="ERROR")
97
+ except Exception as e:
98
+ print("Unexpected error loading Swagger file {}: {}".format(swagger_path, e))
99
+ MediLink_ConfigLoader.log("Unexpected error loading Swagger file {}: {}".format(swagger_path, e), level="ERROR")
100
+ return None
101
+
102
+ # Function to ensure numeric type
103
+ def ensure_numeric(value):
104
+ if isinstance(value, str):
105
+ try:
106
+ value = float(value)
107
+ except ValueError:
108
+ raise ValueError("Cannot convert {} to a numeric type".format(value))
109
+ return value
110
+
111
+ # Import utilities from api_utils
112
+ try:
113
+ from MediCafe.api_utils import TokenCache
114
+ except ImportError:
115
+ # Fallback if api_utils is not available
116
+ class TokenCache:
117
+ def __init__(self):
118
+ self.tokens = {}
119
+ def get(self, endpoint_name, current_time):
120
+ return None
121
+ def set(self, endpoint_name, access_token, expires_in, current_time):
122
+ pass
123
+
124
+ class BaseAPIClient:
125
+ def __init__(self, config):
126
+ self.config = config
127
+ self.token_cache = TokenCache()
128
+
129
+ def get_access_token(self, endpoint_name):
130
+ raise NotImplementedError("Subclasses should implement this!")
131
+
132
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
133
+ raise NotImplementedError("Subclasses should implement this!")
134
+
135
+ class APIClient(BaseAPIClient):
136
+ def __init__(self):
137
+ config, _ = MediLink_ConfigLoader.load_configuration()
138
+ super().__init__(config)
139
+
140
+ # Add enhanced features if available
141
+ try:
142
+ from MediCafe.api_utils import APICircuitBreaker, APICache, APIRateLimiter
143
+ from MediLink.MediLink_insurance_utils import get_feature_flag
144
+
145
+ # Initialize enhancements if enabled
146
+ enable_circuit_breaker = get_feature_flag('api_circuit_breaker', default=False)
147
+ enable_caching = get_feature_flag('api_caching', default=False)
148
+ enable_rate_limiting = get_feature_flag('api_rate_limiting', default=False)
149
+
150
+ self.circuit_breaker = APICircuitBreaker() if enable_circuit_breaker else None
151
+ self.api_cache = APICache() if enable_caching else None
152
+ self.rate_limiter = APIRateLimiter() if enable_rate_limiting else None
153
+
154
+ if any([enable_circuit_breaker, enable_caching, enable_rate_limiting]):
155
+ MediLink_ConfigLoader.log("Enhanced API client initialized with circuit_breaker={}, caching={}, rate_limiting={}".format(
156
+ enable_circuit_breaker, enable_caching, enable_rate_limiting), level="INFO")
157
+ except ImportError:
158
+ MediLink_ConfigLoader.log("API enhancements not available, using standard client", level="DEBUG")
159
+ self.circuit_breaker = None
160
+ self.api_cache = None
161
+ self.rate_limiter = None
162
+
163
+ def detect_environment(self, endpoint_name='UHCAPI'):
164
+ """Detect if we're running in staging/test environment for a specific endpoint"""
165
+ try:
166
+ # Look for api_url in the specified endpoint configuration
167
+ api_url = self.config.get('MediLink_Config', {}).get('endpoints', {}).get(endpoint_name, {}).get('api_url', '')
168
+ MediLink_ConfigLoader.log("DEBUG: Found API URL for {}: {}".format(endpoint_name, api_url), level="DEBUG")
169
+
170
+ if 'stg' in api_url.lower() or 'stage' in api_url.lower() or 'test' in api_url.lower():
171
+ MediLink_ConfigLoader.log("DEBUG: Detected staging environment for {} from URL: {}".format(endpoint_name, api_url), level="DEBUG")
172
+ return 'sandbox'
173
+ else:
174
+ MediLink_ConfigLoader.log("DEBUG: No staging indicators found in {} URL: {}".format(endpoint_name, api_url), level="DEBUG")
175
+ except Exception as e:
176
+ MediLink_ConfigLoader.log("DEBUG: Error in environment detection for {}: {}".format(endpoint_name, e), level="DEBUG")
177
+
178
+ # Default to production (no env parameter)
179
+ return None
180
+
181
+ def add_environment_headers(self, headers, endpoint_name):
182
+ """Add environment-specific headers based on detected environment"""
183
+ # TODO: ENVIRONMENT DETECTION ARCHITECTURE ISSUE
184
+ #
185
+ # Current Problem:
186
+ # - Environment detection is endpoint-specific but the logic may be too broad
187
+ # - If UHCAPI has staging URL, it might incorrectly add env headers to other endpoints
188
+ # - Different endpoints may have different environment requirements
189
+ #
190
+ # Potential Solutions:
191
+ # 1. Make environment detection truly per-endpoint (check each endpoint's own URL)
192
+ # 2. Add endpoint-specific configuration for which endpoints need env headers
193
+ # 3. Create a whitelist of endpoints that support environment headers
194
+ # 4. Move environment logic to endpoint-specific API functions rather than global client
195
+ #
196
+ # Current Implementation:
197
+ # - Only adds env headers for UHCAPI endpoint
198
+ # - Uses endpoint-specific URL detection
199
+ # - May need refinement for multi-endpoint scenarios
200
+ if endpoint_name == 'UHCAPI':
201
+ environment = self.detect_environment(endpoint_name)
202
+ if environment:
203
+ headers['env'] = environment
204
+ MediLink_ConfigLoader.log("Added env parameter for staging environment: {}".format(environment), level="INFO", console_output=CONSOLE_LOGGING)
205
+ else:
206
+ MediLink_ConfigLoader.log("No env parameter - using production environment", level="INFO", console_output=CONSOLE_LOGGING)
207
+ return headers
208
+
209
+ def get_access_token(self, endpoint_name):
210
+ MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
211
+ current_time = time.time()
212
+ cached_token = self.token_cache.get(endpoint_name, current_time)
213
+
214
+ if cached_token:
215
+ expires_at = self.token_cache.tokens[endpoint_name]['expires_at']
216
+ MediLink_ConfigLoader.log("Cached token expires at {}".format(expires_at), level="DEBUG")
217
+ return cached_token
218
+
219
+ # Validate that we actually need a token before fetching
220
+ # Check if the endpoint configuration exists and is valid
221
+ try:
222
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
223
+ if not endpoint_config:
224
+ MediLink_ConfigLoader.log("No configuration found for endpoint: {}".format(endpoint_name), level="ERROR")
225
+ return None
226
+
227
+ # Validate required configuration fields
228
+ required_fields = ['token_url', 'client_id', 'client_secret']
229
+ missing_fields = [field for field in required_fields if field not in endpoint_config]
230
+ if missing_fields:
231
+ MediLink_ConfigLoader.log("Missing required configuration fields for {}: {}".format(endpoint_name, missing_fields), level="ERROR")
232
+ return None
233
+
234
+ except KeyError:
235
+ MediLink_ConfigLoader.log("Endpoint {} not found in configuration".format(endpoint_name), level="ERROR")
236
+ return None
237
+ except Exception as e:
238
+ MediLink_ConfigLoader.log("Error validating endpoint configuration for {}: {}".format(endpoint_name, str(e)), level="ERROR")
239
+ return None
240
+
241
+ # If no valid token, fetch a new one
242
+ token_url = endpoint_config['token_url']
243
+ data = {
244
+ 'grant_type': 'client_credentials',
245
+ 'client_id': endpoint_config['client_id'],
246
+ 'client_secret': endpoint_config['client_secret']
247
+ }
248
+
249
+ # Add scope if specified in the configuration
250
+ if 'scope' in endpoint_config:
251
+ data['scope'] = endpoint_config['scope']
252
+
253
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
254
+
255
+ try:
256
+ response = requests.post(token_url, headers=headers, data=data)
257
+ response.raise_for_status()
258
+ token_data = response.json()
259
+ access_token = token_data['access_token']
260
+ expires_in = token_data.get('expires_in', 3600)
261
+
262
+ self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
263
+ MediLink_ConfigLoader.log("Obtained NEW token for endpoint: {}".format(endpoint_name), level="INFO", console_output=CONSOLE_LOGGING)
264
+ return access_token
265
+ except requests.exceptions.RequestException as e:
266
+ MediLink_ConfigLoader.log("Failed to obtain token for {}: {}".format(endpoint_name, str(e)), level="ERROR")
267
+ return None
268
+ except (KeyError, ValueError) as e:
269
+ MediLink_ConfigLoader.log("Invalid token response for {}: {}".format(endpoint_name, str(e)), level="ERROR")
270
+ return None
271
+
272
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
273
+ # Try enhanced API call if available
274
+ if hasattr(self, 'circuit_breaker') and self.circuit_breaker:
275
+ try:
276
+ return self._make_enhanced_api_call(endpoint_name, call_type, url_extension, params, data, headers)
277
+ except Exception as e:
278
+ MediLink_ConfigLoader.log("Enhanced API call failed, falling back to standard: {}".format(str(e)), level="WARNING")
279
+
280
+ # Standard API call logic
281
+ return self._make_standard_api_call(endpoint_name, call_type, url_extension, params, data, headers)
282
+
283
+ def _make_enhanced_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
284
+ """Enhanced API call with circuit breaker, caching, and rate limiting"""
285
+ # Check cache first (for GET requests)
286
+ if self.api_cache and call_type == 'GET':
287
+ cached_result = self.api_cache.get(endpoint_name, call_type, url_extension, params)
288
+ if cached_result is not None:
289
+ MediLink_ConfigLoader.log("Cache hit for {} {} {}".format(call_type, endpoint_name, url_extension), level="DEBUG")
290
+ return cached_result
291
+
292
+ # Check rate limits
293
+ if self.rate_limiter:
294
+ self.rate_limiter.wait_if_needed()
295
+
296
+ # Make call with circuit breaker protection
297
+ result = self.circuit_breaker.call_with_breaker(
298
+ self._make_standard_api_call, endpoint_name, call_type, url_extension, params, data, headers)
299
+
300
+ # Record rate limit call
301
+ if self.rate_limiter:
302
+ self.rate_limiter.record_call()
303
+
304
+ # Cache result (for GET requests)
305
+ if self.api_cache and call_type == 'GET':
306
+ self.api_cache.set(result, endpoint_name, call_type, url_extension, params)
307
+
308
+ return result
309
+
310
+ def _make_standard_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
311
+ """Standard API call logic preserved for compatibility"""
312
+ token = self.get_access_token(endpoint_name)
313
+ if token:
314
+ MediLink_ConfigLoader.log("[Make API Call] Token found for {}".format(endpoint_name), level="DEBUG")
315
+ else:
316
+ MediLink_ConfigLoader.log("[Make API Call] No token obtained for {}".format(endpoint_name), level="ERROR")
317
+ raise ValueError("No access token available for endpoint: {}".format(endpoint_name))
318
+
319
+ if headers is None:
320
+ headers = {}
321
+ headers.update({'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'})
322
+
323
+ # Add environment-specific headers automatically
324
+ headers = self.add_environment_headers(headers, endpoint_name)
325
+
326
+ base_url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url']
327
+ url = base_url + url_extension
328
+
329
+ # VERBOSE LOGGING: Log all request details before making the call
330
+ if DEBUG:
331
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
332
+ MediLink_ConfigLoader.log("VERBOSE API CALL DETAILS", level="INFO")
333
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
334
+ MediLink_ConfigLoader.log("Endpoint Name: {}".format(endpoint_name), level="INFO")
335
+ MediLink_ConfigLoader.log("Call Type: {}".format(call_type), level="INFO")
336
+ MediLink_ConfigLoader.log("Base URL: {}".format(base_url), level="INFO")
337
+ MediLink_ConfigLoader.log("URL Extension: {}".format(url_extension), level="INFO")
338
+ MediLink_ConfigLoader.log("Full URL: {}".format(url), level="INFO")
339
+ MediLink_ConfigLoader.log("Headers: {}".format(json.dumps(headers, indent=2)), level="INFO")
340
+ if params:
341
+ MediLink_ConfigLoader.log("Query Parameters: {}".format(json.dumps(params, indent=2)), level="INFO")
342
+ else:
343
+ MediLink_ConfigLoader.log("Query Parameters: None", level="INFO")
344
+ if data:
345
+ MediLink_ConfigLoader.log("Request Data: {}".format(json.dumps(data, indent=2)), level="INFO")
346
+ else:
347
+ MediLink_ConfigLoader.log("Request Data: None", level="INFO")
348
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
349
+
350
+ try:
351
+ masked_headers = headers.copy()
352
+ if 'Authorization' in masked_headers:
353
+ masked_headers['Authorization'] = 'Bearer ***'
354
+
355
+ def make_request():
356
+ if call_type == 'GET':
357
+ if DEBUG:
358
+ MediLink_ConfigLoader.log("Making GET request to: {}".format(url), level="INFO")
359
+ return requests.get(url, headers=headers, params=params)
360
+ elif call_type == 'POST':
361
+ # Check if there are custom headers (any headers beyond Authorization and Accept)
362
+ custom_headers = {k: v for k, v in headers.items() if k not in ['Authorization', 'Accept']}
363
+
364
+ if custom_headers:
365
+ # Log that custom headers were detected
366
+ MediLink_ConfigLoader.log("Custom headers detected: {}".format(custom_headers), level="DEBUG")
367
+ else:
368
+ # Set default Content-Type if no custom headers
369
+ headers['Content-Type'] = 'application/json'
370
+
371
+ if DEBUG:
372
+ MediLink_ConfigLoader.log("Making POST request to: {}".format(url), level="INFO")
373
+ return requests.post(url, headers=headers, json=data)
374
+ elif call_type == 'DELETE':
375
+ if DEBUG:
376
+ MediLink_ConfigLoader.log("Making DELETE request to: {}".format(url), level="INFO")
377
+ return requests.delete(url, headers=headers)
378
+ else:
379
+ raise ValueError("Unsupported call type: {}".format(call_type))
380
+
381
+ # Make initial request
382
+ response = make_request()
383
+
384
+ # VERBOSE LOGGING: Log response details
385
+ if DEBUG:
386
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
387
+ MediLink_ConfigLoader.log("VERBOSE RESPONSE DETAILS", level="INFO")
388
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
389
+ MediLink_ConfigLoader.log("Response Status Code: {}".format(response.status_code), level="INFO")
390
+ MediLink_ConfigLoader.log("Response Headers: {}".format(json.dumps(dict(response.headers), indent=2)), level="INFO")
391
+
392
+ try:
393
+ response_json = response.json()
394
+ MediLink_ConfigLoader.log("Response JSON: {}".format(json.dumps(response_json, indent=2)), level="INFO")
395
+ except ValueError:
396
+ MediLink_ConfigLoader.log("Response Text (not JSON): {}".format(response.text), level="INFO")
397
+
398
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
399
+
400
+ # If we get a 5xx error, wait and retry once
401
+ if 500 <= response.status_code < 600:
402
+ error_msg = "Received {} error from server for {} request to {}. Waiting 1 second before retry...".format(
403
+ response.status_code, call_type, url
404
+ )
405
+ MediLink_ConfigLoader.log(error_msg, level="WARNING")
406
+
407
+ # Add more verbose logging for 504 errors specifically
408
+ if response.status_code == 504:
409
+ MediLink_ConfigLoader.log(
410
+ "504 Gateway Timeout detected. This usually indicates the server is overloaded or taking too long to respond. "
411
+ "Retrying after 1 second delay...",
412
+ level="WARNING"
413
+ )
414
+
415
+ time.sleep(1)
416
+ response = make_request()
417
+
418
+ # Log the retry result
419
+ if response.status_code == 200:
420
+ MediLink_ConfigLoader.log(
421
+ "Retry successful! Request to {} now returned 200 status code.".format(url),
422
+ level="INFO"
423
+ )
424
+ else:
425
+ MediLink_ConfigLoader.log(
426
+ "Retry failed. Request to {} still returned {} status code.".format(url, response.status_code),
427
+ level="ERROR"
428
+ )
429
+
430
+ # Raise an HTTPError if the response was unsuccessful
431
+ response.raise_for_status()
432
+
433
+ return response.json()
434
+
435
+ except requests.exceptions.HTTPError as http_err:
436
+ # VERBOSE ERROR LOGGING
437
+ MediLink_ConfigLoader.log("=" * 80, level="ERROR")
438
+ MediLink_ConfigLoader.log("VERBOSE HTTP ERROR DETAILS", level="ERROR")
439
+ MediLink_ConfigLoader.log("=" * 80, level="ERROR")
440
+
441
+ # If http_err.response is None, handle it separately
442
+ if http_err.response is None:
443
+ log_message = (
444
+ "HTTPError with no response. "
445
+ "URL: {url}, "
446
+ "Method: {method}, "
447
+ "Params: {params}, "
448
+ "Data: {data}, "
449
+ "Headers: {masked_headers}, "
450
+ "Error: {error}"
451
+ ).format(
452
+ url=url,
453
+ method=call_type,
454
+ params=params,
455
+ data=data,
456
+ masked_headers=masked_headers,
457
+ error=str(http_err)
458
+ )
459
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
460
+ else:
461
+ # Extract status code and full response content
462
+ status_code = http_err.response.status_code
463
+ MediLink_ConfigLoader.log("HTTP Error Status Code: {}".format(status_code), level="ERROR")
464
+ MediLink_ConfigLoader.log("HTTP Error URL: {}".format(url), level="ERROR")
465
+ MediLink_ConfigLoader.log("HTTP Error Method: {}".format(call_type), level="ERROR")
466
+ MediLink_ConfigLoader.log("HTTP Error Headers: {}".format(json.dumps(masked_headers, indent=2)), level="ERROR")
467
+
468
+ if params:
469
+ MediLink_ConfigLoader.log("HTTP Error Query Params: {}".format(json.dumps(params, indent=2)), level="ERROR")
470
+ if data:
471
+ MediLink_ConfigLoader.log("HTTP Error Request Data: {}".format(json.dumps(data, indent=2)), level="ERROR")
472
+
473
+ try:
474
+ # Try to log the JSON content if available
475
+ response_content = http_err.response.json()
476
+ MediLink_ConfigLoader.log("HTTP Error Response JSON: {}".format(json.dumps(response_content, indent=2)), level="ERROR")
477
+ except ValueError:
478
+ # Fallback to raw text if JSON decoding fails
479
+ response_content = http_err.response.text
480
+ MediLink_ConfigLoader.log("HTTP Error Response Text: {}".format(response_content), level="ERROR")
481
+
482
+ log_message = (
483
+ "HTTPError: Status Code: {status}, "
484
+ "URL: {url}, "
485
+ "Method: {method}, "
486
+ "Params: {params}, "
487
+ "Data: {data}, "
488
+ "Headers: {masked_headers}, "
489
+ "Response Content: {content}"
490
+ ).format(
491
+ status=status_code,
492
+ url=url,
493
+ method=call_type,
494
+ params=params,
495
+ data=data,
496
+ masked_headers=masked_headers,
497
+ content=response_content
498
+ )
499
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
500
+
501
+ MediLink_ConfigLoader.log("=" * 80, level="ERROR")
502
+ raise
503
+
504
+ except requests.exceptions.RequestException as req_err:
505
+ # Log connection-related issues or other request exceptions
506
+ log_message = (
507
+ "RequestException: No response received. "
508
+ "URL: {url}, "
509
+ "Method: {method}, "
510
+ "Params: {params}, "
511
+ "Data: {data}, "
512
+ "Headers: {masked_headers}, "
513
+ "Error: {error}"
514
+ ).format(
515
+ url=url,
516
+ method=call_type,
517
+ params=params,
518
+ data=data,
519
+ masked_headers=masked_headers,
520
+ error=str(req_err)
521
+ )
522
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
523
+ raise
524
+
525
+ except Exception as e:
526
+ # Capture traceback for unexpected exceptions
527
+ tb = traceback.format_exc()
528
+ log_message = (
529
+ "Unexpected error: {error}. "
530
+ "URL: {url}, "
531
+ "Method: {method}, "
532
+ "Params: {params}, "
533
+ "Data: {data}, "
534
+ "Headers: {masked_headers}. "
535
+ "Traceback: {traceback}"
536
+ ).format(
537
+ error=str(e),
538
+ url=url,
539
+ method=call_type,
540
+ params=params,
541
+ data=data,
542
+ masked_headers=masked_headers,
543
+ traceback=tb
544
+ )
545
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
546
+ raise
547
+
548
+ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
549
+ """
550
+ Fetches the payer name using the provided APIClient instance.
551
+
552
+ :param client: An instance of APIClient
553
+ :param payer_id: The payer ID to fetch
554
+ :param primary_endpoint: The primary endpoint to use
555
+ :return: The payer name if found
556
+ """
557
+ # Ensure client is an instance of APIClient
558
+ if not isinstance(client, APIClient):
559
+ error_message = "Invalid client provided. Expected an instance of APIClient."
560
+ print(error_message)
561
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
562
+ exit(1) # Exit the script
563
+
564
+ # TODO: FUTURE IMPLEMENTATION - Remove AVAILITY default when other endpoints have payer-list APIs
565
+ # Currently defaulting to AVAILITY as it's the only endpoint with confirmed payer-list functionality
566
+ # In the future, this should be removed and the system should dynamically detect which endpoints
567
+ # have payer-list capabilities and use them accordingly.
568
+ if primary_endpoint != 'AVAILITY':
569
+ 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")
570
+ primary_endpoint = 'AVAILITY'
571
+
572
+ try:
573
+ endpoints = config['MediLink_Config']['endpoints']
574
+ except KeyError as e:
575
+ error_message = "Configuration loading error in fetch_payer_name_from_api: Missing key {0}... Attempting to reload configuration.".format(e)
576
+ # print(error_message)
577
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
578
+ # Attempt to reload configuration if key is missing
579
+ config, _ = MediLink_ConfigLoader.load_configuration()
580
+ endpoints = config['MediLink_Config']['endpoints'] # Re-attempt to access endpoints
581
+ MediLink_ConfigLoader.log("Re-loaded configuration successfully.", level="INFO")
582
+
583
+ # Sanitize and validate payer_id
584
+ if not isinstance(payer_id, str):
585
+ payer_id = str(payer_id)
586
+
587
+ payer_id = ''.join(char for char in payer_id if char.isalnum())
588
+
589
+ if not payer_id:
590
+ error_message = "Invalid payer_id in API v3: {}. Must contain a string of alphanumeric characters.".format(payer_id)
591
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
592
+ print(error_message)
593
+
594
+ # FUTURE IMPLEMENTATION: Dynamic endpoint selection based on payer-list availability
595
+ # This will replace the hardcoded AVAILITY default when other endpoints have payer-list APIs
596
+ # The logic should:
597
+ # 1. Check all endpoints for 'payer_list_endpoint' configuration
598
+ # 2. Prioritize endpoints that have confirmed payer-list functionality
599
+ # 3. Fall back to endpoints with basic payer lookup if available
600
+ # 4. Use AVAILITY as final fallback
601
+
602
+ # Define endpoint rotation logic with payer-list capability detection
603
+ available_endpoints = []
604
+
605
+ # Check which endpoints have payer-list functionality configured
606
+ for endpoint_name, endpoint_config in endpoints.items():
607
+ if 'payer_list_endpoint' in endpoint_config:
608
+ available_endpoints.append(endpoint_name)
609
+ MediLink_ConfigLoader.log("Found payer-list endpoint for {}: {}".format(endpoint_name, endpoint_config['payer_list_endpoint']), level="DEBUG")
610
+
611
+ # If no endpoints have payer-list configured, fall back to AVAILITY
612
+ if not available_endpoints:
613
+ MediLink_ConfigLoader.log("No endpoints with payer-list configuration found, using AVAILITY as fallback", level="INFO")
614
+ available_endpoints = ['AVAILITY']
615
+
616
+ # Prioritize the primary endpoint if it has payer-list capability
617
+ if primary_endpoint in available_endpoints:
618
+ endpoint_order = [primary_endpoint] + [ep for ep in available_endpoints if ep != primary_endpoint]
619
+ else:
620
+ # If primary endpoint doesn't have payer-list, use available endpoints in order
621
+ endpoint_order = available_endpoints
622
+
623
+ MediLink_ConfigLoader.log("Endpoint order for payer lookup: {}".format(endpoint_order), level="DEBUG")
624
+
625
+ for endpoint_name in endpoint_order:
626
+ try:
627
+ endpoint_url = endpoints[endpoint_name].get('payer_list_endpoint', '/availity-payer-list')
628
+ response = client.make_api_call(endpoint_name, 'GET', endpoint_url, {'payerId': payer_id})
629
+
630
+ # Check if response exists
631
+ if not response:
632
+ log_message = "No response from {0} for Payer ID {1}".format(endpoint_name, payer_id)
633
+ print(log_message)
634
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
635
+ continue
636
+
637
+ # Check if the status code is not 200
638
+ status_code = response.get('statuscode', 200)
639
+ if status_code != 200:
640
+ log_message = "Invalid response status code {0} from {1} for Payer ID {2}. Message: {3}".format(
641
+ status_code, endpoint_name, payer_id, response.get('message', 'No message'))
642
+ print(log_message)
643
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
644
+ continue
645
+
646
+ # Extract payers and validate the response structure
647
+ payers = response.get('payers', [])
648
+ if not payers:
649
+ log_message = "No payer found at {0} for ID {1}. Response: {2}".format(endpoint_name, payer_id, response)
650
+ print(log_message)
651
+ MediLink_ConfigLoader.log(log_message, level="INFO")
652
+ continue
653
+
654
+ # Extract the payer name from the first payer in the list
655
+ payer_name = payers[0].get('displayName') or payers[0].get('name')
656
+ if not payer_name:
657
+ log_message = "Payer name not found in the response from {0} for ID {1}. Response: {2}".format(
658
+ endpoint_name, payer_id, response)
659
+ print(log_message)
660
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
661
+ continue
662
+
663
+ # Log successful payer retrieval
664
+ log_message = "Found payer at {0} for ID {1}: {2}".format(endpoint_name, payer_id, payer_name)
665
+ MediLink_ConfigLoader.log(log_message, level="INFO")
666
+ return payer_name
667
+
668
+ except Exception as e:
669
+ error_message = "Error calling {0} for Payer ID {1}. Exception: {2}".format(endpoint_name, payer_id, e)
670
+ MediLink_ConfigLoader.log(error_message, level="INFO")
671
+
672
+ # If all endpoints fail
673
+ final_error_message = "All endpoints exhausted for Payer ID {0}.".format(payer_id)
674
+ print(final_error_message)
675
+ MediLink_ConfigLoader.log(final_error_message, level="CRITICAL")
676
+ raise ValueError(final_error_message)
677
+
678
+ def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false', transaction_id=None, env=None):
679
+ # VERBOSE LOGGING FOR CLAIM SUMMARY
680
+ if DEBUG:
681
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
682
+ MediLink_ConfigLoader.log("GET CLAIM SUMMARY BY PROVIDER - VERBOSE DETAILS", level="INFO")
683
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
684
+ MediLink_ConfigLoader.log("TIN: {}".format(tin), level="INFO")
685
+ MediLink_ConfigLoader.log("First Service Date: {}".format(first_service_date), level="INFO")
686
+ MediLink_ConfigLoader.log("Last Service Date: {}".format(last_service_date), level="INFO")
687
+ MediLink_ConfigLoader.log("Payer ID: {}".format(payer_id), level="INFO")
688
+ MediLink_ConfigLoader.log("Get Standard Error: {}".format(get_standard_error), level="INFO")
689
+ MediLink_ConfigLoader.log("Transaction ID: {}".format(transaction_id), level="INFO")
690
+ MediLink_ConfigLoader.log("Environment: {}".format(env), level="INFO")
691
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
692
+
693
+ endpoint_name = 'UHCAPI'
694
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_summary_by_provider']
695
+
696
+ if DEBUG:
697
+ MediLink_ConfigLoader.log("URL Extension: {}".format(url_extension), level="INFO")
698
+
699
+ # Build headers according to official API documentation
700
+ # Note: Environment detection is now handled automatically by the API client
701
+ headers = {
702
+ 'tin': tin,
703
+ 'firstServiceDt': first_service_date,
704
+ 'lastServiceDt': last_service_date,
705
+ 'payerId': payer_id,
706
+ 'getStandardError': get_standard_error,
707
+ 'Accept': 'application/json'
708
+ }
709
+
710
+ # Add transactionId if provided (for pagination)
711
+ if transaction_id:
712
+ headers['transactionId'] = transaction_id
713
+
714
+ if DEBUG:
715
+ MediLink_ConfigLoader.log("Headers: {}".format(json.dumps(headers, indent=2)), level="INFO")
716
+
717
+ return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
718
+
719
+ def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
720
+ endpoint_name = 'UHCAPI'
721
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility']
722
+ url_extension = url_extension + '?payerID={}&providerLastName={}&searchOption={}&dateOfBirth={}&memberId={}&npi={}'.format(
723
+ payer_id, provider_last_name, search_option, date_of_birth, member_id, npi)
724
+ return client.make_api_call(endpoint_name, 'GET', url_extension)
725
+
726
+ def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
727
+ first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
728
+ middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
729
+ service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
730
+ corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
731
+ organization_id=None, identify_service_level_deductible=True):
732
+
733
+ # Ensure all required parameters have values
734
+ if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
735
+ raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
736
+
737
+ # Validate payer_id
738
+ valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
739
+ if payer_id not in valid_payer_ids:
740
+ raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
741
+
742
+ endpoint_name = 'UHCAPI'
743
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_v3']
744
+
745
+ # Construct request body
746
+ body = {
747
+ "memberId": member_id,
748
+ "lastName": last_name,
749
+ "firstName": first_name,
750
+ "dateOfBirth": date_of_birth,
751
+ "payerID": payer_id,
752
+ "payerLabel": payer_label,
753
+ "payerName": payer_name,
754
+ "serviceStart": service_start,
755
+ "serviceEnd": service_end,
756
+ "middleName": middle_name,
757
+ "gender": gender,
758
+ "ssn": ssn,
759
+ "city": city,
760
+ "state": state,
761
+ "zip": zip,
762
+ "groupNumber": group_number,
763
+ "serviceTypeCode": service_type_code,
764
+ "providerLastName": provider_last_name,
765
+ "providerFirstName": provider_first_name,
766
+ "taxIdNumber": tax_id_number,
767
+ "providerNameID": provider_name_id,
768
+ "npi": npi,
769
+ "corporateTaxOwnerID": corporate_tax_owner_id,
770
+ "corporateTaxOwnerName": corporate_tax_owner_name,
771
+ "organizationName": organization_name,
772
+ "organizationID": organization_id,
773
+ "searchOption": search_option,
774
+ "identifyServiceLevelDeductible": identify_service_level_deductible
775
+ }
776
+
777
+ # Remove None values from the body
778
+ body = {k: v for k, v in body.items() if v is not None}
779
+
780
+ # Log the request body
781
+ MediLink_ConfigLoader.log("Request body: {}".format(json.dumps(body, indent=4)), level="DEBUG")
782
+
783
+ return client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=body)
784
+
785
+ def get_eligibility_super_connector(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
786
+ first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
787
+ middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
788
+ service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
789
+ corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
790
+ organization_id=None, identify_service_level_deductible=True):
791
+ """
792
+ GraphQL Super Connector version of eligibility check that maps to the same interface as get_eligibility_v3.
793
+ This function provides a drop-in replacement for the REST API with identical input/output behavior.
794
+ """
795
+ # Ensure all required parameters have values
796
+ if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
797
+ raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
798
+
799
+ # Validate payer_id
800
+ valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
801
+ if payer_id not in valid_payer_ids:
802
+ raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
803
+
804
+ endpoint_name = 'UHCAPI'
805
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_super_connector']
806
+
807
+ # Get provider TIN from config (using existing billing_provider_tin)
808
+ provider_tin = client.config['MediLink_Config'].get('billing_provider_tin')
809
+ if not provider_tin:
810
+ raise ValueError("Provider TIN not found in configuration")
811
+
812
+ # Construct GraphQL query variables using the consolidated module
813
+ graphql_variables = MediLink_GraphQL.build_eligibility_variables(
814
+ member_id=member_id,
815
+ date_of_birth=date_of_birth,
816
+ payer_id=payer_id,
817
+ provider_last_name=provider_last_name,
818
+ provider_npi=npi
819
+ )
820
+
821
+ # Validate NPI format (should be 10 digits)
822
+ if 'providerNPI' in graphql_variables:
823
+ npi_value = graphql_variables['providerNPI']
824
+ if not npi_value.isdigit() or len(npi_value) != 10:
825
+ MediLink_ConfigLoader.log("Warning: NPI '{}' is not 10 digits, but continuing anyway".format(npi_value), level="WARNING")
826
+
827
+ # Build GraphQL request using the consolidated module
828
+ # Hardcoded switch to use sample data for testing
829
+ USE_SAMPLE_DATA = False # Set to False to use constructed data
830
+
831
+ if USE_SAMPLE_DATA:
832
+ # Use the sample data from swagger documentation
833
+ graphql_body = MediLink_GraphQL.get_sample_eligibility_request()
834
+ MediLink_ConfigLoader.log("Using SAMPLE DATA from swagger documentation", level="INFO")
835
+ else:
836
+ # Build GraphQL request with actual data using consolidated module
837
+ graphql_body = MediLink_GraphQL.build_eligibility_request(graphql_variables)
838
+ MediLink_ConfigLoader.log("Using CONSTRUCTED DATA with consolidated GraphQL module", level="INFO")
839
+
840
+ # Compare with sample data for debugging
841
+ sample_data = MediLink_GraphQL.get_sample_eligibility_request()
842
+ MediLink_ConfigLoader.log("Sample data structure: {}".format(json.dumps(sample_data, indent=2)), level="DEBUG")
843
+ MediLink_ConfigLoader.log("Constructed data structure: {}".format(json.dumps(graphql_body, indent=2)), level="DEBUG")
844
+
845
+ # Compare key differences
846
+ sample_vars = sample_data['variables']['input']
847
+ constructed_vars = graphql_body['variables']['input']
848
+
849
+ # Log differences in variables
850
+ for key in set(sample_vars.keys()) | set(constructed_vars.keys()):
851
+ sample_val = sample_vars.get(key)
852
+ constructed_val = constructed_vars.get(key)
853
+ if sample_val != constructed_val:
854
+ MediLink_ConfigLoader.log("Variable difference - {}: sample='{}', constructed='{}'".format(
855
+ key, sample_val, constructed_val), level="DEBUG")
856
+
857
+ # Log the GraphQL request
858
+ MediLink_ConfigLoader.log("GraphQL request body: {}".format(json.dumps(graphql_body, indent=2)), level="DEBUG")
859
+ MediLink_ConfigLoader.log("GraphQL variables: {}".format(json.dumps(graphql_variables, indent=2)), level="DEBUG")
860
+
861
+ # Add required headers for Super Connector
862
+ headers = {
863
+ 'Content-Type': 'application/json',
864
+ 'Accept': 'application/json',
865
+ 'tin': str(provider_tin) # Ensure TIN is a string
866
+ }
867
+
868
+ # Only add env header when using sample data
869
+ if USE_SAMPLE_DATA:
870
+ headers['env'] = 'sandbox'
871
+
872
+ # Remove None values from headers
873
+ headers = {k: v for k, v in headers.items() if v is not None}
874
+
875
+ # Log the final headers being sent
876
+ MediLink_ConfigLoader.log("Final headers being sent: {}".format(json.dumps(headers, indent=2)), level="DEBUG")
877
+
878
+ # Make the GraphQL API call
879
+ response = client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=graphql_body, headers=headers)
880
+
881
+ # Transform GraphQL response to match REST API format
882
+ # This ensures the calling code doesn't know the difference
883
+ transformed_response = MediLink_GraphQL.transform_eligibility_response(response)
884
+
885
+ return transformed_response
886
+
887
+ def is_test_mode(client, body, endpoint_type):
888
+ """
889
+ Checks if Test Mode is enabled in the client's configuration and simulates the response if it is.
890
+
891
+ :param client: An instance of APIClient
892
+ :param body: The intended request body
893
+ :param endpoint_type: The type of endpoint being accessed ('claim_submission' or 'claim_details')
894
+ :return: A dummy response simulating the real API call if Test Mode is enabled, otherwise None
895
+ """
896
+ if client.config.get("MediLink_Config", {}).get("TestMode", True):
897
+ print("Test Mode is enabled! API Call not executed.")
898
+ print("\nIntended request body:", body)
899
+ MediLink_ConfigLoader.log("Test Mode is enabled! Simulating 1 second delay for API response for {}.".format(endpoint_type), level="INFO")
900
+ time.sleep(1)
901
+ MediLink_ConfigLoader.log("Intended request body: {}".format(body), level="INFO")
902
+
903
+ if endpoint_type == 'claim_submission':
904
+ dummy_response = {
905
+ "transactionId": "CS07180420240328013411240", # This is the tID for the sandbox Claim Acknowledgement endpoint.
906
+ "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~",
907
+ "responseType": "dummy_response_837999",
908
+ "message": "Test Mode: Claim validated and sent for further processing"
909
+ }
910
+ elif endpoint_type == 'claim_details':
911
+ dummy_response = {
912
+ "responseType": "dummy_response_277CA-CH",
913
+ "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~",
914
+ "statuscode": "000",
915
+ "message:": ""
916
+ }
917
+ return dummy_response
918
+ return None
919
+
920
+ def submit_uhc_claim(client, x12_request_data):
921
+ """
922
+ Submits a UHC claim and retrieves the claim acknowledgement details.
923
+
924
+ This function first submits the claim using the provided x12 837p data. If the client is in Test Mode,
925
+ it returns a simulated response. If Test Mode is not enabled, it submits the claim and then retrieves
926
+ the claim acknowledgement details using the transaction ID from the initial response.
927
+
928
+ NOTE: This function uses endpoints that may not be available in the new swagger version:
929
+ - /Claims/api/claim-submission/v1 (claim submission)
930
+ - /Claims/api/claim-details/v1 (claim acknowledgement)
931
+
932
+ If these endpoints are deprecated in the new swagger, this function will need to be updated
933
+ to use the new available endpoints.
934
+
935
+ :param client: An instance of APIClient
936
+ :param x12_request_data: The x12 837p data as a string
937
+ :return: The final response containing the claim acknowledgement details or a dummy response if in Test Mode
938
+ """
939
+ # VERBOSE LOGGING FOR CLAIM SUBMISSION
940
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
941
+ MediLink_ConfigLoader.log("SUBMIT UHC CLAIM - VERBOSE DETAILS", level="INFO")
942
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
943
+ MediLink_ConfigLoader.log("X12 Request Data Length: {}".format(len(x12_request_data) if x12_request_data else 0), level="INFO")
944
+ if x12_request_data:
945
+ MediLink_ConfigLoader.log("X12 Request Data Preview (first 200 chars): {}".format(x12_request_data[:200]), level="INFO")
946
+ MediLink_ConfigLoader.log("=" * 80, level="INFO")
947
+
948
+ endpoint_name = 'UHCAPI'
949
+ claim_submission_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_submission']
950
+ claim_details_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_details']
951
+
952
+ MediLink_ConfigLoader.log("Claim Submission URL: {}".format(claim_submission_url), level="INFO")
953
+ MediLink_ConfigLoader.log("Claim Details URL: {}".format(claim_details_url), level="INFO")
954
+
955
+ # Headers for the request
956
+ headers = {'Content-Type': 'application/json'}
957
+
958
+ # Request body for claim submission
959
+ claim_body = {'x12RequestData': x12_request_data}
960
+
961
+ MediLink_ConfigLoader.log("Claim Body Keys: {}".format(list(claim_body.keys())), level="INFO")
962
+ MediLink_ConfigLoader.log("Headers: {}".format(json.dumps(headers, indent=2)), level="INFO")
963
+
964
+ # Check if Test Mode is enabled and return simulated response if so
965
+ test_mode_response = is_test_mode(client, claim_body, 'claim_submission')
966
+ if test_mode_response:
967
+ return test_mode_response
968
+
969
+ # Make the API call to submit the claim
970
+ try:
971
+ MediLink_ConfigLoader.log("Making claim submission API call...", level="INFO")
972
+ submission_response = client.make_api_call(endpoint_name, 'POST', claim_submission_url, data=claim_body, headers=headers)
973
+
974
+ # Extract the transaction ID from the submission response
975
+ transaction_id = submission_response.get('transactionId')
976
+ if not transaction_id:
977
+ raise ValueError("transactionId not found in the submission response")
978
+
979
+ # Log the transaction ID for traceability
980
+ MediLink_ConfigLoader.log("UHCAPI claim submission transactionId: {}".format(transaction_id), level="INFO")
981
+
982
+ # Prepare the request body for the claim acknowledgement retrieval
983
+ acknowledgement_body = {'transactionId': transaction_id}
984
+
985
+ # Check if Test Mode is enabled and return simulated response if so
986
+ test_mode_response = is_test_mode(client, acknowledgement_body, 'claim_details')
987
+ if test_mode_response:
988
+ return test_mode_response
989
+
990
+ # Make the API call to retrieve the claim acknowledgement details
991
+ acknowledgement_response = client.make_api_call(endpoint_name, 'POST', claim_details_url, data=acknowledgement_body, headers=headers)
992
+ return acknowledgement_response
993
+
994
+ except Exception as e:
995
+ print("Error during claim processing: {}".format(e))
996
+ raise
997
+
998
+ if __name__ == "__main__":
999
+ # Use factory for consistency but fallback to direct instantiation for testing
1000
+ try:
1001
+ from MediCafe.core_utils import get_api_client
1002
+ client = get_api_client()
1003
+ if client is None:
1004
+ client = APIClient()
1005
+ except ImportError:
1006
+ client = APIClient()
1007
+
1008
+ # Define a configuration to enable or disable tests
1009
+ test_config = {
1010
+ 'test_fetch_payer_name': False,
1011
+ 'test_claim_summary': False,
1012
+ 'test_eligibility': False,
1013
+ 'test_eligibility_v3': False,
1014
+ 'test_eligibility_super_connector': False,
1015
+ 'test_claim_submission': False,
1016
+ }
1017
+
1018
+ try:
1019
+ api_test_cases = client.config['MediLink_Config']['API Test Case']
1020
+
1021
+ # Test 1: Fetch Payer Name
1022
+ if test_config.get('test_fetch_payer_name', False):
1023
+ try:
1024
+ for case in api_test_cases:
1025
+ payer_name = fetch_payer_name_from_api(client, case['payer_id'], client.config)
1026
+ print("*** TEST API: Payer Name: {}".format(payer_name))
1027
+ except Exception as e:
1028
+ print("*** TEST API: Error in Fetch Payer Name Test: {}".format(e))
1029
+
1030
+ # Test 2: Get Claim Summary
1031
+ if test_config.get('test_claim_summary', False):
1032
+ try:
1033
+ for case in api_test_cases:
1034
+ claim_summary = get_claim_summary_by_provider(client, case['provider_tin'], '05/01/2024', '06/23/2024', case['payer_id'])
1035
+ print("TEST API: Claim Summary: {}".format(claim_summary))
1036
+ except Exception as e:
1037
+ print("TEST API: Error in Claim Summary Test: {}".format(e))
1038
+
1039
+ # Test 3: Get Eligibility
1040
+ if test_config.get('test_eligibility', False):
1041
+ try:
1042
+ for case in api_test_cases:
1043
+ eligibility = get_eligibility(client, case['payer_id'], case['provider_last_name'], case['search_option'],
1044
+ case['date_of_birth'], case['member_id'], case['npi'])
1045
+ print("TEST API: Eligibility: {}".format(eligibility))
1046
+ except Exception as e:
1047
+ print("TEST API: Error in Eligibility Test: {}".format(e))
1048
+
1049
+ # Test 4: Get Eligibility v3
1050
+ if test_config.get('test_eligibility_v3', False):
1051
+ try:
1052
+ for case in api_test_cases:
1053
+ eligibility_v3 = get_eligibility_v3(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
1054
+ search_option=case['search_option'], date_of_birth=case['date_of_birth'],
1055
+ member_id=case['member_id'], npi=case['npi'])
1056
+ print("TEST API: Eligibility v3: {}".format(eligibility_v3))
1057
+ except Exception as e:
1058
+ print("TEST API: Error in Eligibility v3 Test: {}".format(e))
1059
+
1060
+ # Test 5: Get Eligibility Super Connector (GraphQL)
1061
+ if test_config.get('test_eligibility_super_connector', False):
1062
+ try:
1063
+ for case in api_test_cases:
1064
+ eligibility_super_connector = get_eligibility_super_connector(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
1065
+ search_option=case['search_option'], date_of_birth=case['date_of_birth'],
1066
+ member_id=case['member_id'], npi=case['npi'])
1067
+ print("TEST API: Eligibility Super Connector: {}".format(eligibility_super_connector))
1068
+ except Exception as e:
1069
+ print("TEST API: Error in Eligibility Super Connector Test: {}".format(e))
1070
+
1071
+ """
1072
+ # Example of iterating over multiple patients (if needed)
1073
+ patients = [
1074
+ {'payer_id': '87726', 'provider_last_name': 'VIDA', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1980-01-01', 'member_id': '123456789', 'npi': '9876543210'},
1075
+ {'payer_id': '87726', 'provider_last_name': 'SMITH', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1970-02-02', 'member_id': '987654321', 'npi': '1234567890'},
1076
+ # Add more patients as needed
1077
+ ]
1078
+
1079
+ for patient in patients:
1080
+ try:
1081
+ eligibility = get_eligibility(client, patient['payer_id'], patient['provider_last_name'], patient['search_option'], patient['date_of_birth'], patient['member_id'], patient['npi'])
1082
+ print("Eligibility for {}: {}".format(patient['provider_last_name'], eligibility))
1083
+ except Exception as e:
1084
+ print("Error in getting eligibility for {}: {}".format(patient['provider_last_name'], e))
1085
+ """
1086
+ # Test 6: UHC Claim Submission
1087
+ if test_config.get('test_claim_submission', False):
1088
+ try:
1089
+ x12_request_data = (
1090
+ "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~"
1091
+ )
1092
+ response = submit_uhc_claim(client, x12_request_data)
1093
+ print("\nTEST API: Claim Submission Response:\n", response)
1094
+ except Exception as e:
1095
+ print("\nTEST API: Error in Claim Submission Test:\n", e)
1096
+
1097
+ except Exception as e:
1098
+ print("TEST API: Unexpected Error: {}".format(e))