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 +48 -28
- MediBot/MediBot_Crosswalk_Library.py +6 -6
- MediBot/MediBot_Preprocessor_lib.py +74 -26
- MediBot/MediBot_UI.py +64 -1
- MediBot/MediBot_dataformat_library.py +0 -2
- MediBot/MediBot_smart_import.py +1 -1
- MediBot/update_medicafe.py +73 -61
- MediCafe/api_core.py +0 -1
- MediLink/MediLink_Down.py +254 -9
- MediLink/MediLink_main.py +41 -10
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/METADATA +1 -1
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/RECORD +16 -16
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/LICENSE +0 -0
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/WHEEL +0 -0
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/entry_points.txt +0 -0
- {medicafe-0.250813.0.dist-info → medicafe-0.250813.2.dist-info}/top_level.txt +0 -0
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
|
-
|
618
|
-
if
|
619
|
-
|
620
|
-
|
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
|
623
|
-
patient_info.append(('Unknown Date', patient_name, patient_id)) # Append with 'Unknown Date' if there's an error
|
624
|
-
|
625
|
-
#
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
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
|
-
|
646
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
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
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
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
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
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
|
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."""
|
MediBot/MediBot_smart_import.py
CHANGED
@@ -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,
|
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__))
|
MediBot/update_medicafe.py
CHANGED
@@ -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
|
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
|
-
|
306
|
-
|
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
|
-
|
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
|
-
|
328
|
-
|
329
|
-
|
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(
|
336
|
-
|
337
|
-
|
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
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
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
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
|
-
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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,25 +1,25 @@
|
|
1
1
|
MediBot/MediBot.bat,sha256=el_8wWuikLkL-cmMX63L3VC0EqcuulkIFaT4xv7suzY,26687
|
2
|
-
MediBot/MediBot.py,sha256=
|
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=
|
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=
|
9
|
-
MediBot/MediBot_UI.py,sha256=
|
10
|
-
MediBot/MediBot_dataformat_library.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
81
|
-
medicafe-0.250813.
|
82
|
-
medicafe-0.250813.
|
83
|
-
medicafe-0.250813.
|
84
|
-
medicafe-0.250813.
|
85
|
-
medicafe-0.250813.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|