medicafe 0.251015.0__py3-none-any.whl → 0.251017.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/__init__.py +1 -1
- MediCafe/__init__.py +1 -1
- MediCafe/deductible_utils.py +83 -1
- MediLink/MediLink_Deductible.py +177 -16
- MediLink/MediLink_Display_Utils.py +8 -1
- MediLink/MediLink_Gmail.py +33 -0
- MediLink/__init__.py +1 -1
- MediLink/webapp.html +41 -13
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/METADATA +1 -1
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/RECORD +14 -14
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/LICENSE +0 -0
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/WHEEL +0 -0
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251015.0.dist-info → medicafe-0.251017.0.dist-info}/top_level.txt +0 -0
MediBot/__init__.py
CHANGED
MediCafe/__init__.py
CHANGED
MediCafe/deductible_utils.py
CHANGED
|
@@ -37,6 +37,62 @@ IMPLEMENTATION NOTES:
|
|
|
37
37
|
import os, sys, json
|
|
38
38
|
from datetime import datetime
|
|
39
39
|
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# LIGHTWEIGHT DIAGNOSTICS HELPERS (3.4.4 compatible)
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
def classify_api_failure(exc, context):
|
|
45
|
+
"""
|
|
46
|
+
Classify common API failure cases into a short (code, message) tuple.
|
|
47
|
+
Codes: TIMEOUT, CONN_ERR, AUTH_FAIL, INVALID_PAYER, MISCONFIG, NON_200, NO_DATA, UNKNOWN
|
|
48
|
+
"""
|
|
49
|
+
code = 'UNKNOWN'
|
|
50
|
+
try:
|
|
51
|
+
detail = str(exc)
|
|
52
|
+
except Exception:
|
|
53
|
+
detail = ''
|
|
54
|
+
try:
|
|
55
|
+
ctx = str(context)
|
|
56
|
+
except Exception:
|
|
57
|
+
ctx = 'API'
|
|
58
|
+
try:
|
|
59
|
+
# Lazy import to avoid hard dependency if requests missing
|
|
60
|
+
try:
|
|
61
|
+
import requests # noqa
|
|
62
|
+
except Exception:
|
|
63
|
+
requests = None # type: ignore
|
|
64
|
+
|
|
65
|
+
if requests and hasattr(requests, 'exceptions'):
|
|
66
|
+
if isinstance(exc, requests.exceptions.Timeout):
|
|
67
|
+
code = 'TIMEOUT'
|
|
68
|
+
elif isinstance(exc, requests.exceptions.ConnectionError):
|
|
69
|
+
code = 'CONN_ERR'
|
|
70
|
+
elif isinstance(exc, requests.exceptions.HTTPError):
|
|
71
|
+
# HTTPError: try to extract status
|
|
72
|
+
try:
|
|
73
|
+
status = getattr(getattr(exc, 'response', None), 'status_code', None)
|
|
74
|
+
except Exception:
|
|
75
|
+
status = None
|
|
76
|
+
code = 'NON_200' if status and status != 200 else 'UNKNOWN'
|
|
77
|
+
# String heuristics as fallbacks
|
|
78
|
+
if 'Invalid payer_id' in detail:
|
|
79
|
+
code = 'INVALID_PAYER'
|
|
80
|
+
elif ('No access token' in detail) or ('token' in detail.lower()):
|
|
81
|
+
code = 'AUTH_FAIL'
|
|
82
|
+
elif ('Eligibility endpoint not configured' in detail) or ('endpoint' in detail.lower() and 'configured' in detail.lower()):
|
|
83
|
+
code = 'MISCONFIG'
|
|
84
|
+
except Exception:
|
|
85
|
+
code = 'UNKNOWN'
|
|
86
|
+
message = "{} failure [{}]: {}".format(ctx or 'API', code, detail)
|
|
87
|
+
return code, message
|
|
88
|
+
|
|
89
|
+
def is_ok_200(value):
|
|
90
|
+
"""Return True if value represents HTTP 200 in either int or string form."""
|
|
91
|
+
try:
|
|
92
|
+
return str(value).strip() == '200'
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
|
|
40
96
|
# Use core utilities for standardized imports
|
|
41
97
|
try:
|
|
42
98
|
from MediCafe.core_utils import get_shared_config_loader
|
|
@@ -1287,4 +1343,30 @@ def backfill_enhanced_result(enhanced_result, csv_row=None):
|
|
|
1287
1343
|
except Exception as e:
|
|
1288
1344
|
if MediLink_ConfigLoader:
|
|
1289
1345
|
MediLink_ConfigLoader.log("Error in backfill_enhanced_result: {}".format(str(e)), level="WARNING")
|
|
1290
|
-
return enhanced_result
|
|
1346
|
+
return enhanced_result
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
# =============================================================================
|
|
1350
|
+
# ROW ERROR REASON COMPUTATION (centralized)
|
|
1351
|
+
# =============================================================================
|
|
1352
|
+
|
|
1353
|
+
def compute_error_reason(record):
|
|
1354
|
+
"""Compute a user-friendly error reason for an eligibility result row."""
|
|
1355
|
+
try:
|
|
1356
|
+
if not isinstance(record, dict):
|
|
1357
|
+
return ""
|
|
1358
|
+
reason = str(record.get('error_reason', '')).strip()
|
|
1359
|
+
name_unknown = (not str(record.get('patient_name', '')).strip()) or (record.get('patient_name') == 'Unknown Patient')
|
|
1360
|
+
has_error = (str(record.get('status', '')) == 'Error') or (str(record.get('data_source', '')) in ['None', 'Error'])
|
|
1361
|
+
amount_missing = (str(record.get('remaining_amount', '')) == 'Not Found')
|
|
1362
|
+
|
|
1363
|
+
if not reason:
|
|
1364
|
+
if name_unknown:
|
|
1365
|
+
reason = 'Patient name could not be determined from API responses or CSV backfill'
|
|
1366
|
+
elif amount_missing:
|
|
1367
|
+
reason = 'Deductible remaining amount not found in eligibility response'
|
|
1368
|
+
elif has_error:
|
|
1369
|
+
reason = 'Eligibility lookup encountered an error; see logs for details'
|
|
1370
|
+
return reason
|
|
1371
|
+
except Exception:
|
|
1372
|
+
return ""
|
MediLink/MediLink_Deductible.py
CHANGED
|
@@ -375,12 +375,16 @@ def manual_deductible_lookup():
|
|
|
375
375
|
|
|
376
376
|
# Fetch eligibility data
|
|
377
377
|
found_data = False
|
|
378
|
+
printed_messages = set()
|
|
378
379
|
for i, payer_id in enumerate(payer_ids, 1):
|
|
379
380
|
print("Checking Payer ID {} ({}/{}): {}".format(payer_id, i, len(payer_ids), payer_id))
|
|
380
381
|
|
|
381
382
|
# Use the current mode setting for validation
|
|
382
383
|
run_validation = DEBUG_MODE
|
|
383
|
-
eligibility_data = get_eligibility_info(
|
|
384
|
+
eligibility_data = get_eligibility_info(
|
|
385
|
+
client, payer_id, provider_last_name, formatted_dob, member_id, npi,
|
|
386
|
+
run_validation=run_validation, is_manual_lookup=True, printed_messages=printed_messages
|
|
387
|
+
)
|
|
384
388
|
if eligibility_data:
|
|
385
389
|
found_data = True
|
|
386
390
|
|
|
@@ -484,13 +488,28 @@ def manual_deductible_lookup():
|
|
|
484
488
|
enhanced_result['policy_status'][:14],
|
|
485
489
|
enhanced_result['remaining_amount'][:14])
|
|
486
490
|
output_file.write(table_row + "\n")
|
|
491
|
+
|
|
492
|
+
# Persist per-row error diagnostics in a user-friendly way
|
|
493
|
+
try:
|
|
494
|
+
row_reason = _compute_error_reason(enhanced_result)
|
|
495
|
+
row_messages = enhanced_result.get('error_messages', []) or []
|
|
496
|
+
if row_reason or row_messages:
|
|
497
|
+
output_file.write(" >> Errors:" + "\n")
|
|
498
|
+
if row_reason:
|
|
499
|
+
output_file.write(" >> - {}\n".format(row_reason))
|
|
500
|
+
for msg in row_messages:
|
|
501
|
+
# Avoid duplicating the reason message if identical
|
|
502
|
+
if not row_reason or msg.strip() != row_reason.strip():
|
|
503
|
+
output_file.write(" >> - {}\n".format(str(msg)))
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
487
506
|
else:
|
|
488
507
|
display_eligibility_info(eligibility_data, formatted_dob, member_id, output_file)
|
|
489
508
|
|
|
490
509
|
# Ask if user wants to open the report
|
|
491
510
|
open_report = input("\nEligibility data found! Open the report? (Y/N): ").strip().lower()
|
|
492
511
|
if open_report in ['y', 'yes']:
|
|
493
|
-
os.
|
|
512
|
+
os.startfile(output_file_path)
|
|
494
513
|
print("Manual eligibility report generated: {}\n".format(output_file_path))
|
|
495
514
|
break # Assuming one payer ID per manual lookup
|
|
496
515
|
else:
|
|
@@ -523,6 +542,13 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
523
542
|
if run_validation:
|
|
524
543
|
# Debug mode: Call both APIs and run validation
|
|
525
544
|
MediLink_ConfigLoader.log("Running in DEBUG MODE - calling both APIs", level="INFO")
|
|
545
|
+
# Always initialize row-level error messages for diagnostics
|
|
546
|
+
error_messages_for_row = []
|
|
547
|
+
# Track Super Connector connection failure (for user-facing diagnostics)
|
|
548
|
+
sc_failure_info = None
|
|
549
|
+
# Enable verbose diagnostics in debug mode without config changes
|
|
550
|
+
diagnostics_verbose = True
|
|
551
|
+
sc_preflight_failed = False
|
|
526
552
|
|
|
527
553
|
# Get legacy response
|
|
528
554
|
MediLink_ConfigLoader.log("Getting legacy get_eligibility_v3 API response", level="INFO")
|
|
@@ -547,11 +573,44 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
547
573
|
MediLink_ConfigLoader.log("Getting new get_eligibility_super_connector API response", level="INFO")
|
|
548
574
|
super_connector_eligibility = None
|
|
549
575
|
try:
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
576
|
+
if not sc_preflight_failed:
|
|
577
|
+
super_connector_eligibility = api_core.get_eligibility_super_connector(
|
|
578
|
+
client, payer_id, provider_last_name, 'MemberIDDateOfBirth', date_of_birth, member_id, npi
|
|
579
|
+
)
|
|
580
|
+
else:
|
|
581
|
+
super_connector_eligibility = None
|
|
553
582
|
except Exception as e:
|
|
554
583
|
MediLink_ConfigLoader.log("Super Connector API failed: {}".format(e), level="ERROR")
|
|
584
|
+
# Best-effort triage classification for clearer downstream messaging
|
|
585
|
+
try:
|
|
586
|
+
# Use centralized classifier when available
|
|
587
|
+
from MediCafe.deductible_utils import classify_api_failure
|
|
588
|
+
code, message = classify_api_failure(e, 'Super Connector API')
|
|
589
|
+
# Sticky preflight failure for subsequent patients in this run
|
|
590
|
+
if code in ['TIMEOUT', 'CONN_ERR', 'AUTH_FAIL', 'MISCONFIG']:
|
|
591
|
+
sc_preflight_failed = True
|
|
592
|
+
except Exception:
|
|
593
|
+
try:
|
|
594
|
+
failure_reason = "Super Connector API connection failed"
|
|
595
|
+
detail = str(e)
|
|
596
|
+
if requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.Timeout):
|
|
597
|
+
failure_reason = "Super Connector API timeout"
|
|
598
|
+
elif requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.ConnectionError):
|
|
599
|
+
failure_reason = "Super Connector API connection error"
|
|
600
|
+
elif "Invalid payer_id" in detail:
|
|
601
|
+
failure_reason = "Super Connector API rejected payer_id"
|
|
602
|
+
elif ("No access token" in detail) or ("token" in detail.lower()):
|
|
603
|
+
failure_reason = "Super Connector API authentication failed"
|
|
604
|
+
elif ("Eligibility endpoint not configured" in detail) or ("endpoint" in detail.lower() and "configured" in detail.lower()):
|
|
605
|
+
failure_reason = "Super Connector API endpoint misconfigured"
|
|
606
|
+
message = "{}: {}".format(failure_reason, detail)
|
|
607
|
+
except Exception:
|
|
608
|
+
message = "Super Connector API failed: {}".format(str(e))
|
|
609
|
+
sc_failure_info = {"message": message}
|
|
610
|
+
try:
|
|
611
|
+
error_messages_for_row.append(message)
|
|
612
|
+
except Exception:
|
|
613
|
+
pass
|
|
555
614
|
|
|
556
615
|
# Run validation if we have at least one response
|
|
557
616
|
# Generate validation report even if one API fails - this helps with debugging
|
|
@@ -596,6 +655,11 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
596
655
|
if detail_msg not in printed_messages:
|
|
597
656
|
print(detail_msg)
|
|
598
657
|
printed_messages.add(detail_msg)
|
|
658
|
+
# Accumulate per-row messages for persistence in reports
|
|
659
|
+
try:
|
|
660
|
+
error_messages_for_row.append("{} - {}".format(error_code, error_desc))
|
|
661
|
+
except Exception:
|
|
662
|
+
pass
|
|
599
663
|
|
|
600
664
|
# Check for data in error extensions (some APIs return data here)
|
|
601
665
|
extensions = error.get('extensions', {})
|
|
@@ -607,20 +671,37 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
607
671
|
if details:
|
|
608
672
|
first_detail = details[0]
|
|
609
673
|
print(" First detail: {}".format(first_detail))
|
|
674
|
+
# Persist a brief extension note without dumping raw objects
|
|
675
|
+
try:
|
|
676
|
+
error_messages_for_row.append("Extensions include {} detail record(s)".format(len(details)))
|
|
677
|
+
except Exception:
|
|
678
|
+
pass
|
|
610
679
|
|
|
611
680
|
# Check status code
|
|
612
681
|
if super_connector_eligibility:
|
|
613
682
|
status_code = super_connector_eligibility.get('statuscode')
|
|
614
|
-
|
|
683
|
+
from MediCafe.deductible_utils import is_ok_200
|
|
684
|
+
if status_code is not None and not is_ok_200(status_code):
|
|
615
685
|
print("Super Connector API status code: {} (non-200 indicates errors)".format(status_code))
|
|
686
|
+
# Record status code for the row diagnostics
|
|
687
|
+
error_messages_for_row.append("Status code {} from Super Connector".format(status_code))
|
|
688
|
+
# If Super Connector failed entirely, append a triage note to the validation report (if created)
|
|
689
|
+
try:
|
|
690
|
+
if sc_failure_info and validation_file_path and os.path.exists(validation_file_path):
|
|
691
|
+
with open(validation_file_path, 'a') as vf:
|
|
692
|
+
vf.write("\n" + "-" * 80 + "\n")
|
|
693
|
+
vf.write("SUPER CONNECTOR CONNECTION FAILURE NOTE\n")
|
|
694
|
+
vf.write("-" * 80 + "\n")
|
|
695
|
+
vf.write(sc_failure_info['message'] + "\n")
|
|
696
|
+
vf.write("Recommendation: Verify network connectivity, credentials, payer ID validity, and endpoint configuration.\n")
|
|
697
|
+
except Exception:
|
|
698
|
+
pass
|
|
616
699
|
|
|
617
700
|
# Open validation report in Notepad (only for manual lookups, not batch processing)
|
|
618
701
|
if validation_file_path and os.path.exists(validation_file_path):
|
|
619
702
|
# Only open in manual mode - batch processing will handle this separately
|
|
620
703
|
if is_manual_lookup: # Check if we're in manual lookup mode
|
|
621
|
-
|
|
622
|
-
if ret != 0:
|
|
623
|
-
print("Failed to open Notepad (exit code: {}). Please open manually: {}".format(ret, validation_file_path))
|
|
704
|
+
os.startfile(validation_file_path)
|
|
624
705
|
elif validation_file_path:
|
|
625
706
|
print("\nValidation report file was not created: {}".format(validation_file_path))
|
|
626
707
|
except Exception as e:
|
|
@@ -628,7 +709,40 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
628
709
|
|
|
629
710
|
# After validation, merge responses
|
|
630
711
|
try:
|
|
631
|
-
|
|
712
|
+
if merge_responses is not None:
|
|
713
|
+
merged_data = merge_responses(super_connector_eligibility, legacy_eligibility, date_of_birth, member_id)
|
|
714
|
+
else:
|
|
715
|
+
MediLink_ConfigLoader.log("merge_responses utility not available; returning raw API response", level="WARNING")
|
|
716
|
+
merged_data = super_connector_eligibility or legacy_eligibility or {}
|
|
717
|
+
# Attach accumulated row-level messages for downstream display/persistence
|
|
718
|
+
try:
|
|
719
|
+
if isinstance(merged_data, dict) and error_messages_for_row:
|
|
720
|
+
merged_data['error_messages'] = error_messages_for_row
|
|
721
|
+
except Exception:
|
|
722
|
+
pass
|
|
723
|
+
# Surface Super Connector failure prominently in user-facing diagnostics
|
|
724
|
+
try:
|
|
725
|
+
if sc_failure_info and isinstance(merged_data, dict):
|
|
726
|
+
merged_data['super_connector_failed'] = True
|
|
727
|
+
# Prefer explaining the connection failure over generic name/amount messages
|
|
728
|
+
if (not merged_data.get('error_reason')) or (merged_data.get('data_source') in ['None', 'Error']) or (not merged_data.get('is_successful', False)):
|
|
729
|
+
merged_data['error_reason'] = sc_failure_info['message']
|
|
730
|
+
# Ensure the failure message is included in error_messages
|
|
731
|
+
if 'error_messages' not in merged_data or merged_data['error_messages'] is None:
|
|
732
|
+
merged_data['error_messages'] = []
|
|
733
|
+
if sc_failure_info['message'] not in merged_data['error_messages']:
|
|
734
|
+
merged_data['error_messages'].append(sc_failure_info['message'])
|
|
735
|
+
# Attach diagnostics envelope (minimal) without breaking existing schema
|
|
736
|
+
try:
|
|
737
|
+
if diagnostics_verbose:
|
|
738
|
+
if 'diagnostics' not in merged_data or merged_data['diagnostics'] is None:
|
|
739
|
+
merged_data['diagnostics'] = []
|
|
740
|
+
if sc_failure_info['message'] not in merged_data['diagnostics']:
|
|
741
|
+
merged_data['diagnostics'].append(sc_failure_info['message'])
|
|
742
|
+
except Exception:
|
|
743
|
+
pass
|
|
744
|
+
except Exception:
|
|
745
|
+
pass
|
|
632
746
|
return merged_data
|
|
633
747
|
except Exception as e:
|
|
634
748
|
MediLink_ConfigLoader.log("Error in merge_responses: {}".format(e), level="ERROR")
|
|
@@ -713,8 +827,10 @@ def display_eligibility_info(data, dob, member_id, output_file, patient_id="", s
|
|
|
713
827
|
if data is None:
|
|
714
828
|
return
|
|
715
829
|
|
|
716
|
-
# Convert to enhanced format
|
|
717
|
-
enhanced_data =
|
|
830
|
+
# Convert to enhanced format (guard if utility missing)
|
|
831
|
+
enhanced_data = None
|
|
832
|
+
if convert_eligibility_to_enhanced_format is not None:
|
|
833
|
+
enhanced_data = convert_eligibility_to_enhanced_format(data, dob, member_id, patient_id, service_date)
|
|
718
834
|
if enhanced_data:
|
|
719
835
|
# Write to output file in legacy format for compatibility
|
|
720
836
|
table_row = "{:<20} | {:<10} | {:<40} | {:<5} | {:<14} | {:<14}".format(
|
|
@@ -727,6 +843,31 @@ def display_eligibility_info(data, dob, member_id, output_file, patient_id="", s
|
|
|
727
843
|
output_file.write(table_row + "\n")
|
|
728
844
|
print(table_row) # Print to console for progressive display
|
|
729
845
|
|
|
846
|
+
# Helper to compute a user-friendly error explanation for a result row
|
|
847
|
+
def _compute_error_reason(record):
|
|
848
|
+
# Delegate to centralized helper to avoid duplication
|
|
849
|
+
try:
|
|
850
|
+
from MediCafe.deductible_utils import compute_error_reason
|
|
851
|
+
return compute_error_reason(record)
|
|
852
|
+
except Exception:
|
|
853
|
+
try:
|
|
854
|
+
if not isinstance(record, dict):
|
|
855
|
+
return ""
|
|
856
|
+
reason = str(record.get('error_reason', '')).strip()
|
|
857
|
+
name_unknown = (not str(record.get('patient_name', '')).strip()) or (record.get('patient_name') == 'Unknown Patient')
|
|
858
|
+
has_error = (str(record.get('status', '')) == 'Error') or (str(record.get('data_source', '')) in ['None', 'Error'])
|
|
859
|
+
amount_missing = (str(record.get('remaining_amount', '')) == 'Not Found')
|
|
860
|
+
if not reason:
|
|
861
|
+
if name_unknown:
|
|
862
|
+
reason = 'Patient name could not be determined from API responses or CSV backfill'
|
|
863
|
+
elif amount_missing:
|
|
864
|
+
reason = 'Deductible remaining amount not found in eligibility response'
|
|
865
|
+
elif has_error:
|
|
866
|
+
reason = 'Eligibility lookup encountered an error; see logs for details'
|
|
867
|
+
return reason
|
|
868
|
+
except Exception:
|
|
869
|
+
return ""
|
|
870
|
+
|
|
730
871
|
# Global mode flags (will be set in main)
|
|
731
872
|
LEGACY_MODE = False
|
|
732
873
|
DEBUG_MODE = False
|
|
@@ -1030,6 +1171,14 @@ if __name__ == "__main__":
|
|
|
1030
1171
|
"Patient Name", "DOB", "Insurance Type Code", "PayID", "Policy Status", "Remaining Amt")
|
|
1031
1172
|
output_file.write(table_header + "\n")
|
|
1032
1173
|
output_file.write("-" * len(table_header) + "\n")
|
|
1174
|
+
|
|
1175
|
+
# Global notice when Super Connector connection failed for any patients
|
|
1176
|
+
try:
|
|
1177
|
+
sc_failed_count = sum(1 for r in eligibility_results if isinstance(r, dict) and r.get('super_connector_failed'))
|
|
1178
|
+
if sc_failed_count:
|
|
1179
|
+
output_file.write("NOTICE: Super Connector API connection failed for {} patient(s). Fallback data used when available.\n".format(sc_failed_count))
|
|
1180
|
+
except Exception:
|
|
1181
|
+
pass
|
|
1033
1182
|
|
|
1034
1183
|
# Write all results to file
|
|
1035
1184
|
for result in eligibility_results:
|
|
@@ -1042,6 +1191,20 @@ if __name__ == "__main__":
|
|
|
1042
1191
|
result['remaining_amount'][:14])
|
|
1043
1192
|
output_file.write(table_row + "\n")
|
|
1044
1193
|
|
|
1194
|
+
# Persist per-row error diagnostics in a user-friendly way
|
|
1195
|
+
try:
|
|
1196
|
+
row_reason = _compute_error_reason(result)
|
|
1197
|
+
row_messages = result.get('error_messages', []) or []
|
|
1198
|
+
if row_reason or row_messages:
|
|
1199
|
+
output_file.write(" >> Errors:" + "\n")
|
|
1200
|
+
if row_reason:
|
|
1201
|
+
output_file.write(" >> - {}\n".format(row_reason))
|
|
1202
|
+
for msg in row_messages:
|
|
1203
|
+
if not row_reason or msg.strip() != row_reason.strip():
|
|
1204
|
+
output_file.write(" >> - {}\n".format(str(msg)))
|
|
1205
|
+
except Exception:
|
|
1206
|
+
pass
|
|
1207
|
+
|
|
1045
1208
|
# Write enhanced error summary to file
|
|
1046
1209
|
if errors:
|
|
1047
1210
|
error_msg = "\nErrors encountered during API calls:\n"
|
|
@@ -1053,7 +1216,7 @@ if __name__ == "__main__":
|
|
|
1053
1216
|
# Ask if user wants to open the report
|
|
1054
1217
|
open_report = input("\nBatch processing complete! Open the eligibility report? (Y/N): ").strip().lower()
|
|
1055
1218
|
if open_report in ['y', 'yes']:
|
|
1056
|
-
os.
|
|
1219
|
+
os.startfile(output_file_path)
|
|
1057
1220
|
|
|
1058
1221
|
# Print summary of validation reports only in debug mode
|
|
1059
1222
|
if DEBUG_MODE:
|
|
@@ -1072,9 +1235,7 @@ if __name__ == "__main__":
|
|
|
1072
1235
|
if open_validation in ['y', 'yes']:
|
|
1073
1236
|
for file_path in validation_files_created:
|
|
1074
1237
|
print("Opening: {}".format(os.path.basename(file_path)))
|
|
1075
|
-
|
|
1076
|
-
if ret != 0:
|
|
1077
|
-
print("Failed to open Notepad for: {}".format(os.path.basename(file_path)))
|
|
1238
|
+
os.startfile(file_path)
|
|
1078
1239
|
else:
|
|
1079
1240
|
print("No validation reports were generated.")
|
|
1080
1241
|
print("This may be because:")
|
|
@@ -518,7 +518,14 @@ def _maybe_display_error_row(record, context):
|
|
|
518
518
|
elif has_error:
|
|
519
519
|
reason = 'Eligibility lookup encountered an error; see logs for details'
|
|
520
520
|
|
|
521
|
-
|
|
521
|
+
# Prefer diagnostics lines when present; otherwise fall back to reason
|
|
522
|
+
diagnostics = record.get('diagnostics', []) or []
|
|
523
|
+
if diagnostics:
|
|
524
|
+
# Only show first 1-2 lines to avoid noise in XP console
|
|
525
|
+
to_show = diagnostics[:2]
|
|
526
|
+
for line in to_show:
|
|
527
|
+
print(" >> {}".format(line))
|
|
528
|
+
elif reason:
|
|
522
529
|
print(" >> Error: {}".format(reason))
|
|
523
530
|
except Exception:
|
|
524
531
|
# Never let diagnostics break table rendering
|
MediLink/MediLink_Gmail.py
CHANGED
|
@@ -174,6 +174,14 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
174
174
|
self.send_response(200)
|
|
175
175
|
self._set_headers()
|
|
176
176
|
self.end_headers()
|
|
177
|
+
try:
|
|
178
|
+
origin = self.headers.get('Origin')
|
|
179
|
+
except Exception:
|
|
180
|
+
origin = None
|
|
181
|
+
try:
|
|
182
|
+
print("[CORS] Preflight {0} from {1}".format(self.path, origin))
|
|
183
|
+
except Exception:
|
|
184
|
+
pass
|
|
177
185
|
|
|
178
186
|
def do_POST(self):
|
|
179
187
|
if self.path == '/download':
|
|
@@ -182,6 +190,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
182
190
|
data = json.loads(post_data.decode('utf-8'))
|
|
183
191
|
links = data.get('links', [])
|
|
184
192
|
log("Received links: {}".format(links))
|
|
193
|
+
try:
|
|
194
|
+
print("[Handshake] Received {0} link(s) from webapp".format(len(links)))
|
|
195
|
+
except Exception:
|
|
196
|
+
pass
|
|
185
197
|
file_ids = [link.get('fileId', None) for link in links if link.get('fileId')]
|
|
186
198
|
log("File IDs received from client: {}".format(file_ids))
|
|
187
199
|
download_docx_files(links)
|
|
@@ -210,6 +222,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
210
222
|
self.end_headers()
|
|
211
223
|
response = json.dumps({"status": "success", "message": "All files downloaded", "fileIds": successful_ids})
|
|
212
224
|
self.wfile.write(response.encode('utf-8'))
|
|
225
|
+
try:
|
|
226
|
+
print("[Handshake] Completed. Returning success for {0} fileId(s)".format(len(successful_ids)))
|
|
227
|
+
except Exception:
|
|
228
|
+
pass
|
|
213
229
|
shutdown_event.set()
|
|
214
230
|
bring_window_to_foreground()
|
|
215
231
|
elif self.path == '/shutdown':
|
|
@@ -244,6 +260,19 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|
|
244
260
|
|
|
245
261
|
def do_GET(self):
|
|
246
262
|
log("Full request path: {}".format(self.path))
|
|
263
|
+
if self.path == '/_health':
|
|
264
|
+
try:
|
|
265
|
+
print("[Health] Probe OK")
|
|
266
|
+
except Exception:
|
|
267
|
+
pass
|
|
268
|
+
self.send_response(200)
|
|
269
|
+
self._set_headers()
|
|
270
|
+
self.end_headers()
|
|
271
|
+
try:
|
|
272
|
+
self.wfile.write(json.dumps({"status": "ok"}).encode('ascii'))
|
|
273
|
+
except Exception:
|
|
274
|
+
self.wfile.write(b'{"status":"ok"}')
|
|
275
|
+
return
|
|
247
276
|
if self.path.startswith("/?code="):
|
|
248
277
|
auth_code = self.path.split('=')[1].split('&')[0]
|
|
249
278
|
auth_code = requests.utils.unquote(auth_code)
|
|
@@ -329,6 +358,10 @@ def run_server():
|
|
|
329
358
|
httpd = HTTPServer(('0.0.0.0', server_port), RequestHandler)
|
|
330
359
|
httpd.socket = ssl.wrap_socket(httpd.socket, certfile=cert_file, keyfile=key_file, server_side=True)
|
|
331
360
|
log("Starting HTTPS server on port {}".format(server_port))
|
|
361
|
+
try:
|
|
362
|
+
print("[Server] HTTPS server ready at https://127.0.0.1:{0}".format(server_port))
|
|
363
|
+
except Exception:
|
|
364
|
+
pass
|
|
332
365
|
httpd.serve_forever()
|
|
333
366
|
except Exception as e:
|
|
334
367
|
log("Error in serving: {}".format(e))
|
MediLink/__init__.py
CHANGED
MediLink/webapp.html
CHANGED
|
@@ -172,6 +172,8 @@
|
|
|
172
172
|
<div id="loadingMessage" class="loading-message" style="display:none;">
|
|
173
173
|
<span>⏳</span> Loading DOCX results...
|
|
174
174
|
</div>
|
|
175
|
+
|
|
176
|
+
|
|
175
177
|
</main>
|
|
176
178
|
<script>
|
|
177
179
|
document.addEventListener("DOMContentLoaded", () => {
|
|
@@ -393,11 +395,13 @@
|
|
|
393
395
|
return;
|
|
394
396
|
}
|
|
395
397
|
|
|
398
|
+
const baseUrl = 'https://127.0.0.1:8000';
|
|
399
|
+
console.log("Python server URL:", baseUrl);
|
|
396
400
|
console.log("Sending download links to Python server:", downloadLinks);
|
|
397
401
|
try {
|
|
398
|
-
const response = await fetch('
|
|
402
|
+
const response = await fetch(baseUrl + '/download', {
|
|
399
403
|
method: 'POST',
|
|
400
|
-
headers: {'Content-Type': 'application/json'},
|
|
404
|
+
headers: { 'Content-Type': 'application/json' },
|
|
401
405
|
body: JSON.stringify({
|
|
402
406
|
links: downloadLinks.map(link => ({
|
|
403
407
|
url: link.url,
|
|
@@ -405,30 +409,54 @@
|
|
|
405
409
|
fileId: link.fileId,
|
|
406
410
|
}))
|
|
407
411
|
}),
|
|
408
|
-
mode: 'cors'
|
|
412
|
+
mode: 'cors'
|
|
409
413
|
});
|
|
410
414
|
|
|
411
|
-
// Log the response status and headers for debugging
|
|
412
|
-
console.log("Response status:", response.status);
|
|
413
|
-
console.log("Response headers:", response.headers);
|
|
414
|
-
|
|
415
415
|
if (!response.ok) {
|
|
416
|
-
const errorText = await response.text()
|
|
417
|
-
console.error('Network response was not ok:', errorText);
|
|
416
|
+
const errorText = await response.text().catch(() => '');
|
|
418
417
|
throw new Error(`Network response was not ok: ${response.status} - ${errorText}`);
|
|
419
418
|
}
|
|
420
419
|
|
|
421
|
-
const data = await response.json();
|
|
420
|
+
const data = await response.json().catch(() => ({}));
|
|
422
421
|
console.log("Python server response:", data);
|
|
423
422
|
if (data.status === 'success') {
|
|
424
|
-
console.log(
|
|
423
|
+
console.log('Python server handshake complete. Accepted ' + downloadLinks.length + ' link(s).');
|
|
425
424
|
} else {
|
|
426
425
|
console.error("Error from Python server:", data.message);
|
|
427
|
-
displayErrorMessage('Error: ' + data.message);
|
|
426
|
+
displayErrorMessage('Error: ' + (data.message || 'Unexpected server response'));
|
|
428
427
|
}
|
|
429
428
|
} catch (error) {
|
|
430
429
|
console.error('Error sending download links to Python server:', error);
|
|
431
|
-
|
|
430
|
+
await diagnoseAfterFailure(baseUrl, error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function diagnoseAfterFailure(baseUrl, originalError) {
|
|
435
|
+
try {
|
|
436
|
+
if (!navigator.onLine) {
|
|
437
|
+
displayErrorMessage('You appear to be offline. Please check your internet connection.');
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
// Probe server reachability
|
|
441
|
+
let healthOk = false;
|
|
442
|
+
let healthStatus = null;
|
|
443
|
+
try {
|
|
444
|
+
const res = await fetch(baseUrl + '/_health', { method: 'GET', mode: 'cors', cache: 'no-store', credentials: 'omit' });
|
|
445
|
+
healthOk = !!res && res.ok;
|
|
446
|
+
healthStatus = res ? (res.status + ' ' + (res.statusText || '')) : null;
|
|
447
|
+
} catch (e) {
|
|
448
|
+
// ignore, handled below
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (healthOk) {
|
|
452
|
+
displayErrorMessage('The Python server is reachable, but the download request failed. Please try again or restart the Python tool.');
|
|
453
|
+
} else {
|
|
454
|
+
// Generic but actionable guidance
|
|
455
|
+
displayErrorMessage('Unable to reach the local Python server at https://127.0.0.1:8000. Ensure it is running and trusted by your browser (open the URL once if needed).');
|
|
456
|
+
}
|
|
457
|
+
} catch (diagErr) {
|
|
458
|
+
// Fallback to original error message
|
|
459
|
+
displayErrorMessage('Unable to send download links to the server. Error: ' + (originalError && originalError.message ? originalError.message : String(originalError)));
|
|
432
460
|
}
|
|
433
461
|
}
|
|
434
462
|
|
|
@@ -12,7 +12,7 @@ MediBot/MediBot_dataformat_library.py,sha256=D46fdPtxcgfWTzaLBtSvjtozzZBNqNiODgu
|
|
|
12
12
|
MediBot/MediBot_debug.bat,sha256=F5Lfi3nFEEo4Ddx9EbX94u3fNAMgzMp3wsn-ULyASTM,6017
|
|
13
13
|
MediBot/MediBot_docx_decoder.py,sha256=9BSjV-kB90VHnqfL_5iX4zl5u0HcHvHuL7YNfx3gXpQ,33143
|
|
14
14
|
MediBot/MediBot_smart_import.py,sha256=Emvz7NwemHGCHvG5kZcUyXMcCheidbGKaPfOTg-YCEs,6684
|
|
15
|
-
MediBot/__init__.py,sha256=
|
|
15
|
+
MediBot/__init__.py,sha256=Gj1gmPtY2Ua-T9wxwcjl2OXVo7dMcZI4FkOg-YVWvLg,3192
|
|
16
16
|
MediBot/clear_cache.bat,sha256=F6-VhETWw6xDdGWG2wUqvtXjCl3lY4sSUFqF90bM8-8,1860
|
|
17
17
|
MediBot/crash_diagnostic.bat,sha256=j8kUtyBg6NOWbXpeFuEqIRHOkVzgUrLOqO3FBMfNxTo,9268
|
|
18
18
|
MediBot/f_drive_diagnostic.bat,sha256=4572hZaiwZ5wVAarPcZJQxkOSTwAdDuT_X914noARak,6878
|
|
@@ -22,13 +22,13 @@ MediBot/process_csvs.bat,sha256=3tI7h1z9eRj8rUUL4wJ7dy-Qrak20lRmpAPtGbUMbVQ,3489
|
|
|
22
22
|
MediBot/update_json.py,sha256=vvUF4mKCuaVly8MmoadDO59M231fCIInc0KI1EtDtPA,3704
|
|
23
23
|
MediBot/update_medicafe.py,sha256=G1lyvVOHYuho1d-TJQNN6qaB4HBWaJ2PpXqemBoPlRQ,17937
|
|
24
24
|
MediCafe/MediLink_ConfigLoader.py,sha256=NoLb2YiJwlkrRYCt2PHvcFJ7yTIRWQCrsvkZIJWreM4,11141
|
|
25
|
-
MediCafe/__init__.py,sha256=
|
|
25
|
+
MediCafe/__init__.py,sha256=5NhwImtNbn5vzqKaqRB0It076F7C2Zmb0REd1jY4BxE,5721
|
|
26
26
|
MediCafe/__main__.py,sha256=mRNyk3D9Ilnu2XhgVI_rut7r5Ro7UIKtwV871giAHI8,12992
|
|
27
27
|
MediCafe/api_core.py,sha256=r9cqa5-HU4A7iz3NLxzRwuxsxOfDiJn9SRgtPjT83qU,90764
|
|
28
28
|
MediCafe/api_factory.py,sha256=I5AeJoyu6m7oCrjc2OvVvO_4KSBRutTsR1riiWhTZV0,12086
|
|
29
29
|
MediCafe/api_utils.py,sha256=KWQB0q1k5E6frOFFlKWcFpHNcqfrS7KJ_82672wbupw,14041
|
|
30
30
|
MediCafe/core_utils.py,sha256=XKUpyv7yKjIQ8iNrhD76PIURyt6GZxb98v0daiI7aaw,27303
|
|
31
|
-
MediCafe/deductible_utils.py,sha256
|
|
31
|
+
MediCafe/deductible_utils.py,sha256=-ixDYwI3JNAyACrFjKqoX_hD3Awzownq441U0PSrwXw,64932
|
|
32
32
|
MediCafe/error_reporter.py,sha256=t9AAkkVsmZMxPMSA6DW7wDf2cxXpFfA9oJW5-thg5VQ,8176
|
|
33
33
|
MediCafe/graphql_utils.py,sha256=3BYl4wrox40063RwCtIqsrxSMA5rdchCP69RGi-U4aY,51317
|
|
34
34
|
MediCafe/logging_config.py,sha256=auT65LN5oDEXVhkMeLke63kJHTWxYf2o8YihAfQFgzU,5493
|
|
@@ -47,11 +47,11 @@ MediLink/MediLink_Charges.py,sha256=82fnqHGvT7tfdfjucnFHiLdUE0WhHDXrcS0k_Ln3c8U,
|
|
|
47
47
|
MediLink/MediLink_ClaimStatus.py,sha256=nKX7QymhCULiaGfISYw_P0Eqy0TP6qKG4C2d3TdGFVo,22720
|
|
48
48
|
MediLink/MediLink_DataMgmt.py,sha256=9hc5jyWU65nYT66afDybOyYAcW-DvEYuHpWTun96U50,52407
|
|
49
49
|
MediLink/MediLink_Decoder.py,sha256=1gzdybNg4Vv69s5PNbX8bPNrXT_N_kPpFpt2HpkauWA,16430
|
|
50
|
-
MediLink/MediLink_Deductible.py,sha256=
|
|
50
|
+
MediLink/MediLink_Deductible.py,sha256=8Npibnv4p7HDGhz8t7bf760lbkGbV3fMVIfKjNgxYjA,68168
|
|
51
51
|
MediLink/MediLink_Deductible_Validator.py,sha256=x6tHJOi88TblUpDPSH6QhIdXXRgr3rXI7kYPVGZYCgU,24998
|
|
52
|
-
MediLink/MediLink_Display_Utils.py,sha256=
|
|
52
|
+
MediLink/MediLink_Display_Utils.py,sha256=MonsX6VPbdvqwY_V8sHUYrXCS0fMKc4toJvG0oyr-V4,24872
|
|
53
53
|
MediLink/MediLink_Down.py,sha256=s4_z-RaqHYanjwbQCl-OSkg4XIpcIQ2Q6jXa8-q_QXw,28111
|
|
54
|
-
MediLink/MediLink_Gmail.py,sha256=
|
|
54
|
+
MediLink/MediLink_Gmail.py,sha256=hMHgseSi58qPMivZpEhZbY6k-tyxC-hHcWaS7xVNFbI,28011
|
|
55
55
|
MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
56
|
MediLink/MediLink_Parser.py,sha256=eRVZ4ckZ5gDOrcvtCUZP3DOd3Djly66rCIk0aYXLz14,12567
|
|
57
57
|
MediLink/MediLink_PatientProcessor.py,sha256=9r2w4p45d30Tn0kbXL3j5574MYOehP83tDirNOw_Aek,19977
|
|
@@ -63,15 +63,15 @@ MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJY
|
|
|
63
63
|
MediLink/MediLink_main.py,sha256=CAXu0IRzhra3ppIFDcCppFNAZp7kCuN6gPtJSdFqGzs,24857
|
|
64
64
|
MediLink/MediLink_smart_import.py,sha256=B5SfBn_4bYEWJJDolXbjnwKx_-MaqGZ76LyXQwWDV80,9838
|
|
65
65
|
MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
|
|
66
|
-
MediLink/__init__.py,sha256=
|
|
66
|
+
MediLink/__init__.py,sha256=xK9uzY41kqKgKT5uxifwKQd001nXnAzeHPJdwjB4Yeo,3888
|
|
67
67
|
MediLink/gmail_http_utils.py,sha256=gtqCCrzJC7e8JFQzMNrf7EbK8na2h4sfTu-NMaZ_UHc,4006
|
|
68
68
|
MediLink/gmail_oauth_utils.py,sha256=Ugr-DEqs4_RddRMSCJ_dbgA3TVeaxpbAor-dktcTIgY,3713
|
|
69
69
|
MediLink/openssl.cnf,sha256=76VdcGCykf0Typyiv8Wd1mMVKixrQ5RraG6HnfKFqTo,887
|
|
70
70
|
MediLink/test.py,sha256=DM_E8gEbhbVfTAm3wTMiNnK2GCD1e5eH6gwTk89QIc4,3116
|
|
71
|
-
MediLink/webapp.html,sha256=
|
|
72
|
-
medicafe-0.
|
|
73
|
-
medicafe-0.
|
|
74
|
-
medicafe-0.
|
|
75
|
-
medicafe-0.
|
|
76
|
-
medicafe-0.
|
|
77
|
-
medicafe-0.
|
|
71
|
+
MediLink/webapp.html,sha256=DwDYjVvluGJ7eDdvEogfKN4t24ZJRoIUuSBfCYCL-3w,21252
|
|
72
|
+
medicafe-0.251017.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
|
|
73
|
+
medicafe-0.251017.0.dist-info/METADATA,sha256=d86uW7sAJWTEBOxVF00UdawI9PmXuNoMuiE7iGCTYE0,3414
|
|
74
|
+
medicafe-0.251017.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
75
|
+
medicafe-0.251017.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
|
|
76
|
+
medicafe-0.251017.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
|
|
77
|
+
medicafe-0.251017.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|