medicafe 0.250813.0__py3-none-any.whl → 0.250813.2__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/MediBot.py CHANGED
@@ -608,48 +608,68 @@ if __name__ == "__main__":
608
608
  existing_patients, patients_to_process = MediBot_Preprocessor.check_existing_patients(selected_patient_ids, _ac().get_mapat_med_path() if _ac() else '')
609
609
 
610
610
  if existing_patients:
611
- print("\nNOTE: The following patient(s) already EXIST in the system and \n will be excluded from processing:")
612
- # BUG There a "ERROR: 'str' object has no attribute 'strftime'" that shows up after this somewhere.
613
611
  # Collect surgery dates and patient info for existing patients
614
612
  patient_info = []
615
613
  for patient_id, patient_name in existing_patients:
616
614
  try:
617
- surgery_date = next((row.get('Surgery Date') for row in csv_data if row.get(reverse_mapping['Patient ID #2']) == patient_id), None)
618
- if surgery_date is None:
619
- raise ValueError("Surgery Date not found for patient ID: {}".format(patient_id))
620
- patient_info.append((surgery_date, patient_name, patient_id))
615
+ # Find the row for this patient
616
+ patient_row = next((row for row in csv_data if row.get(reverse_mapping['Patient ID #2']) == patient_id), None)
617
+ if patient_row is None:
618
+ raise ValueError("Patient row not found for patient ID: {}".format(patient_id))
619
+
620
+ # Get all surgery dates for this patient
621
+ all_surgery_dates = patient_row.get('_all_surgery_dates', [patient_row.get('Surgery Date')])
622
+ surgery_date_to_diagnosis = patient_row.get('_surgery_date_to_diagnosis', {})
623
+
624
+ # Create entries for each surgery date with its specific diagnosis code
625
+ for surgery_date in all_surgery_dates:
626
+ diagnosis_code = surgery_date_to_diagnosis.get(surgery_date, 'N/A')
627
+ patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, patient_row))
628
+
621
629
  except Exception as e:
622
- MediLink_ConfigLoader.log("Warning: Error retrieving Surgery Date for patient ID '{}': {}".format(patient_id, e), level="WARNING")
623
- patient_info.append(('Unknown Date', patient_name, patient_id)) # Append with 'Unknown Date' if there's an error
624
-
625
- # Sort by surgery date first and then by patient name
626
- # NOTE: 'surgery_date' may be a string or a datetime depending on upstream data.
627
- # We sort lexicographically which is stable for uniform string inputs. If mixed types
628
- # are encountered, consider normalizing to string first or using a robust key function
629
- # that attempts datetime parsing with a string fallback.
630
- patient_info.sort(key=lambda x: (x[0], x[1]))
631
-
632
- # Print the sorted patient info
633
- for index, (surgery_date, patient_name, patient_id) in enumerate(patient_info):
634
- # Format surgery_date safely whether it's a datetime/date or a string
635
- # This guards the observed "'str' object has no attribute 'strftime'" error on XP.
636
- try:
637
- formatted_date = surgery_date.strftime('%m-%d')
638
- except Exception:
639
- formatted_date = str(surgery_date)
640
- print("{:03d}: {} (ID: {}) {}".format(index + 1, formatted_date, patient_id, patient_name))
630
+ MediLink_ConfigLoader.log("Warning: Error retrieving data for patient ID '{}': {}".format(patient_id, e), level="WARNING")
631
+ patient_info.append(('Unknown Date', patient_name, patient_id, 'N/A', None)) # Append with 'Unknown Date' if there's an error
632
+
633
+ # Display existing patients table using the enhanced display function
634
+ MediBot_UI.display_enhanced_patient_table(
635
+ patient_info,
636
+ "NOTE: The following patient(s) already EXIST in the system but have new dates of service.\n Their diagnosis codes will need to be updated manually by the user to the following list:",
637
+ show_line_numbers=False
638
+ )
641
639
 
642
640
  # Update csv_data to exclude existing patients
643
641
  # TODO: Update this logic to handle patients that exist but need new charges added.
644
642
  csv_data = [row for row in csv_data if row[reverse_mapping['Patient ID #2']] in patients_to_process]
645
- else:
646
- print("\nSelected patient(s) are NEW patients and will be processed.")
643
+
644
+ # Show NEW patients that will be processed (if any)
645
+ if patients_to_process:
646
+ # Collect surgery dates and patient info for NEW patients
647
+ new_patient_info = []
648
+ for row in csv_data:
649
+ patient_id = row.get(reverse_mapping['Patient ID #2'])
650
+ patient_name = row.get(reverse_mapping['Patient Name'])
651
+
652
+ # Get all surgery dates for this patient
653
+ all_surgery_dates = row.get('_all_surgery_dates', [row.get('Surgery Date')])
654
+ surgery_date_to_diagnosis = row.get('_surgery_date_to_diagnosis', {})
655
+
656
+ # Create entries for each surgery date with its specific diagnosis code
657
+ for surgery_date in all_surgery_dates:
658
+ diagnosis_code = surgery_date_to_diagnosis.get(surgery_date, 'N/A')
659
+ new_patient_info.append((surgery_date, patient_name, patient_id, diagnosis_code, row))
660
+
661
+ # Display new patients table using the enhanced display function
662
+ MediBot_UI.display_enhanced_patient_table(
663
+ new_patient_info,
664
+ "NOTE: The following NEW patient(s) will be automatically entered into Medisoft:",
665
+ show_line_numbers=True
666
+ )
647
667
 
648
668
  # Check if there are patients left to process
649
669
  if len(patients_to_process) == 0:
650
670
  proceed = input("\nAll patients have been processed. Continue anyway?: ").lower().strip() in ['yes', 'y']
651
671
  else:
652
- proceed = input("\nDo you want to proceed with the {} remaining patient(s)? (yes/no): ".format(len(patients_to_process))).lower().strip() in ['yes', 'y']
672
+ proceed = input("\nDo you want to proceed with entering {} new patient(s) into Medisoft? (yes/no): ".format(len(patients_to_process))).lower().strip() in ['yes', 'y']
653
673
 
654
674
  # TODO: Here is where we need to add the step where we move to MediBot_Charges.
655
675
  # The return is an enriched dataset to be picked up by MediBot which means we need to return:
@@ -4,17 +4,13 @@ Core crosswalk library for MediBot
4
4
  Handles crosswalk operations and API interactions.
