medicafe 0.250909.0__py3-none-any.whl → 0.250920.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.
MediBot/__init__.py CHANGED
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.250909.0"
22
+ __version__ = "0.250920.0"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25
 
@@ -33,6 +33,13 @@ def get_default_config():
33
33
  'level': 'INFO',
34
34
  'console_output': True
35
35
  },
36
+ 'error_reporting': {
37
+ 'enabled': False,
38
+ 'endpoint_url': '',
39
+ 'auth_token': '',
40
+ 'insecure_http': False,
41
+ 'max_bundle_bytes': 2097152
42
+ },
36
43
  # STRATEGIC NOTE (COB Configuration): COB library is fully implemented and ready
37
44
  # To enable COB functionality, add the following configuration:
38
45
  # 'cob_settings': {
MediCafe/__init__.py CHANGED
@@ -27,7 +27,7 @@ Smart Import System:
27
27
  api_suite = get_api_access()
28
28
  """
29
29
 
30
- __version__ = "0.250909.0"
30
+ __version__ = "0.250920.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33
 
@@ -0,0 +1,287 @@
1
+ import os, sys, time, json, zipfile, hashlib, platform
2
+
3
+ try:
4
+ import requests
5
+ except Exception:
6
+ requests = None
7
+
8
+ from MediCafe.MediLink_ConfigLoader import load_configuration, log as mc_log
9
+
10
+
11
+ ASCII_SAFE_REPLACEMENTS = [
12
+ ('"', '"'),
13
+ ("'", "'"),
14
+ ]
15
+
16
+
17
+ def _safe_ascii(text):
18
+ try:
19
+ if text is None:
20
+ return ''
21
+ if isinstance(text, bytes):
22
+ try:
23
+ text = text.decode('ascii', 'ignore')
24
+ except Exception:
25
+ text = text.decode('utf-8', 'ignore')
26
+ else:
27
+ text = str(text)
28
+ return text.encode('ascii', 'ignore').decode('ascii', 'ignore')
29
+ except Exception:
30
+ return ''
31
+
32
+
33
+ def _tail_file(path, max_lines):
34
+ lines = []
35
+ try:
36
+ with open(path, 'r') as f:
37
+ for line in f:
38
+ lines.append(line)
39
+ if len(lines) > max_lines:
40
+ lines.pop(0)
41
+ return ''.join(lines)
42
+ except Exception:
43
+ return ''
44
+
45
+
46
+ def _get_latest_log_path(local_storage_path):
47
+ try:
48
+ files = []
49
+ for name in os.listdir(local_storage_path or '.'):
50
+ if name.startswith('Log_') and name.endswith('.log'):
51
+ files.append(os.path.join(local_storage_path, name))
52
+ if not files:
53
+ return None
54
+ files.sort(key=lambda p: os.path.getmtime(p))
55
+ return files[-1]
56
+ except Exception:
57
+ return None
58
+
59
+
60
+ def _redact(text):
61
+ # Best-effort ASCII redaction: mask obvious numeric IDs and bearer tokens
62
+ try:
63
+ text = _safe_ascii(text)
64
+ import re
65
+ patterns = [
66
+ (r'\b(\d{3}-?\d{2}-?\d{4})\b', '***-**-****'),
67
+ (r'\b(\d{9,11})\b', '*********'),
68
+ (r'Bearer\s+[A-Za-z0-9\-._~+/]+=*', 'Bearer ***'),
69
+ (r'Authorization:\s*[^\n\r]+', 'Authorization: ***'),
70
+ ]
71
+ for pat, rep in patterns:
72
+ text = re.sub(pat, rep, text)
73
+ return text
74
+ except Exception:
75
+ return text
76
+
77
+
78
+ def _ensure_dir(path):
79
+ try:
80
+ if not os.path.exists(path):
81
+ os.makedirs(path)
82
+ return True
83
+ except Exception:
84
+ return False
85
+
86
+
87
+ def _compute_report_id(zip_path):
88
+ try:
89
+ h = hashlib.sha256()
90
+ with open(zip_path, 'rb') as f:
91
+ chunk = f.read(256 * 1024)
92
+ h.update(chunk)
93
+ return 'mc-{}-{}'.format(int(time.time()), h.hexdigest()[:12])
94
+ except Exception:
95
+ return 'mc-{}-{}'.format(int(time.time()), '000000000000')
96
+
97
+
98
+ def collect_support_bundle(include_traceback=True, max_log_lines=2000):
99
+ config, _ = load_configuration()
100
+ medi = config.get('MediLink_Config', {})
101
+ local_storage_path = medi.get('local_storage_path', '.')
102
+ queue_dir = os.path.join(local_storage_path, 'reports_queue')
103
+ _ensure_dir(queue_dir)
104
+
105
+ stamp = time.strftime('%Y%m%d_%H%M%S')
106
+ bundle_name = 'support_report_{}.zip'.format(stamp)
107
+ zip_path = os.path.join(queue_dir, bundle_name)
108
+
109
+ latest_log = _get_latest_log_path(local_storage_path)
110
+ log_tail = _tail_file(latest_log, max_log_lines) if latest_log else ''
111
+ log_tail = _redact(log_tail)
112
+
113
+ traceback_txt = ''
114
+ if include_traceback:
115
+ try:
116
+ trace_path = os.path.join(local_storage_path, 'traceback.txt')
117
+ if os.path.exists(trace_path):
118
+ with open(trace_path, 'r') as tf:
119
+ traceback_txt = _redact(tf.read())
120
+ except Exception:
121
+ traceback_txt = ''
122
+
123
+ meta = {
124
+ 'app_version': _safe_ascii(_get_version()),
125
+ 'python_version': _safe_ascii(sys.version.split(' ')[0]),
126
+ 'platform': _safe_ascii(platform.platform()),
127
+ 'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
128
+ 'error_summary': _safe_ascii(_first_line(traceback_txt)),
129
+ 'traceback_present': bool(traceback_txt),
130
+ 'config_flags': {
131
+ 'console_logging': bool(medi.get('logging', {}).get('console_output', False)),
132
+ 'test_mode': bool(medi.get('TestMode', False))
133
+ }
134
+ }
135
+
136
+ try:
137
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
138
+ z.writestr('meta.json', json.dumps(meta, ensure_ascii=True, indent=2))
139
+ if latest_log and log_tail:
140
+ z.writestr('log_tail.txt', log_tail)
141
+ if traceback_txt:
142
+ z.writestr('traceback.txt', traceback_txt)
143
+ z.writestr('README.txt', _readme_text())
144
+ return zip_path
145
+ except Exception as e:
146
+ mc_log('Error creating support bundle: {}'.format(e), level='ERROR')
147
+ return None
148
+
149
+
150
+ def _first_line(text):
151
+ try:
152
+ for line in (text or '').splitlines():
153
+ line = line.strip()
154
+ if line:
155
+ return line[:200]
156
+ return ''
157
+ except Exception:
158
+ return ''
159
+
160
+
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
+ def _get_version():
170
+ try:
171
+ from MediCafe import __version__
172
+ return __version__
173
+ except Exception:
174
+ return 'unknown'
175
+
176
+
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 == 413:
233
+ print("[ERROR] Too large (413). Consider reducing max log lines.")
234
+ return False
235
+ else:
236
+ print("[ERROR] Submission failed with status {}".format(code))
237
+ return False
238
+ except Exception as e:
239
+ print("[ERROR] Submission exception: {}".format(e))
240
+ return False
241
+ finally:
242
+ try:
243
+ bundle_fh.close()
244
+ except Exception:
245
+ pass
246
+
247
+
248
+ def flush_queued_reports():
249
+ config, _ = load_configuration()
250
+ medi = config.get('MediLink_Config', {})
251
+ local_storage_path = medi.get('local_storage_path', '.')
252
+ queue_dir = os.path.join(local_storage_path, 'reports_queue')
253
+ if not os.path.isdir(queue_dir):
254
+ return 0, 0
255
+ count_ok = 0
256
+ count_total = 0
257
+ for name in sorted(os.listdir(queue_dir)):
258
+ if not name.endswith('.zip'):
259
+ continue
260
+ zip_path = os.path.join(queue_dir, name)
261
+ count_total += 1
262
+ print("Attempting upload of queued report: {}".format(name))
263
+ ok = submit_support_bundle(zip_path)
264
+ if ok:
265
+ try:
266
+ os.remove(zip_path)
267
+ except Exception:
268
+ pass
269
+ count_ok += 1
270
+ return count_ok, count_total
271
+
272
+
273
+ def capture_unhandled_traceback(exc_type, exc_value, exc_traceback):
274
+ try:
275
+ config, _ = load_configuration()
276
+ medi = config.get('MediLink_Config', {})
277
+ local_storage_path = medi.get('local_storage_path', '.')
278
+ trace_path = os.path.join(local_storage_path, 'traceback.txt')
279
+ import traceback
280
+ text = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
281
+ text = _redact(text)
282
+ with open(trace_path, 'w') as f:
283
+ f.write(text)
284
+ print("An error occurred. A traceback was saved to {}".format(trace_path))
285
+ except Exception:
286
+ pass
287
+
@@ -390,17 +390,50 @@ def manual_deductible_lookup():
390
390
  # Already processed data from merge_responses
391
391
  enhanced_result = eligibility_data
392
392
  elif convert_eligibility_to_enhanced_format is not None:
393
+ # Attempt CSV backfill context for manual route
394
+ csv_row = patient_row_index.get((formatted_dob, member_id))
395
+ derived_patient_id = ""
396
+ derived_service_date = ""
397
+ if csv_row:
398
+ try:
399
+ derived_patient_id = str(csv_row.get('Patient ID #2', csv_row.get('Patient ID', '')))
400
+ derived_service_date = str(csv_row.get('Service Date', ''))
401
+ except Exception:
402
+ derived_patient_id = ""
403
+ derived_service_date = ""
393
404
  # Raw API data needs conversion with patient info
394
- enhanced_result = convert_eligibility_to_enhanced_format(eligibility_data, formatted_dob, member_id, "", "")
405
+ enhanced_result = convert_eligibility_to_enhanced_format(
406
+ eligibility_data, formatted_dob, member_id, derived_patient_id, derived_service_date
407
+ )
395
408
  else:
396
409
  # Fallback if utility function not available
397
410
  enhanced_result = None
398
411
  if enhanced_result:
399
412
  try:
400
413
  # Backfill with CSV row data when available
401
- enhanced_result = backfill_enhanced_result(enhanced_result, None)
414
+ csv_row = patient_row_index.get((formatted_dob, member_id))
415
+ enhanced_result = backfill_enhanced_result(enhanced_result, csv_row)
416
+ except Exception:
417
+ pass
418
+ # Ensure patient_id present; warn/log and set surrogate if missing
419
+ try:
420
+ pid = str(enhanced_result.get('patient_id', '')).strip()
421
+ if not pid:
422
+ surrogate = "{}:{}".format(formatted_dob, member_id)
423
+ enhanced_result['patient_id'] = surrogate
424
+ print("Warning: Missing Patient ID; using surrogate key {}".format(surrogate))
425
+ MediLink_ConfigLoader.log(
426
+ "Manual lookup: Missing Patient ID; using surrogate key {}".format(surrogate),
427
+ level="WARNING"
428
+ )
402
429
  except Exception:
403
430
  pass
431
+ # Ensure patient_name not blank
432
+ try:
433
+ if not str(enhanced_result.get('patient_name', '')).strip():
434
+ enhanced_result['patient_name'] = 'Unknown Patient'
435
+ except Exception:
436
+ enhanced_result['patient_name'] = 'Unknown Patient'
404
437
  print("\n" + "=" * 60)
405
438
  display_enhanced_deductible_table([enhanced_result], context="post_api",
406
439
  title="Manual Lookup Result")
@@ -441,7 +474,18 @@ def manual_deductible_lookup():
441
474
  "Patient Name", "DOB", "Insurance Type", "PayID", "Policy Status", "Remaining Amt")
442
475
  output_file.write(table_header + "\n")
443
476
  output_file.write("-" * len(table_header) + "\n")
444
- display_eligibility_info(eligibility_data, formatted_dob, member_id, output_file)
477
+ # Write directly from enhanced_result to ensure CSV backfill/defaults are preserved
478
+ if enhanced_result:
479
+ table_row = "{:<20} | {:<10} | {:<40} | {:<5} | {:<14} | {:<14}".format(
480
+ enhanced_result['patient_name'][:20],
481
+ enhanced_result['dob'],
482
+ enhanced_result['insurance_type'][:40],
483
+ enhanced_result['payer_id'][:5],
484
+ enhanced_result['policy_status'][:14],
485
+ enhanced_result['remaining_amount'][:14])
486
+ output_file.write(table_row + "\n")
487
+ else:
488
+ display_eligibility_info(eligibility_data, formatted_dob, member_id, output_file)
445
489
 
446
490
  # Ask if user wants to open the report
447
491
  open_report = input("\nEligibility data found! Open the report? (Y/N): ").strip().lower()
@@ -177,6 +177,20 @@ def _normalize_pre_api_data(row):
177
177
  member_id = row.get('Primary Policy Number', row.get('Ins1 Member ID', '')).strip()
178
178
  payer_id = row.get('Ins1 Payer ID', '')
179
179
  patient_id = row.get('Patient ID #2', row.get('Patient ID', ''))
180
+
181
+ # Surrogate key and warnings if patient_id missing/blank
182
+ if not str(patient_id).strip():
183
+ surrogate = "{}:{}".format(dob, member_id)
184
+ patient_id = surrogate
185
+ try:
186
+ # Print visible warning and log as WARNING event
187
+ print("Warning: Missing Patient ID in CSV row; using surrogate key {}".format(surrogate))
188
+ MediLink_ConfigLoader.log(
189
+ "Missing Patient ID in CSV; using surrogate key {}".format(surrogate),
190
+ level="WARNING"
191
+ )
192
+ except Exception:
193
+ pass
180
194
 
181
195
  return {
182
196
  'patient_id': str(patient_id),
@@ -200,7 +214,7 @@ def _normalize_post_api_data(eligibility_result):
200
214
  try:
201
215
  # Handle the enhanced format that comes from convert_eligibility_to_enhanced_format
202
216
  if isinstance(eligibility_result, dict):
203
- return {
217
+ normalized = {
204
218
  'patient_id': str(eligibility_result.get('patient_id', '')),
205
219
  'patient_name': str(eligibility_result.get('patient_name', '')),
206
220
  'dob': str(eligibility_result.get('dob', '')),
@@ -214,6 +228,28 @@ def _normalize_post_api_data(eligibility_result):
214
228
  'remaining_amount': str(eligibility_result.get('remaining_amount', '')),
215
229
  'data_source': str(eligibility_result.get('data_source', ''))
216
230
  }
231
+
232
+ # Default unknown patient name when blank
233
+ try:
234
+ if not normalized['patient_name'].strip():
235
+ normalized['patient_name'] = 'Unknown Patient'
236
+ except Exception:
237
+ normalized['patient_name'] = 'Unknown Patient'
238
+
239
+ # Surrogate key and warnings if patient_id missing/blank
240
+ try:
241
+ if not normalized['patient_id'].strip():
242
+ surrogate = "{}:{}".format(normalized.get('dob', ''), normalized.get('member_id', ''))
243
+ normalized['patient_id'] = surrogate
244
+ print("Warning: Missing Patient ID in eligibility result; using surrogate key {}".format(surrogate))
245
+ MediLink_ConfigLoader.log(
246
+ "Missing Patient ID in eligibility result; using surrogate key {}".format(surrogate),
247
+ level="WARNING"
248
+ )
249
+ except Exception:
250
+ pass
251
+
252
+ return normalized
217
253
  else:
218
254
  MediLink_ConfigLoader.log("Unexpected eligibility result format: {}".format(type(eligibility_result)), level="WARNING")
219
255
  return None
@@ -185,10 +185,30 @@ class RequestHandler(BaseHTTPRequestHandler):
185
185
  file_ids = [link.get('fileId', None) for link in links if link.get('fileId')]
186
186
  log("File IDs received from client: {}".format(file_ids))
187
187
  download_docx_files(links)
188
+ # Only delete files that actually downloaded successfully
189
+ downloaded_names = load_downloaded_emails()
190
+ successful_ids = []
191
+ try:
192
+ name_to_id = { (link.get('filename') or ''): link.get('fileId') for link in links if link.get('fileId') }
193
+ for name in downloaded_names:
194
+ fid = name_to_id.get(name)
195
+ if fid:
196
+ successful_ids.append(fid)
197
+ except Exception as e:
198
+ log("Error computing successful file IDs for cleanup: {}".format(e))
199
+ successful_ids = file_ids # Fallback: attempt all provided IDs
200
+ # Trigger cleanup in Apps Script with auth
201
+ try:
202
+ if successful_ids:
203
+ send_delete_request_to_gas(successful_ids)
204
+ else:
205
+ log("No successful file IDs to delete after download.")
206
+ except Exception as e:
207
+ log("Cleanup trigger failed: {}".format(e))
188
208
  self.send_response(200)
189
209
  self._set_headers()
190
210
  self.end_headers()
191
- response = json.dumps({"status": "success", "message": "All files downloaded", "fileIds": file_ids})
211
+ response = json.dumps({"status": "success", "message": "All files downloaded", "fileIds": successful_ids})
192
212
  self.wfile.write(response.encode('utf-8'))
193
213
  shutdown_event.set()
194
214
  bring_window_to_foreground()
@@ -437,6 +457,38 @@ def handle_post_response(url, payload, headers):
437
457
  log("Unexpected error during link retrieval initiation: {}".format(e))
438
458
  shutdown_event.set()
439
459
 
460
+ def send_delete_request_to_gas(file_ids):
461
+ """Send a delete_files action to the Apps Script web app for the provided Drive file IDs.
462
+ Relies on OAuth token previously obtained. Sends user notifications via GAS.
463
+ """
464
+ try:
465
+ medi = extract_medilink_config(config)
466
+ url = "https://script.google.com/macros/s/{}/exec".format(medi.get('webapp_deployment_id', ''))
467
+ access_token = get_access_token()
468
+ if not access_token:
469
+ log("Access token not found. Skipping cleanup request to GAS.")
470
+ return
471
+ headers = {'Authorization': 'Bearer {}'.format(access_token), 'Content-Type': 'application/json'}
472
+ payload = {"action": "delete_files", "fileIds": list(file_ids)}
473
+ log("Initiating cleanup request to GAS. Payload size: {} id(s)".format(len(file_ids)))
474
+ resp = requests.post(url, json=payload, headers=headers)
475
+ log("Cleanup response status: {}".format(resp.status_code))
476
+ # Print a concise console message
477
+ if resp.ok:
478
+ try:
479
+ body = resp.json()
480
+ msg = body.get('message', 'Files deleted successfully') if isinstance(body, dict) else 'Files deleted successfully'
481
+ except Exception:
482
+ msg = 'Files deleted successfully'
483
+ print("Cleanup complete: {} ({} file(s))".format(msg, len(file_ids)))
484
+ else:
485
+ print("Cleanup failed with status {}: {}".format(resp.status_code, resp.text))
486
+ if resp.status_code != 200:
487
+ raise RuntimeError("Cleanup request failed with status {}".format(resp.status_code))
488
+ except Exception as e:
489
+ log("Error sending delete request to GAS: {}".format(e))
490
+ print("Cleanup request error: {}".format(e))
491
+
440
492
  def inspect_token(access_token):
441
493
  return http_inspect_token(access_token, log, delete_token_file_fn=delete_token_file, stop_server_fn=stop_server)
442
494
 
MediLink/MediLink_main.py CHANGED
@@ -22,6 +22,7 @@ if PERFORMANCE_LOGGING:
22
22
 
23
23
  # Now import core utilities after path setup
24
24
  from MediCafe.core_utils import get_shared_config_loader, setup_module_paths, extract_medilink_config
25
+ from MediCafe.error_reporter import flush_queued_reports, collect_support_bundle, submit_support_bundle, capture_unhandled_traceback
25
26
  setup_module_paths(__file__)
26
27
 
27
28
  # Import modules after path setup
@@ -57,6 +58,8 @@ def _tools_menu(config, medi):
57
58
  print("\nMaintenance Tools:")
58
59
  options = [
59
60
  "Rebuild submission index now",
61
+ "Submit Error Report (online)",
62
+ "Create Support Bundle (offline)",
60
63
  "Back"
61
64
  ]
62
65
  MediLink_UI.display_menu(options)
@@ -75,6 +78,27 @@ def _tools_menu(config, medi):
75
78
  except Exception as e:
76
79
  print("Index rebuild error: {}".format(e))
77
80
  elif choice == '2':
81
+ try:
82
+ print("\nSubmitting Error Report (online)...")
83
+ zip_path = collect_support_bundle(include_traceback=True)
84
+ if not zip_path:
85
+ print("Failed to create support bundle.")
86
+ else:
87
+ ok = submit_support_bundle(zip_path)
88
+ if not ok:
89
+ print("Submission failed. Bundle saved at {} for later retry.".format(zip_path))
90
+ except Exception as e:
91
+ print("Error during report submission: {}".format(e))
92
+ elif choice == '3':
93
+ try:
94
+ zip_path = collect_support_bundle(include_traceback=True)
95
+ if zip_path:
96
+ print("Support bundle created: {}".format(zip_path))
97
+ else:
98
+ print("Failed to create support bundle.")
99
+ except Exception as e:
100
+ print("Error creating support bundle: {}".format(e))
101
+ elif choice == '4':
78
102
  break
79
103
  else:
80
104
  MediLink_UI.display_invalid_choice()
@@ -167,6 +191,15 @@ def main_menu():
167
191
  if PERFORMANCE_LOGGING:
168
192
  print("Welcome display completed in {:.2f} seconds".format(welcome_end - welcome_start))
169
193
 
194
+ # Startup: flush any queued error reports (non-blocking style)
195
+ try:
196
+ print("\nChecking for queued error reports...")
197
+ uploaded, total = flush_queued_reports()
198
+ if total:
199
+ print("Queued reports: {} | Uploaded now: {}".format(total, uploaded))
200
+ except Exception:
201
+ pass
202
+
170
203
  # Show message if new records were found during boot-time scan. TODO we need this to use the 'Last acknowledgements update:' timestamp to decide if it has already run in the last day so
171
204
  # that we're not running it multiple times in rapid succession automatically. (user-initiated checks are fine like via selection of (1. Check for new remittances))
172
205
  if ack_result:
@@ -428,6 +461,11 @@ if __name__ == "__main__":
428
461
  total_start_time = time.time()
429
462
  exit_code = 0
430
463
  try:
464
+ # Install unhandled exception hook to capture tracebacks
465
+ try:
466
+ sys.excepthook = capture_unhandled_traceback
467
+ except Exception:
468
+ pass
431
469
  main_menu()
432
470
  except ValueError as e:
433
471
  # Graceful domain error: show concise message without traceback, then exit
@@ -448,6 +486,25 @@ if __name__ == "__main__":
448
486
  # Unexpected error: still avoid full traceback, present succinct notice
449
487
  sys.stderr.write("An unexpected error occurred; process halted.\n")
450
488
  sys.stderr.write(str(e) + "\n")
489
+ # Offer to create and submit an error report
490
+ try:
491
+ ans = input("Create and submit an error report now? (y/N): ").strip().lower()
492
+ except Exception:
493
+ ans = 'n'
494
+ if ans in ['y', 'yes']:
495
+ try:
496
+ from MediCafe.error_reporter import collect_support_bundle, submit_support_bundle
497
+ zip_path = collect_support_bundle(include_traceback=True)
498
+ if not zip_path:
499
+ print("Failed to create support bundle.")
500
+ else:
501
+ ok = submit_support_bundle(zip_path)
502
+ if ok:
503
+ print("Report submitted successfully.")
504
+ else:
505
+ print("Submission failed. Bundle saved at {} for later retry.".format(zip_path))
506
+ except Exception as _erre:
507
+ print("Error while creating/submitting report: {}".format(_erre))
451
508
  sys.stderr.write("\nPress Enter to exit...\n")
452
509
  try:
453
510
  input()
MediLink/__init__.py CHANGED
@@ -22,7 +22,7 @@ Smart Import Integration:
22
22
  datamgmt = get_components('medilink_datamgmt')
23
23
  """
