medicafe 0.240419.2__py3-none-any.whl → 0.240517.0__py3-none-any.whl

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

Potentially problematic release.


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

@@ -1,8 +1,32 @@
1
1
  import re
2
2
  from datetime import datetime
3
3
  import re #for addresses
4
- from MediBot_Preprocessor import open_csv_for_editing, config, initialize
5
- from MediBot_UI import manage_script_pause
4
+
5
+ # Add parent directory of the project to the Python path
6
+ import os
7
+ import sys
8
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
9
+ sys.path.append(project_dir)
10
+
11
+ try:
12
+ from MediLink import MediLink_ConfigLoader
13
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
14
+ except ImportError:
15
+ from MediLink_ConfigLoader import load_configuration
16
+ config, crosswalk = load_configuration()
17
+
18
+ from MediBot_Preprocessor_lib import open_csv_for_editing, initialize
19
+ from MediBot_UI import manage_script_pause, app_control
20
+
21
+
22
+ """
23
+ - [X] (TEST) Address Formatting 30-character Limit:
24
+ (LOW) Address the issue where the format_street function in Medibot may produce addresses exceeding
25
+ the 30-character limit. Current stop-gap is removing period characters and the abbreviation "APT"
26
+ surrounded by spaces from all records as a temporary solution.
27
+ If the address still exceeds 30 characters, the function will attempt to remove spaces from right to left
28
+ until it reaches 30 significant digits or runs out of spaces, then truncate to 30 characters if necessary.
29
+ """
6
30
 
7
31
  # Bring in all the constants
8
32
  initialize(config)
@@ -11,11 +35,13 @@ initialize(config)
11
35
  def format_name(value):
12
36
  if ',' in value:
13
37
  return value
14
- hyphenated_name_pattern = r'(?P<First>[\w-]+)\s+(?P<Middle>[\w-]+)?\s+(?P<Last>[\w-]+)'
38
+ hyphenated_name_pattern = r'(?P<First>[\w-]+)\s+(?P<Middle>[\w-]?)\s+(?P<Last>[\w-]+)'
15
39
  match = re.match(hyphenated_name_pattern, value)
16
40
  if match:
17
41
  first_name = match.group('First')
18
42
  middle_name = match.group('Middle') or ''
43
+ if len(middle_name) > 1:
44
+ middle_name = middle_name[0] # take only the first character
19
45
  last_name = match.group('Last')
20
46
  return '{}, {} {}'.format(last_name, first_name, middle_name).strip()
21
47
  parts = value.split()
@@ -43,70 +69,97 @@ def format_policy(value):
43
69
  def format_gender(value):
44
70
  return value[0].upper()
45
71
 
72
+ def enforce_significant_length(output):
73
+ # Replace spaces with a placeholder that counts as one significant digit
74
+ temp_output = output.replace('{Space}', ' ')
75
+
76
+ # Check if the number of significant digits exceeds 30
77
+ if len(temp_output) > 30:
78
+
79
+ # First line of defense: Replace ' APT ' with ' #' if the original length is longer than 30 characters.
80
+ temp_output = temp_output.replace(' APT ', ' #')
81
+
82
+ # Remove spaces in a controlled manner from right to left if still too long
83
+ while len(temp_output) > 30:
84
+ # Find the last space
85
+ last_space_index = temp_output.rfind(' ')
86
+ if last_space_index == -1:
87
+ break
88
+ # Remove the last space
89
+ temp_output = temp_output[:last_space_index] + temp_output[last_space_index+7:]
90
+
91
+ # If still greater than 30, truncate to 30 characters
92
+ if len(temp_output) > 30:
93
+ temp_output = temp_output[:30]
94
+
95
+ # Replace placeholder back with actual space for final return
96
+ return temp_output.replace(' ', '{Space}')
97
+
46
98
  def format_street(value, csv_data, reverse_mapping, parsed_address_components):