5
5
  """
6
6
 
7
- import os
8
7
  import sys
9
- import csv
10
- import json
11
- import re
12
- from datetime import datetime
13
8
 
14
9
  # Use core utilities for standardized imports
15
10
  from MediCafe.core_utils import (
16
11
  import_medibot_module,
17
- get_config_loader_with_fallback
12
+ get_config_loader_with_fallback,
13
+ smart_import
18
14
  )
19
15
 
20
16
  # Initialize configuration loader with fallback
@@ -45,6 +41,10 @@ else:
45
41
  update_crosswalk_with_new_payer_id = None
46
42
  load_and_parse_z_data = None
47
43
 
44
+ # Import API functions using centralized import pattern
45
+ MediLink_API_v3 = smart_import(['MediCafe.api_core', 'MediCafe.api_core_backup'])
46
+ fetch_payer_name_from_api = getattr(MediLink_API_v3, 'fetch_payer_name_from_api', None) if MediLink_API_v3 else None
47
+
48
48
  # Module-level cache to prevent redundant API calls
49
49
  _api_cache = {}
50
50
 
@@ -262,7 +262,7 @@ def add_columns(csv_data, column_headers):
262
262
 
263
263
  # Extracting the list to a variable for future refactoring:
264
264
  def filter_rows(csv_data):
265
- # TODO: This should be handled in the crosswalk.
265
+ # TODO: This should be written in the crosswalk and not hardcoded here.
266
266
  excluded_insurance = {'AETNA', 'AETNA MEDICARE', 'HUMANA MED HMO'}
267
267
  csv_data[:] = [row for row in csv_data if row.get('Patient ID') and row.get('Primary Insurance') not in excluded_insurance]
268
268
 
@@ -515,17 +515,40 @@ def convert_surgery_date(csv_data):
515
515
  def sort_and_deduplicate(csv_data):
516
516
  # Create a dictionary to hold unique patients based on Patient ID
517
517
  unique_patients = {}
518
+ # Create a dictionary to store multiple surgery dates per patient
519
+ patient_surgery_dates = {}
518
520
 
519
521
  # Iterate through the CSV data and populate the unique_patients dictionary
520
522
  for row in csv_data:
521
523
  patient_id = row.get('Patient ID')
524
+ surgery_date = row.get('Surgery Date')
525
+
522
526
  if patient_id not in unique_patients:
523
527
  unique_patients[patient_id] = row
528
+ patient_surgery_dates[patient_id] = [surgery_date]
524
529
  else:
525
530
  # If the patient ID already exists, compare surgery dates
526
531
  existing_row = unique_patients[patient_id]
527
- if row['Surgery Date'] < existing_row['Surgery Date']:
532
+ existing_date = existing_row['Surgery Date']
533
+
534
+ # Keep the most current demographic data (later surgery date takes precedence)
535
+ if surgery_date > existing_date:
536
+ # Store the old row's surgery date before replacing
537
+ old_date = existing_row['Surgery Date']
538
+ patient_surgery_dates[patient_id].append(old_date)
539
+ # Replace with newer row (better demographics)
528
540
  unique_patients[patient_id] = row
541
+ # Update the surgery dates list to reflect the new primary date
542
+ patient_surgery_dates[patient_id] = [surgery_date] + [d for d in patient_surgery_dates[patient_id] if d != surgery_date]
543
+ else:
544
+ # Add this surgery date to the list for this patient
545
+ if surgery_date not in patient_surgery_dates[patient_id]:
546
+ patient_surgery_dates[patient_id].append(surgery_date)
547
+
548
+ # Store the surgery dates information in the first row of each patient for later access
549
+ for patient_id, row in unique_patients.items():
550
+ row['_all_surgery_dates'] = sorted(patient_surgery_dates[patient_id])
551
+ row['_primary_surgery_date'] = row['Surgery Date'] # Keep track of which date has the demographics
529
552
 
530
553
  # Convert the unique_patients dictionary back to a list and sort it
531
554
  csv_data[:] = sorted(unique_patients.values(), key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip())) # TODO Does this need to be sorted twice? once before and once after?
@@ -1096,7 +1119,7 @@ def update_diagnosis_codes(csv_data):
1096
1119
  updated_count = 0
1097
1120
 
1098
1121
  # PERFORMANCE OPTIMIZATION: Single pass through CSV data with pre-processed lookups
1099
- # Update the "Default Diagnosis #1" column in the CSV data
1122
+ # Update the "Default Diagnosis #1" column in the CSV data and store diagnosis codes for all surgery dates
1100
1123
  for row_num, row in enumerate(csv_data, start=1):
1101
1124
  patient_id = row.get('Patient ID', '').strip()
1102
1125
  # Use pre-processed patient ID lookup for efficiency
@@ -1104,31 +1127,56 @@ def update_diagnosis_codes(csv_data):
1104
1127
  continue # Skip rows that do not match any patient ID
1105
1128
 
1106
1129
  MediLink_ConfigLoader.log("Processing row number {}.".format(row_num), level="DEBUG")
1107
- # Use pre-converted surgery date string for efficient lookup
1108
- surgery_date_str = surgery_date_strings.get(patient_id, '')
1109
-
1110
- MediLink_ConfigLoader.log("Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1111
-
1130
+
1131
+ # Get all surgery dates for this patient
1132
+ all_surgery_dates = row.get('_all_surgery_dates', [row.get('Surgery Date')])
1133
+
1134
+ # Create a mapping of surgery dates to diagnosis codes for this patient
1135
+ surgery_date_to_diagnosis = {}
1136
+
1112
1137
  if patient_id in all_patient_data:
1113
- if surgery_date_str in all_patient_data[patient_id]:
1114
- diagnosis_code, left_or_right_eye, femto_yes_or_no = all_patient_data[patient_id][surgery_date_str]
1115
- MediLink_ConfigLoader.log("Found diagnosis data for Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1116
-
1117
- # Convert diagnosis code to Medisoft shorthand format.
1118
- medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, None)
1119
- if medisoft_shorthand is None and diagnosis_code:
1120
- # Use fallback logic for missing mapping
1121
- defaulted_code = diagnosis_code.lstrip('H').lstrip('T8').replace('.', '')[-5:]
1122
- medisoft_shorthand = defaulted_code
1123
- MediLink_ConfigLoader.log("Missing diagnosis mapping for '{}', using fallback code '{}'".format(
1124
- diagnosis_code, medisoft_shorthand), level="WARNING")
1125
- MediLink_ConfigLoader.log("Converted diagnosis code to Medisoft shorthand: {}".format(medisoft_shorthand), level="DEBUG")
1138
+ # Process each surgery date for this patient
1139
+ for surgery_date in all_surgery_dates:
1140
+ # Convert surgery date to string format for lookup
1141
+ try:
1142
+ if hasattr(surgery_date, 'strftime'):
1143
+ surgery_date_str = surgery_date.strftime('%m-%d-%Y')
1144
+ else:
1145
+ surgery_date_str = str(surgery_date)
1146
+ except Exception:
1147
+ surgery_date_str = str(surgery_date)
1126
1148
 
1127
- row['Default Diagnosis #1'] = medisoft_shorthand
1128
- updated_count += 1
1129
- MediLink_ConfigLoader.log("Updated row number {} with new diagnosis code.".format(row_num), level="INFO")
1130
- else:
1131
- MediLink_ConfigLoader.log("No matching surgery date found for Patient ID: {} in row {}.".format(patient_id, row_num), level="INFO")
1149
+ MediLink_ConfigLoader.log("Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1150
+
1151
+ if surgery_date_str in all_patient_data[patient_id]:
1152
+ diagnosis_code, left_or_right_eye, femto_yes_or_no = all_patient_data[patient_id][surgery_date_str]
1153
+ MediLink_ConfigLoader.log("Found diagnosis data for Patient ID: {}, Surgery Date: {}".format(patient_id, surgery_date_str), level="DEBUG")
1154
+
1155
+ # Convert diagnosis code to Medisoft shorthand format.
1156
+ medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, None)
1157
+ if medisoft_shorthand is None and diagnosis_code:
1158
+ # Use fallback logic for missing mapping
1159
+ defaulted_code = diagnosis_code.lstrip('H').lstrip('T8').replace('.', '')[-5:]
1160
+ medisoft_shorthand = defaulted_code
1161
+ MediLink_ConfigLoader.log("Missing diagnosis mapping for '{}', using fallback code '{}'".format(
1162
+ diagnosis_code, medisoft_shorthand), level="WARNING")
1163
+ MediLink_ConfigLoader.log("Converted diagnosis code to Medisoft shorthand: {}".format(medisoft_shorthand), level="DEBUG")
1164
+
1165
+ surgery_date_to_diagnosis[surgery_date] = medisoft_shorthand
1166
+ else:
1167
+ MediLink_ConfigLoader.log("No matching surgery date found for Patient ID: {} on date {}.".format(patient_id, surgery_date_str), level="INFO")
1168
+ surgery_date_to_diagnosis[surgery_date] = 'N/A'
1169
+
1170
+ # Store the diagnosis mapping for all surgery dates
1171
+ row['_surgery_date_to_diagnosis'] = surgery_date_to_diagnosis
1172
+
1173
+ # Set the primary diagnosis code (for the main surgery date)
1174
+ primary_surgery_date = row.get('Surgery Date')
1175
+ primary_diagnosis = surgery_date_to_diagnosis.get(primary_surgery_date, 'N/A')
1176
+ row['Default Diagnosis #1'] = primary_diagnosis
1177
+
1178
+ updated_count += 1
1179
+ MediLink_ConfigLoader.log("Updated row number {} with diagnosis codes for {} surgery dates.".format(row_num, len(all_surgery_dates)), level="INFO")
1132
1180
  else:
1133
1181
  MediLink_ConfigLoader.log("Patient ID: {} not found in DOCX data for row {}.".format(patient_id, row_num), level="INFO")
1134
1182
 
MediBot/MediBot_UI.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #MediBot_UI.py
2
- import ctypes, time, re, os, sys
2
+ import ctypes, time, re
3
3
  from ctypes import wintypes
4
4
  from sys import exit
5
5
 
@@ -27,6 +27,69 @@ except ImportError:
27
27
  from MediCafe.core_utils import create_config_cache
28
28
  _get_config, (_config_cache, _crosswalk_cache) = create_config_cache()
29
29
 
30
+ def display_enhanced_patient_table(patient_info, title, show_line_numbers=True):
31
+ """
32
+ Display an enhanced patient table with multiple surgery dates and diagnosis codes.
33
+
34
+ Args:
35
+ patient_info: List of tuples (surgery_date, patient_name, patient_id, diagnosis_code, patient_row)
36
+ title: Title for the table section
37
+ show_line_numbers: Whether to show line numbers (True for new patients, False for existing)
38
+ """
39
+ if not patient_info:
40
+ return
41
+
42
+ print(title)
43
+ print()
44
+
45
+ # Sort by surgery date first and then by patient name
46
+ patient_info.sort(key=lambda x: (x[0], x[1]))
47
+
48
+ # Calculate column widths for proper alignment
49
+ max_patient_id_len = max(len(str(pid)) for _, _, pid, _, _ in patient_info)
50
+ max_patient_name_len = max(len(pname) for _, pname, _, _, _ in patient_info)
51
+ max_diagnosis_len = max(len(dcode) for _, _, _, dcode, _ in patient_info)
52
+
53
+ # Ensure minimum widths for readability
54
+ max_patient_id_len = max(max_patient_id_len, 5) # 5-digit ID max
55
+ max_patient_name_len = max(max_patient_name_len, 12) # "Patient Name" header
56
+ max_diagnosis_len = max(max_diagnosis_len, 10) # "Diagnosis" header
57
+
58
+ # Print the sorted patient info with multiple surgery dates
59
+ current_patient = None
60
+ line_number = 1
61
+
62
+ for surgery_date, patient_name, patient_id, diagnosis_code, patient_row in patient_info:
63
+ # Format surgery_date safely whether it's a datetime/date or a string
64
+ try:
65
+ formatted_date = surgery_date.strftime('%m-%d')
66
+ except Exception:
67
+ formatted_date = str(surgery_date)
68
+
69
+ # Transform diagnosis code display: show "-Not Found-" instead of "N/A"
70
+ display_diagnosis = "-Not Found-" if diagnosis_code == "N/A" else diagnosis_code
71
+
72
+ # Check if this is the same patient as the previous line
73
+ if current_patient == patient_id:
74
+ # Secondary surgery date - indent and show dashes for patient info
75
+ # Use exact character count matching for dashes
76
+ patient_id_dashes = '-' * len(str(patient_id))
77
+ patient_name_dashes = '-' * len(patient_name)
78
+
79
+ secondary_format = " {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
80
+ print(secondary_format.format(formatted_date, patient_id_dashes, patient_name_dashes, display_diagnosis))
81
+ else:
82
+ # New patient - show full patient info
83
+ current_patient = patient_id
84
+ if show_line_numbers:
85
+ primary_format = "{:03d}: {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
86
+ print(primary_format.format(line_number, formatted_date, str(patient_id), patient_name, display_diagnosis))
87
+ line_number += 1
88
+ else:
89
+ # For existing patients, don't show line numbers
90
+ primary_format = " {:<6} | {:<" + str(max_patient_id_len) + "} | {:<" + str(max_patient_name_len) + "} | {:<" + str(max_diagnosis_len) + "}"
91
+ print(primary_format.format(formatted_date, str(patient_id), patient_name, display_diagnosis))
92
+
30
93
  # Function to check if a specific key is pressed
31
94
  def _get_vk_codes():
32
95
  """Get VK codes from config."""
@@ -6,8 +6,6 @@ Contains functions for formatting various data types and handling CSV operations
6
6
 
7
7
  import os
8
8
  import sys
9
- import csv
10
- import json
11
9
  import re
12
10
  from datetime import datetime
13
11
 
@@ -7,7 +7,7 @@ new centralized smart import system, eliminating sys.path manipulation
7
7
  and circular import risks.
8
8
  """