24
24
 
25
- __version__ = "0.250909.0"
25
+ __version__ = "0.250920.0"
26
26
  __author__ = "Daniel Vidaud"
27
27
  __email__ = "daniel@personalizedtransformation.com"
28
28
 
MediLink/webapp.html CHANGED
@@ -402,6 +402,7 @@
402
402
  links: downloadLinks.map(link => ({
403
403
  url: link.url,
404
404
  filename: link.filename,
405
+ fileId: link.fileId,
405
406
  }))
406
407
  }),
407
408
  mode: 'cors' // Ensure the request includes CORS headers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250909.0
3
+ Version: 0.250920.0
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -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=S5Y80QAjAxFKJn6Uz9KNzNHXEHjCU0boj0joB8JcPlc,3192
15
+ MediBot/__init__.py,sha256=YeIJIhZaVER4TBToGej_8967Kliu4v7zuGGsLVF9DTo,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
@@ -21,14 +21,15 @@ MediBot/get_medicafe_version.py,sha256=uyL_UIE42MyFuJ3SRYxJp8sZx8xjTqlYZ3FdQuxLd
21
21
  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
- MediCafe/MediLink_ConfigLoader.py,sha256=zzI3cFe2_TdwgIW_UAVmY_LFFlLLdlmpCokouUqx6u0,10898
25
- MediCafe/__init__.py,sha256=U8Vg_cQIvjHI-ynz8513QPOIAUsTvWRlj0aYnbvSAWQ,5721
24
+ MediCafe/MediLink_ConfigLoader.py,sha256=NoLb2YiJwlkrRYCt2PHvcFJ7yTIRWQCrsvkZIJWreM4,11141
25
+ MediCafe/__init__.py,sha256=21mCrtgBzffbvGpa_eKEEE_BZTwlO1XygnJFKk3Xa7I,5721
26
26
  MediCafe/__main__.py,sha256=mRNyk3D9Ilnu2XhgVI_rut7r5Ro7UIKtwV871giAHI8,12992
