medicafe 0.250711.1__py3-none-any.whl → 0.250720.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.
- MediBot/MediBot_Crosswalk_Library.py +109 -1
- MediLink/MediLink_837p_cob_library.py +862 -0
- MediLink/MediLink_837p_encoder.py +40 -0
- MediLink/MediLink_837p_encoder_library.py +54 -6
- MediLink/MediLink_ClaimStatus.py +4 -3
- MediLink/MediLink_Deductible.py +345 -108
- MediLink/MediLink_Deductible_Validator.py +440 -0
- MediLink/test_cob_library.py +436 -0
- MediLink/test_validation.py +127 -0
- {medicafe-0.250711.1.dist-info → medicafe-0.250720.0.dist-info}/METADATA +1 -1
- {medicafe-0.250711.1.dist-info → medicafe-0.250720.0.dist-info}/RECORD +14 -10
- {medicafe-0.250711.1.dist-info → medicafe-0.250720.0.dist-info}/LICENSE +0 -0
- {medicafe-0.250711.1.dist-info → medicafe-0.250720.0.dist-info}/WHEEL +0 -0
- {medicafe-0.250711.1.dist-info → medicafe-0.250720.0.dist-info}/top_level.txt +0 -0
MediLink/MediLink_Deductible.py
CHANGED
|
@@ -52,6 +52,11 @@ try:
|
|
|
52
52
|
except ImportError:
|
|
53
53
|
import MediLink_ConfigLoader
|
|
54
54
|
|
|
55
|
+
try:
|
|
56
|
+
from MediLink import MediLink_Deductible_Validator
|
|
57
|
+
except ImportError:
|
|
58
|
+
import MediLink_Deductible_Validator
|
|
59
|
+
|
|
55
60
|
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
56
61
|
if project_dir not in sys.path:
|
|
57
62
|
sys.path.append(project_dir)
|
|
@@ -122,26 +127,39 @@ patients = [
|
|
|
122
127
|
# Function to handle manual patient deductible lookup
|
|
123
128
|
def manual_deductible_lookup():
|
|
124
129
|
print("\n--- Manual Patient Deductible Lookup ---")
|
|
130
|
+
print("Available Payer IDs: {}".format(", ".join(payer_ids)))
|
|
131
|
+
print("Enter 'quit' at any time to return to main menu.\n")
|
|
132
|
+
|
|
125
133
|
while True:
|
|
126
|
-
member_id = input("Enter the Member ID of the subscriber (or
|
|
127
|
-
if
|
|
128
|
-
print("
|
|
134
|
+
member_id = input("Enter the Member ID of the subscriber (or 'quit' to exit): ").strip()
|
|
135
|
+
if member_id.lower() == 'quit':
|
|
136
|
+
print("Returning to main menu.\n")
|
|
129
137
|
break
|
|
138
|
+
if not member_id:
|
|
139
|
+
print("No Member ID entered. Please try again.\n")
|
|
140
|
+
continue
|
|
130
141
|
|
|
131
142
|
dob_input = input("Enter the Date of Birth (YYYY-MM-DD): ").strip()
|
|
143
|
+
if dob_input.lower() == 'quit':
|
|
144
|
+
print("Returning to main menu.\n")
|
|
145
|
+
break
|
|
146
|
+
|
|
132
147
|
formatted_dob = validate_and_format_date(dob_input)
|
|
133
148
|
if not formatted_dob:
|
|
134
149
|
print("Invalid DOB format. Please enter in YYYY-MM-DD format.\n")
|
|
135
150
|
continue
|
|
136
151
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
print("Processing manual lookup for Member ID: {}, DOB: {}".format(member_id, formatted_dob))
|
|
152
|
+
print("\nProcessing manual lookup for Member ID: {}, DOB: {}".format(member_id, formatted_dob))
|
|
153
|
+
print("Checking {} payer IDs...".format(len(payer_ids)))
|
|
140
154
|
|
|
141
155
|
# Fetch eligibility data
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
found_data = False
|
|
157
|
+
for i, payer_id in enumerate(payer_ids, 1):
|
|
158
|
+
print("Checking Payer ID {} ({}/{}): {}".format(payer_id, i, len(payer_ids), payer_id))
|
|
159
|
+
|
|
160
|
+
eligibility_data = get_eligibility_info(client, payer_id, provider_last_name, formatted_dob, member_id, npi, run_validation=True)
|
|
144
161
|
if eligibility_data:
|
|
162
|
+
found_data = True
|
|
145
163
|
# Generate unique output file for manual request
|
|
146
164
|
output_file_name = "eligibility_report_manual_{}_{}.txt".format(member_id, formatted_dob)
|
|
147
165
|
output_file_path = os.path.join(os.getenv('TEMP'), output_file_name)
|
|
@@ -153,26 +171,27 @@ def manual_deductible_lookup():
|
|
|
153
171
|
print(table_header)
|
|
154
172
|
print("-" * len(table_header))
|
|
155
173
|
display_eligibility_info(eligibility_data, formatted_dob, member_id, output_file)
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
|
|
175
|
+
# Ask if user wants to open the report
|
|
176
|
+
open_report = input("\nEligibility data found! Open the report? (Y/N): ").strip().lower()
|
|
177
|
+
if open_report in ['y', 'yes']:
|
|
178
|
+
os.system('notepad.exe "{}"'.format(output_file_path))
|
|
158
179
|
print("Manual eligibility report generated: {}\n".format(output_file_path))
|
|
159
180
|
break # Assuming one payer ID per manual lookup
|
|
160
181
|
else:
|
|
161
182
|
print("No eligibility data found for Payer ID: {}".format(payer_id))
|
|
162
183
|
|
|
184
|
+
if not found_data:
|
|
185
|
+
print("\nNo eligibility data found for any Payer ID.")
|
|
186
|
+
|
|
163
187
|
# Ask if the user wants to perform another manual lookup
|
|
164
188
|
continue_choice = input("\nDo you want to perform another manual lookup? (Y/N): ").strip().lower()
|
|
165
189
|
if continue_choice in ['n', 'no']:
|
|
166
190
|
break
|
|
167
191
|
|
|
168
|
-
# Display available Payer IDs as a note
|
|
169
|
-
print("\nNOTE: The tool can only look up the following Payer IDs:")
|
|
170
|
-
print(", ".join(payer_ids))
|
|
171
|
-
print("-------------------------------------------------\n")
|
|
172
|
-
|
|
173
192
|
|
|
174
193
|
# Function to get eligibility information
|
|
175
|
-
def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, member_id, npi):
|
|
194
|
+
def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, member_id, npi, run_validation=False):
|
|
176
195
|
try:
|
|
177
196
|
# Log the parameters being sent to the function
|
|
178
197
|
MediLink_ConfigLoader.log("Calling eligibility check with parameters:", level="DEBUG")
|
|
@@ -184,20 +203,46 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
184
203
|
|
|
185
204
|
# Configuration flag to control which API to use
|
|
186
205
|
# Set to False to use the new Super Connector API, True to use the legacy v3 API
|
|
187
|
-
USE_LEGACY_API =
|
|
206
|
+
USE_LEGACY_API = True # Changed to True to use legacy as primary
|
|
207
|
+
|
|
208
|
+
# Always get legacy response first
|
|
209
|
+
MediLink_ConfigLoader.log("Getting legacy get_eligibility_v3 API response", level="INFO")
|
|
210
|
+
legacy_eligibility = MediLink_API_v3.get_eligibility_v3(
|
|
211
|
+
client, payer_id, provider_last_name, 'MemberIDDateOfBirth', date_of_birth, member_id, npi
|
|
212
|
+
)
|
|
188
213
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
# Also get Super Connector response for comparison
|
|
215
|
+
MediLink_ConfigLoader.log("Getting new get_eligibility_super_connector API response", level="INFO")
|
|
216
|
+
super_connector_eligibility = None
|
|
217
|
+
try:
|
|
218
|
+
super_connector_eligibility = MediLink_API_v3.get_eligibility_super_connector(
|
|
193
219
|
client, payer_id, provider_last_name, 'MemberIDDateOfBirth', date_of_birth, member_id, npi
|
|
194
220
|
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
221
|
+
except Exception as e:
|
|
222
|
+
MediLink_ConfigLoader.log("Super Connector API failed: {}".format(e), level="ERROR")
|
|
223
|
+
|
|
224
|
+
# Run validation if requested and we have both responses
|
|
225
|
+
if run_validation and legacy_eligibility and super_connector_eligibility:
|
|
226
|
+
validation_file_path = os.path.join(os.getenv('TEMP'), 'validation_report_{}_{}.txt'.format(member_id, date_of_birth))
|
|
227
|
+
validation_report = MediLink_Deductible_Validator.run_validation_comparison(
|
|
228
|
+
legacy_eligibility, super_connector_eligibility, validation_file_path
|
|
200
229
|
)
|
|
230
|
+
print("\nValidation report generated: {}".format(validation_file_path))
|
|
231
|
+
|
|
232
|
+
# Log any Super Connector API errors
|
|
233
|
+
if super_connector_eligibility and "rawGraphQLResponse" in super_connector_eligibility:
|
|
234
|
+
raw_response = super_connector_eligibility.get('rawGraphQLResponse', {})
|
|
235
|
+
errors = raw_response.get('errors', [])
|
|
236
|
+
if errors:
|
|
237
|
+
print("Super Connector API returned {} error(s):".format(len(errors)))
|
|
238
|
+
for i, error in enumerate(errors):
|
|
239
|
+
print(" Error {}: {} - {}".format(i+1, error.get('code', 'UNKNOWN'), error.get('description', 'No description')))
|
|
240
|
+
|
|
241
|
+
# Open validation report in Notepad
|
|
242
|
+
os.system('notepad.exe "{}"'.format(validation_file_path))
|
|
243
|
+
|
|
244
|
+
# Return the primary response (legacy for now)
|
|
245
|
+
eligibility = legacy_eligibility if USE_LEGACY_API else super_connector_eligibility
|
|
201
246
|
|
|
202
247
|
# Log the response
|
|
203
248
|
MediLink_ConfigLoader.log("Eligibility response: {}".format(json.dumps(eligibility, indent=4)), level="DEBUG")
|
|
@@ -229,7 +274,25 @@ def extract_super_connector_patient_info(eligibility_data):
|
|
|
229
274
|
if not eligibility_data:
|
|
230
275
|
return {'lastName': '', 'firstName': '', 'middleName': ''}
|
|
231
276
|
|
|
232
|
-
#
|
|
277
|
+
# Handle multiple eligibility records - use the first one with valid data
|
|
278
|
+
if "rawGraphQLResponse" in eligibility_data:
|
|
279
|
+
raw_response = eligibility_data.get('rawGraphQLResponse', {})
|
|
280
|
+
data = raw_response.get('data', {})
|
|
281
|
+
check_eligibility = data.get('checkEligibility', {})
|
|
282
|
+
eligibility_list = check_eligibility.get('eligibility', [])
|
|
283
|
+
|
|
284
|
+
# Try to get from the first eligibility record
|
|
285
|
+
if eligibility_list:
|
|
286
|
+
first_eligibility = eligibility_list[0]
|
|
287
|
+
member_info = first_eligibility.get('eligibilityInfo', {}).get('member', {})
|
|
288
|
+
if member_info:
|
|
289
|
+
return {
|
|
290
|
+
'lastName': member_info.get("lastName", ""),
|
|
291
|
+
'firstName': member_info.get("firstName", ""),
|
|
292
|
+
'middleName': member_info.get("middleName", "")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Fallback to top-level fields
|
|
233
296
|
return {
|
|
234
297
|
'lastName': eligibility_data.get("lastName", ""),
|
|
235
298
|
'firstName': eligibility_data.get("firstName", ""),
|
|
@@ -258,44 +321,95 @@ def extract_super_connector_remaining_amount(eligibility_data):
|
|
|
258
321
|
if met_amount is not None:
|
|
259
322
|
return str(met_amount)
|
|
260
323
|
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
if not raw_response:
|
|
264
|
-
return "Not Found"
|
|
265
|
-
|
|
266
|
-
data = raw_response.get('data', {})
|
|
267
|
-
check_eligibility = data.get('checkEligibility', {})
|
|
268
|
-
eligibility_list = check_eligibility.get('eligibility', [])
|
|
269
|
-
|
|
270
|
-
if not eligibility_list:
|
|
271
|
-
return "Not Found"
|
|
272
|
-
|
|
273
|
-
first_eligibility = eligibility_list[0]
|
|
274
|
-
service_levels = first_eligibility.get('serviceLevels', [])
|
|
324
|
+
# Collect all deductible amounts to find the most relevant one
|
|
325
|
+
all_deductible_amounts = []
|
|
275
326
|
|
|
276
|
-
# Look for deductible information in
|
|
277
|
-
|
|
278
|
-
individual_services = service_level.get('individual', [])
|
|
279
|
-
for individual in individual_services:
|
|
280
|
-
services = individual.get('services', [])
|
|
281
|
-
for service in services:
|
|
282
|
-
# Look for deductible-related information
|
|
283
|
-
if service.get('service') == 'deductible' or 'deductible' in service.get('text', '').lower():
|
|
284
|
-
return service.get('remainingAmount', "")
|
|
285
|
-
|
|
286
|
-
# Check the message.deductible.text field for deductible information
|
|
287
|
-
message = service.get('message', {})
|
|
288
|
-
deductible_msg = message.get('deductible', {})
|
|
289
|
-
if deductible_msg and deductible_msg.get('text'):
|
|
290
|
-
return deductible_msg.get('text', "")
|
|
291
|
-
|
|
292
|
-
# If no specific deductible found, try to get from plan levels
|
|
293
|
-
plan_levels = first_eligibility.get('eligibilityInfo', {}).get('planLevels', [])
|
|
327
|
+
# Look for deductible information in planLevels (based on validation report)
|
|
328
|
+
plan_levels = eligibility_data.get('planLevels', [])
|
|
294
329
|
for plan_level in plan_levels:
|
|
295
|
-
if plan_level.get('level') == 'deductibleInfo
|
|
330
|
+
if plan_level.get('level') == 'deductibleInfo':
|
|
331
|
+
# Collect individual deductible amounts
|
|
296
332
|
individual_levels = plan_level.get('individual', [])
|
|
297
333
|
if individual_levels:
|
|
298
|
-
|
|
334
|
+
for individual in individual_levels:
|
|
335
|
+
remaining = individual.get('remainingAmount')
|
|
336
|
+
if remaining is not None:
|
|
337
|
+
try:
|
|
338
|
+
amount = float(remaining)
|
|
339
|
+
all_deductible_amounts.append(('individual', amount))
|
|
340
|
+
except (ValueError, TypeError):
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
# Collect family deductible amounts
|
|
344
|
+
family_levels = plan_level.get('family', [])
|
|
345
|
+
if family_levels:
|
|
346
|
+
for family in family_levels:
|
|
347
|
+
remaining = family.get('remainingAmount')
|
|
348
|
+
if remaining is not None:
|
|
349
|
+
try:
|
|
350
|
+
amount = float(remaining)
|
|
351
|
+
all_deductible_amounts.append(('family', amount))
|
|
352
|
+
except (ValueError, TypeError):
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
# Navigate to the rawGraphQLResponse structure as fallback
|
|
356
|
+
raw_response = eligibility_data.get('rawGraphQLResponse', {})
|
|
357
|
+
if raw_response:
|
|
358
|
+
data = raw_response.get('data', {})
|
|
359
|
+
check_eligibility = data.get('checkEligibility', {})
|
|
360
|
+
eligibility_list = check_eligibility.get('eligibility', [])
|
|
361
|
+
|
|
362
|
+
# Try all eligibility records for deductible information
|
|
363
|
+
for eligibility in eligibility_list:
|
|
364
|
+
plan_levels = eligibility.get('eligibilityInfo', {}).get('planLevels', [])
|
|
365
|
+
for plan_level in plan_levels:
|
|
366
|
+
if plan_level.get('level') == 'deductibleInfo':
|
|
367
|
+
# Collect individual deductible amounts
|
|
368
|
+
individual_levels = plan_level.get('individual', [])
|
|
369
|
+
if individual_levels:
|
|
370
|
+
for individual in individual_levels:
|
|
371
|
+
remaining = individual.get('remainingAmount')
|
|
372
|
+
if remaining is not None:
|
|
373
|
+
try:
|
|
374
|
+
amount = float(remaining)
|
|
375
|
+
all_deductible_amounts.append(('individual', amount))
|
|
376
|
+
except (ValueError, TypeError):
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
# Collect family deductible amounts
|
|
380
|
+
family_levels = plan_level.get('family', [])
|
|
381
|
+
if family_levels:
|
|
382
|
+
for family in family_levels:
|
|
383
|
+
remaining = family.get('remainingAmount')
|
|
384
|
+
if remaining is not None:
|
|
385
|
+
try:
|
|
386
|
+
amount = float(remaining)
|
|
387
|
+
all_deductible_amounts.append(('family', amount))
|
|
388
|
+
except (ValueError, TypeError):
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
# Select the most relevant deductible amount
|
|
392
|
+
if all_deductible_amounts:
|
|
393
|
+
# Strategy: Prefer individual over family, and prefer non-zero amounts
|
|
394
|
+
# First, try to find non-zero individual amounts
|
|
395
|
+
non_zero_individual = [amt for type_, amt in all_deductible_amounts if type_ == 'individual' and amt > 0]
|
|
396
|
+
if non_zero_individual:
|
|
397
|
+
return str(max(non_zero_individual)) # Return highest non-zero individual amount
|
|
398
|
+
|
|
399
|
+
# If no non-zero individual, try non-zero family amounts
|
|
400
|
+
non_zero_family = [amt for type_, amt in all_deductible_amounts if type_ == 'family' and amt > 0]
|
|
401
|
+
if non_zero_family:
|
|
402
|
+
return str(max(non_zero_family)) # Return highest non-zero family amount
|
|
403
|
+
|
|
404
|
+
# If all amounts are zero, return the first individual amount (or family if no individual)
|
|
405
|
+
individual_amounts = [amt for type_, amt in all_deductible_amounts if type_ == 'individual']
|
|
406
|
+
if individual_amounts:
|
|
407
|
+
return str(individual_amounts[0])
|
|
408
|
+
|
|
409
|
+
# Fallback to first family amount
|
|
410
|
+
family_amounts = [amt for type_, amt in all_deductible_amounts if type_ == 'family']
|
|
411
|
+
if family_amounts:
|
|
412
|
+
return str(family_amounts[0])
|
|
299
413
|
|
|
300
414
|
return "Not Found"
|
|
301
415
|
|
|
@@ -314,12 +428,57 @@ def extract_super_connector_insurance_info(eligibility_data):
|
|
|
314
428
|
if not eligibility_data:
|
|
315
429
|
return {'insuranceType': '', 'insuranceTypeCode': '', 'memberId': '', 'payerId': ''}
|
|
316
430
|
|
|
317
|
-
#
|
|
431
|
+
# Handle multiple eligibility records - use the first one with valid data
|
|
432
|
+
if "rawGraphQLResponse" in eligibility_data:
|
|
433
|
+
raw_response = eligibility_data.get('rawGraphQLResponse', {})
|
|
434
|
+
data = raw_response.get('data', {})
|
|
435
|
+
check_eligibility = data.get('checkEligibility', {})
|
|
436
|
+
eligibility_list = check_eligibility.get('eligibility', [])
|
|
437
|
+
|
|
438
|
+
# Try to get from the first eligibility record
|
|
439
|
+
if eligibility_list:
|
|
440
|
+
first_eligibility = eligibility_list[0]
|
|
441
|
+
insurance_info = first_eligibility.get('eligibilityInfo', {}).get('insuranceInfo', {})
|
|
442
|
+
if insurance_info:
|
|
443
|
+
return {
|
|
444
|
+
'insuranceType': insurance_info.get("planTypeDescription", ""),
|
|
445
|
+
'insuranceTypeCode': insurance_info.get("productServiceCode", ""),
|
|
446
|
+
'memberId': insurance_info.get("memberId", ""),
|
|
447
|
+
'payerId': insurance_info.get("payerId", "")
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# Fallback to top-level fields
|
|
318
451
|
insurance_type = eligibility_data.get("planTypeDescription", "")
|
|
452
|
+
if not insurance_type:
|
|
453
|
+
insurance_type = eligibility_data.get("productType", "")
|
|
454
|
+
|
|
455
|
+
# Clean up the insurance type if it's too long (like the LPPO description)
|
|
456
|
+
if insurance_type and len(insurance_type) > 50:
|
|
457
|
+
# Extract just the plan type part
|
|
458
|
+
if "PPO" in insurance_type:
|
|
459
|
+
insurance_type = "Preferred Provider Organization (PPO)"
|
|
460
|
+
elif "HMO" in insurance_type:
|
|
461
|
+
insurance_type = "Health Maintenance Organization (HMO)"
|
|
462
|
+
elif "EPO" in insurance_type:
|
|
463
|
+
insurance_type = "Exclusive Provider Organization (EPO)"
|
|
464
|
+
elif "POS" in insurance_type:
|
|
465
|
+
insurance_type = "Point of Service (POS)"
|
|
466
|
+
|
|
467
|
+
# Get insurance type code from multiple possible locations
|
|
468
|
+
insurance_type_code = eligibility_data.get("productServiceCode", "")
|
|
469
|
+
if not insurance_type_code:
|
|
470
|
+
# Try to get from coverageTypes
|
|
471
|
+
coverage_types = eligibility_data.get("coverageTypes", [])
|
|
472
|
+
if coverage_types:
|
|
473
|
+
insurance_type_code = coverage_types[0].get("typeCode", "")
|
|
474
|
+
|
|
475
|
+
# Note: We're not mapping "M" to "PR" as "M" likely means "Medical"
|
|
476
|
+
# and "PR" should be "12" for PPO according to CMS standards
|
|
477
|
+
# This mapping should be handled by the API developers
|
|
319
478
|
|
|
320
479
|
return {
|
|
321
480
|
'insuranceType': insurance_type,
|
|
322
|
-
'insuranceTypeCode':
|
|
481
|
+
'insuranceTypeCode': insurance_type_code,
|
|
323
482
|
'memberId': eligibility_data.get("subscriberId", ""),
|
|
324
483
|
'payerId': eligibility_data.get("payerId", "") # Use payerId instead of legalEntityCode (this should be payer_id from the inputs)
|
|
325
484
|
}
|
|
@@ -334,7 +493,21 @@ def extract_super_connector_policy_status(eligibility_data):
|
|
|
334
493
|
if not eligibility_data:
|
|
335
494
|
return ""
|
|
336
495
|
|
|
337
|
-
#
|
|
496
|
+
# Handle multiple eligibility records - use the first one with valid data
|
|
497
|
+
if "rawGraphQLResponse" in eligibility_data:
|
|
498
|
+
raw_response = eligibility_data.get('rawGraphQLResponse', {})
|
|
499
|
+
data = raw_response.get('data', {})
|
|
500
|
+
check_eligibility = data.get('checkEligibility', {})
|
|
501
|
+
eligibility_list = check_eligibility.get('eligibility', [])
|
|
502
|
+
|
|
503
|
+
# Try to get from the first eligibility record
|
|
504
|
+
if eligibility_list:
|
|
505
|
+
first_eligibility = eligibility_list[0]
|
|
506
|
+
insurance_info = first_eligibility.get('eligibilityInfo', {}).get('insuranceInfo', {})
|
|
507
|
+
if insurance_info:
|
|
508
|
+
return insurance_info.get("policyStatus", "")
|
|
509
|
+
|
|
510
|
+
# Fallback to top-level field
|
|
338
511
|
return eligibility_data.get("policyStatus", "")
|
|
339
512
|
|
|
340
513
|
def is_legacy_response_format(data):
|
|
@@ -403,47 +576,111 @@ def display_eligibility_info(data, dob, member_id, output_file):
|
|
|
403
576
|
|
|
404
577
|
# Main Execution Flow
|
|
405
578
|
if __name__ == "__main__":
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
print("
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
print(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
579
|
+
print("\n" + "=" * 80)
|
|
580
|
+
print("MEDILINK DEDUCTIBLE LOOKUP TOOL")
|
|
581
|
+
print("=" * 80)
|
|
582
|
+
print("This tool provides manual and batch eligibility lookups with validation.")
|
|
583
|
+
print("Validation reports compare legacy vs Super Connector API responses.")
|
|
584
|
+
print("=" * 80)
|
|
585
|
+
|
|
586
|
+
while True:
|
|
587
|
+
print("\nChoose an option:")
|
|
588
|
+
print("1. Manual Patient Lookup (with validation)")
|
|
589
|
+
print("2. Batch CSV Processing (with validation)")
|
|
590
|
+
print("3. Exit")
|
|
591
|
+
|
|
592
|
+
choice = input("\nEnter your choice (1-3): ").strip()
|
|
593
|
+
|
|
594
|
+
if choice == "1":
|
|
595
|
+
# Step 1: Handle Manual Deductible Lookups
|
|
596
|
+
manual_deductible_lookup()
|
|
597
|
+
|
|
598
|
+
# Ask if user wants to continue
|
|
599
|
+
continue_choice = input("\nDo you want to perform another operation? (Y/N): ").strip().lower()
|
|
600
|
+
if continue_choice in ['n', 'no']:
|
|
601
|
+
print("\nExiting. Thank you for using MediLink Deductible Tool!")
|
|
602
|
+
break
|
|
603
|
+
|
|
604
|
+
elif choice == "2":
|
|
605
|
+
# Step 2: Proceed with Existing CSV Processing
|
|
606
|
+
print("\n--- Starting Batch Eligibility Processing ---")
|
|
607
|
+
print("Processing {} patients from CSV data...".format(len(patients)))
|
|
608
|
+
|
|
609
|
+
# Ask for confirmation before starting batch processing
|
|
610
|
+
confirm = input("Proceed with batch processing? (Y/N): ").strip().lower()
|
|
611
|
+
if confirm not in ['y', 'yes']:
|
|
612
|
+
print("Batch processing cancelled.")
|
|
613
|
+
continue
|
|
614
|
+
|
|
615
|
+
output_file_path = os.path.join(os.getenv('TEMP'), 'eligibility_report.txt')
|
|
616
|
+
with open(output_file_path, 'w') as output_file:
|
|
617
|
+
table_header = "{:<20} | {:<10} | {:<40} | {:<5} | {:<14} | {:<14}".format(
|
|
618
|
+
"Patient Name", "DOB", "Insurance Type", "PayID", "Policy Status", "Remaining Amt")
|
|
619
|
+
output_file.write(table_header + "\n")
|
|
620
|
+
output_file.write("-" * len(table_header) + "\n")
|
|
621
|
+
print(table_header)
|
|
622
|
+
print("-" * len(table_header))
|
|
623
|
+
|
|
624
|
+
# Set to keep track of processed patients
|
|
625
|
+
processed_patients = set()
|
|
626
|
+
|
|
627
|
+
# Loop through each payer_id and patient to call the API, then display the eligibility information
|
|
628
|
+
errors = []
|
|
629
|
+
validation_reports = []
|
|
630
|
+
total_patients = len(patients) * len(payer_ids)
|
|
631
|
+
processed_count = 0
|
|
632
|
+
|
|
633
|
+
for payer_id in payer_ids:
|
|
634
|
+
for dob, member_id in patients:
|
|
635
|
+
# Skip if this patient has already been processed
|
|
636
|
+
if (dob, member_id) in processed_patients:
|
|
637
|
+
continue
|
|
638
|
+
try:
|
|
639
|
+
processed_count += 1
|
|
640
|
+
print("Processing patient {}/{}: Member ID {}, DOB {}".format(
|
|
641
|
+
processed_count, total_patients, member_id, dob))
|
|
642
|
+
|
|
643
|
+
# Run with validation enabled for batch processing
|
|
644
|
+
eligibility_data = get_eligibility_info(client, payer_id, provider_last_name, dob, member_id, npi, run_validation=True)
|
|
645
|
+
if eligibility_data is not None:
|
|
646
|
+
display_eligibility_info(eligibility_data, dob, member_id, output_file) # Display as we get the result
|
|
647
|
+
processed_patients.add((dob, member_id)) # Mark this patient as processed
|
|
648
|
+
except Exception as e:
|
|
649
|
+
errors.append((dob, member_id, str(e)))
|
|
650
|
+
|
|
651
|
+
# Display errors if any
|
|
652
|
+
if errors:
|
|
653
|
+
error_msg = "\nErrors encountered during API calls:\n"
|
|
654
|
+
output_file.write(error_msg)
|
|
655
|
+
print(error_msg)
|
|
656
|
+
for error in errors:
|
|
657
|
+
error_details = "DOB: {}, Member ID: {}, Error: {}\n".format(error[0], error[1], error[2])
|
|
658
|
+
output_file.write(error_details)
|
|
659
|
+
print(error_details)
|
|
660
|
+
|
|
661
|
+
# Ask if user wants to open the report
|
|
662
|
+
open_report = input("\nBatch processing complete! Open the eligibility report? (Y/N): ").strip().lower()
|
|
663
|
+
if open_report in ['y', 'yes']:
|
|
664
|
+
os.system('notepad.exe "{}"'.format(output_file_path))
|
|
665
|
+
|
|
666
|
+
# Print summary of validation reports
|
|
667
|
+
print("\n" + "=" * 80)
|
|
668
|
+
print("VALIDATION SUMMARY")
|
|
669
|
+
print("=" * 80)
|
|
670
|
+
print("Validation reports have been generated for each patient processed.")
|
|
671
|
+
print("Each report compares the legacy API response with the Super Connector API response.")
|
|
672
|
+
print("Check the TEMP directory for validation_report_*.txt files.")
|
|
673
|
+
print("=" * 80)
|
|
674
|
+
|
|
675
|
+
# Ask if user wants to continue
|
|
676
|
+
continue_choice = input("\nDo you want to perform another operation? (Y/N): ").strip().lower()
|
|
677
|
+
if continue_choice in ['n', 'no']:
|
|
678
|
+
print("\nExiting. Thank you for using MediLink Deductible Tool!")
|
|
679
|
+
break
|
|
680
|
+
|
|
681
|
+
elif choice == "3":
|
|
682
|
+
print("\nExiting. Thank you for using MediLink Deductible Tool!")
|
|
683
|
+
break
|
|
684
|
+
|
|
685
|
+
else:
|
|
686
|
+
print("Invalid choice. Please enter 1, 2, or 3.")
|