medicafe 0.251016.0__py3-none-any.whl → 0.251017.1__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 +105 -20
- MediLink/MediLink_Display_Utils.py +8 -1
- MediLink/MediLink_Gmail.py +48 -6
- MediLink/__init__.py +1 -1
- MediLink/gmail_http_utils.py +2 -3
- MediLink/webapp.html +41 -13
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.dist-info}/METADATA +1 -1
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.dist-info}/RECORD +15 -15
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.dist-info}/LICENSE +0 -0
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.dist-info}/WHEEL +0 -0
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251016.0.dist-info → medicafe-0.251017.1.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
|
@@ -544,6 +544,11 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
544
544
|
MediLink_ConfigLoader.log("Running in DEBUG MODE - calling both APIs", level="INFO")
|
|
545
545
|
# Always initialize row-level error messages for diagnostics
|
|
546
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
|
|
547
552
|
|
|
548
553
|
# Get legacy response
|
|
549
554
|
MediLink_ConfigLoader.log("Getting legacy get_eligibility_v3 API response", level="INFO")
|
|
@@ -568,11 +573,44 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
568
573
|
MediLink_ConfigLoader.log("Getting new get_eligibility_super_connector API response", level="INFO")
|
|
569
574
|
super_connector_eligibility = None
|
|
570
575
|
try:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
574
582
|
except Exception as e:
|
|
575
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
|
|
576
614
|
|
|
577
615
|
# Run validation if we have at least one response
|
|
578
616
|
# Generate validation report even if one API fails - this helps with debugging
|
|
@@ -642,10 +680,22 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
642
680
|
# Check status code
|
|
643
681
|
if super_connector_eligibility:
|
|
644
682
|
status_code = super_connector_eligibility.get('statuscode')
|
|
645
|
-
|
|
683
|
+
from MediCafe.deductible_utils import is_ok_200
|
|
684
|
+
if status_code is not None and not is_ok_200(status_code):
|
|
646
685
|
print("Super Connector API status code: {} (non-200 indicates errors)".format(status_code))
|
|
647
686
|
# Record status code for the row diagnostics
|
|
648
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
|
|
649
699
|
|
|
650
700
|
# Open validation report in Notepad (only for manual lookups, not batch processing)
|
|
651
701
|
if validation_file_path and os.path.exists(validation_file_path):
|
|
@@ -670,6 +720,29 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
670
720
|
merged_data['error_messages'] = error_messages_for_row
|
|
671
721
|
except Exception:
|
|
672
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
|
|
673
746
|
return merged_data
|
|
674
747
|
except Exception as e:
|
|
675
748
|
MediLink_ConfigLoader.log("Error in merge_responses: {}".format(e), level="ERROR")
|
|
@@ -772,24 +845,28 @@ def display_eligibility_info(data, dob, member_id, output_file, patient_id="", s
|
|
|
772
845
|
|
|
773
846
|
# Helper to compute a user-friendly error explanation for a result row
|
|
774
847
|
def _compute_error_reason(record):
|
|
848
|
+
# Delegate to centralized helper to avoid duplication
|
|
775
849
|
try:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
reason = str(record.get('error_reason', '')).strip()
|
|
779
|
-
name_unknown = (not str(record.get('patient_name', '')).strip()) or (record.get('patient_name') == 'Unknown Patient')
|
|
780
|
-
has_error = (str(record.get('status', '')) == 'Error') or (str(record.get('data_source', '')) in ['None', 'Error'])
|
|
781
|
-
amount_missing = (str(record.get('remaining_amount', '')) == 'Not Found')
|
|
782
|
-
|
|
783
|
-
if not reason:
|
|
784
|
-
if name_unknown:
|
|
785
|
-
reason = 'Patient name could not be determined from API responses or CSV backfill'
|
|
786
|
-
elif amount_missing:
|
|
787
|
-
reason = 'Deductible remaining amount not found in eligibility response'
|
|
788
|
-
elif has_error:
|
|
789
|
-
reason = 'Eligibility lookup encountered an error; see logs for details'
|
|
790
|
-
return reason
|
|
850
|
+
from MediCafe.deductible_utils import compute_error_reason
|
|
851
|
+
return compute_error_reason(record)
|
|
791
852
|
except Exception:
|
|
792
|
-
|
|
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 ""
|
|
793
870
|
|
|
794
871
|
# Global mode flags (will be set in main)
|
|
795
872
|
LEGACY_MODE = False
|
|
@@ -1094,6 +1171,14 @@ if __name__ == "__main__":
|
|
|
1094
1171
|
"Patient Name", "DOB", "Insurance Type Code", "PayID", "Policy Status", "Remaining Amt")
|
|
1095
1172
|
output_file.write(table_header + "\n")
|
|
1096
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
|
|
1097
1182
|
|
|
1098
1183
|
# Write all results to file
|
|
1099
1184
|
for result in eligibility_results:
|
|
@@ -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))
|
|
@@ -437,13 +470,22 @@ def handle_post_response(url, payload, headers):
|
|
|
437
470
|
else:
|
|
438
471
|
log("Link retrieval initiated successfully.")
|
|
439
472
|
elif response.status_code == 401:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
473
|
+
# Automatic re-auth: clear token and prompt user to re-consent, keep server up
|
|
474
|
+
log("Unauthorized (401). Clearing cached token and initiating re-authentication flow. Response body: {}".format(response.text))
|
|
475
|
+
delete_token_file()
|
|
476
|
+
auth_url = get_authorization_url()
|
|
477
|
+
print("Your Google session needs to be refreshed to regain permissions. A browser window will open to re-authorize the app with the required scopes.")
|
|
478
|
+
open_browser_with_executable(auth_url)
|
|
479
|
+
# Wait for the OAuth redirect/flow to complete; the server remains running
|
|
480
|
+
shutdown_event.wait()
|
|
444
481
|
elif response.status_code == 403:
|
|
445
|
-
|
|
446
|
-
|
|
482
|
+
# Treat 403 similarly; scopes may be missing/changed. Force a fresh consent.
|
|
483
|
+
log("Forbidden (403). Clearing cached token and prompting for fresh consent. Response body: {}".format(response.text))
|
|
484
|
+
delete_token_file()
|
|
485
|
+
auth_url = get_authorization_url()
|
|
486
|
+
print("Permissions appear insufficient (403). Opening browser to request the correct Google permissions.")
|
|
487
|
+
open_browser_with_executable(auth_url)
|
|
488
|
+
shutdown_event.wait()
|
|
447
489
|
elif response.status_code == 404:
|
|
448
490
|
log("Not Found. Verify the URL and ensure the Apps Script is deployed correctly. Response body: {}".format(response.text))
|
|
449
491
|
shutdown_event.set()
|
MediLink/__init__.py
CHANGED
MediLink/gmail_http_utils.py
CHANGED
|
@@ -76,11 +76,10 @@ def inspect_token(access_token, log, delete_token_file_fn=None, stop_server_fn=N
|
|
|
76
76
|
else:
|
|
77
77
|
log("Failed to inspect token. Status code: {}, Body: {}".format(response.status_code, response.text))
|
|
78
78
|
if response.status_code == 400 and "invalid_token" in response.text:
|
|
79
|
-
|
|
79
|
+
# Token is invalid (revoked/expired). Clear cache and let caller trigger re-auth.
|
|
80
|
+
log("Access token is invalid. Clearing token cache and keeping server running for re-auth.")
|
|
80
81
|
if delete_token_file_fn:
|
|
81
82
|
delete_token_file_fn()
|
|
82
|
-
if stop_server_fn:
|
|
83
|
-
stop_server_fn()
|
|
84
83
|
return None
|
|
85
84
|
return None
|
|
86
85
|
except Exception as e:
|
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=tPc3GRS2hYeSa5NEwTCpUOiplwJyo9DZvaiQBvuaDzU,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=ya-2HryLmR_NeJXCktxeWdfoFvaI3NK7obHmRu6-A5M,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=KnTm7Z_Q5B1n9hNtCR00wDWWWmRVf86f-JtKdZKRKpA,28637
|
|
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=
|
|
67
|
-
MediLink/gmail_http_utils.py,sha256=
|
|
66
|
+
MediLink/__init__.py,sha256=wN-T-InivqDJu-lk3TyAyx6-ozGaDOsqbqBSLQWo4zw,3888
|
|
67
|
+
MediLink/gmail_http_utils.py,sha256=mYChIhkbA1oJaAJA-nY3XgHQY-H7zvZJUZPhUagomsI,4047
|
|
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.1.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
|
|
73
|
+
medicafe-0.251017.1.dist-info/METADATA,sha256=55QUT57-HhWixs_lDUf-5pEre_oqUdEIJtvWJUPaL0M,3414
|
|
74
|
+
medicafe-0.251017.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
75
|
+
medicafe-0.251017.1.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
|
|
76
|
+
medicafe-0.251017.1.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
|
|
77
|
+
medicafe-0.251017.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|