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

@@ -1,14 +1,19 @@
1
- import time
2
- import requests
3
- import yaml
4
- import json
5
- import os
1
+ # MediLink_API_v3.py
2
+ import time, requests, yaml, json, os, traceback
6
3
 
7
4
  try:
8
5
  from MediLink import MediLink_ConfigLoader
9
6
  except ImportError:
10
7
  import MediLink_ConfigLoader
11
8
 
9
+ """
10
+ TODO At some point it might make sense to test their acknoledgment endpoint. body is transactionId.
11
+ This API is used to extract the claim acknowledgement details for the given transactionid which was
12
+ generated for 837 requests in claim submission process. Claims Acknowledgement (277CA) will provide
13
+ a status of claim-level acknowledgement of all claims received in the front-end processing system and
14
+ adjudication system.
15
+ """
16
+
12
17
  class ConfigLoader:
13
18
  @staticmethod
14
19
  def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
@@ -56,18 +61,37 @@ class TokenCache:
56
61
 
57
62
  def get(self, endpoint_name, current_time):
58
63
  token_info = self.tokens.get(endpoint_name, {})
59
- if token_info and token_info['expires_at'] > current_time:
60
- return token_info['access_token']
64
+ if token_info:
65
+ expires_at = token_info['expires_at']
66
+ # Log cache hit and expiration time
67
+ log_message = "Token for {} expires at {}. Current time: {}".format(endpoint_name, expires_at, current_time)
68
+ MediLink_ConfigLoader.log(log_message, level="DEBUG")
69
+
70
+ if expires_at > current_time:
71
+ return token_info['access_token']
72
+
73
+ # Log cache miss
74
+ log_message = "No valid token found for {}".format(endpoint_name)
75
+ MediLink_ConfigLoader.log(log_message, level="INFO")
76
+
61
77
  return None
62
78
 
63
79
  def set(self, endpoint_name, access_token, expires_in, current_time):
64
- # Ensure types are correct
65
80
  current_time = ensure_numeric(current_time)
66
81
  expires_in = ensure_numeric(expires_in)
67
-
82
+
83
+ # Log the expires_in value to debug
84
+ log_message = "Token expires in: {} seconds for {}".format(expires_in, endpoint_name)
85
+ MediLink_ConfigLoader.log(log_message, level="INFO")
86
+
87
+ # Adjust expiration time by subtracting a buffer of 120 seconds
88
+ expires_at = current_time + expires_in - 120
89
+ log_message = "Setting token for {}. Expires at: {}".format(endpoint_name, expires_at)
90
+ MediLink_ConfigLoader.log(log_message, level="INFO")
91
+
68
92
  self.tokens[endpoint_name] = {
69
93
  'access_token': access_token,
70
- 'expires_at': current_time + expires_in - 120
94
+ 'expires_at': expires_at
71
95
  }
72
96
 
73
97
  class BaseAPIClient:
@@ -87,12 +111,16 @@ class APIClient(BaseAPIClient):
87
111
  super().__init__(config)
88
112
 
89
113
  def get_access_token(self, endpoint_name):
114
+ MediLink_ConfigLoader.log("[Get Access Token] Called for {}".format(endpoint_name), level="DEBUG")
90
115
  current_time = time.time()
91
116
  cached_token = self.token_cache.get(endpoint_name, current_time)
117
+
92
118
  if cached_token:
93
- MediLink_ConfigLoader.log("Using cached token for endpoint: {}".format(endpoint_name), level="INFO")
119
+ expires_at = self.token_cache.tokens[endpoint_name]['expires_at']
120
+ MediLink_ConfigLoader.log("Cached token expires at {}".format(expires_at), level="DEBUG")
94
121
  return cached_token
95
-
122
+
123
+ # If no valid token, fetch a new one
96
124
  endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
97
125
  token_url = endpoint_config['token_url']
