medicafe 0.251015.0__py3-none-any.whl → 0.251017.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of medicafe might be problematic. Click here for more details.

MediBot/__init__.py CHANGED
@@ -19,7 +19,7 @@ Smart Import Integration:
19
19
  medibot_main = get_components('medibot_main')
20
20
  """
21
21
 
22
- __version__ = "0.251015.0"
22
+ __version__ = "0.251017.0"
23
23
  __author__ = "Daniel Vidaud"
24
24
  __email__ = "daniel@personalizedtransformation.com"
25
25
 
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.251015.0"
30
+ __version__ = "0.251017.0"
31
31
  __author__ = "Daniel Vidaud"
32
32
  __email__ = "daniel@personalizedtransformation.com"
33
33
 
@@ -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 ""
@@ -375,12 +375,16 @@ def manual_deductible_lookup():
375
375
 
376
376
  # Fetch eligibility data
377
377
  found_data = False
378
+ printed_messages = set()
378
379
  for i, payer_id in enumerate(payer_ids, 1):
379
380
  print("Checking Payer ID {} ({}/{}): {}".format(payer_id, i, len(payer_ids), payer_id))
380
381
 
381
382
  # Use the current mode setting for validation
382
383
  run_validation = DEBUG_MODE
383
- eligibility_data = get_eligibility_info(client, payer_id, provider_last_name, formatted_dob, member_id, npi, run_validation=run_validation, is_manual_lookup=True)
384
+ eligibility_data = get_eligibility_info(
385
+ client, payer_id, provider_last_name, formatted_dob, member_id, npi,
386
+ run_validation=run_validation, is_manual_lookup=True, printed_messages=printed_messages
387
+ )
384
388
  if eligibility_data:
385
389
  found_data = True
386
390
 
@@ -484,13 +488,28 @@ def manual_deductible_lookup():
484
488
  enhanced_result['policy_status'][:14],
485
489
  enhanced_result['remaining_amount'][:14])
486
490
  output_file.write(table_row + "\n")
491
+
492
+ # Persist per-row error diagnostics in a user-friendly way
493
+ try:
494
+ row_reason = _compute_error_reason(enhanced_result)
495
+ row_messages = enhanced_result.get('error_messages', []) or []
496
+ if row_reason or row_messages:
497
+ output_file.write(" >> Errors:" + "\n")
498
+ if row_reason:
499
+ output_file.write(" >> - {}\n".format(row_reason))
500
+ for msg in row_messages:
501
+ # Avoid duplicating the reason message if identical
502
+ if not row_reason or msg.strip() != row_reason.strip():
503
+ output_file.write(" >> - {}\n".format(str(msg)))
504
+ except Exception:
505
+ pass
487
506
  else:
488
507
  display_eligibility_info(eligibility_data, formatted_dob, member_id, output_file)
489
508
 
490
509
  # Ask if user wants to open the report
491
510
  open_report = input("\nEligibility data found! Open the report? (Y/N): ").strip().lower()
492
511
  if open_report in ['y', 'yes']:
493
- os.system('notepad.exe "{}"'.format(output_file_path))
512
+ os.startfile(output_file_path)
494
513
  print("Manual eligibility report generated: {}\n".format(output_file_path))
495
514
  break # Assuming one payer ID per manual lookup
496
515
  else:
@@ -523,6 +542,13 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
523
542
  if run_validation:
524
543
  # Debug mode: Call both APIs and run validation
525
544
  MediLink_ConfigLoader.log("Running in DEBUG MODE - calling both APIs", level="INFO")
545
+ # Always initialize row-level error messages for diagnostics
546
+ error_messages_for_row = []
547
+ # Track Super Connector connection failure (for user-facing diagnostics)
548
+ sc_failure_info = None
549
+ # Enable verbose diagnostics in debug mode without config changes
550
+ diagnostics_verbose = True
551
+ sc_preflight_failed = False
526
552
 
527
553
  # Get legacy response
528
554
  MediLink_ConfigLoader.log("Getting legacy get_eligibility_v3 API response", level="INFO")
@@ -547,11 +573,44 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
547
573
  MediLink_ConfigLoader.log("Getting new get_eligibility_super_connector API response", level="INFO")
548
574
  super_connector_eligibility = None
549
575
  try:
550
- super_connector_eligibility = api_core.get_eligibility_super_connector(
551
- client, payer_id, provider_last_name, 'MemberIDDateOfBirth', date_of_birth, member_id, npi
552
- )
576
+ if not sc_preflight_failed:
577
+ super_connector_eligibility = api_core.get_eligibility_super_connector(
578
+ client, payer_id, provider_last_name, 'MemberIDDateOfBirth', date_of_birth, member_id, npi
579
+ )
580
+ else:
581
+ super_connector_eligibility = None
553
582
  except Exception as e:
554
583
  MediLink_ConfigLoader.log("Super Connector API failed: {}".format(e), level="ERROR")
584
+ # Best-effort triage classification for clearer downstream messaging
585
+ try:
586
+ # Use centralized classifier when available
587
+ from MediCafe.deductible_utils import classify_api_failure
588
+ code, message = classify_api_failure(e, 'Super Connector API')
589
+ # Sticky preflight failure for subsequent patients in this run
590
+ if code in ['TIMEOUT', 'CONN_ERR', 'AUTH_FAIL', 'MISCONFIG']:
591
+ sc_preflight_failed = True
592
+ except Exception:
593
+ try:
594
+ failure_reason = "Super Connector API connection failed"
595
+ detail = str(e)
596
+ if requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.Timeout):
597
+ failure_reason = "Super Connector API timeout"
598
+ elif requests and hasattr(requests, 'exceptions') and isinstance(e, requests.exceptions.ConnectionError):
599
+ failure_reason = "Super Connector API connection error"
600
+ elif "Invalid payer_id" in detail:
601
+ failure_reason = "Super Connector API rejected payer_id"
602
+ elif ("No access token" in detail) or ("token" in detail.lower()):
603
+ failure_reason = "Super Connector API authentication failed"
604
+ elif ("Eligibility endpoint not configured" in detail) or ("endpoint" in detail.lower() and "configured" in detail.lower()):
605
+ failure_reason = "Super Connector API endpoint misconfigured"
606
+ message = "{}: {}".format(failure_reason, detail)
607
+ except Exception:
608
+ message = "Super Connector API failed: {}".format(str(e))
609
+ sc_failure_info = {"message": message}
610
+ try:
611
+ error_messages_for_row.append(message)
612
+ except Exception:
613
+ pass
555
614
 
556
615
  # Run validation if we have at least one response
557
616
  # Generate validation report even if one API fails - this helps with debugging
@@ -596,6 +655,11 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
596
655
  if detail_msg not in printed_messages:
597
656
  print(detail_msg)
598
657
  printed_messages.add(detail_msg)
658
+ # Accumulate per-row messages for persistence in reports
659
+ try:
660
+ error_messages_for_row.append("{} - {}".format(error_code, error_desc))
661
+ except Exception:
662
+ pass
599
663
 
600
664
  # Check for data in error extensions (some APIs return data here)
601
665
  extensions = error.get('extensions', {})
@@ -607,20 +671,37 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
607
671
  if details:
608
672
  first_detail = details[0]
609
673
  print(" First detail: {}".format(first_detail))
674
+ # Persist a brief extension note without dumping raw objects
675
+ try:
676
+ error_messages_for_row.append("Extensions include {} detail record(s)".format(len(details)))
677
+ except Exception:
678
+ pass
610
679
 
611
680
  # Check status code
612
681
  if super_connector_eligibility:
613
682
  status_code = super_connector_eligibility.get('statuscode')
614
- if status_code and status_code != '200':
683
+ from MediCafe.deductible_utils import is_ok_200
684
+ if status_code is not None and not is_ok_200(status_code):
615
685
  print("Super Connector API status code: {} (non-200 indicates errors)".format(status_code))
686
+ # Record status code for the row diagnostics
687
+ error_messages_for_row.append("Status code {} from Super Connector".format(status_code))
688
+ # If Super Connector failed entirely, append a triage note to the validation report (if created)
689
+ try:
690
+ if sc_failure_info and validation_file_path and os.path.exists(validation_file_path):
691
+ with open(validation_file_path, 'a') as vf:
692
+ vf.write("\n" + "-" * 80 + "\n")
693
+ vf.write("SUPER CONNECTOR CONNECTION FAILURE NOTE\n")
694
+ vf.write("-" * 80 + "\n")
695
+ vf.write(sc_failure_info['message'] + "\n")
696
+ vf.write("Recommendation: Verify network connectivity, credentials, payer ID validity, and endpoint configuration.\n")
697
+ except Exception:
698
+ pass
616
699
 
617
700
  # Open validation report in Notepad (only for manual lookups, not batch processing)
618
701
  if validation_file_path and os.path.exists(validation_file_path):
619
702
  # Only open in manual mode - batch processing will handle this separately
620
703
  if is_manual_lookup: # Check if we're in manual lookup mode
621
- ret = os.system('notepad.exe "{}"'.format(validation_file_path))
622
- if ret != 0:
623
- print("Failed to open Notepad (exit code: {}). Please open manually: {}".format(ret, validation_file_path))
704
+ os.startfile(validation_file_path)
624
705
  elif validation_file_path:
625
706
  print("\nValidation report file was not created: {}".format(validation_file_path))
626
707
  except Exception as e:
@@ -628,7 +709,40 @@ def get_eligibility_info(client, payer_id, provider_last_name, date_of_birth, me
628
709
 
629
710
  # After validation, merge responses
630
711
  try:
631
- merged_data = merge_responses(super_connector_eligibility, legacy_eligibility, date_of_birth, member_id)
712
+ if merge_responses is not None:
713
+ merged_data = merge_responses(super_connector_eligibility, legacy_eligibility, date_of_birth, member_id)
714
+ else:
715
+ MediLink_ConfigLoader.log("merge_responses utility not available; returning raw API response", level="WARNING")
716
+ merged_data = super_connector_eligibility or legacy_eligibility or {}
717
+ # Attach accumulated row-level messages for downstream display/persistence
718
+ try:
719
+ if isinstance(merged_data, dict) and error_messages_for_row:
720
+ merged_data['error_messages'] = error_messages_for_row
721
+ except Exception:
722
+ pass
723
+ # Surface Super Connector failure prominently in user-facing diagnostics
724
+ try:
725
+ if sc_failure_info and isinstance(merged_data, dict):
726
+ merged_data['super_connector_failed'] = True
727
+ # Prefer explaining the connection failure over generic name/amount messages
728
+ if (not merged_data.get('error_reason')) or (merged_data.get('data_source') in ['None', 'Error']) or (not merged_data.get('is_successful', False)):
729
+ merged_data['error_reason'] = sc_failure_info['message']
730
+ # Ensure the failure message is included in error_messages
731
+ if 'error_messages' not in merged_data or merged_data['error_messages'] is None:
732
+ merged_data['error_messages'] = []
733
+ if sc_failure_info['message'] not in merged_data['error_messages']:
734
+ merged_data['error_messages'].append(sc_failure_info['message'])
735
+ # Attach diagnostics envelope (minimal) without breaking existing schema
736
+ try:
737
+ if diagnostics_verbose:
738
+ if 'diagnostics' not in merged_data or merged_data['diagnostics'] is None:
739
+ merged_data['diagnostics'] = []
740
+ if sc_failure_info['message'] not in merged_data['diagnostics']:
741
+ merged_data['diagnostics'].append(sc_failure_info['message'])
742
+ except Exception:
743
+ pass
744
+ except Exception:
745
+ pass
632
746
  return merged_data
633
747
  except Exception as e:
634
748
  MediLink_ConfigLoader.log("Error in merge_responses: {}".format(e), level="ERROR")
@@ -713,8 +827,10 @@ def display_eligibility_info(data, dob, member_id, output_file, patient_id="", s
713
827
  if data is None:
714
828
  return
715
829
 
716
- # Convert to enhanced format
717
- enhanced_data = convert_eligibility_to_enhanced_format(data, dob, member_id, patient_id, service_date)
830
+ # Convert to enhanced format (guard if utility missing)
831
+ enhanced_data = None
832
+ if convert_eligibility_to_enhanced_format is not None:
833
+ enhanced_data = convert_eligibility_to_enhanced_format(data, dob, member_id, patient_id, service_date)
718
834
  if enhanced_data:
719
835
  # Write to output file in legacy format for compatibility
720
836
  table_row = "{:<20} | {:<10} | {:<40} | {:<5} | {:<14} | {:<14}".format(
@@ -727,6 +843,31 @@ def display_eligibility_info(data, dob, member_id, output_file, patient_id="", s
727
843
  output_file.write(table_row + "\n")
728
844
  print(table_row) # Print to console for progressive display
729
845
 
846
+ # Helper to compute a user-friendly error explanation for a result row
847
+ def _compute_error_reason(record):
848
+ # Delegate to centralized helper to avoid duplication
849
+ try:
850
+ from MediCafe.deductible_utils import compute_error_reason
851
+ return compute_error_reason(record)
852
+ except Exception:
853
+ try:
854
+ if not isinstance(record, dict):
855
+ return ""
856
+ reason = str(record.get('error_reason', '')).strip()
857
+ name_unknown = (not str(record.get('patient_name', '')).strip()) or (record.get('patient_name') == 'Unknown Patient')
858
+ has_error = (str(record.get('status', '')) == 'Error') or (str(record.get('data_source', '')) in ['None', 'Error'])
859
+ amount_missing = (str(record.get('remaining_amount', '')) == 'Not Found')
860
+ if not reason:
861
+ if name_unknown:
862
+ reason = 'Patient name could not be determined from API responses or CSV backfill'
863
+ elif amount_missing:
864
+ reason = 'Deductible remaining amount not found in eligibility response'
865
+ elif has_error:
866
+ reason = 'Eligibility lookup encountered an error; see logs for details'
867
+ return reason
868
+ except Exception:
869
+ return ""
870
+
730
871
  # Global mode flags (will be set in main)
731
872
  LEGACY_MODE = False
732
873
  DEBUG_MODE = False
@@ -1030,6 +1171,14 @@ if __name__ == "__main__":
1030
1171
  "Patient Name", "DOB", "Insurance Type Code", "PayID", "Policy Status", "Remaining Amt")
1031
1172
  output_file.write(table_header + "\n")
1032
1173
  output_file.write("-" * len(table_header) + "\n")
1174
+
1175
+ # Global notice when Super Connector connection failed for any patients
1176
+ try:
1177
+ sc_failed_count = sum(1 for r in eligibility_results if isinstance(r, dict) and r.get('super_connector_failed'))
1178
+ if sc_failed_count:
1179
+ output_file.write("NOTICE: Super Connector API connection failed for {} patient(s). Fallback data used when available.\n".format(sc_failed_count))
1180
+ except Exception:
1181
+ pass
1033
1182
 
1034
1183
  # Write all results to file
1035
1184
  for result in eligibility_results:
@@ -1042,6 +1191,20 @@ if __name__ == "__main__":
1042
1191
  result['remaining_amount'][:14])
1043
1192
  output_file.write(table_row + "\n")
1044
1193
 
1194
+ # Persist per-row error diagnostics in a user-friendly way
1195
+ try:
1196
+ row_reason = _compute_error_reason(result)
1197
+ row_messages = result.get('error_messages', []) or []
1198
+ if row_reason or row_messages:
1199
+ output_file.write(" >> Errors:" + "\n")
1200
+ if row_reason:
1201
+ output_file.write(" >> - {}\n".format(row_reason))
1202
+ for msg in row_messages:
1203
+ if not row_reason or msg.strip() != row_reason.strip():
1204
+ output_file.write(" >> - {}\n".format(str(msg)))
1205
+ except Exception:
1206
+ pass
1207
+
1045
1208
  # Write enhanced error summary to file
1046
1209
  if errors:
1047
1210
  error_msg = "\nErrors encountered during API calls:\n"
@@ -1053,7 +1216,7 @@ if __name__ == "__main__":
1053
1216
  # Ask if user wants to open the report
1054
1217
  open_report = input("\nBatch processing complete! Open the eligibility report? (Y/N): ").strip().lower()
1055
1218
  if open_report in ['y', 'yes']:
1056
- os.system('notepad.exe "{}"'.format(output_file_path))
1219
+ os.startfile(output_file_path)
1057
1220
 
1058
1221
  # Print summary of validation reports only in debug mode
1059
1222
  if DEBUG_MODE:
@@ -1072,9 +1235,7 @@ if __name__ == "__main__":
1072
1235
  if open_validation in ['y', 'yes']:
1073
1236
  for file_path in validation_files_created:
1074
1237
  print("Opening: {}".format(os.path.basename(file_path)))
1075
- ret = os.system('notepad.exe "{}"'.format(file_path))
1076
- if ret != 0:
1077
- print("Failed to open Notepad for: {}".format(os.path.basename(file_path)))
1238
+ os.startfile(file_path)
1078
1239
  else:
1079
1240
  print("No validation reports were generated.")
1080
1241
  print("This may be because:")
@@ -518,7 +518,14 @@ def _maybe_display_error_row(record, context):
518
518
  elif has_error:
519
519
  reason = 'Eligibility lookup encountered an error; see logs for details'
520
520
 
521
- if reason:
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
@@ -174,6 +174,14 @@ class RequestHandler(BaseHTTPRequestHandler):
174
174
  self.send_response(200)
175
175
  self._set_headers()
176
176
  self.end_headers()
177
+ try:
178
+ origin = self.headers.get('Origin')
179
+ except Exception:
180
+ origin = None
181
+ try:
182
+ print("[CORS] Preflight {0} from {1}".format(self.path, origin))
183
+ except Exception:
184
+ pass
177
185
 
178
186
  def do_POST(self):
179
187
  if self.path == '/download':
@@ -182,6 +190,10 @@ class RequestHandler(BaseHTTPRequestHandler):
182
190
  data = json.loads(post_data.decode('utf-8'))
183
191
  links = data.get('links', [])
184
192
  log("Received links: {}".format(links))
193
+ try:
194
+ print("[Handshake] Received {0} link(s) from webapp".format(len(links)))
195
+ except Exception:
196
+ pass
185
197
  file_ids = [link.get('fileId', None) for link in links if link.get('fileId')]
186
198
  log("File IDs received from client: {}".format(file_ids))
187
199
  download_docx_files(links)
@@ -210,6 +222,10 @@ class RequestHandler(BaseHTTPRequestHandler):
210
222
  self.end_headers()
211
223
  response = json.dumps({"status": "success", "message": "All files downloaded", "fileIds": successful_ids})
212
224
  self.wfile.write(response.encode('utf-8'))
225
+ try:
226
+ print("[Handshake] Completed. Returning success for {0} fileId(s)".format(len(successful_ids)))
227
+ except Exception:
228
+ pass
213
229
  shutdown_event.set()
214
230
  bring_window_to_foreground()
215
231
  elif self.path == '/shutdown':
@@ -244,6 +260,19 @@ class RequestHandler(BaseHTTPRequestHandler):
244
260
 
245
261
  def do_GET(self):
246
262
  log("Full request path: {}".format(self.path))
263
+ if self.path == '/_health':
264
+ try:
265
+ print("[Health] Probe OK")
266
+ except Exception:
267
+ pass
268
+ self.send_response(200)
269
+ self._set_headers()
270
+ self.end_headers()
271
+ try:
272
+ self.wfile.write(json.dumps({"status": "ok"}).encode('ascii'))
273
+ except Exception:
274
+ self.wfile.write(b'{"status":"ok"}')
275
+ return
247
276
  if self.path.startswith("/?code="):
248
277
  auth_code = self.path.split('=')[1].split('&')[0]
249
278
  auth_code = requests.utils.unquote(auth_code)
@@ -329,6 +358,10 @@ def run_server():
329
358
  httpd = HTTPServer(('0.0.0.0', server_port), RequestHandler)
330
359
  httpd.socket = ssl.wrap_socket(httpd.socket, certfile=cert_file, keyfile=key_file, server_side=True)
331
360
  log("Starting HTTPS server on port {}".format(server_port))
361
+ try:
362
+ print("[Server] HTTPS server ready at https://127.0.0.1:{0}".format(server_port))
363
+ except Exception:
364
+ pass
332
365
  httpd.serve_forever()
333
366
  except Exception as e:
334
367
  log("Error in serving: {}".format(e))
MediLink/__init__.py CHANGED
@@ -22,7 +22,7 @@ Smart Import Integration:
22
22
  datamgmt = get_components('medilink_datamgmt')
23
23
  """