47
- global script_paused
48
- script_paused = False
99
+ # Temporarily disable script pause status
100
+ app_control.set_pause_status(False)
49
101
 
50
- # Remove periods from the input (seems to be an XP-only issue?)
102
+ # Remove period characters.
51
103
  value = value.replace('.', '')
52
104
 
53
- # Only proceed with parsing if a comma is present in the value
105
+ # Proceed only if there's a comma, indicating a likely full address
54
106
  if ',' in value:
55
107
  try:
56
- # Access the common cities from the loaded configuration
108
+ MediLink_ConfigLoader.log("Attempting to resolve address via regex...")
109
+ # Retrieve common city names from configuration and prepare a regex pattern
57
110
  common_cities = config.get('cities', [])
58
-
59
- # Convert cities to a case-insensitive regex pattern
60
111
  city_pattern = '|'.join(re.escape(city) for city in common_cities)
61
112
  city_regex_pattern = r'(?P<City>{})'.format(city_pattern)
62
113
  city_regex = re.compile(city_regex_pattern, re.IGNORECASE)
63
114
 
64
- # Check if the address contains one of the common cities
115
+ # Search for a common city in the address
65
116
  city_match = city_regex.search(value)
66
117
 
67
118
  if city_match:
68
- city = city_match.group('City').upper() # Normalize city name to uppercase
119
+ # Extract city name and partition the value around it
120
+ city = city_match.group('City').upper()
69
121
  street, _, remainder = value.partition(city)
70
-
71
- # Extract state and zip code from the remainder
122
+
123
+ # Regex pattern to find state and zip code in the remainder
72
124
  address_pattern = r',\s*(?P<State>[A-Z]{2})\s*(?P<Zip>\d{5}(?:-\d{4})?)?'
73
-
74
125
  match = re.search(address_pattern, remainder)
75
-
126
+
76
127
  if match:
77
- # Update global parsed address components
128
+ # Update parsed address components
78
129
  parsed_address_components['City'] = city
79
130
  parsed_address_components['State'] = match.group('State')
80
131
  parsed_address_components['Zip Code'] = match.group('Zip')
81
-
82
- return street.strip() # Return formatted street address
132
+ # Return formatted street address, enforcing significant length
133
+ return enforce_significant_length(street.strip())
83
134
  else:
84
- # Fallback to old regex
85
- # value = street + ', ' + city + remainder
135
+ # Fallback regex for parsing addresses without a common city
86
136
  address_pattern = r'(?P<Street>[\w\s]+),?\s+(?P<City>[\w\s]+),\s*(?P<State>[A-Z]{2})\s*(?P<Zip>\d{5}(-\d{4})?)'
87
137
  match = re.match(address_pattern, value)
88
138
 
89
139
  if match:
90
- # Update global parsed address components
140
+ # Update parsed address components
91
141
  parsed_address_components['City'] = match.group('City')
92
142
  parsed_address_components['State'] = match.group('State')
93
143
  parsed_address_components['Zip Code'] = match.group('Zip')
94
-
95
- return match.group('Street').strip() # Return formatted street address
144
+ # Return formatted street address, enforcing significant length
145
+ return enforce_significant_length(match.group('Street').strip())
96
146
 
97
147
  except Exception as e:
148
+ # Handle exceptions by logging and offering to correct data manually
98
149
  print("Address format error: Unable to parse address '{}'. Error: {}".format(value, e))
99
- print("Please update the CSV file for this record.")
100
- script_paused = True
101
- open_csv_for_editing(CSV_FILE_PATH) # Offer to open the CSV for manual correction
102
- manage_script_pause(csv_data, e, reverse_mapping)
103
- return value.replace(' ', '{Space}') # Fallback to return original value with spaces escaped
150
+ app_control.set_pause_status(True)
151
+ open_csv_for_editing(CSV_FILE_PATH)
152
+ manage_script_pause(csv_data, e, reverse_mapping)
153
+ # Return original value with spaces formatted, enforcing significant length
154
+ return enforce_significant_length(value.replace(' ', '{Space}'))
104
155
  else:
