medicafe 0.251027.2__py3-none-any.whl → 0.251027.3__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/error_reporter.py +259 -108
- MediLink/MediLink_Gmail.py +65 -0
- MediLink/__init__.py +1 -1
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/METADATA +1 -1
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/RECORD +11 -11
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/LICENSE +0 -0
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/WHEEL +0 -0
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/entry_points.txt +0 -0
- {medicafe-0.251027.2.dist-info → medicafe-0.251027.3.dist-info}/top_level.txt +0 -0
MediBot/__init__.py
CHANGED
MediCafe/__init__.py
CHANGED
MediCafe/error_reporter.py
CHANGED
|
@@ -99,72 +99,106 @@ def _ensure_dir(path):
|
|
|
99
99
|
return False
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
# Resolve a writable queue directory with fallback to ./reports_queue
|
|
103
|
+
def _resolve_queue_dir(medi_config):
|
|
104
|
+
try:
|
|
105
|
+
local_storage_path = medi_config.get('local_storage_path', '.') if isinstance(medi_config, dict) else '.'
|
|
106
|
+
except Exception:
|
|
107
|
+
local_storage_path = '.'
|
|
108
|
+
primary = os.path.join(local_storage_path, 'reports_queue')
|
|
109
|
+
if _ensure_dir(primary):
|
|
110
|
+
return primary
|
|
111
|
+
fallback = os.path.join('.', 'reports_queue')
|
|
112
|
+
if _ensure_dir(fallback):
|
|
113
|
+
try:
|
|
114
|
+
mc_log("Queue directory fallback to ./reports_queue due to path/permission issue.", level="WARNING")
|
|
115
|
+
print("Falling back to ./reports_queue for support bundles (check local_storage_path permissions).")
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
return fallback
|
|
119
|
+
return primary
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _build_support_zip(zip_path, local_storage_path, max_log_lines, traceback_text, include_winscp, meta):
|
|
123
|
+
latest_log = _get_latest_log_path(local_storage_path)
|
|
124
|
+
log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
|
|
125
|
+
log_tail = _redact(log_tail)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
|
|
129
|
+
z.writestr('meta.json', json.dumps(meta, ensure_ascii=True, indent=2))
|
|
130
|
+
if latest_log and log_tail:
|
|
131
|
+
z.writestr('log_tail.txt', log_tail)
|
|
132
|
+
if traceback_text:
|
|
133
|
+
z.writestr('traceback.txt', _redact(traceback_text))
|
|
134
|
+
if include_winscp:
|
|
135
|
+
upload_log = os.path.join(local_storage_path, 'winscp_upload.log')
|
|
136
|
+
download_log = os.path.join(local_storage_path, 'winscp_download.log')
|
|
137
|
+
winscp_logs = [(p, os.path.getmtime(p)) for p in [upload_log, download_log] if os.path.exists(p)]
|
|
138
|
+
if winscp_logs:
|
|
139
|
+
latest_winscp = max(winscp_logs, key=lambda x: x[1])[0]
|
|
140
|
+
winscp_tail = _tail_file(latest_winscp, max_log_lines) if latest_winscp else ''
|
|
141
|
+
winscp_tail = _redact(winscp_tail)
|
|
142
|
+
if winscp_tail:
|
|
143
|
+
z.writestr('winscp_log_tail.txt', winscp_tail)
|
|
144
|
+
return True
|
|
145
|
+
except Exception as e:
|
|
146
|
+
mc_log('Error creating support bundle at {}: {}'.format(zip_path, e), level='ERROR')
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Centralized self-healing helpers for email reporting
|
|
150
|
+
def _attempt_gmail_reauth_interactive(max_wait_seconds=120):
|
|
151
|
+
"""
|
|
152
|
+
Delegate to MediLink.MediLink_Gmail.ensure_authenticated_for_gmail_send to
|
|
153
|
+
reuse existing OAuth/server logic without duplicating implementations.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
from MediLink.MediLink_Gmail import ensure_authenticated_for_gmail_send
|
|
157
|
+
return bool(ensure_authenticated_for_gmail_send(max_wait_seconds=max_wait_seconds))
|
|
158
|
+
except Exception as e:
|
|
159
|
+
try:
|
|
160
|
+
mc_log("Failed to initiate Gmail re-authorization: {0}".format(e), level="ERROR")
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
return False
|
|
164
|
+
|
|
102
165
|
# _compute_report_id removed (unused)
|
|
103
166
|
|
|
104
167
|
|
|
105
168
|
def collect_support_bundle(include_traceback=True, max_log_lines=500):
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
_ensure_dir(queue_dir)
|
|
111
|
-
|
|
112
|
-
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
113
|
-
bundle_name = 'support_report_{}.zip'.format(stamp)
|
|
114
|
-
zip_path = os.path.join(queue_dir, bundle_name)
|
|
115
|
-
|
|
116
|
-
latest_log = _get_latest_log_path(local_storage_path)
|
|
117
|
-
log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
|
|
118
|
-
log_tail = _redact(log_tail)
|
|
119
|
-
|
|
120
|
-
traceback_txt = ''
|
|
121
|
-
if include_traceback:
|
|
122
|
-
try:
|
|
123
|
-
trace_path = os.path.join(local_storage_path, 'traceback.txt')
|
|
124
|
-
if os.path.exists(trace_path):
|
|
125
|
-
with open(trace_path, 'r') as tf:
|
|
126
|
-
traceback_txt = _redact(tf.read())
|
|
127
|
-
except Exception:
|
|
128
|
-
traceback_txt = ''
|
|
129
|
-
|
|
130
|
-
meta = {
|
|
131
|
-
'app_version': _safe_ascii(_get_version()),
|
|
132
|
-
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
133
|
-
'platform': _safe_ascii(platform.platform()),
|
|
134
|
-
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
135
|
-
'error_summary': _safe_ascii(_first_line(traceback_txt)),
|
|
136
|
-
'traceback_present': bool(traceback_txt),
|
|
137
|
-
'config_flags': {
|
|
138
|
-
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
139
|
-
'test_mode': bool(medi.get('TestMode', False))
|
|
140
|
-
}
|
|
141
|
-
}
|
|
169
|
+
config, _ = load_configuration()
|
|
170
|
+
medi = config.get('MediLink_Config', {})
|
|
171
|
+
local_storage_path = medi.get('local_storage_path', '.')
|
|
172
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
142
173
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
175
|
+
zip_path = os.path.join(queue_dir, 'support_report_{}.zip'.format(stamp))
|
|
176
|
+
|
|
177
|
+
traceback_txt = ''
|
|
178
|
+
if include_traceback:
|
|
179
|
+
try:
|
|
180
|
+
trace_path = os.path.join(local_storage_path, 'traceback.txt')
|
|
181
|
+
if os.path.exists(trace_path):
|
|
182
|
+
with open(trace_path, 'r') as tf:
|
|
183
|
+
traceback_txt = tf.read()
|
|
184
|
+
except Exception:
|
|
185
|
+
traceback_txt = ''
|
|
186
|
+
|
|
187
|
+
meta = {
|
|
188
|
+
'app_version': _safe_ascii(_get_version()),
|
|
189
|
+
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
190
|
+
'platform': _safe_ascii(platform.platform()),
|
|
191
|
+
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
192
|
+
'error_summary': _safe_ascii(_first_line(traceback_txt)),
|
|
193
|
+
'traceback_present': bool(traceback_txt),
|
|
194
|
+
'config_flags': {
|
|
195
|
+
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
196
|
+
'test_mode': bool(medi.get('TestMode', False))
|
|
197
|
+
}
|
|
198
|
+
}
|
|
165
199
|
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, traceback_txt, True, meta)
|
|
201
|
+
return zip_path if ok else None
|
|
168
202
|
|
|
169
203
|
|
|
170
204
|
def collect_test_support_bundle(max_log_lines=500):
|
|
@@ -179,17 +213,10 @@ def collect_test_support_bundle(max_log_lines=500):
|
|
|
179
213
|
config, _ = load_configuration()
|
|
180
214
|
medi = config.get('MediLink_Config', {})
|
|
181
215
|
local_storage_path = medi.get('local_storage_path', '.')
|
|
182
|
-
queue_dir =
|
|
183
|
-
_ensure_dir(queue_dir)
|
|
216
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
184
217
|
|
|
185
218
|
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
186
|
-
|
|
187
|
-
zip_path = os.path.join(queue_dir, bundle_name)
|
|
188
|
-
|
|
189
|
-
# Prepare components
|
|
190
|
-
latest_log = _get_latest_log_path(local_storage_path)
|
|
191
|
-
log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
|
|
192
|
-
log_tail = _redact(log_tail)
|
|
219
|
+
zip_path = os.path.join(queue_dir, 'support_report_TEST_{}.zip'.format(stamp))
|
|
193
220
|
|
|
194
221
|
# Build a placeholder traceback - ASCII-only, no real data
|
|
195
222
|
fake_tb = (
|
|
@@ -199,8 +226,6 @@ def collect_test_support_bundle(max_log_lines=500):
|
|
|
199
226
|
"Exception: This is a TEST placeholder traceback for pipeline verification only.\n"
|
|
200
227
|
"-- No real patient or PHI data is included. --\n"
|
|
201
228
|
)
|
|
202
|
-
fake_tb = _redact(fake_tb)
|
|
203
|
-
|
|
204
229
|
meta = {
|
|
205
230
|
'app_version': _safe_ascii(_get_version()),
|
|
206
231
|
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
@@ -213,25 +238,8 @@ def collect_test_support_bundle(max_log_lines=500):
|
|
|
213
238
|
'test_mode': True
|
|
214
239
|
}
|
|
215
240
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
z.writestr('meta.json', json.dumps(meta, ensure_ascii=True, indent=2))
|
|
219
|
-
if latest_log and log_tail:
|
|
220
|
-
z.writestr('log_tail.txt', log_tail)
|
|
221
|
-
# Always include the fake traceback for this test bundle
|
|
222
|
-
z.writestr('traceback.txt', fake_tb)
|
|
223
|
-
# Include WinSCP tail if present
|
|
224
|
-
upload_log = os.path.join(local_storage_path, 'winscp_upload.log')
|
|
225
|
-
download_log = os.path.join(local_storage_path, 'winscp_download.log')
|
|
226
|
-
winscp_logs = [(p, os.path.getmtime(p)) for p in [upload_log, download_log] if os.path.exists(p)]
|
|
227
|
-
if winscp_logs:
|
|
228
|
-
latest_winscp = max(winscp_logs, key=lambda x: x[1])[0]
|
|
229
|
-
winscp_tail = _tail_file(latest_winscp, max_log_lines) if latest_winscp else ''
|
|
230
|
-
winscp_tail = _redact(winscp_tail)
|
|
231
|
-
if winscp_tail:
|
|
232
|
-
z.writestr('winscp_log_tail.txt', winscp_tail)
|
|
233
|
-
|
|
234
|
-
return zip_path
|
|
241
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, fake_tb, True, meta)
|
|
242
|
+
return zip_path if ok else None
|
|
235
243
|
except Exception as e:
|
|
236
244
|
try:
|
|
237
245
|
mc_log('Error creating TEST support bundle: {}'.format(e), level='ERROR')
|
|
@@ -281,6 +289,10 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
281
289
|
zip_path = collect_support_bundle(include_traceback)
|
|
282
290
|
if not zip_path:
|
|
283
291
|
mc_log("Failed to create bundle.", level="ERROR")
|
|
292
|
+
try:
|
|
293
|
+
print("Failed to create support bundle. Ensure 'MediLink_Config.local_storage_path' is writable and logs exist.")
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
284
296
|
return False
|
|
285
297
|
bundle_size = os.path.getsize(zip_path)
|
|
286
298
|
config, _ = load_configuration()
|
|
@@ -292,18 +304,56 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
292
304
|
max_bytes = 1572864
|
|
293
305
|
if bundle_size > max_bytes:
|
|
294
306
|
mc_log("Bundle too large ({} bytes > {} bytes) - leaving in queue.".format(bundle_size, max_bytes), level="WARNING")
|
|
307
|
+
try:
|
|
308
|
+
print("Bundle too large to email ({} KB > {} KB). Left in 'reports_queue'. Path: {}".format(int(bundle_size/1024), int(max_bytes/1024), zip_path))
|
|
309
|
+
print("Attempting to create and send a smaller LITE bundle (reduced logs, no traceback).")
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
# Attempt a smaller bundle automatically
|
|
313
|
+
lite_zip = collect_support_bundle_lite(max_log_lines=200)
|
|
314
|
+
if lite_zip and os.path.exists(lite_zip):
|
|
315
|
+
try:
|
|
316
|
+
lite_size = os.path.getsize(lite_zip)
|
|
317
|
+
except Exception:
|
|
318
|
+
lite_size = -1
|
|
319
|
+
if 0 < lite_size <= max_bytes:
|
|
320
|
+
return submit_support_bundle_email(zip_path=lite_zip, include_traceback=False)
|
|
321
|
+
else:
|
|
322
|
+
try:
|
|
323
|
+
print("LITE bundle is still too large ({} KB).".format(int(max(lite_size, 0)/1024)))
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
try:
|
|
327
|
+
print("Tip: Reduce log size or increase 'MediLink_Config.error_reporting.email.max_bundle_bytes'.")
|
|
328
|
+
except Exception:
|
|
329
|
+
pass
|
|
295
330
|
return False
|
|
296
331
|
# Feature is always available; proceed if recipients and token are available
|
|
297
332
|
# Normalize and validate recipients
|
|
298
333
|
to_emails = _normalize_recipients(email_config.get('to', []))
|
|
299
334
|
if not to_emails:
|
|
300
335
|
mc_log("No valid recipients configured in error_reporting.email.to", level="ERROR")
|
|
336
|
+
try:
|
|
337
|
+
print("No recipients configured. Set 'MediLink_Config.error_reporting.email.to' to one or more email addresses.")
|
|
338
|
+
except Exception:
|
|
339
|
+
pass
|
|
301
340
|
return False
|
|
302
341
|
subject_prefix = email_config.get('subject_prefix', 'MediCafe Error Report')
|
|
303
342
|
access_token = get_access_token()
|
|
304
343
|
if not access_token:
|
|
305
|
-
mc_log("No access token -
|
|
306
|
-
|
|
344
|
+
mc_log("No access token - attempting Gmail re-authorization.", level="ERROR")
|
|
345
|
+
try:
|
|
346
|
+
print("No Gmail token found. Starting re-authorization...")
|
|
347
|
+
except Exception:
|
|
348
|
+
pass
|
|
349
|
+
if _attempt_gmail_reauth_interactive():
|
|
350
|
+
access_token = get_access_token()
|
|
351
|
+
if not access_token:
|
|
352
|
+
try:
|
|
353
|
+
print("Authentication incomplete. Please finish Gmail consent, then retry.")
|
|
354
|
+
except Exception:
|
|
355
|
+
pass
|
|
356
|
+
return False
|
|
307
357
|
mc_log("Building email...", level="INFO")
|
|
308
358
|
msg = MIMEMultipart()
|
|
309
359
|
msg['To'] = ', '.join(to_emails)
|
|
@@ -318,7 +368,16 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
318
368
|
mc_log("Sending report...", level="INFO")
|
|
319
369
|
headers = {'Authorization': 'Bearer {}'.format(access_token), 'Content-Type': 'application/json'}
|
|
320
370
|
data = {'raw': raw}
|
|
321
|
-
|
|
371
|
+
try:
|
|
372
|
+
resp = requests.post('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', headers=headers, json=data)
|
|
373
|
+
except Exception as e:
|
|
374
|
+
mc_log("Network error during Gmail send: {0}".format(e), level="ERROR")
|
|
375
|
+
try:
|
|
376
|
+
print("Network error while sending report: {0}".format(e))
|
|
377
|
+
print("Check internet connectivity or proxy settings and retry.")
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
return False
|
|
322
381
|
if resp.status_code == 200:
|
|
323
382
|
mc_log("Report sent successfully!", level="INFO")
|
|
324
383
|
try:
|
|
@@ -327,20 +386,62 @@ def submit_support_bundle_email(zip_path=None, include_traceback=True):
|
|
|
327
386
|
pass
|
|
328
387
|
return True
|
|
329
388
|
else:
|
|
330
|
-
# Handle auth errors by prompting re-consent using existing OAuth helpers
|
|
389
|
+
# Handle auth errors by prompting re-consent using existing OAuth helpers, then retry once
|
|
331
390
|
if resp.status_code in (401, 403):
|
|
391
|
+
mc_log("Gmail send unauthorized ({}). Attempting re-authorization and retry.".format(resp.status_code), level="WARNING")
|
|
332
392
|
try:
|
|
333
|
-
|
|
334
|
-
auth_url = get_authorization_url()
|
|
335
|
-
mc_log("Gmail send unauthorized ({}). Opening browser for re-consent.".format(resp.status_code), level="WARNING")
|
|
336
|
-
try:
|
|
337
|
-
print("Your Google session needs to be refreshed to gain gmail.send permission. A browser will open to re-authorize.")
|
|
338
|
-
except Exception:
|
|
339
|
-
pass
|
|
340
|
-
open_browser_with_executable(auth_url)
|
|
393
|
+
print("Gmail permission issue detected ({}). Starting re-authorization...".format(resp.status_code))
|
|
341
394
|
except Exception:
|
|
342
395
|
pass
|
|
396
|
+
if _attempt_gmail_reauth_interactive():
|
|
397
|
+
new_token = get_access_token()
|
|
398
|
+
if new_token:
|
|
399
|
+
headers['Authorization'] = 'Bearer {}'.format(new_token)
|
|
400
|
+
try:
|
|
401
|
+
resp = requests.post('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', headers=headers, json=data)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
mc_log("Network error on retry: {0}".format(e), level="ERROR")
|
|
404
|
+
try:
|
|
405
|
+
print("Network error on retry: {0}".format(e))
|
|
406
|
+
except Exception:
|
|
407
|
+
pass
|
|
408
|
+
return False
|
|
409
|
+
if resp.status_code == 200:
|
|
410
|
+
mc_log("Report sent successfully after re-authorization!", level="INFO")
|
|
411
|
+
try:
|
|
412
|
+
os.remove(zip_path)
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
return True
|
|
416
|
+
# Map common Gmail errors to actionable hints
|
|
417
|
+
hint = ''
|
|
418
|
+
try:
|
|
419
|
+
body = resp.json()
|
|
420
|
+
err = body.get('error', {}) if isinstance(body, dict) else {}
|
|
421
|
+
status = (err.get('status') or '').upper()
|
|
422
|
+
message = err.get('message') or ''
|
|
423
|
+
reasons = ','.join([e.get('reason') for e in err.get('errors', []) if isinstance(e, dict) and e.get('reason')])
|
|
424
|
+
if 'RATELIMIT' in status or 'rateLimitExceeded' in reasons:
|
|
425
|
+
hint = 'Quota exceeded. Wait and retry later.'
|
|
426
|
+
elif 'DAILY' in status or 'dailyLimitExceeded' in reasons:
|
|
427
|
+
hint = 'Daily quota exceeded. Try again tomorrow.'
|
|
428
|
+
elif status in ('PERMISSION_DENIED', 'FORBIDDEN'):
|
|
429
|
+
hint = 'Permissions insufficient. Re-authorize Gmail with required scopes.'
|
|
430
|
+
elif status == 'INVALID_ARGUMENT':
|
|
431
|
+
hint = 'Invalid request. Check recipient emails and attachment size.'
|
|
432
|
+
elif status == 'UNAUTHENTICATED':
|
|
433
|
+
hint = 'Authentication required. Re-authorize and retry.'
|
|
434
|
+
except Exception:
|
|
435
|
+
pass
|
|
343
436
|
mc_log("Failed to send: {} - {}".format(resp.status_code, _redact(resp.text)), level="ERROR")
|
|
437
|
+
try:
|
|
438
|
+
base_msg = "Failed to send report: HTTP {}.".format(resp.status_code)
|
|
439
|
+
if hint:
|
|
440
|
+
base_msg += " Hint: {}".format(hint)
|
|
441
|
+
print(base_msg)
|
|
442
|
+
print("The bundle remains in 'reports_queue'. See latest log for details.")
|
|
443
|
+
except Exception:
|
|
444
|
+
pass
|
|
344
445
|
# Preserve bundle in queue for manual retry
|
|
345
446
|
return False
|
|
346
447
|
|
|
@@ -349,7 +450,8 @@ def _normalize_recipients(to_field):
|
|
|
349
450
|
try:
|
|
350
451
|
# Flatten to a list of strings
|
|
351
452
|
if isinstance(to_field, str):
|
|
352
|
-
|
|
453
|
+
separators_normalized = to_field.replace(';', ',').replace('\n', ',').replace('\r', ',')
|
|
454
|
+
candidates = [p.strip() for p in separators_normalized.split(',')]
|
|
353
455
|
elif isinstance(to_field, list):
|
|
354
456
|
candidates = [str(p).strip() for p in to_field]
|
|
355
457
|
else:
|
|
@@ -373,16 +475,48 @@ def _normalize_recipients(to_field):
|
|
|
373
475
|
return []
|
|
374
476
|
|
|
375
477
|
|
|
478
|
+
def collect_support_bundle_lite(max_log_lines=200):
|
|
479
|
+
"""Create a smaller 'lite' support bundle with reduced logs and no traceback/winscp logs."""
|
|
480
|
+
try:
|
|
481
|
+
config, _ = load_configuration()
|
|
482
|
+
medi = config.get('MediLink_Config', {})
|
|
483
|
+
local_storage_path = medi.get('local_storage_path', '.')
|
|
484
|
+
queue_dir = _resolve_queue_dir(medi)
|
|
485
|
+
stamp = time.strftime('%Y%m%d_%H%M%S')
|
|
486
|
+
zip_path = os.path.join(queue_dir, 'support_report_LITE_{}.zip'.format(stamp))
|
|
487
|
+
meta = {
|
|
488
|
+
'app_version': _safe_ascii(_get_version()),
|
|
489
|
+
'python_version': _safe_ascii(sys.version.split(' ')[0]),
|
|
490
|
+
'platform': _safe_ascii(platform.platform()),
|
|
491
|
+
'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
492
|
+
'error_summary': 'LITE: No traceback included',
|
|
493
|
+
'traceback_present': False,
|
|
494
|
+
'config_flags': {
|
|
495
|
+
'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
|
|
496
|
+
'test_mode': bool(medi.get('TestMode', False))
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
ok = _build_support_zip(zip_path, local_storage_path, max_log_lines, None, False, meta)
|
|
500
|
+
return zip_path if ok else None
|
|
501
|
+
except Exception:
|
|
502
|
+
return None
|
|
503
|
+
|
|
504
|
+
|
|
376
505
|
def list_queued_bundles():
|
|
377
506
|
try:
|
|
378
507
|
config, _ = load_configuration()
|
|
379
508
|
medi = config.get('MediLink_Config', {})
|
|
380
509
|
local_storage_path = medi.get('local_storage_path', '.')
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
510
|
+
primary = os.path.join(local_storage_path, 'reports_queue')
|
|
511
|
+
fallback = os.path.join('.', 'reports_queue')
|
|
512
|
+
files = []
|
|
513
|
+
for q in (primary, fallback):
|
|
514
|
+
try:
|
|
515
|
+
if os.path.isdir(q):
|
|
516
|
+
files.extend([os.path.join(q, f) for f in os.listdir(q) if f.endswith('.zip')])
|
|
517
|
+
except Exception:
|
|
518
|
+
pass
|
|
519
|
+
files = sorted(set(files))
|
|
386
520
|
return files
|
|
387
521
|
except Exception:
|
|
388
522
|
return []
|
|
@@ -426,6 +560,10 @@ def email_error_report_flow():
|
|
|
426
560
|
return 0 if sent else 1
|
|
427
561
|
except Exception as e:
|
|
428
562
|
mc_log("[ERROR] Exception during email report flow: {0}".format(e), level="ERROR")
|
|
563
|
+
try:
|
|
564
|
+
print("Unexpected error while sending error report: {0}".format(e))
|
|
565
|
+
except Exception:
|
|
566
|
+
pass
|
|
429
567
|
return 1
|
|
430
568
|
|
|
431
569
|
def email_test_error_report_flow():
|
|
@@ -437,14 +575,27 @@ def email_test_error_report_flow():
|
|
|
437
575
|
try:
|
|
438
576
|
zip_path = collect_test_support_bundle()
|
|
439
577
|
if not zip_path:
|
|
578
|
+
try:
|
|
579
|
+
print("Failed to create TEST support bundle. Ensure 'MediLink_Config.local_storage_path' is writable.")
|
|
580
|
+
except Exception:
|
|
581
|
+
pass
|
|
440
582
|
return 1
|
|
441
583
|
sent = submit_support_bundle_email(zip_path=zip_path, include_traceback=False)
|
|
584
|
+
if not sent:
|
|
585
|
+
try:
|
|
586
|
+
print("TEST error report was not sent. See messages above for the reason. The ZIP remains queued if creation succeeded.")
|
|
587
|
+
except Exception:
|
|
588
|
+
pass
|
|
442
589
|
return 0 if sent else 1
|
|
443
590
|
except Exception as e:
|
|
444
591
|
try:
|
|
445
592
|
mc_log("[ERROR] Exception during test email report flow: {0}".format(e), level="ERROR")
|
|
446
593
|
except Exception:
|
|
447
594
|
pass
|
|
595
|
+
try:
|
|
596
|
+
print("Unexpected error during TEST report flow: {0}".format(e))
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
448
599
|
return 1
|
|
449
600
|
|
|
450
601
|
if __name__ == "__main__":
|
MediLink/MediLink_Gmail.py
CHANGED
|
@@ -594,6 +594,71 @@ def clear_token_cache():
|
|
|
594
594
|
def check_invalid_grant_causes(auth_code):
|
|
595
595
|
log("FUTURE IMPLEMENTATION: Checking common causes for invalid_grant error with auth code: {}".format(auth_code))
|
|
596
596
|
|
|
597
|
+
|
|
598
|
+
def ensure_authenticated_for_gmail_send(max_wait_seconds=120):
|
|
599
|
+
"""Ensure a valid Gmail access token is available for sending.
|
|
600
|
+
|
|
601
|
+
- Reuses existing OAuth helpers in this module.
|
|
602
|
+
- Starts the local HTTPS server if needed, opens the browser for consent,
|
|
603
|
+
and polls for a token for up to max_wait_seconds.
|
|
604
|
+
- Returns True if a usable access token is available after the flow; otherwise False.
|
|
605
|
+
"""
|
|
606
|
+
try:
|
|
607
|
+
token = get_access_token()
|
|
608
|
+
except Exception:
|
|
609
|
+
token = None
|
|
610
|
+
if token:
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
# Prepare server and certificates
|
|
614
|
+
try:
|
|
615
|
+
generate_self_signed_cert(cert_file, key_file)
|
|
616
|
+
except Exception as e:
|
|
617
|
+
log("Warning: could not ensure self-signed certs: {}".format(e))
|
|
618
|
+
|
|
619
|
+
server_started_here = False
|
|
620
|
+
global httpd
|
|
621
|
+
try:
|
|
622
|
+
if httpd is None:
|
|
623
|
+
log("Starting local HTTPS server for OAuth redirect handling.")
|
|
624
|
+
server_thread = Thread(target=run_server)
|
|
625
|
+
server_thread.daemon = True
|
|
626
|
+
server_thread.start()
|
|
627
|
+
server_started_here = True
|
|
628
|
+
time.sleep(0.5)
|
|
629
|
+
except Exception as e:
|
|
630
|
+
log("Failed to start OAuth local server: {}".format(e))
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
auth_url = get_authorization_url()
|
|
634
|
+
print("Opening browser to authorize Gmail permission for sending...")
|
|
635
|
+
open_browser_with_executable(auth_url)
|
|
636
|
+
except Exception as e:
|
|
637
|
+
log("Failed to open authorization URL: {}".format(e))
|
|
638
|
+
|
|
639
|
+
# Poll for token availability within timeout
|
|
640
|
+
start_ts = time.time()
|
|
641
|
+
token = None
|
|
642
|
+
while time.time() - start_ts < max_wait_seconds:
|
|
643
|
+
try:
|
|
644
|
+
token = get_access_token()
|
|
645
|
+
except Exception:
|
|
646
|
+
token = None
|
|
647
|
+
if token:
|
|
648
|
+
break
|
|
649
|
+
time.sleep(3)
|
|
650
|
+
|
|
651
|
+
if server_started_here:
|
|
652
|
+
try:
|
|
653
|
+
stop_server()
|
|
654
|
+
except Exception:
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
if not token:
|
|
658
|
+
print("Gmail authorization not completed within timeout. Please finish consent and retry.")
|
|
659
|
+
|
|
660
|
+
return bool(token)
|
|
661
|
+
|
|
597
662
|
if __name__ == "__main__":
|
|
598
663
|
signal.signal(signal.SIGINT, signal_handler)
|
|
599
664
|
signal.signal(signal.SIGTERM, signal_handler)
|
MediLink/__init__.py
CHANGED
|
@@ -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=W7JANBDt-VBMAzdtL-KXgJ3-ODNLji-8stnIn38LUIQ,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,14 +22,14 @@ 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=heTbZ0ItwlpxqbAb0oV2dbTFRSTMnZyhBT9fS8nI0YU,12735
|
|
25
|
-
MediCafe/__init__.py,sha256=
|
|
25
|
+
MediCafe/__init__.py,sha256=56u8QPxfilu-bGcU5MF8kleMtvRMpsOVEQ1VKKlZai4,5721
|
|
26
26
|
MediCafe/__main__.py,sha256=NZMgsjuXYF39YS7mCWMjTO0vYf_0IAotQpNyUXfbahs,13233
|
|
27
27
|
MediCafe/api_core.py,sha256=wLAdRNZdmovKReXvzsmAgKrbYon4-wbJbGCyOm_C3AU,89896
|
|
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
31
|
MediCafe/deductible_utils.py,sha256=-ixDYwI3JNAyACrFjKqoX_hD3Awzownq441U0PSrwXw,64932
|
|
32
|
-
MediCafe/error_reporter.py,sha256=
|
|
32
|
+
MediCafe/error_reporter.py,sha256=421z3lYMlHD0p-OIJSDyvKWRYsImVJKvm7nG0s5drFc,23848
|
|
33
33
|
MediCafe/graphql_utils.py,sha256=jo4CboMb9i5_qD0jkfrLbL87_Q3aFiwOntZhjF9fMsI,51928
|
|
34
34
|
MediCafe/logging_config.py,sha256=auT65LN5oDEXVhkMeLke63kJHTWxYf2o8YihAfQFgzU,5493
|
|
35
35
|
MediCafe/logging_demo.py,sha256=TwUhzafna5pMdN3zSKGrpUWRqX96F1JGGsSUtr3dygs,1975
|
|
@@ -51,7 +51,7 @@ MediLink/MediLink_Deductible.py,sha256=opGa5YQ6tfowurlf8xDWRtAtQMmoNYout0gYe3R5f
|
|
|
51
51
|
MediLink/MediLink_Deductible_Validator.py,sha256=x6tHJOi88TblUpDPSH6QhIdXXRgr3rXI7kYPVGZYCgU,24998
|
|
52
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=Gnc-tI8ACeFaw0YVPp5JUBAXhwYfptY8QGxShJu4Pbg,31476
|
|
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=1nmDtbJ9IfPwLnFhCZvDtBPSG_8gzj6LneuoV60l4IQ,26736
|
|
64
64
|
MediLink/MediLink_smart_import.py,sha256=ZUXvAkIA2Pk2uuyLZazKfKK8YGdkZt1VAeZo_ZSUyxk,9942
|
|
65
65
|
MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
|
|
66
|
-
MediLink/__init__.py,sha256=
|
|
66
|
+
MediLink/__init__.py,sha256=I6BiRTvpQ5QhK_7dLbb_4VFtyYWZf1nH2dLX1OYtgr0,3888
|
|
67
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
71
|
MediLink/webapp.html,sha256=DwDYjVvluGJ7eDdvEogfKN4t24ZJRoIUuSBfCYCL-3w,21252
|
|
72
|
-
medicafe-0.251027.
|
|
73
|
-
medicafe-0.251027.
|
|
74
|
-
medicafe-0.251027.
|
|
75
|
-
medicafe-0.251027.
|
|
76
|
-
medicafe-0.251027.
|
|
77
|
-
medicafe-0.251027.
|
|
72
|
+
medicafe-0.251027.3.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
|
|
73
|
+
medicafe-0.251027.3.dist-info/METADATA,sha256=xsE3H4MHme5SYio00FfRVaSBO51wYxwQIC190GL8_7Y,3414
|
|
74
|
+
medicafe-0.251027.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
75
|
+
medicafe-0.251027.3.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
|
|
76
|
+
medicafe-0.251027.3.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
|
|
77
|
+
medicafe-0.251027.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|