medicafe 0.251023.1__py3-none-any.whl → 0.251026.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.bat +867 -835
- MediBot/__init__.py +1 -1
- MediCafe/MediLink_ConfigLoader.py +8 -1
- MediCafe/__init__.py +1 -1
- MediCafe/api_core.py +11 -43
- MediCafe/error_reporter.py +90 -116
- MediLink/MediLink_Deductible.py +24 -23
- MediLink/MediLink_Gmail.py +1 -0
- MediLink/MediLink_main.py +45 -52
- MediLink/__init__.py +1 -1
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/METADATA +1 -1
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/RECORD +16 -16
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/LICENSE +0 -0
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/WHEEL +0 -0
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251023.1.dist-info → medicafe-0.251026.0.dist-info}/top_level.txt +0 -0
MediBot/__init__.py
CHANGED
|
@@ -38,7 +38,14 @@ def get_default_config():
|
|
|
38
38
|
'endpoint_url': '',
|
|
39
39
|
'auth_token': '',
|
|
40
40
|
'insecure_http': False,
|
|
41
|
-
'max_bundle_bytes': 2097152
|
|
41
|
+
'max_bundle_bytes': 2097152,
|
|
42
|
+
'email': {
|
|
43
|
+
'enabled': False,
|
|
44
|
+
'to': '',
|
|
45
|
+
'subject_prefix': 'MediCafe Error Report',
|
|
46
|
+
'max_bundle_bytes': 1572864,
|
|
47
|
+
'transport': 'gmail_api'
|
|
48
|
+
}
|
|
42
49
|
},
|
|
43
50
|
# STRATEGIC NOTE (COB Configuration): COB library is fully implemented and ready
|
|
44
51
|
# To enable COB functionality, add the following configuration:
|
MediCafe/__init__.py
CHANGED
MediCafe/api_core.py
CHANGED
|
@@ -1212,14 +1212,14 @@ def get_eligibility_super_connector(client, payer_id, provider_last_name, search
|
|
|
1212
1212
|
corporate_tax_owner_id=None, corporate_tax_owner_name=None, organization_name=None,
|
|
1213
1213
|
organization_id=None, identify_service_level_deductible=True):
|
|
1214
1214
|
"""
|
|
1215
|
-
|
|
1216
|
-
This function
|
|
1215
|
+
OPTUMAI eligibility (GraphQL) that maps to the same interface as get_eligibility_v3.
|
|
1216
|
+
This function does not perform legacy fallback; callers should invoke legacy v3 separately if desired.
|
|
1217
1217
|
"""
|
|
1218
1218
|
# Ensure all required parameters have values
|
|
1219
1219
|
if not all([client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi]):
|
|
1220
1220
|
raise ValueError("All required parameters must have values: client, payer_id, provider_last_name, search_option, date_of_birth, member_id, npi")
|
|
1221
1221
|
|
|
1222
|
-
# Prefer OPTUMAI endpoint if configured, otherwise fall back to legacy UHCAPI
|
|
1222
|
+
# Prefer OPTUMAI endpoint if configured, otherwise fall back to legacy UHCAPI v3 (REST)
|
|
1223
1223
|
try:
|
|
1224
1224
|
endpoints_cfg = client.config['MediLink_Config']['endpoints']
|
|
1225
1225
|
except Exception:
|
|
@@ -1240,12 +1240,8 @@ def get_eligibility_super_connector(client, payer_id, provider_last_name, search
|
|
|
1240
1240
|
pass
|
|
1241
1241
|
|
|
1242
1242
|
if not endpoint_name:
|
|
1243
|
-
#
|
|
1244
|
-
|
|
1245
|
-
try:
|
|
1246
|
-
url_extension = endpoints_cfg.get(endpoint_name, {}).get('additional_endpoints', {}).get('eligibility_super_connector')
|
|
1247
|
-
except Exception:
|
|
1248
|
-
url_extension = None
|
|
1243
|
+
# No fallback from this function; surface configuration error
|
|
1244
|
+
raise ValueError("OPTUMAI eligibility endpoint not configured")
|
|
1249
1245
|
|
|
1250
1246
|
# Validate payer_id against the selected endpoint's list
|
|
1251
1247
|
# - If OPTUMAI is used, allow the augmented list (includes LIFE1, WELM2, etc.).
|
|
@@ -1258,17 +1254,17 @@ def get_eligibility_super_connector(client, payer_id, provider_last_name, search
|
|
|
1258
1254
|
if not url_extension:
|
|
1259
1255
|
raise ValueError("Eligibility endpoint not configured for {}".format(endpoint_name))
|
|
1260
1256
|
|
|
1261
|
-
# Debug/trace: indicate
|
|
1257
|
+
# Debug/trace: indicate that OPTUMAI eligibility path is active
|
|
1262
1258
|
try:
|
|
1263
1259
|
MediLink_ConfigLoader.log(
|
|
1264
|
-
"
|
|
1260
|
+
"Eligibility using OPTUMAI endpoint with path '{}'".format(url_extension),
|
|
1265
1261
|
level="INFO",
|
|
1266
1262
|
console_output=CONSOLE_LOGGING
|
|
1267
1263
|
)
|
|
1268
1264
|
except Exception:
|
|
1269
1265
|
pass
|
|
1270
1266
|
try:
|
|
1271
|
-
print("[Eligibility] Using
|
|
1267
|
+
print("[Eligibility] Using OPTUMAI (path: {})".format(url_extension))
|
|
1272
1268
|
except Exception:
|
|
1273
1269
|
pass
|
|
1274
1270
|
|
|
@@ -1405,36 +1401,8 @@ def get_eligibility_super_connector(client, payer_id, provider_last_name, search
|
|
|
1405
1401
|
except Exception:
|
|
1406
1402
|
pass
|
|
1407
1403
|
|
|
1408
|
-
#
|
|
1409
|
-
|
|
1410
|
-
disable_fallback = False
|
|
1411
|
-
try:
|
|
1412
|
-
disable_fallback = bool(medi.get('optumai_disable_fallback', False))
|
|
1413
|
-
except Exception:
|
|
1414
|
-
disable_fallback = False
|
|
1415
|
-
if not disable_fallback and endpoint_name == 'OPTUMAI':
|
|
1416
|
-
fallback_url = endpoints_cfg.get('UHCAPI', {}).get('additional_endpoints', {}).get('eligibility_super_connector')
|
|
1417
|
-
if fallback_url:
|
|
1418
|
-
try:
|
|
1419
|
-
MediLink_ConfigLoader.log(
|
|
1420
|
-
"OPTUMAI call failed. Falling back to UHCAPI path '{}'".format(fallback_url),
|
|
1421
|
-
level="WARNING",
|
|
1422
|
-
console_output=CONSOLE_LOGGING
|
|
1423
|
-
)
|
|
1424
|
-
except Exception:
|
|
1425
|
-
pass
|
|
1426
|
-
try:
|
|
1427
|
-
print("[Eligibility] Fallback to UHCAPI (path: {})".format(fallback_url))
|
|
1428
|
-
except Exception:
|
|
1429
|
-
pass
|
|
1430
|
-
response = client.make_api_call('UHCAPI', 'POST', fallback_url, params=None, data=graphql_body, headers=headers)
|
|
1431
|
-
else:
|
|
1432
|
-
raise
|
|
1433
|
-
else:
|
|
1434
|
-
raise
|
|
1435
|
-
except Exception:
|
|
1436
|
-
# Propagate original error if fallback not possible or also fails
|
|
1437
|
-
raise
|
|
1404
|
+
# No fallback from this function; re-raise so callers can handle or try legacy explicitly
|
|
1405
|
+
raise
|
|
1438
1406
|
|
|
1439
1407
|
# Transform GraphQL response to match REST API format
|
|
1440
1408
|
# This ensures the calling code doesn't know the difference
|
|
@@ -1446,7 +1414,7 @@ def get_eligibility_super_connector(client, payer_id, provider_last_name, search
|
|
|
1446
1414
|
if sc_status and sc_status != '200':
|
|
1447
1415
|
msg = transformed_response.get('message')
|
|
1448
1416
|
MediLink_ConfigLoader.log(
|
|
1449
|
-
"
|
|
1417
|
+
"OPTUMAI eligibility transformed response status: {} msg: {}".format(sc_status, msg),
|
|
1450
1418
|
level="INFO",
|
|
1451
1419
|
console_output=CONSOLE_LOGGING
|
|
1452
1420
|
)
|
MediCafe/error_reporter.py
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import zipfile
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
import requests
|
|
5
|
-
except Exception:
|
|
6
|
-
requests = None
|
|
10
|
+
import requests
|
|
7
11
|
|
|
8
|
-
from
|
|
12
|
+
from email.mime.application import MIMEApplication
|
|
13
|
+
from email.mime.multipart import MIMEMultipart
|
|
14
|
+
from email.mime.text import MIMEText
|
|
9
15
|
|
|
16
|
+
from MediCafe.MediLink_ConfigLoader import load_configuration, log as mc_log
|
|
17
|
+
from MediLink.MediLink_Gmail import get_access_token
|
|
10
18
|
|
|
11
19
|
ASCII_SAFE_REPLACEMENTS = [
|
|
12
20
|
('"', '"'),
|
|
@@ -95,7 +103,7 @@ def _compute_report_id(zip_path):
|
|
|
95
103
|
return 'mc-{}-{}'.format(int(time.time()), '000000000000')
|
|
96
104
|
|
|
97
105
|
|
|
98
|
-
def collect_support_bundle(include_traceback=True, max_log_lines=
|
|
106
|
+
def collect_support_bundle(include_traceback=True, max_log_lines=500):
|
|
99
107
|
config, _ = load_configuration()
|
|
100
108
|
medi = config.get('MediLink_Config', {})
|
|
101
109
|
local_storage_path = medi.get('local_storage_path', '.')
|
|
@@ -140,12 +148,29 @@ def collect_support_bundle(include_traceback=True, max_log_lines=2000):
|
|
|
140
148
|
z.writestr('log_tail.txt', log_tail)
|
|
141
149
|
if traceback_txt:
|
|
142
150
|
z.writestr('traceback.txt', traceback_txt)
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
# Get latest WinSCP log
|
|
152
|
+
upload_log = os.path.join(local_storage_path, 'winscp_upload.log')
|
|
153
|
+
download_log = os.path.join(local_storage_path, 'winscp_download.log')
|
|
154
|
+
winscp_logs = [(p, os.path.getmtime(p)) for p in [upload_log, download_log] if os.path.exists(p)]
|
|
155
|
+
if winscp_logs:
|
|
156
|
+
latest_winscp = max(winscp_logs, key=lambda x: x[1])[0]
|
|
157
|
+
winscp_tail = _tail_file(latest_winscp, max_log_lines) if latest_winscp else ''
|
|
158
|
+
winscp_tail = _redact(winscp_tail)
|
|
159
|
+
else:
|
|
160
|
+
winscp_tail = ''
|
|
161
|
+
if winscp_tail:
|
|
162
|
+
z.writestr('winscp_log_tail.txt', winscp_tail)
|
|
145
163
|
except Exception as e:
|
|
146
164
|
mc_log('Error creating support bundle: {}'.format(e), level='ERROR')
|
|
147
165
|
return None
|
|
148
166
|
|
|
167
|
+
bundle_size = os.path.getsize(zip_path)
|
|
168
|
+
if bundle_size > 1572864:
|
|
169
|
+
os.remove(zip_path)
|
|
170
|
+
mc_log('Bundle too large ({} bytes) - discarded'.format(bundle_size), level='WARNING')
|
|
171
|
+
return None
|
|
172
|
+
return zip_path
|
|
173
|
+
|
|
149
174
|
|
|
150
175
|
def _first_line(text):
|
|
151
176
|
try:
|
|
@@ -158,14 +183,6 @@ def _first_line(text):
|
|
|
158
183
|
return ''
|
|
159
184
|
|
|
160
185
|
|
|
161
|
-
def _readme_text():
|
|
162
|
-
return (
|
|
163
|
-
"MediCafe Support Bundle\n\n"
|
|
164
|
-
"This archive contains a redacted log tail, optional traceback, and metadata.\n"
|
|
165
|
-
"You may submit this bundle automatically from the app or send it manually to support.\n"
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
186
|
def _get_version():
|
|
170
187
|
try:
|
|
171
188
|
from MediCafe import __version__
|
|
@@ -174,105 +191,6 @@ def _get_version():
|
|
|
174
191
|
return 'unknown'
|
|
175
192
|
|
|
176
193
|
|
|
177
|
-
def submit_support_bundle(zip_path):
|
|
178
|
-
config, _ = load_configuration()
|
|
179
|
-
medi = config.get('MediLink_Config', {})
|
|
180
|
-
rep = medi.get('error_reporting', {}) if isinstance(medi, dict) else {}
|
|
181
|
-
endpoint_url = _safe_ascii(rep.get('endpoint_url', ''))
|
|
182
|
-
auth_token = _safe_ascii(rep.get('auth_token', ''))
|
|
183
|
-
insecure = bool(rep.get('insecure_http', False))
|
|
184
|
-
max_bytes = int(rep.get('max_bundle_bytes', 2097152))
|
|
185
|
-
|
|
186
|
-
if not requests:
|
|
187
|
-
print("[ERROR] requests module not available; cannot submit report.")
|
|
188
|
-
return False
|
|
189
|
-
if not endpoint_url:
|
|
190
|
-
print("[ERROR] error_reporting.endpoint_url not configured.")
|
|
191
|
-
return False
|
|
192
|
-
if not os.path.exists(zip_path):
|
|
193
|
-
print("[ERROR] Bundle not found: {}".format(zip_path))
|
|
194
|
-
return False
|
|
195
|
-
try:
|
|
196
|
-
size = os.path.getsize(zip_path)
|
|
197
|
-
if size > max_bytes:
|
|
198
|
-
print("[INFO] Bundle size {} exceeds cap {}; rebuilding smaller not implemented here.".format(size, max_bytes))
|
|
199
|
-
except Exception:
|
|
200
|
-
pass
|
|
201
|
-
|
|
202
|
-
report_id = _compute_report_id(zip_path)
|
|
203
|
-
headers = {
|
|
204
|
-
'X-Auth-Token': auth_token or '',
|
|
205
|
-
'X-Report-Id': report_id,
|
|
206
|
-
'User-Agent': 'MediCafe-Reporter/1.0'
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
# Prepare meta.json stream derived from inside the zip for server convenience
|
|
210
|
-
meta_json = '{}'
|
|
211
|
-
try:
|
|
212
|
-
with zipfile.ZipFile(zip_path, 'r') as z:
|
|
213
|
-
if 'meta.json' in z.namelist():
|
|
214
|
-
meta_json = z.read('meta.json')
|
|
215
|
-
except Exception:
|
|
216
|
-
meta_json = '{}'
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
bundle_fh = open(zip_path, 'rb')
|
|
220
|
-
files = {
|
|
221
|
-
'meta': ('meta.json', meta_json, 'application/json'),
|
|
222
|
-
'bundle': (os.path.basename(zip_path), bundle_fh, 'application/zip')
|
|
223
|
-
}
|
|
224
|
-
r = requests.post(endpoint_url, headers=headers, files=files, timeout=(10, 20), verify=(not insecure))
|
|
225
|
-
code = getattr(r, 'status_code', None)
|
|
226
|
-
if code == 200:
|
|
227
|
-
print("[SUCCESS] Report submitted. ID: {}".format(report_id))
|
|
228
|
-
return True
|
|
229
|
-
elif code == 401:
|
|
230
|
-
print("[ERROR] Unauthorized (401). Check error_reporting.auth_token.")
|
|
231
|
-
return False
|
|
232
|
-
elif code == 403:
|
|
233
|
-
print("[ERROR] Forbidden (403). The receiver denied access. Verify REPORT_TOKEN and receiver permissions.")
|
|
234
|
-
return False
|
|
235
|
-
elif code == 413:
|
|
236
|
-
print("[ERROR] Too large (413). Consider reducing max log lines.")
|
|
237
|
-
return False
|
|
238
|
-
else:
|
|
239
|
-
print("[ERROR] Submission failed with status {}".format(code))
|
|
240
|
-
return False
|
|
241
|
-
except Exception as e:
|
|
242
|
-
print("[ERROR] Submission exception: {}".format(e))
|
|
243
|
-
return False
|
|
244
|
-
finally:
|
|
245
|
-
try:
|
|
246
|
-
bundle_fh.close()
|
|
247
|
-
except Exception:
|
|
248
|
-
pass
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
def flush_queued_reports():
|
|
252
|
-
config, _ = load_configuration()
|
|
253
|
-
medi = config.get('MediLink_Config', {})
|
|
254
|
-
local_storage_path = medi.get('local_storage_path', '.')
|
|
255
|
-
queue_dir = os.path.join(local_storage_path, 'reports_queue')
|
|
256
|
-
if not os.path.isdir(queue_dir):
|
|
257
|
-
return 0, 0
|
|
258
|
-
count_ok = 0
|
|
259
|
-
count_total = 0
|
|
260
|
-
for name in sorted(os.listdir(queue_dir)):
|
|
261
|
-
if not name.endswith('.zip'):
|
|
262
|
-
continue
|
|
263
|
-
zip_path = os.path.join(queue_dir, name)
|
|
264
|
-
count_total += 1
|
|
265
|
-
print("Attempting upload of queued report: {}".format(name))
|
|
266
|
-
ok = submit_support_bundle(zip_path)
|
|
267
|
-
if ok:
|
|
268
|
-
try:
|
|
269
|
-
os.remove(zip_path)
|
|
270
|
-
except Exception:
|
|
271
|
-
pass
|
|
272
|
-
count_ok += 1
|
|
273
|
-
return count_ok, count_total
|
|
274
|
-
|
|
275
|
-
|
|
276
194
|
def capture_unhandled_traceback(exc_type, exc_value, exc_traceback):
|
|
277
195
|
try:
|
|
278
196
|
config, _ = load_configuration()
|
|
@@ -288,3 +206,59 @@ def capture_unhandled_traceback(exc_type, exc_value, exc_traceback):
|
|
|
288
206
|
except Exception:
|
|
289
207
|
pass
|
|
290
208
|
|
|
209
|
+
def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
210
|
+
if not zip_path:
|
|
211
|
+
zip_path = collect_support_bundle(include_traceback)
|
|
212
|
+
if not zip_path:
|
|
213
|
+
mc_log("Failed to create bundle.", level="ERROR")
|
|
214
|
+
return False
|
|
215
|
+
bundle_size = os.path.getsize(zip_path)
|
|
216
|
+
if bundle_size > 1572864:
|
|
217
|
+
mc_log("Bundle too large ({} bytes) - discarding.".format(bundle_size), level="WARNING")
|
|
218
|
+
os.remove(zip_path)
|
|
219
|
+
return False
|
|
220
|
+
config, _ = load_configuration()
|
|
221
|
+
email_config = config.get('MediLink_Config', {}).get('error_reporting', {}).get('email', {})
|
|
222
|
+
if not email_config.get('enabled', False):
|
|
223
|
+
mc_log("Email reporting disabled.", level="INFO")
|
|
224
|
+
return False
|
|
225
|
+
to_emails = email_config.get('to', [])
|
|
226
|
+
subject_prefix = email_config.get('subject_prefix', 'MediCafe Error Report')
|
|
227
|
+
access_token = get_access_token()
|
|
228
|
+
if not access_token:
|
|
229
|
+
mc_log("No access token - authenticate first.", level="ERROR")
|
|
230
|
+
return False
|
|
231
|
+
mc_log("Building email...", level="INFO")
|
|
232
|
+
msg = MIMEMultipart()
|
|
233
|
+
msg['To'] = ', '.join(to_emails)
|
|
234
|
+
msg['Subject'] = '{} - {}'.format(subject_prefix, time.strftime('%Y%m%d_%H%M%S'))
|
|
235
|
+
with open(zip_path, 'rb') as f:
|
|
236
|
+
attach = MIMEApplication(f.read(), _subtype='zip')
|
|
237
|
+
attach.add_header('Content-Disposition', 'attachment', filename=os.path.basename(zip_path))
|
|
238
|
+
msg.attach(attach)
|
|
239
|
+
body = "Error report attached."
|
|
240
|
+
msg.attach(MIMEText(body, 'plain'))
|
|
241
|
+
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
|
|
242
|
+
mc_log("Sending report...", level="INFO")
|
|
243
|
+
headers = {'Authorization': 'Bearer {}'.format(access_token), 'Content-Type': 'application/json'}
|
|
244
|
+
data = {'raw': raw}
|
|
245
|
+
resp = requests.post('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', headers=headers, json=data)
|
|
246
|
+
if resp.status_code == 200:
|
|
247
|
+
mc_log("Report sent successfully!", level="INFO")
|
|
248
|
+
os.remove(zip_path)
|
|
249
|
+
return True
|
|
250
|
+
else:
|
|
251
|
+
mc_log("Failed to send: {} - {}".format(resp.status_code, resp.text), level="ERROR")
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
def email_error_report_flow():
|
|
255
|
+
try:
|
|
256
|
+
sent = submit_support_bundle_email(zip_path=None, include_traceback=True)
|
|
257
|
+
return 0 if sent else 1
|
|
258
|
+
except Exception as e:
|
|
259
|
+
mc_log("[ERROR] Exception during email report flow: {0}".format(e), level="ERROR")
|
|
260
|
+
return 1
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
raise SystemExit(email_error_report_flow())
|
|
264
|
+
|
MediLink/MediLink_Deductible.py
CHANGED
|
@@ -549,6 +549,7 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
549
549
|
# Enable verbose diagnostics in debug mode without config changes
|
|
550
550
|
diagnostics_verbose = True
|
|
551
551
|
sc_preflight_failed = False
|
|
552
|
+
# NOTE: No config flag mutation needed; OPTUMAI call now never auto-falls back
|
|
552
553
|
|
|
553
554
|
# Get legacy response
|
|
554
555
|
MediLink_ConfigLoader.log("Getting legacy get_eligibility_v3 API response", level="INFO")
|
|
@@ -569,8 +570,8 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
569
570
|
else:
|
|
570
571
|
MediLink_ConfigLoader.log("API client does not support token authentication for Legacy API.", level="WARNING")
|
|
571
572
|
|
|
572
|
-
# Get
|
|
573
|
-
MediLink_ConfigLoader.log("Getting
|
|
573
|
+
# Get OPTUMAI eligibility response for comparison (formerly Super Connector)
|
|
574
|
+
MediLink_ConfigLoader.log("Getting OPTUMAI eligibility API response", level="INFO")
|
|
574
575
|
super_connector_eligibility = None
|
|
575
576
|
try:
|
|
576
577
|
if not sc_preflight_failed:
|
|
@@ -580,32 +581,32 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
580
581
|
else:
|
|
581
582
|
super_connector_eligibility = None
|
|
582
583
|
except Exception as e:
|
|
583
|
-
MediLink_ConfigLoader.log("
|
|
584
|
+
MediLink_ConfigLoader.log("OPTUMAI eligibility API failed: {}".format(e), level="ERROR")
|
|
584
585
|
# Best-effort triage classification for clearer downstream messaging
|
|
585
586
|
try:
|
|
586
587
|
# Use centralized classifier when available
|
|
587
588
|
from MediCafe.deductible_utils import classify_api_failure
|
|
588
|
-
code, message = classify_api_failure(e, '
|
|
589
|
+
code, message = classify_api_failure(e, 'OPTUMAI eligibility API')
|
|
589
590
|
# Sticky preflight failure for subsequent patients in this run
|
|
590
591
|
if code in ['TIMEOUT', 'CONN_ERR', 'AUTH_FAIL', 'MISCONFIG']:
|
|
591
592
|
sc_preflight_failed = True
|
|
592
593
|
except Exception:
|
|
593
594
|
try:
|
|
594
|
-
failure_reason = "
|
|
595
|
+
failure_reason = "OPTUMAI eligibility API connection failed"
|
|
595
596
|
detail = str(e)
|
|
596
597
|
if requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.Timeout):
|
|
597
|
-
failure_reason = "
|
|
598
|
+
failure_reason = "OPTUMAI eligibility API timeout"
|
|
598
599
|
elif requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.ConnectionError):
|
|
599
|
-
failure_reason = "
|
|
600
|
+
failure_reason = "OPTUMAI eligibility API connection error"
|
|
600
601
|
elif "Invalid payer_id" in detail:
|
|
601
|
-
failure_reason = "
|
|
602
|
+
failure_reason = "OPTUMAI eligibility API rejected payer_id"
|
|
602
603
|
elif ("No access token" in detail) or ("token" in detail.lower()):
|
|
603
|
-
failure_reason = "
|
|
604
|
+
failure_reason = "OPTUMAI eligibility API authentication failed"
|
|
604
605
|
elif ("Eligibility endpoint not configured" in detail) or ("endpoint" in detail.lower() and "configured" in detail.lower()):
|
|
605
|
-
failure_reason = "
|
|
606
|
+
failure_reason = "OPTUMAI eligibility API endpoint misconfigured"
|
|
606
607
|
message = "{}: {}".format(failure_reason, detail)
|
|
607
608
|
except Exception:
|
|
608
|
-
message = "
|
|
609
|
+
message = "OPTUMAI eligibility API failed: {}".format(str(e))
|
|
609
610
|
sc_failure_info = {"message": message}
|
|
610
611
|
try:
|
|
611
612
|
error_messages_for_row.append(message)
|
|
@@ -629,22 +630,22 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
629
630
|
)
|
|
630
631
|
print("\nValidation report generated (legacy only): {}".format(validation_file_path))
|
|
631
632
|
elif super_connector_eligibility:
|
|
632
|
-
# Only
|
|
633
|
+
# Only OPTUMAI eligibility API returned data
|
|
633
634
|
validation_report = MediLink_Deductible_Validator.run_validation_comparison(
|
|
634
635
|
None, super_connector_eligibility, validation_file_path
|
|
635
636
|
)
|
|
636
|
-
print("\nValidation report generated (
|
|
637
|
+
print("\nValidation report generated (OPTUMAI only): {}".format(validation_file_path))
|
|
637
638
|
else:
|
|
638
639
|
# Neither API returned data
|
|
639
640
|
print("\nNo validation report generated - both APIs failed")
|
|
640
641
|
validation_file_path = None
|
|
641
642
|
|
|
642
|
-
# Log any
|
|
643
|
+
# Log any OPTUMAI eligibility API errors if we have that data
|
|
643
644
|
if super_connector_eligibility and "rawGraphQLResponse" in super_connector_eligibility:
|
|
644
645
|
raw_response = super_connector_eligibility.get('rawGraphQLResponse', {})
|
|
645
646
|
errors = raw_response.get('errors', [])
|
|
646
647
|
if errors:
|
|
647
|
-
error_msg = "
|
|
648
|
+
error_msg = "OPTUMAI eligibility API returned {} error(s):".format(len(errors))
|
|
648
649
|
if error_msg not in printed_messages:
|
|
649
650
|
print(error_msg)
|
|
650
651
|
printed_messages.add(error_msg)
|
|
@@ -703,15 +704,15 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
703
704
|
status_code = super_connector_eligibility.get('statuscode')
|
|
704
705
|
from MediCafe.deductible_utils import is_ok_200
|
|
705
706
|
if status_code is not None and not is_ok_200(status_code):
|
|
706
|
-
print("
|
|
707
|
+
print("OPTUMAI eligibility API status code: {} (non-200 indicates errors)".format(status_code))
|
|
707
708
|
# Record status code for the row diagnostics
|
|
708
|
-
error_messages_for_row.append("Status code {} from
|
|
709
|
+
error_messages_for_row.append("Status code {} from OPTUMAI eligibility".format(status_code))
|
|
709
710
|
# If Super Connector failed entirely, append a triage note to the validation report (if created)
|
|
710
711
|
try:
|
|
711
712
|
if sc_failure_info and validation_file_path and os.path.exists(validation_file_path):
|
|
712
713
|
with open(validation_file_path, 'a') as vf:
|
|
713
714
|
vf.write("\n" + "-" * 80 + "\n")
|
|
714
|
-
vf.write("
|
|
715
|
+
vf.write("OPTUMAI ELIGIBILITY CONNECTION FAILURE NOTE\n")
|
|
715
716
|
vf.write("-" * 80 + "\n")
|
|
716
717
|
vf.write(sc_failure_info['message'] + "\n")
|
|
717
718
|
vf.write("Recommendation: Verify network connectivity, credentials, payer ID validity, and endpoint configuration.\n")
|
|
@@ -741,7 +742,7 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
|
|
|
741
742
|
merged_data['error_messages'] = error_messages_for_row
|
|
742
743
|
except Exception:
|
|
743
744
|
pass
|
|
744
|
-
# Surface
|
|
745
|
+
# Surface OPTUMAI eligibility failure prominently in user-facing diagnostics
|
|
745
746
|
try:
|
|
746
747
|
if sc_failure_info and isinstance(merged_data, dict):
|
|
747
748
|
merged_data['super_connector_failed'] = True
|
|
@@ -943,7 +944,7 @@ if __name__ == "__main__":
|
|
|
943
944
|
print("- Crosswalk-based payer ID resolution (O(N) complexity)")
|
|
944
945
|
elif DEBUG_MODE:
|
|
945
946
|
print("\nRunning in DEBUG MODE")
|
|
946
|
-
print("- Dual API calls (Legacy +
|
|
947
|
+
print("- Dual API calls (Legacy + OPTUMAI eligibility)")
|
|
947
948
|
print("- Validation reports and comparisons")
|
|
948
949
|
print("- Detailed logging and error reporting")
|
|
949
950
|
print("- Crosswalk-based payer ID resolution (O(N) complexity)")
|
|
@@ -1193,11 +1194,11 @@ if __name__ == "__main__":
|
|
|
1193
1194
|
output_file.write(table_header + "\n")
|
|
1194
1195
|
output_file.write("-" * len(table_header) + "\n")
|
|
1195
1196
|
|
|
1196
|
-
# Global notice when
|
|
1197
|
+
# Global notice when OPTUMAI eligibility connection failed for any patients
|
|
1197
1198
|
try:
|
|
1198
1199
|
sc_failed_count = sum(1 for r in eligibility_results if isinstance(r, dict) and r.get('super_connector_failed'))
|
|
1199
1200
|
if sc_failed_count:
|
|
1200
|
-
output_file.write("NOTICE:
|
|
1201
|
+
output_file.write("NOTICE: OPTUMAI eligibility API connection failed for {} patient(s). Fallback data used when available.\n".format(sc_failed_count))
|
|
1201
1202
|
except Exception:
|
|
1202
1203
|
pass
|
|
1203
1204
|
|
|
@@ -1260,7 +1261,7 @@ if __name__ == "__main__":
|
|
|
1260
1261
|
else:
|
|
1261
1262
|
print("No validation reports were generated.")
|
|
1262
1263
|
print("This may be because:")
|
|
1263
|
-
print(" -
|
|
1264
|
+
print(" - OPTUMAI eligibility API calls failed")
|
|
1264
1265
|
print(" - Both APIs didn't return data for the same patients")
|
|
1265
1266
|
print(" - Validation report generation encountered errors")
|
|
1266
1267
|
print("=" * 80)
|
MediLink/MediLink_Gmail.py
CHANGED
|
@@ -84,6 +84,7 @@ SCOPES = ' '.join([
|
|
|
84
84
|
'https://www.googleapis.com/auth/gmail.modify',
|
|
85
85
|
'https://www.googleapis.com/auth/gmail.compose',
|
|
86
86
|
'https://www.googleapis.com/auth/gmail.readonly',
|
|
87
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
87
88
|
'https://www.googleapis.com/auth/script.external_request',
|
|
88
89
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
89
90
|
'https://www.googleapis.com/auth/script.scriptapp',
|