9
9
 
10
- import os, subprocess, tempfile, traceback, re, time, sys
10
+ import os, sys
11
11
 
12
12
  # Add workspace directory to Python path for MediCafe imports
13
13
  current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -292,7 +292,7 @@ def compare_versions(version1, version2):
292
292
 
293
293
  def upgrade_package(package, retries=4, delay=2, target_version=None): # Updated retries to 4
294
294
  """
295
- Attempts to upgrade the package multiple times with delays in between.
295
+ Attempts to upgrade the package multiple times with escalating techniques.
296
296
  """
297
297
  if not check_internet_connection():
298
298
  print_status("No internet connection detected. Please check your internet connection and try again.", "ERROR")
@@ -302,82 +302,94 @@ def upgrade_package(package, retries=4, delay=2, target_version=None): # Update
302
302
  if target_version:
303
303
  print("Pinned target version: {}".format(target_version))
304
304
 
305
- for attempt in range(1, retries + 1):
306
- print("Attempt {}/{} to upgrade {}...".format(attempt, retries, package))
305
+ def get_installed_version_fresh(package):
306
+ """Get installed version using a fresh subprocess to avoid pkg_resources cache issues."""
307
+ try:
308
+ process = subprocess.Popen(
309
+ [sys.executable, '-m', 'pip', 'show', package],
310
+ stdout=subprocess.PIPE,
311
+ stderr=subprocess.PIPE
312
+ )
313
+ stdout, stderr = process.communicate()
314
+ if process.returncode == 0:
315
+ for line in stdout.decode().splitlines():
316
+ if line.startswith("Version:"):
317
+ return line.split(":", 1)[1].strip()
318
+ return None
319
+ except Exception as e:
320
+ print("Warning: Could not get fresh version: {}".format(e))
321
+ return None
322
+
323
+ def try_upgrade_with_strategy(attempt, strategy_name, cmd_args):
324
+ """Try upgrade with specific strategy and return success status."""
325
+ print("Attempt {}/{}: Using {} strategy...".format(attempt, retries, strategy_name))
307
326
 