27
27
  MediCafe/api_core.py,sha256=yNFfLO70bF91NNBLSUADYWZFkR5yh8NctxnT98fkAxk,78515
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=bsI5YRO8QzaEU-sxi2F5zIx6k4D53rYUt0mlg-6-Jc8,57644
32
+ MediCafe/error_reporter.py,sha256=t9AAkkVsmZMxPMSA6DW7wDf2cxXpFfA9oJW5-thg5VQ,8176
32
33
  MediCafe/graphql_utils.py,sha256=xrREl0mqktEBkV6SZeAImuuDc8Sp2Q80rWxKIh-zD7Q,44499
33
34
  MediCafe/logging_config.py,sha256=auT65LN5oDEXVhkMeLke63kJHTWxYf2o8YihAfQFgzU,5493
34
35
  MediCafe/logging_demo.py,sha256=TwUhzafna5pMdN3zSKGrpUWRqX96F1JGGsSUtr3dygs,1975
@@ -46,11 +47,11 @@ MediLink/MediLink_Charges.py,sha256=82fnqHGvT7tfdfjucnFHiLdUE0WhHDXrcS0k_Ln3c8U,
46
47
  MediLink/MediLink_ClaimStatus.py,sha256=75LzWfYaxrLWelId-8rHHN_lchHXxAe34pRgvKPdP2o,22118