24
24
 
25
- __version__ = "0.251015.0"
25
+ __version__ = "0.251017.0"
26
26
  __author__ = "Daniel Vidaud"
27
27
  __email__ = "daniel@personalizedtransformation.com"
28
28
 
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('https://127.0.0.1:8000/download', {
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' // Ensure the request includes CORS headers
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(); // Get the error message from the response
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("Download links successfully sent to Python server.");
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
- displayErrorMessage('Unable to send download links to the server. Error: ' + error.message);
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.251015.0
3
+ Version: 0.251017.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=RZDruyBkYuWNTwJuX6TMpymOyX3V6jq-jUTBwV249rE,3192
15
+ MediBot/__init__.py,sha256=Gj1gmPtY2Ua-T9wxwcjl2OXVo7dMcZI4FkOg-YVWvLg,3192
16
16
  MediBot/clear_cache.bat,sha256=F6-VhETWw6xDdGWG2wUqvtXjCl3lY4sSUFqF90bM8-8,1860
17
17
  MediBot/crash_diagnostic.bat,sha256=j8kUtyBg6NOWbXpeFuEqIRHOkVzgUrLOqO3FBMfNxTo,9268
18
18
  MediBot/f_drive_diagnostic.bat,sha256=4572hZaiwZ5wVAarPcZJQxkOSTwAdDuT_X914noARak,6878
@@ -22,13 +22,13 @@ MediBot/process_csvs.bat,sha256=3tI7h1z9eRj8rUUL4wJ7dy-Qrak20lRmpAPtGbUMbVQ,3489
22
22
  MediBot/update_json.py,sha256=vvUF4mKCuaVly8MmoadDO59M231fCIInc0KI1EtDtPA,3704
23
23
  MediBot/update_medicafe.py,sha256=G1lyvVOHYuho1d-TJQNN6qaB4HBWaJ2PpXqemBoPlRQ,17937
24
24
  MediCafe/MediLink_ConfigLoader.py,sha256=NoLb2YiJwlkrRYCt2PHvcFJ7yTIRWQCrsvkZIJWreM4,11141
25
- MediCafe/__init__.py,sha256=DGCg0jShqGBlX0QwzU-oOvl21DxN0Yadu1qEYVTFVtU,5721
25
+ MediCafe/__init__.py,sha256=5NhwImtNbn5vzqKaqRB0It076F7C2Zmb0REd1jY4BxE,5721
26
26
  MediCafe/__main__.py,sha256=mRNyk3D9Ilnu2XhgVI_rut7r5Ro7UIKtwV871giAHI8,12992
27
27
  MediCafe/api_core.py,sha256=r9cqa5-HU4A7iz3NLxzRwuxsxOfDiJn9SRgtPjT83qU,90764
28
28
  MediCafe/api_factory.py,sha256=I5AeJoyu6m7oCrjc2OvVvO_4KSBRutTsR1riiWhTZV0,12086
29
29
  MediCafe/api_utils.py,sha256=KWQB0q1k5E6frOFFlKWcFpHNcqfrS7KJ_82672wbupw,14041
30
30
  MediCafe/core_utils.py,sha256=XKUpyv7yKjIQ8iNrhD76PIURyt6GZxb98v0daiI7aaw,27303
31
- MediCafe/deductible_utils.py,sha256=Qpp5joZ0KxSWbN6qQU1OM0fE-n__v5jtzdOV4UR0CT8,61446
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=jr-TrfOTxQDc4dwvwUy4jSSb_AnsU0pWLq_kyktAgZk,57942
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=wWGbqhGL_6vZrVE4_7-xYg7fdjVbTR1149VJTS3deI0,24513
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=muqZI3girXDu92KT5Nft7dLHtpnQAjH-QfvdcGo28_Y,26801
54
+ MediLink/MediLink_Gmail.py,sha256=hMHgseSi58qPMivZpEhZbY6k-tyxC-hHcWaS7xVNFbI,28011
55
55
  MediLink/MediLink_Mailer.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
56
  MediLink/MediLink_Parser.py,sha256=eRVZ4ckZ5gDOrcvtCUZP3DOd3Djly66rCIk0aYXLz14,12567
57
57
  MediLink/MediLink_PatientProcessor.py,sha256=9r2w4p45d30Tn0kbXL3j5574MYOehP83tDirNOw_Aek,19977
@@ -63,15 +63,15 @@ MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJY
63
63
  MediLink/MediLink_main.py,sha256=CAXu0IRzhra3ppIFDcCppFNAZp7kCuN6gPtJSdFqGzs,24857
64
64
  MediLink/MediLink_smart_import.py,sha256=B5SfBn_4bYEWJJDolXbjnwKx_-MaqGZ76LyXQwWDV80,9838
65
65
  MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
66
- MediLink/__init__.py,sha256=pUz0lDh96t0FYdCxUQlawfKMvDiib088-00IsHgRwfk,3888
66
+ MediLink/__init__.py,sha256=xK9uzY41kqKgKT5uxifwKQd001nXnAzeHPJdwjB4Yeo,3888
67
67
  MediLink/gmail_http_utils.py,sha256=gtqCCrzJC7e8JFQzMNrf7EbK8na2h4sfTu-NMaZ_UHc,4006
68
68
  MediLink/gmail_oauth_utils.py,sha256=Ugr-DEqs4_RddRMSCJ_dbgA3TVeaxpbAor-dktcTIgY,3713
69
69
  MediLink/openssl.cnf,sha256=76VdcGCykf0Typyiv8Wd1mMVKixrQ5RraG6HnfKFqTo,887
70
70
  MediLink/test.py,sha256=DM_E8gEbhbVfTAm3wTMiNnK2GCD1e5eH6gwTk89QIc4,3116
71
- MediLink/webapp.html,sha256=MI9zZ4y1_h5Ji3nz2fmm6Q29AsPunks-wR-R8Ct73-w,19856
72
- medicafe-0.251015.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
73
- medicafe-0.251015.0.dist-info/METADATA,sha256=AVt3fFn191rtXLPHcmal5udTp73FL8RTu8fu-YpMndU,3414
74
- medicafe-0.251015.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
75
- medicafe-0.251015.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
76
- medicafe-0.251015.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
77
- medicafe-0.251015.0.dist-info/RECORD,,
71
+ MediLink/webapp.html,sha256=DwDYjVvluGJ7eDdvEogfKN4t24ZJRoIUuSBfCYCL-3w,21252
72
+ medicafe-0.251017.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
73
+ medicafe-0.251017.0.dist-info/METADATA,sha256=d86uW7sAJWTEBOxVF00UdawI9PmXuNoMuiE7iGCTYE0,3414
74
+ medicafe-0.251017.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
75
+ medicafe-0.251017.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
76
+ medicafe-0.251017.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
77
+ medicafe-0.251017.0.dist-info/RECORD,,