medicafe 0.240517.0__py3-none-any.whl → 0.240716.2__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 +46 -6
- MediBot/MediBot.py +9 -36
- MediBot/MediBot_Charges.py +0 -28
- MediBot/MediBot_Crosswalk_Library.py +16 -8
- MediBot/MediBot_Post.py +0 -0
- MediBot/MediBot_Preprocessor.py +26 -63
- MediBot/MediBot_Preprocessor_lib.py +182 -43
- MediBot/MediBot_UI.py +2 -7
- MediBot/MediBot_dataformat_library.py +0 -9
- MediBot/MediBot_docx_decoder.py +283 -60
- MediLink/MediLink.py +80 -120
- MediLink/MediLink_837p_encoder.py +3 -28
- MediLink/MediLink_837p_encoder_library.py +19 -53
- MediLink/MediLink_API_Generator.py +246 -0
- MediLink/MediLink_API_v2.py +2 -0
- MediLink/MediLink_API_v3.py +325 -0
- MediLink/MediLink_APIs.py +2 -0
- MediLink/MediLink_ClaimStatus.py +144 -0
- MediLink/MediLink_ConfigLoader.py +13 -7
- MediLink/MediLink_DataMgmt.py +224 -68
- MediLink/MediLink_Decoder.py +165 -0
- MediLink/MediLink_Deductible.py +203 -0
- MediLink/MediLink_Down.py +122 -96
- MediLink/MediLink_Gmail.py +453 -74
- MediLink/MediLink_Mailer.py +0 -7
- MediLink/MediLink_Parser.py +193 -0
- MediLink/MediLink_Scan.py +0 -0
- MediLink/MediLink_Scheduler.py +2 -172
- MediLink/MediLink_StatusCheck.py +0 -4
- MediLink/MediLink_UI.py +54 -18
- MediLink/MediLink_Up.py +6 -15
- {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/METADATA +4 -1
- medicafe-0.240716.2.dist-info/RECORD +47 -0
- {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/WHEEL +1 -1
- medicafe-0.240517.0.dist-info/RECORD +0 -39
- {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/LICENSE +0 -0
- {medicafe-0.240517.0.dist-info → medicafe-0.240716.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import requests
|
|
3
|
+
import yaml
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from MediLink import MediLink_ConfigLoader
|
|
9
|
+
except ImportError:
|
|
10
|
+
import MediLink_ConfigLoader
|
|
11
|
+
|
|
12
|
+
class ConfigLoader:
|
|
13
|
+
@staticmethod
|
|
14
|
+
def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
|
|
15
|
+
crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
|
|
16
|
+
return MediLink_ConfigLoader.load_configuration(config_path, crosswalk_path)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def load_swagger_file(swagger_path):
|
|
20
|
+
try:
|
|
21
|
+
print("Attempting to load Swagger file: {}".format(swagger_path))
|
|
22
|
+
with open(swagger_path, 'r') as swagger_file:
|
|
23
|
+
if swagger_path.endswith('.yaml') or swagger_path.endswith('.yml'):
|
|
24
|
+
print("Parsing YAML file: {}".format(swagger_path))
|
|
25
|
+
swagger_data = yaml.safe_load(swagger_file)
|
|
26
|
+
elif swagger_path.endswith('.json'):
|
|
27
|
+
print("Parsing JSON file: {}".format(swagger_path))
|
|
28
|
+
swagger_data = json.load(swagger_file)
|
|
29
|
+
else:
|
|
30
|
+
raise ValueError("Unsupported Swagger file format.")
|
|
31
|
+
print("Successfully loaded Swagger file: {}".format(swagger_path))
|
|
32
|
+
return swagger_data
|
|
33
|
+
except ValueError as e:
|
|
34
|
+
print("Error parsing Swagger file {}: {}".format(swagger_path, e))
|
|
35
|
+
MediLink_ConfigLoader.log("Error parsing Swagger file {}: {}".format(swagger_path, e), level="ERROR")
|
|
36
|
+
except FileNotFoundError:
|
|
37
|
+
print("Swagger file not found: {}".format(swagger_path))
|
|
38
|
+
MediLink_ConfigLoader.log("Swagger file not found: {}".format(swagger_path), level="ERROR")
|
|
39
|
+
except Exception as e:
|
|
40
|
+
print("Unexpected error loading Swagger file {}: {}".format(swagger_path, e))
|
|
41
|
+
MediLink_ConfigLoader.log("Unexpected error loading Swagger file {}: {}".format(swagger_path, e), level="ERROR")
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Function to ensure numeric type
|
|
45
|
+
def ensure_numeric(value):
|
|
46
|
+
if isinstance(value, str):
|
|
47
|
+
try:
|
|
48
|
+
value = float(value)
|
|
49
|
+
except ValueError:
|
|
50
|
+
raise ValueError("Cannot convert {} to a numeric type".format(value))
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
class TokenCache:
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.tokens = {}
|
|
56
|
+
|
|
57
|
+
def get(self, endpoint_name, current_time):
|
|
58
|
+
token_info = self.tokens.get(endpoint_name, {})
|
|
59
|
+
if token_info and token_info['expires_at'] > current_time:
|
|
60
|
+
return token_info['access_token']
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def set(self, endpoint_name, access_token, expires_in, current_time):
|
|
64
|
+
# Ensure types are correct
|
|
65
|
+
current_time = ensure_numeric(current_time)
|
|
66
|
+
expires_in = ensure_numeric(expires_in)
|
|
67
|
+
|
|
68
|
+
self.tokens[endpoint_name] = {
|
|
69
|
+
'access_token': access_token,
|
|
70
|
+
'expires_at': current_time + expires_in - 120
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class BaseAPIClient:
|
|
74
|
+
def __init__(self, config):
|
|
75
|
+
self.config = config
|
|
76
|
+
self.token_cache = TokenCache()
|
|
77
|
+
|
|
78
|
+
def get_access_token(self, endpoint_name):
|
|
79
|
+
raise NotImplementedError("Subclasses should implement this!")
|
|
80
|
+
|
|
81
|
+
def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
|
|
82
|
+
raise NotImplementedError("Subclasses should implement this!")
|
|
83
|
+
|
|
84
|
+
class APIClient(BaseAPIClient):
|
|
85
|
+
def __init__(self):
|
|
86
|
+
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
87
|
+
super().__init__(config)
|
|
88
|
+
|
|
89
|
+
def get_access_token(self, endpoint_name):
|
|
90
|
+
current_time = time.time()
|
|
91
|
+
cached_token = self.token_cache.get(endpoint_name, current_time)
|
|
92
|
+
if cached_token:
|
|
93
|
+
MediLink_ConfigLoader.log("Using cached token for endpoint: {}".format(endpoint_name), level="INFO")
|
|
94
|
+
return cached_token
|
|
95
|
+
|
|
96
|
+
endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
|
|
97
|
+
token_url = endpoint_config['token_url']
|
|
98
|
+
data = {
|
|
99
|
+
'grant_type': 'client_credentials',
|
|
100
|
+
'client_id': endpoint_config['client_id'],
|
|
101
|
+
'client_secret': endpoint_config['client_secret']
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Add scope if specified in the configuration
|
|
105
|
+
if 'scope' in endpoint_config:
|
|
106
|
+
data['scope'] = endpoint_config['scope']
|
|
107
|
+
|
|
108
|
+
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
109
|
+
|
|
110
|
+
response = requests.post(token_url, headers=headers, data=data)
|
|
111
|
+
response.raise_for_status()
|
|
112
|
+
token_data = response.json()
|
|
113
|
+
access_token = token_data['access_token']
|
|
114
|
+
expires_in = token_data.get('expires_in', 3600)
|
|
115
|
+
|
|
116
|
+
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")
|
|
118
|
+
return access_token
|
|
119
|
+
|
|
120
|
+
def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
|
|
121
|
+
token = self.get_access_token(endpoint_name)
|
|
122
|
+
if headers is None:
|
|
123
|
+
headers = {}
|
|
124
|
+
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")
|
|
142
|
+
|
|
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()
|
|
147
|
+
|
|
148
|
+
return response.json()
|
|
149
|
+
|
|
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']
|
|
154
|
+
|
|
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())
|
|
159
|
+
|
|
160
|
+
for endpoint_name in endpoint_order:
|
|
161
|
+
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})
|
|
163
|
+
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")
|
|
170
|
+
except Exception as e:
|
|
171
|
+
MediLink_ConfigLoader.log("API call to {} failed: {}".format(endpoint_name, e), level="ERROR")
|
|
172
|
+
|
|
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)
|
|
176
|
+
|
|
177
|
+
def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false'):
|
|
178
|
+
endpoint_name = 'UHCApi'
|
|
179
|
+
url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_summary_by_provider']
|
|
180
|
+
headers = {
|
|
181
|
+
'tin': tin,
|
|
182
|
+
'firstServiceDt': first_service_date,
|
|
183
|
+
'lastServiceDt': last_service_date,
|
|
184
|
+
'payerId': payer_id,
|
|
185
|
+
'getStandardError': get_standard_error,
|
|
186
|
+
'Accept': 'application/json'
|
|
187
|
+
}
|
|
188
|
+
return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
|
|
189
|
+
|
|
190
|
+
def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
|
|
191
|
+
endpoint_name = 'UHCApi'
|
|
192
|
+
url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility']
|
|
193
|
+
url_extension = url_extension + '?payerID={}&providerLastName={}&searchOption={}&dateOfBirth={}&memberId={}&npi={}'.format(
|
|
194
|
+
payer_id, provider_last_name, search_option, date_of_birth, member_id, npi)
|
|
195
|
+
return client.make_api_call(endpoint_name, 'GET', url_extension)
|
|
196
|
+
|
|
197
|
+
def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
|
|
198
|
+
first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
|
|
199
|
+
middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
|
|
200
|
+
service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
|
|
201
|
+
corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
|
|
202
|
+
organization_id=None, identify_service_level_deductible=True):
|
|
203
|
+
|
|
204
|
+
# Ensure all required parameters have values
|
|
205
|
+
if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
|
|
206
|
+
raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
|
|
207
|
+
|
|
208
|
+
# Validate payer_id
|
|
209
|
+
valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
|
|
210
|
+
if payer_id not in valid_payer_ids:
|
|
211
|
+
raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
|
|
212
|
+
|
|
213
|
+
endpoint_name = 'UHCApi'
|
|
214
|
+
url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_v3']
|
|
215
|
+
|
|
216
|
+
# Construct request body
|
|
217
|
+
body = {
|
|
218
|
+
"memberId": member_id,
|
|
219
|
+
"lastName": last_name,
|
|
220
|
+
"firstName": first_name,
|
|
221
|
+
"dateOfBirth": date_of_birth,
|
|
222
|
+
"payerID": payer_id,
|
|
223
|
+
"payerLabel": payer_label,
|
|
224
|
+
"payerName": payer_name,
|
|
225
|
+
"serviceStart": service_start,
|
|
226
|
+
"serviceEnd": service_end,
|
|
227
|
+
"middleName": middle_name,
|
|
228
|
+
"gender": gender,
|
|
229
|
+
"ssn": ssn,
|
|
230
|
+
"city": city,
|
|
231
|
+
"state": state,
|
|
232
|
+
"zip": zip,
|
|
233
|
+
"groupNumber": group_number,
|
|
234
|
+
"serviceTypeCode": service_type_code,
|
|
235
|
+
"providerLastName": provider_last_name,
|
|
236
|
+
"providerFirstName": provider_first_name,
|
|
237
|
+
"taxIdNumber": tax_id_number,
|
|
238
|
+
"providerNameID": provider_name_id,
|
|
239
|
+
"npi": npi,
|
|
240
|
+
"corporateTaxOwnerID": corporate_tax_owner_id,
|
|
241
|
+
"corporateTaxOwnerName": corporate_tax_owner_name,
|
|
242
|
+
"organizationName": organization_name,
|
|
243
|
+
"organizationID": organization_id,
|
|
244
|
+
"searchOption": search_option,
|
|
245
|
+
"identifyServiceLevelDeductible": identify_service_level_deductible
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Remove None values from the body
|
|
249
|
+
body = {k: v for k, v in body.items() if v is not None}
|
|
250
|
+
|
|
251
|
+
# Log the request body
|
|
252
|
+
MediLink_ConfigLoader.log("Request body: {}".format(json.dumps(body, indent=4)), level="DEBUG")
|
|
253
|
+
|
|
254
|
+
return client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=body)
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
client = APIClient()
|
|
258
|
+
|
|
259
|
+
# Define a configuration to enable or disable tests
|
|
260
|
+
test_config = {
|
|
261
|
+
'test_fetch_payer_name': False,
|
|
262
|
+
'test_claim_summary': False,
|
|
263
|
+
'test_eligibility': False,
|
|
264
|
+
'test_eligibility_v3': True,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
api_test_cases = client.config['MediLink_Config']['API Test Case']
|
|
269
|
+
|
|
270
|
+
# Test 1: Fetch Payer Name
|
|
271
|
+
if test_config.get('test_fetch_payer_name', False):
|
|
272
|
+
try:
|
|
273
|
+
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))
|
|
276
|
+
except Exception as e:
|
|
277
|
+
print("TEST API: Error in Fetch Payer Name Test: {}".format(e))
|
|
278
|
+
|
|
279
|
+
# Test 2: Get Claim Summary
|
|
280
|
+
if test_config.get('test_claim_summary', False):
|
|
281
|
+
try:
|
|
282
|
+
for case in api_test_cases:
|
|
283
|
+
claim_summary = get_claim_summary_by_provider(client, case['provider_tin'], '05/01/2024', '06/23/2024', case['payer_id'])
|
|
284
|
+
print("TEST API: Claim Summary: {}".format(claim_summary))
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print("TEST API: Error in Claim Summary Test: {}".format(e))
|
|
287
|
+
|
|
288
|
+
# Test 3: Get Eligibility
|
|
289
|
+
if test_config.get('test_eligibility', False):
|
|
290
|
+
try:
|
|
291
|
+
for case in api_test_cases:
|
|
292
|
+
eligibility = get_eligibility(client, case['payer_id'], case['provider_last_name'], case['search_option'],
|
|
293
|
+
case['date_of_birth'], case['member_id'], case['npi'])
|
|
294
|
+
print("TEST API: Eligibility: {}".format(eligibility))
|
|
295
|
+
except Exception as e:
|
|
296
|
+
print("TEST API: Error in Eligibility Test: {}".format(e))
|
|
297
|
+
|
|
298
|
+
# Test 4: Get Eligibility v3
|
|
299
|
+
if test_config.get('test_eligibility_v3', False):
|
|
300
|
+
try:
|
|
301
|
+
for case in api_test_cases:
|
|
302
|
+
eligibility_v3 = get_eligibility_v3(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
|
|
303
|
+
search_option=case['search_option'], date_of_birth=case['date_of_birth'],
|
|
304
|
+
member_id=case['member_id'], npi=case['npi'])
|
|
305
|
+
print("TEST API: Eligibility v3: {}".format(eligibility_v3))
|
|
306
|
+
except Exception as e:
|
|
307
|
+
print("TEST API: Error in Eligibility v3 Test: {}".format(e))
|
|
308
|
+
|
|
309
|
+
"""
|
|
310
|
+
# Example of iterating over multiple patients (if needed)
|
|
311
|
+
patients = [
|
|
312
|
+
{'payer_id': '87726', 'provider_last_name': 'VIDA', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1980-01-01', 'member_id': '123456789', 'npi': '9876543210'},
|
|
313
|
+
{'payer_id': '87726', 'provider_last_name': 'SMITH', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1970-02-02', 'member_id': '987654321', 'npi': '1234567890'},
|
|
314
|
+
# Add more patients as needed
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
for patient in patients:
|
|
318
|
+
try:
|
|
319
|
+
eligibility = get_eligibility(client, patient['payer_id'], patient['provider_last_name'], patient['search_option'], patient['date_of_birth'], patient['member_id'], patient['npi'])
|
|
320
|
+
print("Eligibility for {}: {}".format(patient['provider_last_name'], eligibility))
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print("Error in getting eligibility for {}: {}".format(patient['provider_last_name'], e))
|
|
323
|
+
"""
|
|
324
|
+
except Exception as e:
|
|
325
|
+
print("TEST API: Unexpected Error: {}".format(e))
|
MediLink/MediLink_APIs.py
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
import MediLink_API_v3
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from MediLink import MediLink_ConfigLoader
|
|
6
|
+
except ImportError:
|
|
7
|
+
import MediLink_ConfigLoader
|
|
8
|
+
|
|
9
|
+
# Load configuration
|
|
10
|
+
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
11
|
+
|
|
12
|
+
# Calculate start_date as 60 days before today's date and end_date as today's date
|
|
13
|
+
end_date = datetime.today()
|
|
14
|
+
start_date = end_date - timedelta(days=60)
|
|
15
|
+
end_date_str = end_date.strftime('%m/%d/%Y')
|
|
16
|
+
start_date_str = start_date.strftime('%m/%d/%Y')
|
|
17
|
+
|
|
18
|
+
# Get billing provider TIN from configuration
|
|
19
|
+
billing_provider_tin = config['MediLink_Config'].get('billing_provider_tin')
|
|
20
|
+
|
|
21
|
+
# Define the list of payer_id's to iterate over
|
|
22
|
+
payer_ids = ['87726'] # Default value
|
|
23
|
+
# Allowed payer id's for UHC
|
|
24
|
+
# payer_ids = ['87726', '03432', '96385', '95467', '86050', '86047', '95378', '06111', '37602']
|
|
25
|
+
|
|
26
|
+
# Initialize the API client
|
|
27
|
+
client = MediLink_API_v3.APIClient()
|
|
28
|
+
|
|
29
|
+
# Function to process and display the data in a compact, tabular format
|
|
30
|
+
def display_claim_summary(claim_summary, payer_id):
|
|
31
|
+
claims = claim_summary.get('claims', [])
|
|
32
|
+
|
|
33
|
+
# Display header
|
|
34
|
+
header = "Payer ID: {} | Start Date: {} | End Date: {}".format(payer_id, start_date_str, end_date_str)
|
|
35
|
+
print(header)
|
|
36
|
+
print("=" * len(header))
|
|
37
|
+
|
|
38
|
+
# Table header
|
|
39
|
+
table_header = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
|
|
40
|
+
"Claim #", "Status", "Patient", "Proc.", "Serv.", "Allowed", "Paid", "Pt Resp", "Charged")
|
|
41
|
+
print(table_header)
|
|
42
|
+
print("-" * len(table_header))
|
|
43
|
+
|
|
44
|
+
# Process each claim and display it in a compact format
|
|
45
|
+
claims_dict = {}
|
|
46
|
+
for claim in claims:
|
|
47
|
+
claim_number = claim['claimNumber'] # String: e.g., "29285698"
|
|
48
|
+
claim_status = claim['claimStatus'] # String: e.g., "Finalized"
|
|
49
|
+
patient_first_name = claim['memberInfo']['ptntFn'] # String: e.g., "FRANK"
|
|
50
|
+
patient_last_name = claim['memberInfo']['ptntLn'] # String: e.g., "LOHR"
|
|
51
|
+
processed_date = claim['claimSummary']['processedDt'] # String (Date in "MM/DD/YYYY" format): e.g., "06/10/2024"
|
|
52
|
+
first_service_date = claim['claimSummary']['firstSrvcDt'] # String (Date in "MM/DD/YYYY" format): e.g., "05/13/2024"
|
|
53
|
+
total_charged_amount = claim['claimSummary']['totalChargedAmt'] # String (Decimal as String): e.g., "450.00"
|
|
54
|
+
total_allowed_amount = claim['claimSummary']['totalAllowdAmt'] # String (Decimal as String): e.g., "108.95"
|
|
55
|
+
total_paid_amount = claim['claimSummary']['totalPaidAmt'] # String (Decimal as String): e.g., "106.78"
|
|
56
|
+
total_patient_responsibility_amount = claim['claimSummary']['totalPtntRespAmt'] # String (Decimal as String): e.g., "0.00"
|
|
57
|
+
|
|
58
|
+
patient_name = "{} {}".format(patient_first_name, patient_last_name)
|
|
59
|
+
|
|
60
|
+
# Store claims in a dictionary to handle duplicate claim numbers
|
|
61
|
+
if claim_number not in claims_dict:
|
|
62
|
+
claims_dict[claim_number] = []
|
|
63
|
+
claims_dict[claim_number].append({
|
|
64
|
+
'claim_status': claim_status,
|
|
65
|
+
'patient_name': patient_name,
|
|
66
|
+
'processed_date': processed_date,
|
|
67
|
+
'first_service_date': first_service_date,
|
|
68
|
+
'total_charged_amount': total_charged_amount,
|
|
69
|
+
'total_allowed_amount': total_allowed_amount,
|
|
70
|
+
'total_paid_amount': total_paid_amount,
|
|
71
|
+
'total_patient_responsibility_amount': total_patient_responsibility_amount,
|
|
72
|
+
'claim_xwalk_data': claim['claimSummary']['clmXWalkData']
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
# Sort claims by first_service_date
|
|
76
|
+
sorted_claims = sorted(claims_dict.items(), key=lambda x: x[1][0]['first_service_date'])
|
|
77
|
+
|
|
78
|
+
for claim_number, claim_data_list in sorted_claims:
|
|
79
|
+
# Check for repeated claim numbers and validate data
|
|
80
|
+
if len(claim_data_list) > 1:
|
|
81
|
+
# Validate data
|
|
82
|
+
unique_claims = {tuple(claim_data.items()) for claim_data in claim_data_list}
|
|
83
|
+
if len(unique_claims) == 1:
|
|
84
|
+
# Data is the same, only print once
|
|
85
|
+
claim_data = claim_data_list[0]
|
|
86
|
+
table_row = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
|
|
87
|
+
claim_number, claim_data['claim_status'], claim_data['patient_name'][:20],
|
|
88
|
+
claim_data['processed_date'][:5], claim_data['first_service_date'][:5],
|
|
89
|
+
claim_data['total_allowed_amount'], claim_data['total_paid_amount'],
|
|
90
|
+
claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
|
|
91
|
+
)
|
|
92
|
+
print(table_row)
|
|
93
|
+
|
|
94
|
+
if claim_data['total_paid_amount'] == '0.00':
|
|
95
|
+
for xwalk in claim_data['claim_xwalk_data']:
|
|
96
|
+
clm507Cd = xwalk['clm507Cd'] # String: e.g., "F1"
|
|
97
|
+
clm507CdDesc = xwalk['clm507CdDesc'] # String: e.g., "Finalized/Payment-The claim/line has been paid."
|
|
98
|
+
clm508Cd = xwalk['clm508Cd'] # String: e.g., "104"
|
|
99
|
+
clm508CdDesc = xwalk['clm508CdDesc'] # String: e.g., "Processed according to plan provisions..."
|
|
100
|
+
clmIcnSufxCd = xwalk['clmIcnSufxCd'] # String: e.g., "01"
|
|
101
|
+
print(" 507: {} ({}) | 508: {} ({}) | ICN Suffix: {}".format(clm507Cd, clm507CdDesc, clm508Cd, clm508CdDesc, clmIcnSufxCd))
|
|
102
|
+
else:
|
|
103
|
+
# Data is different, print all
|
|
104
|
+
for claim_data in claim_data_list:
|
|
105
|
+
table_row = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
|
|
106
|
+
claim_number, claim_data['claim_status'], claim_data['patient_name'][:20],
|
|
107
|
+
claim_data['processed_date'][:5], claim_data['first_service_date'][:5],
|
|
108
|
+
claim_data['total_allowed_amount'], claim_data['total_paid_amount'],
|
|
109
|
+
claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
|
|
110
|
+
)
|
|
111
|
+
print(table_row + " (Duplicate with different data)")
|
|
112
|
+
|
|
113
|
+
if claim_data['total_paid_amount'] == '0.00':
|
|
114
|
+
for xwalk in claim_data['claim_xwalk_data']:
|
|
115
|
+
clm507Cd = xwalk['clm507Cd'] # String: e.g., "F1"
|
|
116
|
+
clm507CdDesc = xwalk['clm507CdDesc'] # String: e.g., "Finalized/Payment-The claim/line has been paid."
|
|
117
|
+
clm508Cd = xwalk['clm508Cd'] # String: e.g., "104"
|
|
118
|
+
clm508CdDesc = xwalk['clm508CdDesc'] # String: e.g., "Processed according to plan provisions..."
|
|
119
|
+
clmIcnSufxCd = xwalk['clmIcnSufxCd'] # String: e.g., "01"
|
|
120
|
+
print(" 507: {} ({}) | 508: {} ({}) | ICN Suffix: {}".format(clm507Cd, clm507CdDesc, clm508Cd, clm508CdDesc, clmIcnSufxCd))
|
|
121
|
+
else:
|
|
122
|
+
# Only one claim, print normally
|
|
123
|
+
claim_data = claim_data_list[0]
|
|
124
|
+
table_row = "{:<10} | {:<10} | {:<20} | {:<6} | {:<6} | {:<7} | {:<7} | {:<7} | {:<7}".format(
|
|
125
|
+
claim_number, claim_data['claim_status'], claim_data['patient_name'][:20],
|
|
126
|
+
claim_data['processed_date'][:5], claim_data['first_service_date'][:5],
|
|
127
|
+
claim_data['total_allowed_amount'], claim_data['total_paid_amount'],
|
|
128
|
+
claim_data['total_patient_responsibility_amount'], claim_data['total_charged_amount']
|
|
129
|
+
)
|
|
130
|
+
print(table_row)
|
|
131
|
+
|
|
132
|
+
if claim_data['total_paid_amount'] == '0.00':
|
|
133
|
+
for xwalk in claim_data['claim_xwalk_data']:
|
|
134
|
+
clm507Cd = xwalk['clm507Cd'] # String: e.g., "F1"
|
|
135
|
+
clm507CdDesc = xwalk['clm507CdDesc'] # String: e.g., "Finalized/Payment-The claim/line has been paid."
|
|
136
|
+
clm508Cd = xwalk['clm508Cd'] # String: e.g., "104"
|
|
137
|
+
clm508CdDesc = xwalk['clm508CdDesc'] # String: e.g., "Processed according to plan provisions..."
|
|
138
|
+
clmIcnSufxCd = xwalk['clmIcnSufxCd'] # String: e.g., "01"
|
|
139
|
+
print(" 507: {} ({}) | 508: {} ({}) | ICN Suffix: {}".format(clm507Cd, clm507CdDesc, clm508Cd, clm508CdDesc, clmIcnSufxCd))
|
|
140
|
+
|
|
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)
|
|
@@ -5,18 +5,19 @@ from datetime import datetime
|
|
|
5
5
|
from collections import OrderedDict
|
|
6
6
|
import sys
|
|
7
7
|
import platform
|
|
8
|
+
import yaml
|
|
8
9
|
|
|
9
10
|
"""
|
|
10
11
|
This function should be generalizable to have a initialization script over all the Medi* functions
|
|
11
12
|
"""
|
|
12
13
|
def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'), crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
|
|
13
14
|
"""
|
|
14
|
-
Loads endpoint configuration, credentials, and other settings from JSON files.
|
|
15
|
+
Loads endpoint configuration, credentials, and other settings from JSON or YAML files.
|
|
15
16
|
|
|
16
17
|
Returns: A tuple containing dictionaries with configuration settings for the main config and crosswalk.
|
|
17
18
|
"""
|
|
18
19
|
# TODO (Low Config Upgrade) The Medicare / Private differentiator flag probably needs to be pulled or passed to this.
|
|
19
|
-
# BUG
|
|
20
|
+
# BUG Hardcode sucks.
|
|
20
21
|
# Detect the operating system
|
|
21
22
|
if platform.system() == 'Windows' and platform.release() == 'XP':
|
|
22
23
|
# Use F: paths for Windows XP
|
|
@@ -29,10 +30,15 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
|
|
|
29
30
|
|
|
30
31
|
try:
|
|
31
32
|
with open(config_path, 'r') as config_file:
|
|
32
|
-
|
|
33
|
+
if config_path.endswith('.yaml') or config_path.endswith('.yml'):
|
|
34
|
+
config = yaml.safe_load(config_file)
|
|
35
|
+
elif config_path.endswith('.json'):
|
|
36
|
+
config = json.load(config_file, object_pairs_hook=OrderedDict)
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError("Unsupported configuration format.")
|
|
39
|
+
|
|
33
40
|
if 'MediLink_Config' not in config:
|
|
34
41
|
raise KeyError("MediLink_Config key is missing from the loaded configuration.")
|
|
35
|
-
# MediLink_config = config['MediLink_Config']
|
|
36
42
|
|
|
37
43
|
with open(crosswalk_path, 'r') as crosswalk_file:
|
|
38
44
|
crosswalk = json.load(crosswalk_file)
|
|
@@ -40,12 +46,12 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
|
|
|
40
46
|
return config, crosswalk
|
|
41
47
|
except ValueError as e:
|
|
42
48
|
if isinstance(e, UnicodeDecodeError):
|
|
43
|
-
print("Error decoding
|
|
49
|
+
print("Error decoding file: {}".format(e))
|
|
44
50
|
else:
|
|
45
|
-
print("Error parsing
|
|
51
|
+
print("Error parsing file: {}".format(e))
|
|
46
52
|
sys.exit(1) # Exit the script due to a critical error in configuration loading
|
|
47
53
|
except FileNotFoundError:
|
|
48
|
-
print("One or both
|
|
54
|
+
print("One or both configuration files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
|
|
49
55
|
sys.exit(1) # Exit the script due to a critical error in configuration loading
|
|
50
56
|
except KeyError as e:
|
|
51
57
|
print("Critical configuration is missing: {}".format(e))
|