47
48
  MediLink/MediLink_DataMgmt.py,sha256=9hc5jyWU65nYT66afDybOyYAcW-DvEYuHpWTun96U50,52407
48
49
  MediLink/MediLink_Decoder.py,sha256=1gzdybNg4Vv69s5PNbX8bPNrXT_N_kPpFpt2HpkauWA,16430
49
- MediLink/MediLink_Deductible.py,sha256=e_Oi5c55g4fTcPf-2hLnIRJ_Q_pBAyyXnTKiciQULd8,55208
50
+ MediLink/MediLink_Deductible.py,sha256=8EaJpJICcWcWL_Oge646bXE9STJhvbfIEmHl-5y-DMo,57937
50
51
  MediLink/MediLink_Deductible_Validator.py,sha256=1h5GJ9jbcVarxd6a-lKPEOhJYz4QkeeBlHw0Zdd9vUU,24855
51
- MediLink/MediLink_Display_Utils.py,sha256=a0t1VU9Mm_vhsXVTEA1EpUlElrnyie4XaCeif7GwRgw,21213
52
+ MediLink/MediLink_Display_Utils.py,sha256=pZqd3KRck27zzwnGh_qN1NN1Q-2FF6NFBtRPSO-syiI,22899
52
53
  MediLink/MediLink_Down.py,sha256=s4_z-RaqHYanjwbQCl-OSkg4XIpcIQ2Q6jXa8-q_QXw,28111
