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.
- MediBot/MediBot.bat +56 -16
- MediBot/MediBot.py +100 -78
- MediBot/MediBot_Crosswalk_Library.py +496 -194
- MediBot/MediBot_Preprocessor.py +22 -14
- MediBot/MediBot_Preprocessor_lib.py +301 -143
- MediBot/MediBot_UI.py +25 -24
- MediBot/MediBot_dataformat_library.py +17 -25
- MediBot/MediBot_docx_decoder.py +267 -110
- MediBot/update_json.py +26 -1
- MediBot/update_medicafe.py +134 -44
- MediLink/MediLink.py +95 -53
- MediLink/MediLink_837p_encoder.py +83 -66
- MediLink/MediLink_837p_encoder_library.py +159 -102
- MediLink/MediLink_API_Generator.py +1 -7
- MediLink/MediLink_API_v3.py +348 -63
- MediLink/MediLink_APIs.py +1 -2
- MediLink/MediLink_ClaimStatus.py +21 -6
- MediLink/MediLink_ConfigLoader.py +9 -9
- MediLink/MediLink_DataMgmt.py +321 -100
- MediLink/MediLink_Decoder.py +249 -87
- MediLink/MediLink_Deductible.py +62 -56
- MediLink/MediLink_Down.py +115 -121
- MediLink/MediLink_Gmail.py +2 -11
- MediLink/MediLink_Parser.py +63 -36
- MediLink/MediLink_UI.py +36 -23
- MediLink/MediLink_Up.py +188 -115
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/METADATA +1 -1
- medicafe-0.240925.9.dist-info/RECORD +47 -0
- medicafe-0.240716.2.dist-info/RECORD +0 -47
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/LICENSE +0 -0
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/WHEEL +0 -0
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/top_level.txt +0 -0
MediLink/MediLink_API_v3.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
|
|
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
|
|
60
|
-
|
|
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':
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
174
|
+
masked_headers = headers.copy()
|
|
175
|
+
if 'Authorization' in masked_headers:
|
|
176
|
+
masked_headers['Authorization'] = 'Bearer ***'
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 = '
|
|
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 = '
|
|
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 = '
|
|
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':
|
|
534
|
+
'test_fetch_payer_name': True,
|
|
262
535
|
'test_claim_summary': False,
|
|
263
536
|
'test_eligibility': False,
|
|
264
|
-
'test_eligibility_v3':
|
|
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
MediLink/MediLink_ClaimStatus.py
CHANGED
|
@@ -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
|
-
#
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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')
|