308
- # Use a more compatible approach for Python 3.4
309
- # Try with --no-deps first to avoid dependency resolution issues
310
327
  pkg_spec = package
311
328
  if target_version:
312
329
  pkg_spec = "{}=={}".format(package, target_version)
313
330
 
314
- cmd = [
315
- sys.executable, '-m', 'pip', 'install', '--upgrade',
316
- '--no-deps', '--no-cache-dir', '--disable-pip-version-check', '-q', pkg_spec
317
- ]
331
+ cmd = [sys.executable, '-m', 'pip', 'install'] + cmd_args + [pkg_spec]
318
332
 
319
- print("Using pip upgrade with --no-deps and --no-cache-dir")
320
333
  process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
321
334
  stdout, stderr = process.communicate()
322
335
 
323
336
  if process.returncode == 0:
324
337
  print(stdout.decode().strip())
325
- new_version = get_installed_version(package) # Get new version after upgrade
338
+ # Add delay to allow file system to settle
339
+ time.sleep(1)
340
+ new_version = get_installed_version_fresh(package)
326
341
  expected_version = target_version or get_latest_version(package)
327
- if expected_version and compare_versions(new_version, expected_version) >= 0: # Compare versions
328
- if attempt == 1:
329
- print_status("Upgrade succeeded!", "SUCCESS")
330
- else:
331
- print_status("Attempt {}: Upgrade succeeded!".format(attempt), "SUCCESS")
332
- time.sleep(delay)
342
+
343
+ if expected_version and new_version and compare_versions(new_version, expected_version) >= 0:
344
+ print_status("Attempt {}: Upgrade succeeded with {}!".format(attempt, strategy_name), "SUCCESS")
333
345
  return True
334
346
  else:
335
- print_status("Upgrade incomplete. Current version: {} Expected at least: {}".format(new_version, expected_version), "WARNING")
336
- if attempt < retries:
337
- print("Retrying in {} seconds...".format(delay))
338
- try:
339
- time.sleep(delay + (random.random() * 0.5))
340
- except Exception:
341
- time.sleep(delay)
347
+ print_status("Upgrade incomplete. Current version: {} Expected at least: {}".format(
348
+ new_version or "unknown", expected_version), "WARNING")
349
+ return False
342
350
  else:
343
351
  print(stderr.decode().strip())
344
- print_status("Attempt {}: Upgrade failed with --no-deps.".format(attempt), "WARNING")
345
-
346
- # If --no-deps failed, try with --force-reinstall to bypass dependency issues
347
- if attempt < retries:
348
- print("Fallback this attempt: retrying with --force-reinstall...")
349
- pkg_spec = package
350
- if target_version:
351
- pkg_spec = "{}=={}".format(package, target_version)
352
-
353
- cmd = [
354
- sys.executable, '-m', 'pip', 'install', '--upgrade',
355
- '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '-q', pkg_spec
356
- ]
357
-
358
- process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
359
- stdout, stderr = process.communicate()
360
-
361
- if process.returncode == 0:
362
- print(stdout.decode().strip())
363
- new_version = get_installed_version(package)
364
- expected_version = target_version or get_latest_version(package)
365
- if expected_version and compare_versions(new_version, expected_version) >= 0:
366
- print_status("Attempt {}: Upgrade succeeded with --force-reinstall!".format(attempt), "SUCCESS")
367
- time.sleep(delay)
368
- return True
369
- else:
370
- print_status("Upgrade incomplete. Current version: {} Expected at least: {}".format(new_version, expected_version), "WARNING")
371
- else:
372
- print(stderr.decode().strip())
373
- print_status("Attempt {}: Upgrade failed with --force-reinstall.".format(attempt), "WARNING")
374
-
375
- if attempt < retries:
376
- print("Retrying in {} seconds...".format(delay))
377
- try:
378
- time.sleep(delay + (random.random() * 0.5))
379
- except Exception:
380
- time.sleep(delay)
352
+ print_status("Attempt {}: Upgrade failed with {}.".format(attempt, strategy_name), "WARNING")
353
+ return False
354
+
355
+ # Define escalation strategies for each attempt
356
+ strategies = {
357
+ 1: [
358
+ ("Gentle Upgrade", ['--upgrade', '--no-deps', '--no-cache-dir', '--disable-pip-version-check', '-q']),
359
+ ("Force Reinstall", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '-q'])
360
+ ],
361
+ 2: [
362
+ ("Clean Install", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '-q']),
363
+ ("User Install", ['--upgrade', '--user', '--no-cache-dir', '--disable-pip-version-check', '-q'])
364
+ ],
365
+ 3: [
366
+ ("Aggressive Clean", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '-q']),
367
+ ("Pre-download", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--pre', '-q'])
368
+ ],
369
+ 4: [
370
+ ("Nuclear Option", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '--pre', '-q']),
371
+ ("Last Resort", ['--upgrade', '--force-reinstall', '--no-cache-dir', '--disable-pip-version-check', '--ignore-installed', '--no-deps', '--pre', '--user', '-q'])
372
+ ]
373
+ }
374
+
375
+ for attempt in range(1, retries + 1):
376
+ print("Attempt {}/{} to upgrade {}...".format(attempt, retries, package))
377
+
378
+ # Try each strategy for this attempt
379
+ for strategy_name, cmd_args in strategies[attempt]:
380
+ if try_upgrade_with_strategy(attempt, strategy_name, cmd_args):
381
+ time.sleep(delay)
382
+ return True
383
+
384
+ # If we get here, all strategies for this attempt failed
385
+ if attempt < retries:
386
+ # Escalating delays: 2s, 3s, 5s
387
+ current_delay = delay + (attempt - 1)
388
+ print("All strategies failed for attempt {}. Retrying in {} seconds...".format(attempt, current_delay))
389
+ try:
390
+ time.sleep(current_delay + (random.random() * 1.0))
391
+ except Exception:
392
+ time.sleep(current_delay)
381
393
 
382
394
  print_status("All upgrade attempts failed.", "ERROR")
383
395
  return False
MediCafe/api_core.py CHANGED
@@ -7,7 +7,6 @@ COMPATIBILITY: Python 3.4.4 and Windows XP compatible
7
7
  """
8
8
 
9
9
  import time, json, os, traceback, sys, requests
10
- from datetime import datetime, timedelta
11
10
 
12
11
  # Import centralized logging configuration
13
12
  try:
MediLink/MediLink_Down.py CHANGED
@@ -279,15 +279,23 @@ def main(desired_endpoint=None):
279
279
  return None, None
280
280
 
281
281
 
282
- def check_for_new_remittances(config=None):
282
+ def check_for_new_remittances(config=None, is_boot_scan=False):
283
283
  """
284
284
  Function to check for new remittance files across all configured endpoints.
285
285
  Loads the configuration, validates it, and processes each endpoint to download and handle files.
286
286
  Accumulates results from all endpoints and processes them together at the end.