53
- MediLink/MediLink_Gmail.py,sha256=8iQjqcJMSa_Zfr5azR0dShKAQeXqt-9C-s8seYB9pic,23961
54
+ MediLink/MediLink_Gmail.py,sha256=muqZI3girXDu92KT5Nft7dLHtpnQAjH-QfvdcGo28_Y,26801
54
55
  MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
56
  MediLink/MediLink_Parser.py,sha256=eRVZ4ckZ5gDOrcvtCUZP3DOd3Djly66rCIk0aYXLz14,12567
56
57
  MediLink/MediLink_PatientProcessor.py,sha256=9r2w4p45d30Tn0kbXL3j5574MYOehP83tDirNOw_Aek,19977
@@ -59,18 +60,18 @@ MediLink/MediLink_Scheduler.py,sha256=UJvxhDvHraqra2_TlQVlGeh5jRFrrfK6nCVUHnKOEM
59
60
  MediLink/MediLink_UI.py,sha256=ZEJ14EGh7pDu1XjAdORDFiay4UtTsLNWwNSJ0prHFWg,10381
60
61
  MediLink/MediLink_Up.py,sha256=uB6J63hWOn8Ot8iGtc2_OgcejNWVgnX7Se_e_UWBNho,38082
61
62
  MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJYfysT7t8,9301