105
- # If no comma is present, return the value as is, assuming it's just a street name
106
- return value.replace(' ', '{Space}')
107
-
108
- # This return acts as a fallback in case the initial comma check passes but no address components are matched
109
- return value.replace(' ', '{Space}')
156
+ # If no comma is present, treat the input as a simple street name
157
+ formatted_value = value.replace(' ', '{Space}')
158
+ enforced_format = enforce_significant_length(formatted_value)
159
+ return enforced_format
160
+
161
+ # Fallback return in case no address components are matched even though a comma was present
162
+ return enforce_significant_length(value.replace(' ', '{Space}'))
110
163
 
111
164
  def format_zip(value):
112
165
  # Ensure the value is a string, in case it's provided as an integer
@@ -138,7 +191,7 @@ def format_data(medisoft_field, value, csv_data, reverse_mapping, parsed_address
138
191
  elif medisoft_field == 'Secondary Group Number':
139
192
  formatted_value = format_policy(value)
140
193
  else:
141
- formatted_value = value
194
+ formatted_value = str(value) # Ensure value is always a string
142
195
 
143
196
  formatted_value = formatted_value.replace(',', '{,}').replace(' ', '{Space}')
144
197
  ahk_command = 'SendInput, {}{{Enter}}'.format(formatted_value)
@@ -0,0 +1,80 @@
1
+ """
2
+ Using docx-utils 0.1.3,
3
+
4
+ This script parses a .docx file containing a table of patient information and extracts
5
+ relevant data into a dictionary. Each row in the table corresponds to a new patient,
6
+ and the data from each cell is parsed into specific variables. The resulting dictionary
7
+ uses the 'Patient ID Number' as keys and lists containing 'Diagnosis Code',
8
+ 'Left or Right Eye', and 'Femto yes or no' as values.
9
+
10
+ Functions:
11
+ parse_docx(filepath): Reads the .docx file and constructs the patient data dictionary.
12
+ parse_patient_id(text): Extracts the Patient ID Number from the text.
13
+ parse_diagnosis_code(text): Extracts the Diagnosis Code from the text.
14
+ parse_left_or_right_eye(text): Extracts the eye information (Left or Right) from the text.
15
+ parse_femto_yes_or_no(text): Extracts the Femto information (yes or no) from the text.
16
+ """
17
+
18
+ from docx import Document
19
+
20
+ def parse_docx(filepath):
21
+ # Open the .docx file
22
+ doc = Document(filepath)
23
+
24
+ # Initialize the dictionary to store data
25
+ patient_data = {}
26
+
27
+ # Assuming the first table contains the required data
28
+ table = doc.tables[0]
29
+
30
+ # Iterate over the rows in the table
31
+ for row in table.rows[1:]: # Skip header row if it exists
32
+ cells = row.cells
33
+
34
+ # Extract and parse data from each cell
35
+ patient_id = parse_patient_id(cells[0].text.strip())
36
+ diagnosis_code = parse_diagnosis_code(cells[1].text.strip())
37
+ left_or_right_eye = parse_left_or_right_eye(cells[2].text.strip())
38
+ femto_yes_or_no = parse_femto_yes_or_no(cells[3].text.strip())
39
+
40
+ # Construct the dictionary entry
41
+ patient_data[patient_id] = [diagnosis_code, left_or_right_eye, femto_yes_or_no]
42
+
43
+ return patient_data
44
+
45
+ def parse_patient_id(text):
46
+ # Implement parsing logic for Patient ID Number
47
+ # Example: Assume the ID is the first part of the text, separated by a space or newline
48
+ return text.split()[0]
49
+
50
+ def parse_diagnosis_code(text):
51
+ # Implement parsing logic for Diagnosis Code
52
+ # Example: Extract the code from a known pattern or location in the text
53
+ return text.split(':')[1].strip() if ':' in text else text
54
+
55
+ def parse_left_or_right_eye(text):
56
+ # Implement parsing logic for Left or Right Eye
57
+ # Example: Assume the text contains 'Left' or 'Right' and extract it
58
+ if 'Left' in text:
59
+ return 'Left'
60
+ elif 'Right' in text:
61
+ return 'Right'
62
+ else:
63
+ return 'Unknown'
64
+
65
+ def parse_femto_yes_or_no(text):
66
+ # Implement parsing logic for Femto yes or no
67
+ # Example: Check for presence of keywords 'yes' or 'no'
68
+ if 'yes' in text.lower():
69
+ return 'Yes'
70
+ elif 'no' in text.lower():
71
+ return 'No'
72
+ else:
73
+ return 'Unknown'
74
+
75
+ # Placeholder function call (replace 'path_to_docx' with the actual file path)
76
+ filepath = 'path_to_docx'
77
+ patient_data_dict = parse_docx(filepath)
78
+
79
+ # Print the resulting dictionary
80
+ print(patient_data_dict)
@@ -1,17 +1,55 @@
1
1
  import subprocess
2
2
  import sys
3
3
  from tqdm import tqdm
4
+ import requests
5
+ import time
6
+
7
+ def check_internet_connection():
8
+ try:
9
+ requests.get("http://www.google.com", timeout=5)
10
+ return True
11
+ except requests.ConnectionError:
12
+ return False
4
13
 
5
14
  def upgrade_medicafe(package):
6
15
  try:
7
- # Use tqdm to create a progress bar
8
- with tqdm(total=100, desc="Upgrading %s" % package, unit="%") as progress_bar:
9
- subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', package, '--no-deps', '--disable-pip-version-check'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
10
- # Update progress bar to 100% upon completion
11
- progress_bar.update(100 - progress_bar.n)
12
- print("Update successful.")
13
- except subprocess.CalledProcessError:
14
- print("Update failed. Please check your internet connection and try again later.")
16
+ # Check internet connection
17
+ if not check_internet_connection():
18
+ print("Error: No internet connection detected. Please check your internet connection and try again.")
19
+ sys.exit(1)
20
+
21
+ total_progress = 200 # Total progress for two runs
22
+
23
+ with tqdm(total=total_progress, desc="Upgrading %s" % package, unit="%") as progress_bar:
24
+ stdout_accumulator = b""
25
+ stderr_accumulator = b""
26
+
27
+ for _ in range(2): # Run pip install twice
28
+ process = subprocess.Popen([sys.executable, '-m', 'pip', 'install', '--upgrade', package, '--no-cache-dir', '--disable-pip-version-check', '--no-deps'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
29
+ stdout, stderr = process.communicate()
30
+ stdout_accumulator += stdout
31
+ stderr_accumulator += stderr
32
+
33
+ if process.returncode != 0:
34
+ # If the return code is non-zero, print error details
35
+ print("Error: Upgrade failed. Details:")
36
+ print("stdout:", stdout)
37
+ print("stderr:", stderr)
38
+ sys.exit(1)
39
+
40
+ progress_bar.update(total_progress // 2) # Update progress bar
41
+
42
+ # Add a 3-second sleep between runs
43
+ time.sleep(3)
44
+
45
+ progress_bar.update(total_progress // 2) # Update progress bar
46
+ print("stdout:", stdout_accumulator.decode("utf-8"))
47
+ print("stderr:", stderr_accumulator.decode("utf-8"))
48
+ time.sleep(1)
49
+ except Exception as e:
50
+ # Log any other exceptions
51
+ print("Error:", e)
52
+ time.sleep(3)
15
53
  sys.exit(1)
16
54
 
17
55
  if __name__ == "__main__":
MediLink/MediLink.py CHANGED
@@ -1,13 +1,22 @@
1
1
  import os
2
- import MediLink_ConfigLoader
3
- import MediLink_837p_encoder
4
- import logging
5
2
  import MediLink_Down
6
3
  import MediLink_Up
4
+ import MediLink_ConfigLoader
5
+ import MediLink_837p_encoder
7
6
 
8
7
  # For UI Functions
9
8
  import os
10
9
  import MediLink_UI # Import UI module for handling all user interfaces
10
+ from tqdm import tqdm
11
+
12
+ # Add parent directory of the project to the Python path
13
+ import sys
14
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
15
+ sys.path.append(project_dir)
16
+
17
+ from MediBot import MediBot_Preprocessor_lib
18
+ load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
19
+ from MediBot import MediBot_Crosswalk_Library
11
20
 
12
21
  """
13
22
  Development Tasks for Backend Enhancement in MediSoft Claims Submittal (MediLink) Script:
@@ -22,17 +31,43 @@ any new endpoint changes to Optum. May need to "de-confirm" patients, but leave
22
31
  confirmed endpoints. This should be similar logic to if the user made a mistake and wants to go back and fix it.
23
32
  These tasks involve backend enhancements such as dynamic configurations, file detection improvements, file transmission verification, automation of response file handling, and management of intermediate files and transmission failures.
24
33
 
25
- TODO Crosswalk should be to PayerID key vs Medisoft:Endpoint.
26
- TODO Availity has a response file that says "File was received at TIME. File was sent for processing." as a confirmation
34
+ TODO (Low) Availity has a response file that says "File was received at TIME. File was sent for processing." as a confirmation
27
35
  that sits in the SendFiles folder after a submittal.
28
36
 
37
+ TODO (Crosswalk) When an endpoint is updated in the UI, the crosswalk should also be updated and saved for that payer ID because that payer ID
38
+ would basically forever need to be going to that endpoint for any patient. the suggested_endpoint should eventually be always correct.
39
+
29
40
  BUG Suggested Endpoint when you say 'n' to proceed with transmission is not getting updated with the endpoint
30
41
  that was selected previously by the user. However, when we go back to the confirmation list, we do have a persist of the assignment.
31
42
  This can be confusing for the user.
32
- """
33
43
 
34
- # Setup basic logging
35
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s\n')
44
+ MediLink
45
+ | - import MediLink_Down
46
+ | - import MediLink_ERA_decoder
47
+ | | - from MediLink_ConfigLoader import load_configuration
48
+ | | | - None
49
+ | | - from MediLink_DataMgmt import consolidate_csvs
50
+ | | | - from MediLink import MediLink_ConfigLoader
51
+ | | - None
52
+ | - from MediLink_DataMgmt import operate_winscp
53
+ | - from MediLink import MediLink_ConfigLoader
54
+ | - None
55
+ | - import MediLink_Up
56
+ | - None
57
+ | - import MediLink_ConfigLoader
58
+ | - None
59
+ | - import MediLink_837p_encoder
60
+ | - import MediLink_ConfigLoader
61
+ | | - None
62
+ | - from MediLink_DataMgmt import parse_fixed_width_data, read_fixed_width_data
63
+ | - from MediLink import MediLink_ConfigLoader
64
+ | - None
65
+ | - import MediLink_837p_encoder_library
66
+ | - from MediLink import MediLink_ConfigLoader
67
+ | - None
68
+ | - import MediLink_UI
69
+ | - None
70
+ """
36
71
 
37
72
  def detect_and_display_file_summaries(directory_path, config, crosswalk):
38
73
  """
@@ -88,15 +123,42 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
88
123
  """
89
124
  detailed_patient_data = []
90
125
 
91
- for personal_info, insurance_info, service_info in MediLink_837p_encoder.read_fixed_width_data(file_path, config.get('MediLink_Config', {})):
92
- parsed_data = MediLink_837p_encoder.parse_fixed_width_data(personal_info, insurance_info, service_info, config.get('MediLink_Config', {}))
126
+ # Load insurance data from MAINS to create a mapping from insurance names to their respective IDs
127
+ insurance_to_id = load_insurance_data_from_mains(config)
128
+ MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)))
129
+
130
+ for personal_info, insurance_info, service_info in MediLink_837p_encoder.read_fixed_width_data(file_path):
131
+ parsed_data = MediLink_837p_encoder.parse_fixed_width_data(personal_info, insurance_info, service_info, config.get('MediLink_Config', config))
93
132
 
94
133
  primary_insurance = parsed_data.get('INAME')
95
134
 
96
- # TODO This suggested endpoint should be a payerid_to_endpoint_mapping.
97
- suggested_endpoint = crosswalk['insurance_to_endpoint_mapping'].get(primary_insurance, 'AVAILITY')
135
+ # Retrieve the insurance ID associated with the primary insurance
136
+ insurance_id = insurance_to_id.get(primary_insurance)
137
+ MediLink_ConfigLoader.log("Primary insurance ID retrieved for '{}': {}".format(primary_insurance, insurance_id))
138
+
139
+ # Use insurance ID to retrieve the payer ID(s) associated with the insurance
140
+ payer_ids = []
141
+ if insurance_id:
142
+ for payer_id, payer_data in crosswalk.get('payer_id', {}).items():
143
+ medisoft_ids = [str(id) for id in payer_data.get('medisoft_id', [])]
144
+ # MediLink_ConfigLoader.log("Payer ID: {}, Medisoft IDs: {}".format(payer_id, medisoft_ids))
145
+ if str(insurance_id) in medisoft_ids:
146
+ payer_ids.append(payer_id)
147
+ if payer_ids:
148
+ MediLink_ConfigLoader.log("Payer IDs retrieved for insurance '{}': {}".format(primary_insurance, payer_ids))
149
+ else:
150
+ MediLink_ConfigLoader.log("No payer IDs found for insurance '{}'".format(primary_insurance))
98
151
 
99
- # Directly enrich detailed patient data with additional information and suggested endpoint
152
+ # Find the suggested endpoint from the crosswalk based on the payer IDs
153
+ suggested_endpoint = 'AVAILITY' # Default endpoint if no matching payer IDs found
154
+ if payer_ids:
155
+ payer_id = payer_ids[0] # Select the first payer ID
156
+ suggested_endpoint = crosswalk['payer_id'].get(payer_id, {}).get('endpoint', 'AVAILITY')
157
+ MediLink_ConfigLoader.log("Suggested endpoint for payer ID '{}': {}".format(payer_id, suggested_endpoint))
158
+ else:
159
+ MediLink_ConfigLoader.log("No suggested endpoint found for payer IDs: {}".format(payer_ids))
160
+
161
+ # Enrich detailed patient data with additional information and suggested endpoint
100
162
  detailed_data = parsed_data.copy() # Copy parsed_data to avoid modifying the original dictionary
101
163
  detailed_data.update({
102
164
  'file_path': file_path,
@@ -134,20 +196,50 @@ def organize_patient_data_by_endpoint(detailed_patient_data):
134
196
  def check_for_new_remittances(config):
135
197
  print("\nChecking for new files across all endpoints...")
136
198
  endpoints = config['MediLink_Config']['endpoints']
137
- for endpoint_key, endpoint_info in endpoints.items():
138
- try:
139
- # Pass the endpoint key to MediLink_Down.main() as an argument
140
- ERA_path = MediLink_Down.main(desired_endpoint=endpoint_key)
141
- # BUG This needs to check to see if this actually worked maybe winscplog before saying it completed successfully
142
- print("New remittances for {} completed successfully.".format(endpoint_info['name']))
143
- print("Results saved to: {}\n".format(ERA_path))
144
- except Exception as e:
145
- print("An error occurred while checking remittances for {}: {}".format(endpoint_info['name'], e))
199
+ processed_endpoints = []
200
+
201
+ if isinstance(endpoints, dict): # BUG This check can probably be removed later.
202
+ for endpoint_key, endpoint_info in tqdm(endpoints.items(), desc="Processing endpoints"):
203
+ if 'remote_directory_down' in endpoint_info: # Check if the 'remote_directory_down' key exists
204
+ #print("Processing endpoint: ", endpoint_info['name'])
205
+ # BUG (Debug and verbosity removal) this is really for debug only. Positive statements can be muted.
206
+ try:
207
+ ERA_path = MediLink_Down.main(desired_endpoint=endpoint_key)
208
+ processed_endpoints.append((endpoint_info['name'], ERA_path))
209
+ MediLink_ConfigLoader.log("Results for {} saved to: {}".format(endpoint_info['name'], ERA_path))
210
+ # TODO (Low SFTP - Download side) This needs to check to see if this actually worked maybe winscplog before saying it completed successfully
211
+ # Check if there is commonality with the upload side so we can use the same validation function.
212
+ except Exception as e:
213
+ print("An error occurred while checking remittances for {}: {}".format(endpoint_info['name'], e))
214
+ else:
215
+ MediLink_ConfigLoader.log("Skipping endpoint '{}' as it does not have 'remote_directory_down' configured.".format(endpoint_info['name']))
216
+ else:
217
+ print("Error: Endpoint config is not a 'dictionary' as expected.")
218
+ # Check if all ERA paths are the same
219
+ unique_era_paths = set(path for _, path in processed_endpoints)
220
+ if len(unique_era_paths) == 1:
221
+ common_era_path = unique_era_paths.pop() # Get the common ERA path
222
+ endpoints_list = ", ".join(endpoint for endpoint, _ in processed_endpoints)
223
+ print("\nProcessed Endpoints: {}".format(endpoints_list))
224
+ print("File located at: {}\n".format(common_era_path))
225
+ # TODO (MediPost) These prints will eventually be logs when MediPost is made.
226
+
227
+ else:
228
+ if processed_endpoints:
229
+ print("\nProcessed Endpoints:")
230
+ for endpoint, path in processed_endpoints:
231
+ print("Endpoint: {}, ERA Path: {}".format(endpoint, path))
232
+ else:
233
+ print("No endpoints were processed.")
146
234
 
147
235
  def user_decision_on_suggestions(detailed_patient_data, config):
148
236
  """
149
237
  Presents the user with all patient summaries and suggested endpoints,
150
238
  then asks for confirmation to proceed with all or specify adjustments manually.
239
+
240
+ BUG (Med suggested_endpoint) The display summary suggested_endpoint key isn't updating per the user's decision
241
+ although the user decision is persisting. Possibly consider making the current/suggested/confirmed endpoint
242
+ part of a class that the user can interact with via these menus. Probably better handling that way.
151
243
  """
152
244
  # Display summaries of patient details and endpoints.
153
245
  MediLink_UI.display_patient_summaries(detailed_patient_data)
@@ -174,15 +266,19 @@ def confirm_all_suggested_endpoints(detailed_patient_data):
174
266
  def select_and_adjust_files(detailed_patient_data, config):
175
267
  """
176
268
  Allows users to select patients and adjust their endpoints by interfacing with UI functions.
269
+
270
+ BUG (Med suggested_endpoint) After the user is done making their selection (probably via a class?),
271
+ Then suggested_endpoint should update to persist the user selection as priority over its original suggestion.
272
+ Which means the crosswalk should persist the change in the endpoint as well.
177
273
  """
178
274
  # Display options for patients
179
275
  MediLink_UI.display_patient_options(detailed_patient_data)
180
276
 
181
277
  # Get user-selected indices for adjustment
182
278
  selected_indices = MediLink_UI.get_selected_indices(len(detailed_patient_data))
183
-
184
- # Fetch endpoint names dynamically from the JSON config
185
- endpoint_mapping = {str(i + 1): config['MediLink_Config']['endpoints'][endpoint]['name'] for i, endpoint in enumerate(config['MediLink_Config']['endpoints'])}
279
+
280
+ # Get an ordered list of endpoint keys
281
+ endpoint_keys = list(config['MediLink_Config']['endpoints'].keys())
186
282
 
187
283
  # Iterate over each selected index and process endpoint changes
188
284
  for i in selected_indices:
@@ -190,20 +286,20 @@ def select_and_adjust_files(detailed_patient_data, config):
190
286
  MediLink_UI.display_patient_for_adjustment(data['patient_name'], data.get('suggested_endpoint', 'N/A'))
191
287
 
192
288
  endpoint_change = MediLink_UI.get_endpoint_decision()
193
-
194
289
  if endpoint_change == 'y':
195
- MediLink_UI.display_endpoint_options(endpoint_mapping)
196
- new_endpoint_choice = MediLink_UI.get_new_endpoint_choice()
290
+ MediLink_UI.display_endpoint_options(config['MediLink_Config']['endpoints'])
291
+ endpoint_index = int(MediLink_UI.get_new_endpoint_choice()) - 1 # Adjusting for zero-based index
197
292
 
198
- if new_endpoint_choice in endpoint_mapping:
199
- data['confirmed_endpoint'] = endpoint_mapping[new_endpoint_choice]
200
- print("Endpoint changed to {0} for patient {1}.".format(data['confirmed_endpoint'], data['patient_name']))
293
+ if 0 <= endpoint_index < len(endpoint_keys):
294
+ selected_endpoint_key = endpoint_keys[endpoint_index]
295
+ data['confirmed_endpoint'] = selected_endpoint_key
296
+ print("Endpoint changed to {0} for patient {1}.".format(config['MediLink_Config']['endpoints'][selected_endpoint_key]['name'], data['patient_name']))
297
+ # BUG (Med, Crosswalk & suggested_endpoint) Probably update crosswalk and suggested endpoint here???
201
298
  else:
202
299
  print("Invalid selection. Keeping the suggested endpoint.")
203
300
  else:
204
301
  data['confirmed_endpoint'] = data.get('suggested_endpoint', 'N/A')
205
302
 
206
- # Return the updated data
207
303
  return detailed_patient_data
208
304
 
209
305
  def main_menu():
@@ -212,7 +308,14 @@ def main_menu():
212
308
  including loading configurations and managing user input for menu selections.
213
309
  """
214
310
  # Load configuration settings and display the initial welcome message.
215
- config, crosswalk = MediLink_ConfigLoader.load_configuration() # BUG does this need an argument?
311
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
312
+
313
+ # Check to make sure payer_id key is available in crosswalk, otherwise, go through that crosswalk initialization flow
314
+ MediBot_Crosswalk_Library.check_and_initialize_crosswalk(config)
315
+
316
+ # Check if the application is in test mode
317
+ if config.get("MediLink_Config", {}).get("TestMode", False):
318
+ print("\n--- MEDILINK TEST MODE --- \nTo enable full functionality, please update the config file \nand set 'TestMode' to 'false'.")
216
319
 
217
320
  # Display Welcome Message
218
321
  MediLink_UI.display_welcome()
@@ -265,7 +368,8 @@ def handle_submission(detailed_patient_data, config):
265
368
  if MediLink_Up.confirm_transmission(organized_data):
266
369
  if MediLink_Up.check_internet_connection():
267
370
  # Submit claims if internet connectivity is confirmed.
268
- MediLink_Up.submit_claims(organized_data, config)
371
+ _ = MediLink_Up.submit_claims(organized_data, config)
372
+ # TODO submit_claims will have a receipt return in the future.
269
373
  else:
270
374
  # Notify the user of an internet connection error.
271
375
  print("Internet connection error. Please ensure you're connected and try again.")