287
+
288
+ Args:
289
+ config: Configuration object
290
+ is_boot_scan: If True, suppresses "No records" message for boot-time scans
291
+
292
+ Returns:
293
+ bool: True if new records were found, False otherwise
287
294
  """
288
295
  # Start the process and log the initiation
289
296
  log("Starting check_for_new_remittances function")
290
- print("\nChecking for new files across all endpoints...")
297
+ if not is_boot_scan:
298
+ print("\nChecking for new files across all endpoints...")
291
299
  log("Checking for new files across all endpoints...")
292
300
 
293
301
  # Step 1: Load and validate the configuration
@@ -297,44 +305,95 @@ def check_for_new_remittances(config=None):
297
305
  medi = extract_medilink_config(config)
298
306
  if not medi or 'endpoints' not in medi:
299
307
  log("Error: Config is missing necessary sections. Aborting...", level="ERROR")
300
- return
308
+ return False
301
309
 
302
310
  endpoints = medi.get('endpoints')
303
311
  if not isinstance(endpoints, dict):
304
312
  log("Error: 'endpoints' is not a dictionary. Aborting...", level="ERROR")
305
- return
313
+ return False
314
+
315
+ # DIAGNOSTIC: Log endpoint configuration details
316
+ log("Found {} configured endpoints: {}".format(len(endpoints), list(endpoints.keys())), level="INFO")
317
+ for endpoint_key, endpoint_info in endpoints.items():
318
+ log("Endpoint '{}': session_name={}, remote_directory_down={}, has_filemask={}".format(
319
+ endpoint_key,
320
+ endpoint_info.get('session_name', 'NOT_SET'),
321
+ endpoint_info.get('remote_directory_down', 'NOT_SET'),
322
+ 'filemask' in endpoint_info
323
+ ), level="DEBUG")
306
324
 
307
325
  # Lists to accumulate all consolidated records and translated files across all endpoints
308
326
  all_consolidated_records = []
309
327
  all_translated_files = []
328
+ endpoint_results = {} # Track results per endpoint for diagnostics
310
329
 
311
330
  # Step 2: Process each endpoint and accumulate results
312
331
  for endpoint_key, endpoint_info in tqdm(endpoints.items(), desc="Processing endpoints"):
332
+ log("=== Processing endpoint: {} ===".format(endpoint_key), level="INFO")
333
+
313
334
  # Validate endpoint structure
314
335
  if not endpoint_info or not isinstance(endpoint_info, dict):
315
336
  log("Error: Invalid endpoint structure for {}. Skipping...".format(endpoint_key), level="ERROR")
337
+ endpoint_results[endpoint_key] = {"status": "error", "reason": "invalid_structure"}
316
338
  continue
317
339
 
318
340
  if 'remote_directory_down' in endpoint_info:
319
341
  # Process the endpoint and handle the files
320
- log("Processing endpoint: {}".format(endpoint_key))
342
+ log("Processing endpoint: {} with remote_directory_down: {}".format(
343
+ endpoint_key, endpoint_info.get('remote_directory_down')), level="INFO")
344
+
321
345
  consolidated_records, translated_files = process_endpoint(endpoint_key, endpoint_info, config)
322
346
 
347
+ # Track results for diagnostics
348
+ endpoint_results[endpoint_key] = {
349
+ "status": "processed",
350
+ "records_found": len(consolidated_records) if consolidated_records else 0,
351
+ "files_translated": len(translated_files) if translated_files else 0
352
+ }
353
+
323
354
  # Accumulate the results for later processing
324
355
  if consolidated_records:
325
356
  all_consolidated_records.extend(consolidated_records)
357
+ log("Added {} records from endpoint {}".format(len(consolidated_records), endpoint_key), level="INFO")
326
358
  if translated_files:
327
359
  all_translated_files.extend(translated_files)
360
+ log("Added {} translated files from endpoint {}".format(len(translated_files), endpoint_key), level="INFO")
328
361
  else:
329
362
  log("Skipping endpoint '{}'. 'remote_directory_down' not configured.".format(endpoint_info.get('name', 'Unknown')), level="WARNING")
363
+ endpoint_results[endpoint_key] = {"status": "skipped", "reason": "no_remote_directory_down"}
364
+
365
+ # DIAGNOSTIC: Log summary of endpoint processing
366
+ log("=== Endpoint Processing Summary ===", level="INFO")
367
+ for endpoint_key, result in endpoint_results.items():
368
+ if result["status"] == "processed":
369
+ log("Endpoint '{}': {} records found, {} files translated".format(
370
+ endpoint_key, result["records_found"], result["files_translated"]), level="INFO")
371
+ else:
372
+ log("Endpoint '{}': {} ({})".format(
373
+ endpoint_key, result["status"], result.get("reason", "unknown")), level="WARNING")
330
374
 
331
375
  # Step 3: After processing all endpoints, handle the accumulated results
332
376
  if all_consolidated_records:
377
+ log("Total records found across all endpoints: {}".format(len(all_consolidated_records)), level="INFO")
333
378
  display_consolidated_records(all_consolidated_records) # Ensure this is called only once
334
379
  prompt_csv_export(all_consolidated_records, medi.get('local_storage_path', '.'))
380
+ return True
335
381
  else:
336
382
  log("No records to display after processing all endpoints.", level="WARNING")
337
- print("No records to display after processing all endpoints.")
383
+ # Enhanced diagnostic message when no records found
384
+ if not is_boot_scan:
385
+ print("No records to display after processing all endpoints.")
386
+ print("\nDiagnostic Information:")
387
+ print("- Total endpoints configured: {}".format(len(endpoints)))
388
+ print("- Endpoints with remote_directory_down: {}".format(
389
+ sum(1 for ep in endpoints.values() if 'remote_directory_down' in ep)))
390
+ print("- Endpoints processed: {}".format(
391
+ sum(1 for result in endpoint_results.values() if result["status"] == "processed")))
392
+ print("- Endpoints skipped: {}".format(
393
+ sum(1 for result in endpoint_results.values() if result["status"] == "skipped")))
394
+ print("- Endpoints with errors: {}".format(
395
+ sum(1 for result in endpoint_results.values() if result["status"] == "error")))
396
+ return False
338
397
 
339
398
 
340
399
  def process_endpoint(endpoint_key, endpoint_info, config):
@@ -347,19 +406,205 @@ def process_endpoint(endpoint_key, endpoint_info, config):
347
406
  medi = extract_medilink_config(config)
348
407
  local_storage_path = medi.get('local_storage_path', '.')
349
408
  log("[Process Endpoint] Local storage path set to {}".format(local_storage_path))
409
+
410
+ # DIAGNOSTIC: Check WinSCP availability and configuration
411
+ try:
412
+ from MediLink_DataMgmt import get_winscp_path
413
+ winscp_path = get_winscp_path(config)
414
+ if os.path.exists(winscp_path):
415
+ log("[Process Endpoint] WinSCP found at: {}".format(winscp_path), level="INFO")
416
+ else:
417
+ log("[Process Endpoint] WinSCP not found at: {}".format(winscp_path), level="ERROR")
418
+ return [], []
419
+ except Exception as e:
420
+ log("[Process Endpoint] Error checking WinSCP path: {}".format(e), level="ERROR")
421
+ return [], []
422
+
423
+ # DIAGNOSTIC: Log endpoint configuration details
424
+ log("[Process Endpoint] Endpoint config - session_name: {}, remote_directory_down: {}, filemask: {}".format(
425
+ endpoint_info.get('session_name', 'NOT_SET'),
426
+ endpoint_info.get('remote_directory_down', 'NOT_SET'),
427
+ endpoint_info.get('filemask', 'NOT_SET')
428
+ ), level="DEBUG")
429
+
430
+ # DIAGNOSTIC: Check if we're in test mode
431
+ if config.get("MediLink_Config", {}).get("TestMode", False):
432
+ log("[Process Endpoint] Test mode is enabled - simulating download", level="WARNING")
433
+
350
434
  downloaded_files = operate_winscp("download", None, endpoint_info, local_storage_path, config)
351
435
 
352
436
  if downloaded_files:
353
437
  log("[Process Endpoint] WinSCP Downloaded the following files: \n{}".format(downloaded_files))
354
- return handle_files(local_storage_path, downloaded_files)
438
+ consolidated_records, translated_files = handle_files(local_storage_path, downloaded_files)
439
+ log("[Process Endpoint] File processing complete - {} records, {} translated files".format(
440
+ len(consolidated_records) if consolidated_records else 0,
441
+ len(translated_files) if translated_files else 0
442
+ ), level="INFO")
443
+ return consolidated_records, translated_files
355
444
  else:
356
- log("[Process Endpoint]No files were downloaded for endpoint: {}.".format(endpoint_key), level="WARNING")
445
+ log("[Process Endpoint] No files were downloaded for endpoint: {}.".format(endpoint_key), level="WARNING")
446
+
447
+ # DIAGNOSTIC: Check if WinSCP log exists and analyze it
448
+ try:
449
+ log_filename = "winscp_download.log"
450
+ log_path = os.path.join(local_storage_path, log_filename)
451
+ if os.path.exists(log_path):
452
+ log("[Process Endpoint] WinSCP log exists at: {}".format(log_path), level="INFO")
453
+ # Read last few lines of log for diagnostics
454
+ try:
455
+ with open(log_path, 'r') as f:
456
+ lines = f.readlines()
457
+ if lines:
458
+ last_lines = lines[-5:] # Last 5 lines
459
+ log("[Process Endpoint] Last 5 lines of WinSCP log:", level="DEBUG")
460
+ for line in last_lines:
461
+ log("[Process Endpoint] Log: {}".format(line.strip()), level="DEBUG")
462
+ except Exception as e:
463
+ log("[Process Endpoint] Error reading WinSCP log: {}".format(e), level="ERROR")
464
+ else:
465
+ log("[Process Endpoint] WinSCP log not found at: {}".format(log_path), level="WARNING")
466
+ except Exception as e:
467
+ log("[Process Endpoint] Error checking WinSCP log: {}".format(e), level="ERROR")
468
+
357
469
  return [], []
358
470
 
359
471
  except Exception as e:
360
472
  # Handle any exceptions that occur during the processing
361
473
  log("Error processing endpoint {}: {}".format(endpoint_key, e), level="ERROR")
474
+ import traceback
475
+ log("Full traceback: {}".format(traceback.format_exc()), level="DEBUG")
362
476
  return [], []
363
477
 
478
+ def test_endpoint_connectivity(config=None, endpoint_key=None):
479
+ """
480
+ Test basic connectivity to a specific endpoint or all endpoints.
481
+ This is a diagnostic function to help identify connection issues.
482
+
483
+ Args:
484
+ config: Configuration object
485
+ endpoint_key: Specific endpoint to test, or None for all endpoints
486
+
487
+ Returns:
488
+ dict: Results of connectivity tests
489
+ """
490
+ if config is None:
491
+ config, _ = load_configuration()
492
+
493
+ medi = extract_medilink_config(config)
494
+ if not medi or 'endpoints' not in medi:
495
+ log("Error: Config is missing necessary sections.", level="ERROR")
496
+ return {}
497
+
498
+ endpoints = medi.get('endpoints')
499
+ results = {}
500
+
501
+ # Determine which endpoints to test
502
+ if endpoint_key:
503
+ if endpoint_key in endpoints:
504
+ test_endpoints = {endpoint_key: endpoints[endpoint_key]}
505
+ else:
506
+ log("Error: Endpoint '{}' not found in configuration.".format(endpoint_key), level="ERROR")
507
+ return {}
508
+ else:
509
+ test_endpoints = endpoints
510
+
511
+ log("Testing connectivity for {} endpoint(s)...".format(len(test_endpoints)), level="INFO")
512
+
513
+ for ep_key, ep_info in test_endpoints.items():
514
+ log("Testing endpoint: {}".format(ep_key), level="INFO")
515
+ result = {"status": "unknown", "details": []}
516
+
517
+ # Check basic configuration
518
+ if not ep_info.get('session_name'):
519
+ result["status"] = "error"
520
+ result["details"].append("Missing session_name")
521
+ elif not ep_info.get('remote_directory_down'):
522
+ result["status"] = "error"
523
+ result["details"].append("Missing remote_directory_down")
524
+ else:
525
+ result["details"].append("Configuration appears valid")
526
+
527
+ # Check WinSCP availability
528
+ try:
529
+ from MediLink_DataMgmt import get_winscp_path
530
+ winscp_path = get_winscp_path(config)
531
+ if os.path.exists(winscp_path):
532
+ result["details"].append("WinSCP found at: {}".format(winscp_path))
533
+ else:
534
+ result["status"] = "error"
535
+ result["details"].append("WinSCP not found at: {}".format(winscp_path))
536
+ except Exception as e:
537
+ result["status"] = "error"
538
+ result["details"].append("Error checking WinSCP: {}".format(e))
539
+
540
+ # Check test mode
541
+ if config.get("MediLink_Config", {}).get("TestMode", False):
542
+ result["details"].append("Test mode is enabled - no real connection will be made")
543
+ result["status"] = "test_mode"
544
+
545
+ results[ep_key] = result
546
+
547
+ return results
548
+
549
+
364
550
  if __name__ == "__main__":
365
- main()
551
+ import sys
552
+
553
+ print("=" * 60)
554
+ print("MediLink_Down Standalone Testing Tool")
555
+ print("=" * 60)
556
+ print()
557
+
558
+ # Check if endpoint was provided as command line argument
559
+ if len(sys.argv) > 1:
560
+ desired_endpoint = sys.argv[1]
561
+ print("Testing specific endpoint: {}".format(desired_endpoint))
562
+ print()
563
+ main(desired_endpoint)
564
+ else:
565
+ # No specific endpoint provided - run connectivity diagnostics
566
+ print("No specific endpoint provided.")
567
+ print("Running connectivity diagnostics for all endpoints...")
568
+ print()
569
+
570
+ try:
571
+ config, _ = load_configuration()
572
+ connectivity_results = test_endpoint_connectivity(config)
573
+
574
+ if connectivity_results:
575
+ print("Connectivity Test Results:")
576
+ print("-" * 40)
577
+
578
+ for endpoint, result in connectivity_results.items():
579
+ status = result["status"]
580
+ details = result["details"]
581
+
582
+ if status == "error":
583
+ print("[ERROR] {}: {}".format(endpoint, status))
584
+ elif status == "test_mode":
585
+ print("[TEST] {}: {} (Test Mode)".format(endpoint, status))
586
+ else:
587
+ print("[OK] {}: {}".format(endpoint, status))
588
+
589
+ for detail in details:
590
+ print(" - {}".format(detail))
591
+ print()
592
+
593
+ # Show available endpoints for testing
594
+ medi = extract_medilink_config(config)
595
+ endpoints = medi.get('endpoints', {})
596
+ if endpoints:
597
+ print("Available endpoints for testing:")
598
+ print("-" * 30)
599
+ for endpoint in endpoints.keys():
600
+ print(" - {}".format(endpoint))
601
+ print()
602
+ print("To test a specific endpoint, run:")
603
+ print(" python MediLink_Down.py <endpoint_name>")
604
+ else:
605
+ print("ERROR: No connectivity test results returned.")
606
+
607
+ except Exception as e:
608
+ print("ERROR: Failed to run diagnostics: {}".format(e))
609
+ import traceback
610
+ traceback.print_exc()
MediLink/MediLink_main.py CHANGED
@@ -140,6 +140,21 @@ def main_menu():
140
140
  if PERFORMANCE_LOGGING:
141
141
  print("Test mode check completed in {:.2f} seconds".format(test_mode_end - test_mode_start))
142
142
 
143
+ # Boot-time one-time ack poll (silent policy: just show summary output)
144
+ try:
145
+ print("\nChecking acknowledgements (boot-time scan)...")
146
+ ack_result = MediLink_Down.check_for_new_remittances(config, is_boot_scan=True)
147
+ _last_ack_updated_at = int(time.time())
148
+ except Exception:
149
+ ack_result = False
150
+ pass
151
+
152
+ # Clear screen before showing menu header
153
+ try:
154
+ os.system('cls' if os.name == 'nt' else 'clear')
155
+ except Exception:
156
+ pass # Fallback if cls/clear fails
157
+
143
158
  # Display Welcome Message
144
159
  welcome_start = time.time()
145
160
  MediLink_UI.display_welcome()
@@ -147,6 +162,11 @@ def main_menu():
147
162
  if PERFORMANCE_LOGGING:
148
163
  print("Welcome display completed in {:.2f} seconds".format(welcome_end - welcome_start))
149
164
 
165
+ # Show message if new records were found during boot-time scan
166
+ if ack_result:
167
+ print("\n[INFO] New records were found during the boot-time acknowledgement scan.")
168
+ print("You can view them by selecting 'Check for new remittances' from the menu.")
169
+
150
170
  # Normalize the directory path for file operations.
151
171
  path_norm_start = time.time()
152
172
  medi = extract_medilink_config(config)
@@ -205,14 +225,6 @@ def main_menu():
205
225
  end_date_str = end_date.strftime('%m/%d/%Y')
206
226
  start_date_str = start_date.strftime('%m/%d/%Y')
207
227
 
208
- # Boot-time one-time ack poll (silent policy: just show summary output)
209
- try:
210
- print("\nChecking acknowledgements (boot-time scan)...")
211
- MediLink_Down.check_for_new_remittances(config)
212
- _last_ack_updated_at = int(time.time())
213
- except Exception:
214
- pass
215
-
216
228
  while True:
217
229
  # Run any due scheduled ack checks before showing menu
218
230
  try:
@@ -221,7 +233,7 @@ def main_menu():
221
233
  due = [t for t in _scheduled_ack_checks if t <= now_ts]
222
234
  if due:
223
235
  print("\nAuto-checking acknowledgements (scheduled)...")
224
- MediLink_Down.check_for_new_remittances(config)
236
+ MediLink_Down.check_for_new_remittances(config, is_boot_scan=False)
225
237
  _last_ack_updated_at = now_ts
226
238
  # remove executed
227
239
  _scheduled_ack_checks = [t for t in _scheduled_ack_checks if t > now_ts]
@@ -255,11 +267,30 @@ def main_menu():
255
267
  if choice == '1':
256
268
  # Handle remittance checking.
257
269
  remittance_start = time.time()
258
- MediLink_Down.check_for_new_remittances(config)
270
+ result = MediLink_Down.check_for_new_remittances(config, is_boot_scan=False)
259
271
  _last_ack_updated_at = int(time.time())
260
272
  remittance_end = time.time()
261
273
  if PERFORMANCE_LOGGING:
262
274
  print("Remittance check completed in {:.2f} seconds".format(remittance_end - remittance_start))
275
+
276
+ # If no records found, offer connectivity diagnostics
277
+ if not result:
278
+ print("\nNo records found. Would you like to run connectivity diagnostics? (y/n): ", end="")
279
+ try:
280
+ diagnostic_choice = input().strip().lower()
281
+ if diagnostic_choice in ['y', 'yes']:
282
+ print("\nRunning connectivity diagnostics...")
283
+ connectivity_results = MediLink_Down.test_endpoint_connectivity(config)
284
+ print("\nConnectivity Test Results:")
285
+ for endpoint, result in connectivity_results.items():
286
+ print(" {}: {} - {}".format(
287
+ endpoint,
288
+ result["status"],
289
+ "; ".join(result["details"])
290
+ ))
291
+ except Exception:
292
+ pass # Ignore input errors
293
+
263
294
  # UX hint: suggest deeper United details
264
295
  try:
265
296
  print("Tip: For United details, run the United Claims Status option for the same date window.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250813.0
3
+ Version: 0.250813.2
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2/MediCafe
6
6
  Author: Daniel Vidaud
@@ -1,25 +1,25 @@
1
1
  MediBot/MediBot.bat,sha256=el_8wWuikLkL-cmMX63L3VC0EqcuulkIFaT4xv7suzY,26687
2
- MediBot/MediBot.py,sha256=sHE2vqaXvjiISKuvTvymhur3Ho9smVKuvvPvHbgnluc,36019
2
+ MediBot/MediBot.py,sha256=9u22KWhA7S3gKEXdo_4jnUFXpKqQb2lEeqsVIVSI62k,37089
3
3
  MediBot/MediBot_Charges.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- MediBot/MediBot_Crosswalk_Library.py,sha256=K_cz2o1e86qKKRRDcjb6eu5T6rtBgOK-R1nA8t8Z8QA,24957
4
+ MediBot/MediBot_Crosswalk_Library.py,sha256=jIaYdoxfT9YgQ5dWZC4jmTYxRX1Y14X-AJ6YEjR58Gc,25158
5
5
  MediBot/MediBot_Crosswalk_Utils.py,sha256=KVq2budurwdHB7dglOuPZEQGup-hjD1SeSPyySLpy9M,39015
6
6
  MediBot/MediBot_Post.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  MediBot/MediBot_Preprocessor.py,sha256=zAcfyuE8wl9JRzLGsUnnXiHxAr-hbCCIB2M-Jb3LUqI,16203
8
- MediBot/MediBot_Preprocessor_lib.py,sha256=oHQ6_3RUR-X6tikKPpVjOMwHjIRgZ9IKARgje8tj8l0,73162
9
- MediBot/MediBot_UI.py,sha256=h-vcf5zMPgbK2uiwOFaqUDHqgZ-sKZJ5VvaoTdpjMtE,14305
10
- MediBot/MediBot_dataformat_library.py,sha256=dxh_SQQMEd9nCEeuBr-6E_Uu6enjLXeoyLpd45nUcZk,10771
8
+ MediBot/MediBot_Preprocessor_lib.py,sha256=BiVbbDTSHXLYVxxSxr7oo7Z6-Jqjsb49YWKoTO7fzTU,76110
9
+ MediBot/MediBot_UI.py,sha256=DjdSSljLnbaoij04AA8F55JQ5xmp5PYMrlyrnWEZ6D8,17659
10
+ MediBot/MediBot_dataformat_library.py,sha256=D46fdPtxcgfWTzaLBtSvjtozzZBNqNiODgu4vKMZrBg,10746
11
11
  MediBot/MediBot_docx_decoder.py,sha256=gn7I7Ng5khVIzU0HTTOqi31YSSn1yW8Pyk-i_P9r1oA,32472
12
- MediBot/MediBot_smart_import.py,sha256=_ggP0I9djNwMGck04U6fNahrH7frVkoM8OzjtHbLUc0,6727
12
+ MediBot/MediBot_smart_import.py,sha256=Emvz7NwemHGCHvG5kZcUyXMcCheidbGKaPfOTg-YCEs,6684
13
13
  MediBot/MediPost.py,sha256=C1hZJFr65rN6F_dckjdBxFC0vL2CoqY9W3YFqU5HXtE,336
14
14
  MediBot/PDF_to_CSV_Cleaner.py,sha256=ZZphmq-5K04DkrZNlcwNAIoZPOD_ROWvS3PMkKFxeiM,8799
15
15
  MediBot/__init__.py,sha256=6IdVLXaWxV5ZdpefonWrC1R8RsJn4V26K0PmUEZ_vU8,3192
16
16
  MediBot/get_medicafe_version.py,sha256=uyL_UIE42MyFuJ3SRYxJp8sZx8xjTqlYZ3FdQuxLduY,728
17
17
  MediBot/update_json.py,sha256=vvUF4mKCuaVly8MmoadDO59M231fCIInc0KI1EtDtPA,3704
18
- MediBot/update_medicafe.py,sha256=KuoGOsSvnNis9EgpAZe_j0Ny6ExVav3fnRQCAu4evnk,28560
18
+ MediBot/update_medicafe.py,sha256=VTcQA_tfVILSAV29DM8nG-X4RJAQYdEiXY6oaeZfy4I,29072
19
19
  MediCafe/MediLink_ConfigLoader.py,sha256=Ia79dZQBvgbc6CtOaNZVlFHaN-fvUmJRpmmVHz_MFv8,8205
20
20
  MediCafe/__init__.py,sha256=DF0XUu3G43AejXvEmd5aCyy0GDQahQD0pMwexmxem-E,5477
21
21
  MediCafe/__main__.py,sha256=mRNyk3D9Ilnu2XhgVI_rut7r5Ro7UIKtwV871giAHI8,12992
22
- MediCafe/api_core.py,sha256=rF-8XNc6ILSsoD_YQV-L9R_nW9_XAd0D4VMgqAMY5U4,66420
22
+ MediCafe/api_core.py,sha256=IZaBXnP4E7eHzxVbCk2HtxywiVBuhaUyHeaqss8llgY,66378
23
23
  MediCafe/api_core_backup.py,sha256=Oy_Fqt0SEvGkQN1Oqw5iUPVFxPEokyju5CuPEb9k0OY,18686
24
24
  MediCafe/api_factory.py,sha256=I5AeJoyu6m7oCrjc2OvVvO_4KSBRutTsR1riiWhTZV0,12086
25
25
  MediCafe/api_utils.py,sha256=KWQB0q1k5E6frOFFlKWcFpHNcqfrS7KJ_82672wbupw,14041
@@ -49,7 +49,7 @@ MediLink/MediLink_Decoder.py,sha256=1gzdybNg4Vv69s5PNbX8bPNrXT_N_kPpFpt2HpkauWA,
49
49
  MediLink/MediLink_Deductible.py,sha256=fLBDQHDcTk86JtJUtUwrVl-o0KfNackFrWosMxr7qHU,45559
50
50
  MediLink/MediLink_Deductible_Validator.py,sha256=2g-lZd-Y5fJ1mfP87vM6oABg0t5Om-7EkEkilVvDWYY,22888
51
51
  MediLink/MediLink_Display_Utils.py,sha256=QyHk23VU1rJtNZr_QhtL76Avo66CEc7MZU84uIs-1Lo,4187
52
- MediLink/MediLink_Down.py,sha256=us3xKM5AcGpvqnbrKkV8iEt7MmLCkSU7CDFfCoqXO4o,16201
52
+ MediLink/MediLink_Down.py,sha256=q4ByEh1h1WSHUyRy68e8wT8pXMXP6q8NaqS1LKveMFo,28093
53
53
  MediLink/MediLink_ERA_decoder.py,sha256=MiOtDcXnmevPfHAahIlTLlUc14VcQWAor9Xa7clA2Ts,8710
54
54
  MediLink/MediLink_Gmail.py,sha256=8iQjqcJMSa_Zfr5azR0dShKAQeXqt-9C-s8seYB9pic,23961
55
55
  MediLink/MediLink_GraphQL.py,sha256=O6OCaumT0zIC7YcIAwLOOYxiQnYhoMc48UL8ilNIBec,45720
@@ -64,7 +64,7 @@ MediLink/MediLink_Up.py,sha256=QFdUtpEySc7ceZfFJ2q9XWClnhYJssG-UywFFedlv9w,34899
64
64
  MediLink/MediLink_api_utils.py,sha256=dsGLRPRvSwfXPLrrfgnkIKGDIF00wE93TrDB6HMDPQU,11857
65
65
  MediLink/MediLink_batch.bat,sha256=nqL5QwCLyRQFSPdv6kgtcV_cpky7FXSOWVl6OxjRXb4,118
66
66
  MediLink/MediLink_insurance_utils.py,sha256=g741Fj2K26cMy0JX5d_XavMw9LgkK6hjaUJYfysT7t8,9301
67
- MediLink/MediLink_main.py,sha256=iyMcEToFl2aPHP6xE51OnHQqqbGBh0owRqUfFV1F01M,21745
67
+ MediLink/MediLink_main.py,sha256=Y26Bl_7KNIbz18lbgK-18dkqANfWK6QO4sQLFFRQGGw,23337
68
68
  MediLink/MediLink_smart_import.py,sha256=B5SfBn_4bYEWJJDolXbjnwKx_-MaqGZ76LyXQwWDV80,9838
69
69
  MediLink/Soumit_api.py,sha256=5JfOecK98ZC6NpZklZW2AkOzkjvrbYxpJpZNH3rFxDw,497
70
70
  MediLink/__init__.py,sha256=Z4Uxt4XZk4n-GwAkUoEeFiL-D7xHbttYiiWGjgKT_ng,3391
@@ -77,9 +77,9 @@ MediLink/test_cob_library.py,sha256=wUMv0-Y6fNsKcAs8Z9LwfmEBRO7oBzBAfWmmzwoNd1g,
77
77
  MediLink/test_timing.py,sha256=yH2b8QPLDlp1Zy5AhgtjzjnDHNGhAD16ZtXtZzzESZw,2042
78
78
  MediLink/test_validation.py,sha256=FJrfdUFK--xRScIzrHCg1JeGdm0uJEoRnq6CgkP2lwM,4154
79
79
  MediLink/webapp.html,sha256=JPKT559aFVBi1r42Hz7C77Jj0teZZRumPhBev8eSOLk,19806
80
- medicafe-0.250813.0.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
81
- medicafe-0.250813.0.dist-info/METADATA,sha256=0dCrp1oFMB5Y1CdIUbqVjsuXrm0JFUionXfh9D7TuKk,3384
82
- medicafe-0.250813.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
83
- medicafe-0.250813.0.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
84
- medicafe-0.250813.0.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
85
- medicafe-0.250813.0.dist-info/RECORD,,
80
+ medicafe-0.250813.2.dist-info/LICENSE,sha256=65lb-vVujdQK7uMH3RRJSMwUW-WMrMEsc5sOaUn2xUk,1096
81
+ medicafe-0.250813.2.dist-info/METADATA,sha256=2sZpkNcltzPkLNnx5QqEFpnZbYEd9vQT16cyBqGPWmc,3384
82
+ medicafe-0.250813.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
83
+ medicafe-0.250813.2.dist-info/entry_points.txt,sha256=m3RBUBjr-xRwEkKJ5W4a7NlqHZP_1rllGtjZnrRqKe8,52
84
+ medicafe-0.250813.2.dist-info/top_level.txt,sha256=U6-WBJ9RCEjyIs0BlzbQq_PwedCp_IV9n1616NNV5zA,26
85
+ medicafe-0.250813.2.dist-info/RECORD,,