62
- MediLink/MediLink_main.py,sha256=ZVK2UsgSxC9UqgIYfgVu95ugULcH6-11l67jsf4vdJc,22132
63
+ MediLink/MediLink_main.py,sha256=CAXu0IRzhra3ppIFDcCppFNAZp7kCuN6gPtJSdFqGzs,24857
63
64
  MediLink/MediLink_smart_import.py,sha256=B5SfBn_4bYEWJJDolXbjnwKx_-MaqGZ76LyXQwWDV80,9838
64
65
  MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
65
- MediLink/__init__.py,sha256=Lr3cT-xyO8eyyoxqaKxcjR2CHxNL340JEdIHe_l1rd4,3888
66
+ MediLink/__init__.py,sha256=CuY5EOeMChTnQRw-QptzIPfhDnczINCnApf9KKMC9bw,3888
66
67
  MediLink/gmail_http_utils.py,sha256=gtqCCrzJC7e8JFQzMNrf7EbK8na2h4sfTu-NMaZ_UHc,4006
67
68
  MediLink/gmail_oauth_utils.py,sha256=Ugr-DEqs4_RddRMSCJ_dbgA3TVeaxpbAor-dktcTIgY,3713
68
69
  MediLink/openssl.cnf,sha256=76VdcGCykf0Typyiv8Wd1mMVKixrQ5RraG6HnfKFqTo,887