98
126
  data = {
@@ -114,68 +142,228 @@ class APIClient(BaseAPIClient):
114
142
  expires_in = token_data.get('expires_in', 3600)
115
143
 
116
144
  self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
117
- MediLink_ConfigLoader.log("Obtained new token for endpoint: {}".format(endpoint_name), level="INFO")
145
+ MediLink_ConfigLoader.log("Obtained NEW token for endpoint: {}".format(endpoint_name), level="INFO")
118
146
  return access_token
119
147
 
120
148
  def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
121
149
  token = self.get_access_token(endpoint_name)
150
+ if token:
151
+ MediLink_ConfigLoader.log("[Make API Call] Token found for {}".format(endpoint_name), level="DEBUG")
152
+ else:
153
+ MediLink_ConfigLoader.log("[Make API Call] No token obtained for {}".format(endpoint_name), level="ERROR")
154
+ raise ValueError("No access token available for endpoint: {}".format(endpoint_name))
155
+
122
156
  if headers is None:
123
157
  headers = {}
124
158
  headers.update({'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'})
125
- url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url'] + url_extension
126
-
127
- # Debug: Print request details
128
- # print("Request URL: {}".format(url))
129
- # print("Request Headers: {}".format(headers))
130
- # print("Request Params: {}".format(params))
131
- # print("Request Data: {}".format(data))
132
-
133
- if call_type == 'GET':
134
- response = requests.get(url, headers=headers, params=params)
135
- elif call_type == 'POST':
136
- headers['Content-Type'] = 'application/json'
137
- response = requests.post(url, headers=headers, json=data)
138
- elif call_type == 'DELETE':
139
- response = requests.delete(url, headers=headers)
140
- else:
141
- raise ValueError("Unsupported call type")
159
+ base_url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url']
160
+ url = base_url + url_extension
142
161
 
143
- if response.status_code >= 400:
144
- error_message = "Error {}: {}".format(response.status_code, response.text)
145
- MediLink_ConfigLoader.log(error_message, level="ERROR")
146
- response.raise_for_status()
162
+ try:
163
+ # Make the API call based on call_type
164
+ if call_type == 'GET':
165
+ response = requests.get(url, headers=headers, params=params)
166
+ elif call_type == 'POST':
167
+ headers['Content-Type'] = 'application/json'
168
+ response = requests.post(url, headers=headers, json=data)
169
+ elif call_type == 'DELETE':
170
+ response = requests.delete(url, headers=headers)
171
+ else:
172
+ raise ValueError("Unsupported call type: {}".format(call_type))
147
173
 
148
- return response.json()
174
+ masked_headers = headers.copy()
175
+ if 'Authorization' in masked_headers:
176
+ masked_headers['Authorization'] = 'Bearer ***'
149
177
 
150
- def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
151
- client = APIClient()
152
- config, _ = MediLink_ConfigLoader.load_configuration()
153
- endpoints = config['MediLink_Config']['endpoints']
178
+ # Raise an HTTPError if the response was unsuccessful
179
+ response.raise_for_status()
154
180
 
155
- if primary_endpoint and primary_endpoint in endpoints:
156
- endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
157
- else:
158
- endpoint_order = list(endpoints.keys())
181
+ return response.json()
182
+
183
+ except requests.exceptions.HTTPError as http_err:
184
+ # If http_err.response is None, handle it separately
185
+ if http_err.response is None:
186
+ log_message = (
187
+ "HTTPError with no response. "
188
+ "URL: {url}, "
189
+ "Method: {method}, "
190
+ "Params: {params}, "
191
+ "Data: {data}, "
192
+ "Headers: {masked_headers}, "
193
+ "Error: {error}"
194
+ ).format(
195
+ url=url,
196
+ method=call_type,
197
+ params=params,
198
+ data=data,
199
+ masked_headers=masked_headers,
200
+ error=str(http_err)
201
+ )
202
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
203
+ else:
204
+ # Extract status code and full response content
205
+ status_code = http_err.response.status_code
206
+ try:
207
+ # Try to log the JSON content if available
208
+ response_content = http_err.response.json()
209
+ except ValueError:
210
+ # Fallback to raw text if JSON decoding fails
211
+ response_content = http_err.response.text
212
+
213
+ log_message = (
214
+ "HTTPError: Status Code: {status}, "
215
+ "URL: {url}, "
216
+ "Method: {method}, "
217
+ "Params: {params}, "
218
+ "Data: {data}, "
219
+ "Headers: {masked_headers}, "
220
+ "Response Content: {content}"
221
+ ).format(
222
+ status=status_code,
223
+ url=url,
224
+ method=call_type,
225
+ params=params,
226
+ data=data,
227
+ masked_headers=masked_headers,
228
+ content=response_content
229
+ )
230
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
231
+ raise
232
+
233
+ except requests.exceptions.RequestException as req_err:
234
+ # Log connection-related issues or other request exceptions
235
+ log_message = (
236
+ "RequestException: No response received. "
237
+ "URL: {url}, "
238
+ "Method: {method}, "
239
+ "Params: {params}, "
240
+ "Data: {data}, "
241
+ "Headers: {masked_headers}, "
242
+ "Error: {error}"
243
+ ).format(
244
+ url=url,
245
+ method=call_type,
246
+ params=params,
247
+ data=data,
248
+ masked_headers=masked_headers,
249
+ error=str(req_err)
250
+ )
251
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
252
+ raise
253
+
254
+ except Exception as e:
255
+ # Capture traceback for unexpected exceptions
256
+ tb = traceback.format_exc()
257
+ log_message = (
258
+ "Unexpected error: {error}. "
259
+ "URL: {url}, "
260
+ "Method: {method}, "
261
+ "Params: {params}, "
262
+ "Data: {data}, "
263
+ "Headers: {masked_headers}. "
264
+ "Traceback: {traceback}"
265
+ ).format(
266
+ error=str(e),
267
+ url=url,
268
+ method=call_type,
269
+ params=params,
270
+ data=data,
271
+ masked_headers=masked_headers,
272
+ traceback=tb
273
+ )
274
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
275
+ raise
276
+
277
+ def fetch_payer_name_from_api(client, payer_id, config, primary_endpoint='AVAILITY'):
278
+ """
279
+ Fetches the payer name using the provided APIClient instance.
280
+
281
+ :param client: An instance of APIClient
282
+ :param payer_id: The payer ID to fetch
283
+ :param primary_endpoint: The primary endpoint to use
284
+ :return: The payer name if found
285
+ """
286
+ # Ensure client is an instance of APIClient
287
+ if not isinstance(client, APIClient):
288
+ error_message = "Invalid client provided. Expected an instance of APIClient."
289
+ print(error_message)
290
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
291
+ exit(1) # Exit the script
292
+
293
+ # BUG Force AVAILITY until I can get a payer-list api from any of the other endpoints.
294
+ MediLink_ConfigLoader.log("[Fetch payer name from API] Overriding {} with AVAILITY.".format(primary_endpoint), level="DEBUG")
295
+ primary_endpoint = 'AVAILITY'
296
+
297
+ try:
298
+ endpoints = config['MediLink_Config']['endpoints']
299
+ except KeyError as e:
300
+ error_message = "Configuration loading error: Missing key {0}".format(e)
301
+ print(error_message)
302
+ MediLink_ConfigLoader.log(error_message, level="CRITICAL")
303
+ # Attempt to reload configuration if key is missing
304
+ config, _ = MediLink_ConfigLoader.load_configuration()
305
+ endpoints = config['MediLink_Config']['endpoints'] # Re-attempt to access endpoints
306
+
307
+ # Define endpoint rotation logic
308
+ endpoint_order = ([primary_endpoint] +
309
+ [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
310
+ if primary_endpoint in endpoints else list(endpoints.keys()))
159
311
 
160
312
  for endpoint_name in endpoint_order:
161
313
  try:
162
- response = client.make_api_call(endpoint_name, 'GET', config['MediLink_Config']['endpoints'][endpoint_name].get('payer_list_endpoint', '/availity-payer-list'), {'payerId': payer_id})
314
+ endpoint_url = endpoints[endpoint_name].get('payer_list_endpoint', '/availity-payer-list')
315
+ response = client.make_api_call(endpoint_name, 'GET', endpoint_url, {'payerId': payer_id})
316
+
317
+ # Check if response exists
318
+ if not response:
319
+ log_message = "No response from {0} for Payer ID {1}".format(endpoint_name, payer_id)
320
+ print(log_message)
321
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
322
+ continue
323
+
324
+ # Check if the status code is not 200
325
+ status_code = response.get('statuscode', 200)
326
+ if status_code != 200:
327
+ log_message = "Invalid response status code {0} from {1} for Payer ID {2}. Message: {3}".format(
328
+ status_code, endpoint_name, payer_id, response.get('message', 'No message'))
329
+ print(log_message)
330
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
331
+ continue
332
+
333
+ # Extract payers and validate the response structure
163
334
  payers = response.get('payers', [])
164
- if payers:
165
- payer_name = payers[0].get('displayName', payers[0].get('name'))
166
- MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), level="INFO")
167
- return payer_name
168
- else:
169
- MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), level="INFO")
335
+ if not payers:
336
+ log_message = "No payer found at {0} for ID {1}. Response: {2}".format(endpoint_name, payer_id, response)
337
+ print(log_message)
338
+ MediLink_ConfigLoader.log(log_message, level="INFO")
339
+ continue
340
+
341
+ # Extract the payer name from the first payer in the list
342
+ payer_name = payers[0].get('displayName') or payers[0].get('name')
343
+ if not payer_name:
344
+ log_message = "Payer name not found in the response from {0} for ID {1}. Response: {2}".format(
345
+ endpoint_name, payer_id, response)
346
+ print(log_message)
347
+ MediLink_ConfigLoader.log(log_message, level="ERROR")
348
+ continue
349
+
350
+ # Log successful payer retrieval
351
+ log_message = "Found payer at {0} for ID {1}: {2}".format(endpoint_name, payer_id, payer_name)
352
+ MediLink_ConfigLoader.log(log_message, level="INFO")
353
+ return payer_name
354
+
170
355
  except Exception as e:
171
- MediLink_ConfigLoader.log("API call to {} failed: {}".format(endpoint_name, e), level="ERROR")
356
+ error_message = "Error calling {0} for Payer ID {1}. Exception: {2}".format(endpoint_name, payer_id, e)
357
+ MediLink_ConfigLoader.log(error_message, level="INFO")
172
358
 
173
- error_message = "All endpoints exhausted for Payer ID {}.".format(payer_id)
174
- MediLink_ConfigLoader.log(error_message, level="CRITICAL")
175
- raise ValueError(error_message)
359
+ # If all endpoints fail
360
+ final_error_message = "All endpoints exhausted for Payer ID {0}.".format(payer_id)
361
+ print(final_error_message)
362
+ MediLink_ConfigLoader.log(final_error_message, level="CRITICAL")
363
+ raise ValueError(final_error_message)
176
364
 
177
365
  def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false'):
178
- endpoint_name = 'UHCApi'
366
+ endpoint_name = 'UHCAPI'
179
367
  url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_summary_by_provider']
180
368
  headers = {
181
369
  'tin': tin,
@@ -188,7 +376,7 @@ def get_claim_summary_by_provider(client, tin, first_service_date, last_service_
188
376
  return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
189
377
 
190
378
  def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
191
- endpoint_name = 'UHCApi'
379
+ endpoint_name = 'UHCAPI'
192
380
  url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility']
193
381
  url_extension = url_extension + '?payerID={}&providerLastName={}&searchOption={}&dateOfBirth={}&memberId={}&npi={}'.format(
194
382
  payer_id, provider_last_name, search_option, date_of_birth, member_id, npi)
@@ -210,7 +398,7 @@ def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date
210
398
  if payer_id not in valid_payer_ids:
211
399
  raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
212
400
 
213
- endpoint_name = 'UHCApi'
401
+ endpoint_name = 'UHCAPI'
214
402
  url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_v3']
215
403
 
216
404
  # Construct request body
@@ -253,15 +441,101 @@ def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date
253
441
 
254
442
  return client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=body)
255
443
 
444
+ def is_test_mode(client, body, endpoint_type):
445
+ """
446
+ Checks if Test Mode is enabled in the client's configuration and simulates the response if it is.
447
+
448
+ :param client: An instance of APIClient
449
+ :param body: The intended request body
450
+ :param endpoint_type: The type of endpoint being accessed ('claim_submission' or 'claim_details')
451
+ :return: A dummy response simulating the real API call if Test Mode is enabled, otherwise None
452
+ """
453
+ if client.config.get("MediLink_Config", {}).get("TestMode", True):
454
+ print("Test Mode is enabled! API Call not executed.")
455
+ print("\nIntended request body:", body)
456
+ MediLink_ConfigLoader.log("Test Mode is enabled! Simulating 1 second delay for API response for {}.".format(endpoint_type), level="INFO")
457
+ time.sleep(1)
458
+ MediLink_ConfigLoader.log("Intended request body: {}".format(body), level="INFO")
459
+
460
+ if endpoint_type == 'claim_submission':
461
+ dummy_response = {
462
+ "transactionId": "CS07180420240328013411240", # This is the tID for the sandbox Claim Acknowledgement endpoint.
463
+ "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~",
464
+ "responseType": "dummy_response_837999",
465
+ "message": "Test Mode: Claim validated and sent for further processing"
466
+ }
467
+ elif endpoint_type == 'claim_details':
468
+ dummy_response = {
469
+ "responseType": "dummy_response_277CA-CH",
470
+ "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~",
471
+ "statuscode": "000",
472
+ "message:": ""
473
+ }
474
+ return dummy_response
475
+ return None
476
+
477
+ def submit_uhc_claim(client, x12_request_data):
478
+ """
479
+ Submits a UHC claim and retrieves the claim acknowledgement details.
480
+
481
+ This function first submits the claim using the provided x12 837p data. If the client is in Test Mode,
482
+ it returns a simulated response. If Test Mode is not enabled, it submits the claim and then retrieves
483
+ the claim acknowledgement details using the transaction ID from the initial response.
484
+
485
+ :param client: An instance of APIClient
486
+ :param x12_request_data: The x12 837p data as a string
487
+ :return: The final response containing the claim acknowledgement details or a dummy response if in Test Mode
488
+ """
489
+ endpoint_name = 'UHCAPI'
490
+ claim_submission_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_submission']
491
+ claim_details_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_details']
492
+
493
+ # Headers for the request
494
+ headers = {'Content-Type': 'application/json'}
495
+
496
+ # Request body for claim submission
497
+ claim_body = {'x12RequestData': x12_request_data}
498
+
499
+ # Check if Test Mode is enabled and return simulated response if so
500
+ test_mode_response = is_test_mode(client, claim_body, 'claim_submission')
501
+ if test_mode_response:
502
+ return test_mode_response
503
+
504
+ # Make the API call to submit the claim
505
+ try:
506
+ submission_response = client.make_api_call(endpoint_name, 'POST', claim_submission_url, data=claim_body, headers=headers)
507
+
508
+ # Extract the transaction ID from the submission response
509
+ transaction_id = submission_response.get('transactionId')
510
+ if not transaction_id:
511
+ raise ValueError("transactionId not found in the submission response")
512
+
513
+ # Prepare the request body for the claim acknowledgement retrieval
514
+ acknowledgement_body = {'transactionId': transaction_id}
515
+
516
+ # Check if Test Mode is enabled and return simulated response if so
517
+ test_mode_response = is_test_mode(client, acknowledgement_body, 'claim_details')
518
+ if test_mode_response:
519
+ return test_mode_response
520
+
521
+ # Make the API call to retrieve the claim acknowledgement details
522
+ acknowledgement_response = client.make_api_call(endpoint_name, 'POST', claim_details_url, data=acknowledgement_body, headers=headers)
523
+ return acknowledgement_response
524
+
525
+ except Exception as e:
526
+ print("Error during claim processing: {}".format(e))
527
+ raise
528
+
256
529
  if __name__ == "__main__":
257
530
  client = APIClient()
258
531
 
259
532
  # Define a configuration to enable or disable tests
260
533
  test_config = {
261
- 'test_fetch_payer_name': False,
534
+ 'test_fetch_payer_name': True,
262
535
  'test_claim_summary': False,
263
536
  'test_eligibility': False,
264
- 'test_eligibility_v3': True,
537
+ 'test_eligibility_v3': False,
538
+ 'test_claim_submission': False,
265
539
  }
266
540
 
267
541
  try:
@@ -271,10 +545,10 @@ if __name__ == "__main__":
271
545
  if test_config.get('test_fetch_payer_name', False):
272
546
  try:
273
547
  for case in api_test_cases:
274
- payer_name = fetch_payer_name_from_api(case['payer_id'], client.config)
275
- print("TEST API: Payer Name: {}".format(payer_name))
548
+ payer_name = fetch_payer_name_from_api(client, case['payer_id'], client.config)
549
+ print("*** TEST API: Payer Name: {}".format(payer_name))
276
550
  except Exception as e:
277
- print("TEST API: Error in Fetch Payer Name Test: {}".format(e))
551
+ print("*** TEST API: Error in Fetch Payer Name Test: {}".format(e))
278
552
 
279
553
  # Test 2: Get Claim Summary
280
554
  if test_config.get('test_claim_summary', False):
@@ -321,5 +595,16 @@ if __name__ == "__main__":
321
595
  except Exception as e:
322
596
  print("Error in getting eligibility for {}: {}".format(patient['provider_last_name'], e))
323
597
  """
598
+ # Test 5: UHC Claim Submission
599
+ if test_config.get('test_claim_submission', False):
600
+ try:
601
+ x12_request_data = (
602
+ "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~"
603
+ )
604
+ response = submit_uhc_claim(client, x12_request_data)
605
+ print("\nTEST API: Claim Submission Response:\n", response)
606
+ except Exception as e:
607
+ print("\nTEST API: Error in Claim Submission Test:\n", e)
608
+
324
609
  except Exception as e:
325
- print("TEST API: Unexpected Error: {}".format(e))
610
+ print("TEST API: Unexpected Error: {}".format(e))
MediLink/MediLink_APIs.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # Unused archive backup. This has been superceded by API_v2
2
2
 
3
- import time
4
- import requests
3
+ import time, requests
5
4
  try:
6
5
  from MediLink import MediLink_ConfigLoader
7
6
  except ImportError:
@@ -1,4 +1,6 @@
1
+ # MediLink_ClaimStatus.py
1
2
  from datetime import datetime, timedelta
3
+ import os
2
4
  import MediLink_API_v3
3
5
 
4
6
  try:
@@ -19,7 +21,7 @@ start_date_str = start_date.strftime('%m/%d/%Y')
19
21
  billing_provider_tin = config['MediLink_Config'].get('billing_provider_tin')
20
22
 
21
23
  # Define the list of payer_id's to iterate over
22
- payer_ids = ['87726'] # Default value
24
+ payer_ids = ['87726'] # Default value, getting a bad request error when trying to get all payer_ids.
23
25
  # Allowed payer id's for UHC
24
26
  # payer_ids = ['87726', '03432', '96385', '95467', '86050', '86047', '95378', '06111', '37602']
25
27
 
@@ -27,19 +29,23 @@ payer_ids = ['87726'] # Default value
27
29
  client = MediLink_API_v3.APIClient()
28
30
 
29
31
  # Function to process and display the data in a compact, tabular format
30
- def display_claim_summary(claim_summary, payer_id):
32
+ def display_claim_summary(claim_summary, payer_id, output_file):
31
33
  claims = claim_summary.get('claims', [])
32
34
 
33
35
  # Display header
34
36
  header = "Payer ID: {} | Start Date: {} | End Date: {}".format(payer_id, start_date_str, end_date_str)
35
37
  print(header)
38
+ output_file.write(header + "\n") # Write header to the output file
36
39
  print("=" * len(header))
40
+ output_file.write("=" * len(header) + "\n") # Write separator to the output file
37
41
 
38
42
  # Table header
39
43
  table_header = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
40
44
  "Claim #", "Status", "Patient", "Proc.", "Serv.", "Allowed", "Paid", "Pt Resp", "Charged")
41
45
  print(table_header)
46
+ output_file.write(table_header + "\n") # Write table header to the output file
42
47
  print("-" * len(table_header))
48
+ output_file.write("-" * len(table_header) + "\n") # Write separator to the output file
43
49
 
44
50
  # Process each claim and display it in a compact format
45
51
  claims_dict = {}
@@ -90,6 +96,7 @@ def display_claim_summary(claim_summary, payer_id):
90
96
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
91
97
  )
92
98
  print(table_row)
99
+ output_file.write(table_row + "\n") # Write each row to the output file
93
100
 
94
101
  if claim_data['total_paid_amount'] == '0.00':
95
102
  for xwalk in claim_data['claim_xwalk_data']:
@@ -109,6 +116,7 @@ def display_claim_summary(claim_summary, payer_id):
109
116
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
110
117
  )
111
118
  print(table_row + " (Duplicate with different data)")
119
+ output_file.write(table_row + " (Duplicate with different data)\n") # Write each row to the output file
112
120
 
113
121
  if claim_data['total_paid_amount'] == '0.00':
114
122
  for xwalk in claim_data['claim_xwalk_data']:
@@ -128,6 +136,7 @@ def display_claim_summary(claim_summary, payer_id):
128
136
  claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
129
137
  )
130
138
  print(table_row)
139
+ output_file.write(table_row + "\n") # Write each row to the output file
131
140
 
132
141
  if claim_data['total_paid_amount'] == '0.00':
133
142
  for xwalk in claim_data['claim_xwalk_data']:
@@ -138,7 +147,13 @@ def display_claim_summary(claim_summary, payer_id):
138
147
  clmIcnSufxCd = xwalk['clmIcnSufxCd'] # String: e.g., "01"
139
148
  print(" 507: {} ({}) | 508: {} ({}) | ICN Suffix: {}".format(clm507Cd, clm507CdDesc, clm508Cd, clm508CdDesc, clmIcnSufxCd))
140
149
 
141
- # Loop through each payer_id and call the API, then display the claim summary
142
- for payer_id in payer_ids:
143
- claim_summary = MediLink_API_v3.get_claim_summary_by_provider(client, billing_provider_tin, start_date_str, end_date_str, payer_id=payer_id)
144
- display_claim_summary(claim_summary, payer_id)
150
+ # Create a temporary file to store the claim summary
151
+ output_file_path = os.path.join(os.getenv('TEMP'), 'claim_summary_report.txt')
152
+ with open(output_file_path, 'w') as output_file:
153
+ # Loop through each payer_id and call the API, then display the claim summary
154
+ for payer_id in payer_ids:
155
+ claim_summary = MediLink_API_v3.get_claim_summary_by_provider(client, billing_provider_tin, start_date_str, end_date_str, payer_id=payer_id)
156
+ display_claim_summary(claim_summary, payer_id, output_file) # Pass output_file to the display function
157
+
158
+ # Open the generated file in Notepad
159
+ os.startfile(output_file_path) # Use os.startfile for better handling
@@ -1,11 +1,7 @@
1
- import os
2
- import json
3
- import logging
1
+ # MediLink_ConfigLoader.py
2
+ import os, json, logging, sys, platform, yaml
4
3
  from datetime import datetime
5
4
  from collections import OrderedDict
6
- import sys
7
- import platform
8
- import yaml
9
5
 
10
6
  """
11
7
  This function should be generalizable to have a initialization script over all the Medi* functions
@@ -17,7 +13,7 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
17
13
  Returns: A tuple containing dictionaries with configuration settings for the main config and crosswalk.
18
14
  """
19
15
  # TODO (Low Config Upgrade) The Medicare / Private differentiator flag probably needs to be pulled or passed to this.
20
- # BUG Hardcode sucks.
16
+ # BUG Hardcode sucks. This should probably be some local env variable.
21
17
  # Detect the operating system
22
18
  if platform.system() == 'Windows' and platform.release() == 'XP':
23
19
  # Use F: paths for Windows XP
@@ -61,7 +57,7 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
61
57
  sys.exit(1) # Exit the script due to a critical error in configuration loading
62
58
 
63
59
  # Logs messages with optional error type and claim data.
64
- def log(message, config=None, level="INFO", error_type=None, claim=None):
60
+ def log(message, config=None, level="INFO", error_type=None, claim=None, verbose=False):
65
61
 
66
62
  # If config is not provided, load it
67
63
  if config is None:
@@ -72,7 +68,11 @@ def log(message, config=None, level="INFO", error_type=None, claim=None):
72
68
  local_storage_path = config['MediLink_Config'].get('local_storage_path', '.') if isinstance(config, dict) else '.'
73
69
  log_filename = datetime.now().strftime("Log_%m%d%Y.log")
74
70
  log_filepath = os.path.join(local_storage_path, log_filename)
75
- logging.basicConfig(level=logging.INFO,
71
+
72
+ # Set logging level based on verbosity
73
+ logging_level = logging.DEBUG if verbose else logging.INFO
74
+
75
+ logging.basicConfig(level=logging_level,
76
76
  format='%(asctime)s - %(levelname)s - %(message)s',
77
77
  filename=log_filepath,
78
78
  filemode='a')