medicafe 0.240613.0__py3-none-any.whl → 0.240809.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -0,0 +1,429 @@
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
+ """
13
+ TODO At some point it might make sense to test their acknoledgment endpoint. body is transactionId.
14
+ This API is used to extract the claim acknowledgement details for the given transactionid which was
15
+ generated for 837 requests in claim submission process. Claims Acknowledgement (277CA) will provide
16
+ a status of claim-level acknowledgement of all claims received in the front-end processing system and
17
+ adjudication system.
18
+ """
19
+
20
+ class ConfigLoader:
21
+ @staticmethod
22
+ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'),
23
+ crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
24
+ return MediLink_ConfigLoader.load_configuration(config_path, crosswalk_path)
25
+
26
+ @staticmethod
27
+ def load_swagger_file(swagger_path):
28
+ try:
29
+ print("Attempting to load Swagger file: {}".format(swagger_path))
30
+ with open(swagger_path, 'r') as swagger_file:
31
+ if swagger_path.endswith('.yaml') or swagger_path.endswith('.yml'):
32
+ print("Parsing YAML file: {}".format(swagger_path))
33
+ swagger_data = yaml.safe_load(swagger_file)
34
+ elif swagger_path.endswith('.json'):
35
+ print("Parsing JSON file: {}".format(swagger_path))
36
+ swagger_data = json.load(swagger_file)
37
+ else:
38
+ raise ValueError("Unsupported Swagger file format.")
39
+ print("Successfully loaded Swagger file: {}".format(swagger_path))
40
+ return swagger_data
41
+ except ValueError as e:
42
+ print("Error parsing Swagger file {}: {}".format(swagger_path, e))
43
+ MediLink_ConfigLoader.log("Error parsing Swagger file {}: {}".format(swagger_path, e), level="ERROR")
44
+ except FileNotFoundError:
45
+ print("Swagger file not found: {}".format(swagger_path))
46
+ MediLink_ConfigLoader.log("Swagger file not found: {}".format(swagger_path), level="ERROR")
47
+ except Exception as e:
48
+ print("Unexpected error loading Swagger file {}: {}".format(swagger_path, e))
49
+ MediLink_ConfigLoader.log("Unexpected error loading Swagger file {}: {}".format(swagger_path, e), level="ERROR")
50
+ return None
51
+
52
+ # Function to ensure numeric type
53
+ def ensure_numeric(value):
54
+ if isinstance(value, str):
55
+ try:
56
+ value = float(value)
57
+ except ValueError:
58
+ raise ValueError("Cannot convert {} to a numeric type".format(value))
59
+ return value
60
+
61
+ class TokenCache:
62
+ def __init__(self):
63
+ self.tokens = {}
64
+
65
+ def get(self, endpoint_name, current_time):
66
+ token_info = self.tokens.get(endpoint_name, {})
67
+ if token_info and token_info['expires_at'] > current_time:
68
+ return token_info['access_token']
69
+ return None
70
+
71
+ def set(self, endpoint_name, access_token, expires_in, current_time):
72
+ # Ensure types are correct
73
+ current_time = ensure_numeric(current_time)
74
+ expires_in = ensure_numeric(expires_in)
75
+
76
+ self.tokens[endpoint_name] = {
77
+ 'access_token': access_token,
78
+ 'expires_at': current_time + expires_in - 120
79
+ }
80
+
81
+ class BaseAPIClient:
82
+ def __init__(self, config):
83
+ self.config = config
84
+ self.token_cache = TokenCache()
85
+
86
+ def get_access_token(self, endpoint_name):
87
+ raise NotImplementedError("Subclasses should implement this!")
88
+
89
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
90
+ raise NotImplementedError("Subclasses should implement this!")
91
+
92
+ class APIClient(BaseAPIClient):
93
+ def __init__(self):
94
+ config, _ = MediLink_ConfigLoader.load_configuration()
95
+ super().__init__(config)
96
+
97
+ def get_access_token(self, endpoint_name):
98
+ current_time = time.time()
99
+ cached_token = self.token_cache.get(endpoint_name, current_time)
100
+ if cached_token:
101
+ MediLink_ConfigLoader.log("Using cached token for endpoint: {}".format(endpoint_name), level="INFO")
102
+ return cached_token
103
+
104
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
105
+ token_url = endpoint_config['token_url']
106
+ data = {
107
+ 'grant_type': 'client_credentials',
108
+ 'client_id': endpoint_config['client_id'],
109
+ 'client_secret': endpoint_config['client_secret']
110
+ }
111
+
112
+ # Add scope if specified in the configuration
113
+ if 'scope' in endpoint_config:
114
+ data['scope'] = endpoint_config['scope']
115
+
116
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
117
+
118
+ response = requests.post(token_url, headers=headers, data=data)
119
+ response.raise_for_status()
120
+ token_data = response.json()
121
+ access_token = token_data['access_token']
122
+ expires_in = token_data.get('expires_in', 3600)
123
+
124
+ self.token_cache.set(endpoint_name, access_token, expires_in, current_time)
125
+ MediLink_ConfigLoader.log("Obtained new token for endpoint: {}".format(endpoint_name), level="INFO")
126
+ return access_token
127
+
128
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None, headers=None):
129
+ token = self.get_access_token(endpoint_name)
130
+ if headers is None:
131
+ headers = {}
132
+ headers.update({'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'})
133
+ url = self.config['MediLink_Config']['endpoints'][endpoint_name]['api_url'] + url_extension
134
+
135
+ # Debug: Print request details
136
+ # print("Request URL: {}".format(url))
137
+ # print("Request Headers: {}".format(headers))
138
+ # print("Request Params: {}".format(params))
139
+ # print("Request Data: {}".format(data))
140
+
141
+ if call_type == 'GET':
142
+ response = requests.get(url, headers=headers, params=params)
143
+ elif call_type == 'POST':
144
+ headers['Content-Type'] = 'application/json'
145
+ response = requests.post(url, headers=headers, json=data)
146
+ elif call_type == 'DELETE':
147
+ response = requests.delete(url, headers=headers)
148
+ else:
149
+ raise ValueError("Unsupported call type")
150
+
151
+ if response.status_code >= 400:
152
+ error_message = "Error {}: {}".format(response.status_code, response.text)
153
+ MediLink_ConfigLoader.log(error_message, level="ERROR")
154
+ response.raise_for_status()
155
+
156
+ return response.json()
157
+
158
+ def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
159
+ client = APIClient()
160
+ config, _ = MediLink_ConfigLoader.load_configuration()
161
+ endpoints = config['MediLink_Config']['endpoints']
162
+
163
+ if primary_endpoint and primary_endpoint in endpoints:
164
+ endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
165
+ else:
166
+ endpoint_order = list(endpoints.keys())
167
+
168
+ for endpoint_name in endpoint_order:
169
+ try:
170
+ response = client.make_api_call(endpoint_name, 'GET', config['MediLink_Config']['endpoints'][endpoint_name].get('payer_list_endpoint', '/availity-payer-list'), {'payerId': payer_id})
171
+ payers = response.get('payers', [])
172
+ if payers:
173
+ payer_name = payers[0].get('displayName', payers[0].get('name'))
174
+ MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), level="INFO")
175
+ return payer_name
176
+ else:
177
+ MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), level="INFO")
178
+ except Exception as e:
179
+ MediLink_ConfigLoader.log("API call to {} failed: {}".format(endpoint_name, e), level="ERROR")
180
+
181
+ error_message = "All endpoints exhausted for Payer ID {}.".format(payer_id)
182
+ MediLink_ConfigLoader.log(error_message, level="CRITICAL")
183
+ raise ValueError(error_message)
184
+
185
+ def get_claim_summary_by_provider(client, tin, first_service_date, last_service_date, payer_id, get_standard_error='false'):
186
+ endpoint_name = 'UHCAPI'
187
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_summary_by_provider']
188
+ headers = {
189
+ 'tin': tin,
190
+ 'firstServiceDt': first_service_date,
191
+ 'lastServiceDt': last_service_date,
192
+ 'payerId': payer_id,
193
+ 'getStandardError': get_standard_error,
194
+ 'Accept': 'application/json'
195
+ }
196
+ return client.make_api_call(endpoint_name, 'GET', url_extension, params=None, data=None, headers=headers)
197
+
198
+ def get_eligibility(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi):
199
+ endpoint_name = 'UHCAPI'
200
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility']
201
+ url_extension = url_extension + '?payerID={}&providerLastName={}&searchOption={}&dateOfBirth={}&memberId={}&npi={}'.format(
202
+ payer_id, provider_last_name, search_option, date_of_birth, member_id, npi)
203
+ return client.make_api_call(endpoint_name, 'GET', url_extension)
204
+
205
+ def get_eligibility_v3(client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi,
206
+ first_name=None, last_name=None, payer_label=None, payer_name=None, service_start=None, service_end=None,
207
+ middle_name=None, gender=None, ssn=None, city=None, state=None, zip=None, group_number=None,
208
+ service_type_code=None, provider_first_name=None, tax_id_number=None, provider_name_id=None,
209
+ corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
210
+ organization_id=None, identify_service_level_deductible=True):
211
+
212
+ # Ensure all required parameters have values
213
+ if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
214
+ raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
215
+
216
+ # Validate payer_id
217
+ valid_payer_ids = ["87726", "06111", "25463", "37602", "39026", "74227", "65088", "81400", "03432", "86050", "86047", "95378", "95467"]
218
+ if payer_id not in valid_payer_ids:
219
+ raise ValueError("Invalid payer_id: {}. Must be one of: {}".format(payer_id, ", ".join(valid_payer_ids)))
220
+
221
+ endpoint_name = 'UHCAPI'
222
+ url_extension = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['eligibility_v3']
223
+
224
+ # Construct request body
225
+ body = {
226
+ "memberId": member_id,
227
+ "lastName": last_name,
228
+ "firstName": first_name,
229
+ "dateOfBirth": date_of_birth,
230
+ "payerID": payer_id,
231
+ "payerLabel": payer_label,
232
+ "payerName": payer_name,
233
+ "serviceStart": service_start,
234
+ "serviceEnd": service_end,
235
+ "middleName": middle_name,
236
+ "gender": gender,
237
+ "ssn": ssn,
238
+ "city": city,
239
+ "state": state,
240
+ "zip": zip,
241
+ "groupNumber": group_number,
242
+ "serviceTypeCode": service_type_code,
243
+ "providerLastName": provider_last_name,
244
+ "providerFirstName": provider_first_name,
245
+ "taxIdNumber": tax_id_number,
246
+ "providerNameID": provider_name_id,
247
+ "npi": npi,
248
+ "corporateTaxOwnerID": corporate_tax_owner_id,
249
+ "corporateTaxOwnerName": corporate_tax_owner_name,
250
+ "organizationName": organization_name,
251
+ "organizationID": organization_id,
252
+ "searchOption": search_option,
253
+ "identifyServiceLevelDeductible": identify_service_level_deductible
254
+ }
255
+
256
+ # Remove None values from the body
257
+ body = {k: v for k, v in body.items() if v is not None}
258
+
259
+ # Log the request body
260
+ MediLink_ConfigLoader.log("Request body: {}".format(json.dumps(body, indent=4)), level="DEBUG")
261
+
262
+ return client.make_api_call(endpoint_name, 'POST', url_extension, params=None, data=body)
263
+
264
+ def is_test_mode(client, body, endpoint_type):
265
+ """
266
+ Checks if Test Mode is enabled in the client's configuration and simulates the response if it is.
267
+
268
+ :param client: An instance of APIClient
269
+ :param body: The intended request body
270
+ :param endpoint_type: The type of endpoint being accessed ('claim_submission' or 'claim_details')
271
+ :return: A dummy response simulating the real API call if Test Mode is enabled, otherwise None
272
+ """
273
+ if client.config.get("MediLink_Config", {}).get("TestMode", True):
274
+ print("Test Mode is enabled! API Call not executed.")
275
+ print("\nIntended request body:", body)
276
+ MediLink_ConfigLoader.log("Test Mode is enabled! Simulating API response for {}.".format(endpoint_type), level="INFO")
277
+ MediLink_ConfigLoader.log("Intended request body: {}".format(body), level="INFO")
278
+
279
+ if endpoint_type == 'claim_submission':
280
+ dummy_response = {
281
+ "transactionId": "CS07180420240328013411240", # This is the tID for the sandbox Claim Acknowledgement endpoint.
282
+ "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~",
283
+ "responseType": "dummy_response_837999",
284
+ "message": "Test Mode: Claim validated and sent for further processing"
285
+ }
286
+ elif endpoint_type == 'claim_details':
287
+ dummy_response = {
288
+ "responseType": "dummy_response_277CA-CH",
289
+ "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~",
290
+ "statuscode": "000",
291
+ "message:": ""
292
+ }
293
+ return dummy_response
294
+ return None
295
+
296
+ def submit_uhc_claim(client, x12_request_data):
297
+ """
298
+ Submits a UHC claim and retrieves the claim acknowledgement details.
299
+
300
+ This function first submits the claim using the provided x12 837p data. If the client is in Test Mode,
301
+ it returns a simulated response. If Test Mode is not enabled, it submits the claim and then retrieves
302
+ the claim acknowledgement details using the transaction ID from the initial response.
303
+
304
+ :param client: An instance of APIClient
305
+ :param x12_request_data: The x12 837p data as a string
306
+ :return: The final response containing the claim acknowledgement details or a dummy response if in Test Mode
307
+ """
308
+ endpoint_name = 'UHCAPI'
309
+ claim_submission_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_submission']
310
+ claim_details_url = client.config['MediLink_Config']['endpoints'][endpoint_name]['additional_endpoints']['claim_details']
311
+
312
+ # Headers for the request
313
+ headers = {'Content-Type': 'application/json'}
314
+
315
+ # Request body for claim submission
316
+ claim_body = {'x12RequestData': x12_request_data}
317
+
318
+ # Check if Test Mode is enabled and return simulated response if so
319
+ test_mode_response = is_test_mode(client, claim_body, 'claim_submission')
320
+ if test_mode_response:
321
+ return test_mode_response
322
+
323
+ # Make the API call to submit the claim
324
+ try:
325
+ submission_response = client.make_api_call(endpoint_name, 'POST', claim_submission_url, data=claim_body, headers=headers)
326
+
327
+ # Extract the transaction ID from the submission response
328
+ transaction_id = submission_response.get('transactionId')
329
+ if not transaction_id:
330
+ raise ValueError("transactionId not found in the submission response")
331
+
332
+ # Prepare the request body for the claim acknowledgement retrieval
333
+ acknowledgement_body = {'transactionId': transaction_id}
334
+
335
+ # Check if Test Mode is enabled and return simulated response if so
336
+ test_mode_response = is_test_mode(client, acknowledgement_body, 'claim_details')
337
+ if test_mode_response:
338
+ return test_mode_response
339
+
340
+ # Make the API call to retrieve the claim acknowledgement details
341
+ acknowledgement_response = client.make_api_call(endpoint_name, 'POST', claim_details_url, data=acknowledgement_body, headers=headers)
342
+ return acknowledgement_response
343
+
344
+ except Exception as e:
345
+ print("Error during claim processing: {}".format(e))
346
+ raise
347
+
348
+ if __name__ == "__main__":
349
+ client = APIClient()
350
+
351
+ # Define a configuration to enable or disable tests
352
+ test_config = {
353
+ 'test_fetch_payer_name': False,
354
+ 'test_claim_summary': False,
355
+ 'test_eligibility': False,
356
+ 'test_eligibility_v3': False,
357
+ 'test_claim_submission': True,
358
+ }
359
+
360
+ try:
361
+ api_test_cases = client.config['MediLink_Config']['API Test Case']
362
+
363
+ # Test 1: Fetch Payer Name
364
+ if test_config.get('test_fetch_payer_name', False):
365
+ try:
366
+ for case in api_test_cases:
367
+ payer_name = fetch_payer_name_from_api(case['payer_id'], client.config)
368
+ print("TEST API: Payer Name: {}".format(payer_name))
369
+ except Exception as e:
370
+ print("TEST API: Error in Fetch Payer Name Test: {}".format(e))
371
+
372
+ # Test 2: Get Claim Summary
373
+ if test_config.get('test_claim_summary', False):
374
+ try:
375
+ for case in api_test_cases:
376
+ claim_summary = get_claim_summary_by_provider(client, case['provider_tin'], '05/01/2024', '06/23/2024', case['payer_id'])
377
+ print("TEST API: Claim Summary: {}".format(claim_summary))
378
+ except Exception as e:
379
+ print("TEST API: Error in Claim Summary Test: {}".format(e))
380
+
381
+ # Test 3: Get Eligibility
382
+ if test_config.get('test_eligibility', False):
383
+ try:
384
+ for case in api_test_cases:
385
+ eligibility = get_eligibility(client, case['payer_id'], case['provider_last_name'], case['search_option'],
386
+ case['date_of_birth'], case['member_id'], case['npi'])
387
+ print("TEST API: Eligibility: {}".format(eligibility))
388
+ except Exception as e:
389
+ print("TEST API: Error in Eligibility Test: {}".format(e))
390
+
391
+ # Test 4: Get Eligibility v3
392
+ if test_config.get('test_eligibility_v3', False):
393
+ try:
394
+ for case in api_test_cases:
395
+ eligibility_v3 = get_eligibility_v3(client, payer_id=case['payer_id'], provider_last_name=case['provider_last_name'],
396
+ search_option=case['search_option'], date_of_birth=case['date_of_birth'],
397
+ member_id=case['member_id'], npi=case['npi'])
398
+ print("TEST API: Eligibility v3: {}".format(eligibility_v3))
399
+ except Exception as e:
400
+ print("TEST API: Error in Eligibility v3 Test: {}".format(e))
401
+
402
+ """
403
+ # Example of iterating over multiple patients (if needed)
404
+ patients = [
405
+ {'payer_id': '87726', 'provider_last_name': 'VIDA', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1980-01-01', 'member_id': '123456789', 'npi': '9876543210'},
406
+ {'payer_id': '87726', 'provider_last_name': 'SMITH', 'search_option': 'MemberIDDateOfBirth', 'date_of_birth': '1970-02-02', 'member_id': '987654321', 'npi': '1234567890'},
407
+ # Add more patients as needed
408
+ ]
409
+
410
+ for patient in patients:
411
+ try:
412
+ eligibility = get_eligibility(client, patient['payer_id'], patient['provider_last_name'], patient['search_option'], patient['date_of_birth'], patient['member_id'], patient['npi'])
413
+ print("Eligibility for {}: {}".format(patient['provider_last_name'], eligibility))
414
+ except Exception as e:
415
+ print("Error in getting eligibility for {}: {}".format(patient['provider_last_name'], e))
416
+ """
417
+ # Test 5: UHC Claim Submission
418
+ if test_config.get('test_claim_submission', False):
419
+ try:
420
+ x12_request_data = (
421
+ "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~"
422
+ )
423
+ response = submit_uhc_claim(client, x12_request_data)
424
+ print("\nTEST API: Claim Submission Response:\n", response)
425
+ except Exception as e:
426
+ print("\nTEST API: Error in Claim Submission Test:\n", e)
427
+
428
+ except Exception as e:
429
+ print("TEST API: Unexpected Error: {}".format(e))
@@ -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 (HARDCODE) FOR NOW:
20
+ # BUG Hardcode sucks. This should probably be some local env variable.
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
- config = json.load(config_file, object_pairs_hook=OrderedDict)
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 JSON file: {}".format(e))
49
+ print("Error decoding file: {}".format(e))
44
50
  else:
45
- print("Error parsing JSON file: {}".format(e))
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 JSON files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
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))
@@ -219,11 +219,11 @@ def operate_winscp(operation_type, files, endpoint_config, local_storage_path, c
219
219
  except KeyError as e:
220
220
  # Log the missing key information
221
221
  missing_key = str(e)
222
- message = "KeyError: Endpoint config is missing key: {}".format(missing_key)
222
+ message = "Critical Error: Endpoint config is missing key: {}".format(missing_key)
223
223
  MediLink_ConfigLoader.log(message)
224
- # Set default values or handle the situation accordingly
225
- session_name = ''
226
- remote_directory = ''
224
+ # Raise an exception to halt execution
225
+ raise RuntimeError("Configuration error: The endpoint configuration is missing definitions for the required remote directories. Please check the configuration and try again.")
226
+
227
227
  # Command building
228
228
  command = [
229
229
  winscp_path,