69
70
  MediLink/test.py,sha256=DM_E8gEbhbVfTAm3wTMiNnK2GCD1e5eH6gwTk89QIc4,3116
70
- MediLink/webapp.html,sha256=JPKT559aFVBi1r42Hz7C77Jj0teZZRumPhBev8eSOLk,19806
71
- medicafe-0.250909.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
72
- medicafe-0.250909.0.dist-info/METADATA,sha256=cWgYebKSTCUb2V3c-2TnjmCtUiG0lX8KxVY8g6OlY1g,3414
73
- medicafe-0.250909.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
74
- medicafe-0.250909.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
75
- medicafe-0.250909.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
76
- medicafe-0.250909.0.dist-info/RECORD,,
71
+ MediLink/webapp.html,sha256=MI9zZ4y1_h5Ji3nz2fmm6Q29AsPunks-wR-R8Ct73-w,19856
72
+ medicafe-0.250920.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
73
+ medicafe-0.250920.0.dist-info/METADATA,sha256=hv7aYi-Y8jj_SHLywtNK3h63araet2cTqAEiiOZF3qc,3414
74
+ medicafe-0.250920.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
75
+ medicafe-0.250920.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
76
+ medicafe-0.250920.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
77
+ medicafe-0.250920.0.dist-info/RECORD,,