medicafe 0.240716.2__py3-none-any.whl → 0.240925.9__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,57 @@
1
- import subprocess
2
- import sys
3
- from tqdm import tqdm
4
- import requests
5
- import time
1
+ #update_medicafe.py
2
+ import subprocess, sys, requests, time
3
+
4
+ def get_installed_version(package):
5
+ try:
6
+ process = subprocess.Popen(
7
+ [sys.executable, '-m', 'pip', 'show', package],
8
+ stdout=subprocess.PIPE,
9
+ stderr=subprocess.PIPE
10
+ )
11
+ stdout, stderr = process.communicate()
12
+ if process.returncode == 0:
13
+ for line in stdout.decode().splitlines():
14
+ if line.startswith("Version:"):
15
+ return line.split(":", 1)[1].strip()
16
+ return None
17
+ except Exception as e:
18
+ print("Error retrieving installed version: {}".format(e))
19
+ return None
20
+
21
+ def get_latest_version(package, retries=3, delay=1):
22
+ """
23
+ Fetch the latest version of the specified package from PyPI with retries.
24
+ """
25
+ for attempt in range(1, retries + 1):
26
+ try:
27
+ response = requests.get("https://pypi.org/pypi/{}/json".format(package), timeout=10)
28
+ response.raise_for_status() # Raise an error for bad responses
29
+ data = response.json()
30
+ latest_version = data['info']['version']
31
+
32
+ # Print the version with attempt information
33
+ if attempt == 1:
34
+ print("Latest available version: {}".format(latest_version))
35
+ else:
36
+ print("Latest available version: {} ({} attempt)".format(latest_version, attempt))
37
+
38
+ # Check if the latest version is different from the current version
39
+ current_version = get_installed_version(package)
40
+ if current_version and compare_versions(latest_version, current_version) == 0:
41
+ # If the versions are the same, perform a second request
42
+ time.sleep(delay)
43
+ response = requests.get("https://pypi.org/pypi/{}/json".format(package), timeout=10)
44
+ response.raise_for_status()
45
+ data = response.json()
46
+ latest_version = data['info']['version']
47
+
48
+ return latest_version # Return the version after the check
49
+ except requests.RequestException as e:
50
+ print("Attempt {}: Error fetching latest version: {}".format(attempt, e))
51
+ if attempt < retries:
52
+ print("Retrying in {} seconds...".format(delay))
53
+ time.sleep(delay)
54
+ return None
6
55
 
7
56
  def check_internet_connection():
8
57
  try:
@@ -11,47 +60,88 @@ def check_internet_connection():
11
60
  except requests.ConnectionError:
12
61
  return False
13
62
 
14
- def upgrade_medicafe(package):
15
- try:
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)
63
+ def compare_versions(version1, version2):
64
+ v1_parts = list(map(int, version1.split(".")))
65
+ v2_parts = list(map(int, version2.split(".")))
66
+ return (v1_parts > v2_parts) - (v1_parts < v2_parts)
67
+
68
+ def upgrade_package(package, retries=3, delay=2): # Updated retries to 3
69
+ """
70
+ Attempts to upgrade the package multiple times with delays in between.
71
+ """
72
+ if not check_internet_connection():
73
+ print("Error: No internet connection detected. Please check your internet connection and try again.")
74
+ sys.exit(1)
75
+
76
+ for attempt in range(1, retries + 1):
77
+ print("Attempt {} to upgrade {}...".format(attempt, package))
78
+ process = subprocess.Popen(
79
+ [
80
+ sys.executable, '-m', 'pip', 'install', '--upgrade',
81
+ package, '--no-cache-dir', '--disable-pip-version-check', '-q'
82
+ ],
83
+ stdout=subprocess.PIPE,
84
+ stderr=subprocess.PIPE
85
+ )
20
86
 
21
- total_progress = 200 # Total progress for two runs
87
+ stdout, stderr = process.communicate()
22
88
 
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)
89
+ if process.returncode == 0:
90
+ print(stdout.decode().strip())
91
+ new_version = get_installed_version(package) # Get new version after upgrade
92
+ if compare_versions(new_version, get_latest_version(package)) >= 0: # Compare versions
93
+ if attempt == 1:
94
+ print("Upgrade succeeded!")
95
+ else:
96
+ print("Attempt {}: Upgrade succeeded!".format(attempt))
97
+ time.sleep(delay)
98
+ return True
99
+ else:
100
+ print("Upgrade failed. Current version remains: {}".format(new_version))
101
+ if attempt < retries:
102
+ print("Retrying in {} seconds...".format(delay))
103
+ time.sleep(delay)
104
+ else:
105
+ print(stderr.decode().strip())
106
+ print("Attempt {}: Upgrade failed.".format(attempt))
107
+ if attempt < retries:
108
+ print("Retrying in {} seconds...".format(delay))
109
+ time.sleep(delay)
110
+
111
+ print("Error: All upgrade attempts failed.")
112
+ return False
113
+
114
+ def main():
115
+ package = "medicafe"
116
+
117
+ current_version = get_installed_version(package)
118
+ if not current_version:
119
+ print("{} is not installed.".format(package))
53
120
  sys.exit(1)
121
+
122
+ latest_version = get_latest_version(package)
123
+ if not latest_version:
124
+ print("Could not retrieve the latest version information.")
125
+ sys.exit(1)
126
+
127
+ print("Current version of {}: {}".format(package, current_version))
128
+ print("Latest version of {}: {}".format(package, latest_version))
129
+
130
+ if compare_versions(latest_version, current_version) > 0:
131
+ print("A newer version is available. Proceeding with upgrade.")
132
+ if upgrade_package(package):
133
+ # Verify upgrade
134
+ time.sleep(3)
135
+ new_version = get_installed_version(package)
136
+ if compare_versions(new_version, latest_version) >= 0:
137
+ print("Upgrade successful. New version: {}".format(new_version))
138
+ else:
139
+ print("Upgrade failed. Current version remains: {}".format(new_version))
140
+ sys.exit(1)
141
+ else:
142
+ sys.exit(1)
143
+ else:
144
+ print("You already have the latest version installed.")
54
145
 
55
146
  if __name__ == "__main__":
56
- medicafe_package = "medicafe"
57
- upgrade_medicafe(medicafe_package)
147
+ main()
MediLink/MediLink.py CHANGED
@@ -1,22 +1,21 @@
1
- import os
1
+ # MediLink.py
2
+ import os, sys
2
3
  import MediLink_Down
3
4
  import MediLink_Up
4
5
  import MediLink_ConfigLoader
5
6
  import MediLink_DataMgmt
6
7
 
7
8
  # For UI Functions
8
- import os
9
9
  import MediLink_UI # Import UI module for handling all user interfaces
10
10
  from tqdm import tqdm
11
11
 
12
12
  # Add parent directory of the project to the Python path
13
- import sys
14
13
  project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
15
- sys.path.append(project_dir)
14
+ if project_dir not in sys.path:
15
+ sys.path.append(project_dir)
16
16
 
17
17
  from MediBot import MediBot_Preprocessor_lib
18
18
  load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
19
- from MediBot import MediBot_Crosswalk_Library
20
19
 
21
20
  # Retrieve insurance options with codes and descriptions
22
21
  config, _ = MediLink_ConfigLoader.load_configuration()
@@ -58,7 +57,7 @@ def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_map
58
57
  TODO: Implement a function to provide `patient_insurance_mapping` from a reliable source.
59
58
  """
60
59
  if patient_insurance_type_mapping is None:
61
- MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.")
60
+ MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.", level="WARNING")
62
61
  patient_insurance_type_mapping = {}
63
62
 
64
63
  for data in detailed_patient_data:
@@ -86,7 +85,7 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
86
85
 
87
86
  # Load insurance data from MAINS to create a mapping from insurance names to their respective IDs
88
87
  insurance_to_id = load_insurance_data_from_mains(config)
89
- MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)))
88
+ MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)), level="INFO")
90
89
 
91
90
  for personal_info, insurance_info, service_info, service_info_2, service_info_3 in MediLink_DataMgmt.read_fixed_width_data(file_path):
92
91
  parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, service_info_2, service_info_3, config.get('MediLink_Config', config))
@@ -135,46 +134,86 @@ def extract_and_suggest_endpoint(file_path, config, crosswalk):
135
134
  # Return only the enriched detailed patient data, eliminating the need for a separate summary list
136
135
  return detailed_patient_data
137
136
 
138
- def check_for_new_remittances(config):
137
+ def check_for_new_remittances(config=None):
138
+ """
139
+ Function to check for new remittance files across all configured endpoints.
140
+ Loads the configuration, validates it, and processes each endpoint to download and handle files.
141
+ Accumulates results from all endpoints and processes them together at the end.
142
+ """
143
+ # Start the process and log the initiation
144
+ MediLink_ConfigLoader.log("Starting check_for_new_remittances function")
139
145
  print("\nChecking for new files across all endpoints...")
140
- endpoints = config['MediLink_Config']['endpoints']
141
- processed_endpoints = []
142
-
143
- if isinstance(endpoints, dict): # BUG This check can probably be removed later.
144
- for endpoint_key, endpoint_info in tqdm(endpoints.items(), desc="Processing endpoints"):
145
- if 'remote_directory_down' in endpoint_info: # Check if the 'remote_directory_down' key exists
146
- #print("Processing endpoint: ", endpoint_info['name'])
147
- # BUG (Debug and verbosity removal) this is really for debug only. Positive statements can be muted.
148
- try:
149
- ERA_path = MediLink_Down.main(desired_endpoint=endpoint_key)
150
- processed_endpoints.append((endpoint_info['name'], ERA_path))
151
- MediLink_ConfigLoader.log("Results for {} saved to: {}".format(endpoint_info['name'], ERA_path))
152
- # TODO (Low SFTP - Download side) This needs to check to see if this actually worked maybe winscplog before saying it completed successfully
153
- # Check if there is commonality with the upload side so we can use the same validation function.
154
- except Exception as e:
155
- print("An error occurred while checking remittances for {}: {}".format(endpoint_info['name'], e))
156
- else:
157
- MediLink_ConfigLoader.log("Skipping endpoint '{}' as it does not have 'remote_directory_down' configured.".format(endpoint_info['name']))
146
+ MediLink_ConfigLoader.log("Checking for new files across all endpoints...")
147
+
148
+ # Step 1: Load and validate the configuration
149
+ if config is None:
150
+ config, _ = MediLink_ConfigLoader.load_configuration()
151
+
152
+ if not config or 'MediLink_Config' not in config or 'endpoints' not in config['MediLink_Config']:
153
+ MediLink_ConfigLoader.log("Error: Config is missing necessary sections. Aborting...", level="ERROR")
154
+ return
155
+
156
+ endpoints = config['MediLink_Config'].get('endpoints')
157
+ if not isinstance(endpoints, dict):
158
+ MediLink_ConfigLoader.log("Error: 'endpoints' is not a dictionary. Aborting...", level="ERROR")
159
+ return
160
+
161
+ # Lists to accumulate all consolidated records and translated files across all endpoints
162
+ all_consolidated_records = []
163
+ all_translated_files = []
164
+
165
+ # Step 2: Process each endpoint and accumulate results
166
+ for endpoint_key, endpoint_info in tqdm(endpoints.items(), desc="Processing endpoints"):
167
+ # Validate endpoint structure
168
+ if not endpoint_info or not isinstance(endpoint_info, dict):
169
+ MediLink_ConfigLoader.log("Error: Invalid endpoint structure for {}. Skipping...".format(endpoint_key), level="ERROR")
170
+ continue
171
+
172
+ if 'remote_directory_down' in endpoint_info:
173
+ # Process the endpoint and handle the files
174
+ MediLink_ConfigLoader.log("Processing endpoint: {}".format(endpoint_key))
175
+ consolidated_records, translated_files = process_endpoint(endpoint_key, endpoint_info, config)
176
+
177
+ # Accumulate the results for later processing
178
+ if consolidated_records:
179
+ all_consolidated_records.extend(consolidated_records)
180
+ if translated_files:
181
+ all_translated_files.extend(translated_files)
182
+ else:
183
+ MediLink_ConfigLoader.log("Skipping endpoint '{}'. 'remote_directory_down' not configured.".format(endpoint_info.get('name', 'Unknown')), level="WARNING")
184
+
185
+ # Step 3: After processing all endpoints, handle the accumulated results
186
+ if all_consolidated_records:
187
+ MediLink_Down.display_consolidated_records(all_consolidated_records) # Ensure this is called only once
188
+ MediLink_Down.prompt_csv_export(all_consolidated_records, config['MediLink_Config']['local_storage_path'])
158
189
  else:
159
- print("Error: Endpoint config is not a 'dictionary' as expected.")
160
- # Check if all ERA paths are the same
161
- unique_era_paths = set(path for _, path in processed_endpoints)
162
- if len(unique_era_paths) == 1:
163
- common_era_path = unique_era_paths.pop() # Get the common ERA path
164
- endpoints_list = ", ".join(endpoint for endpoint, _ in processed_endpoints)
165
- print("\nProcessed Endpoints: {}".format(endpoints_list))
166
- print("File located at: {}\n".format(common_era_path))
167
- # TODO (MediPost) These prints will eventually be logs when MediPost is made.
190
+ MediLink_ConfigLoader.log("No records to display after processing all endpoints.", level="WARNING")
191
+ print("No records to display after processing all endpoints.")
192
+
193
+ def process_endpoint(endpoint_key, endpoint_info, config):
194
+ """
195
+ Helper function to process a single endpoint.
196
+ Downloads files from the endpoint, processes them, and returns the consolidated records and translated files.
197
+ """
198
+ try:
199
+ # Process the files for the given endpoint
200
+ local_storage_path = config['MediLink_Config']['local_storage_path']
201
+ MediLink_ConfigLoader.log("[Process Endpoint] Local storage path set to {}".format(local_storage_path))
202
+ downloaded_files = MediLink_Down.operate_winscp("download", None, endpoint_info, local_storage_path, config)
168
203
 
169
- else:
170
- if processed_endpoints:
171
- print("\nProcessed Endpoints:")
172
- for endpoint, path in processed_endpoints:
173
- print("Endpoint: {}, ERA Path: {}".format(endpoint, path))
204
+ if downloaded_files:
205
+ MediLink_ConfigLoader.log("[Process Endpoint] WinSCP Downloaded the following files: \n{}".format(downloaded_files))
206
+ return MediLink_Down.handle_files(local_storage_path, downloaded_files)
174
207
  else:
175
- print("No endpoints were processed.")
208
+ MediLink_ConfigLoader.log("[Process Endpoint]No files were downloaded for endpoint: {}.".format(endpoint_key), level="WARNING")
209
+ return [], []
210
+
211
+ except Exception as e:
212
+ # Handle any exceptions that occur during the processing
213
+ MediLink_ConfigLoader.log("Error processing endpoint {}: {}".format(endpoint_key, e), level="ERROR")
214
+ return [], []
176
215
 
177
- def user_decision_on_suggestions(detailed_patient_data, config):
216
+ def user_decision_on_suggestions(detailed_patient_data, config, insurance_edited):
178
217
  """
179
218
  Presents the user with all patient summaries and suggested endpoints,
180
219
  then asks for confirmation to proceed with all or specify adjustments manually.
@@ -183,11 +222,11 @@ def user_decision_on_suggestions(detailed_patient_data, config):
183
222
  although the user decision is persisting. Possibly consider making the current/suggested/confirmed endpoint
184
223
  part of a class that the user can interact with via these menus? Probably better handling that way.
185
224
  """
186
- # Display summaries of patient details and endpoints.
187
- MediLink_UI.display_patient_summaries(detailed_patient_data)
225
+ if insurance_edited:
226
+ # Display summaries only if insurance types were edited
227
+ MediLink_UI.display_patient_summaries(detailed_patient_data)
188
228
 
189
- # Ask the user if they want to proceed with all suggested endpoints.
190
- proceed = MediLink_UI.ask_for_proceeding_with_endpoints()
229
+ proceed = input("Do you want to proceed with all suggested endpoints? (Y/N): ").strip().lower() == 'y'
191
230
 
192
231
  # If the user agrees to proceed with all suggested endpoints, confirm them.
193
232
  if proceed:
@@ -244,7 +283,9 @@ def main_menu():
244
283
  config, crosswalk = MediLink_ConfigLoader.load_configuration()
245
284
 
246
285
  # Check to make sure payer_id key is available in crosswalk, otherwise, go through that crosswalk initialization flow
247
- MediBot_Crosswalk_Library.check_and_initialize_crosswalk(config)
286
+ if 'payer_id' not in crosswalk:
287
+ print("Crosswalk 'payer_id' not found. Please run MediBot_Preprocessor.py with the --update-crosswalk argument.")
288
+ exit() # BUG Halting the script execution here for now, should be handled in the preprocessor script.
248
289
 
249
290
  # Check if the application is in test mode
250
291
  if config.get("MediLink_Config", {}).get("TestMode", False):
@@ -287,7 +328,7 @@ def main_menu():
287
328
  detailed_patient_data = collect_detailed_patient_data(selected_files, config, crosswalk)
288
329
 
289
330
  # Process the claims submission.
290
- handle_submission(detailed_patient_data, config)
331
+ handle_submission(detailed_patient_data, config, crosswalk)
291
332
  elif choice == '3' or (choice == '2' and not all_files):
292
333
  # Exit the program if the user chooses to exit or if no files are present.
293
334
  MediLink_UI.display_exit_message()
@@ -296,16 +337,17 @@ def main_menu():
296
337
  # Display an error message if the user's choice does not match any valid option.
297
338
  MediLink_UI.display_invalid_choice()
298
339
 
299
- def handle_submission(detailed_patient_data, config):
340
+ def handle_submission(detailed_patient_data, config, crosswalk):
300
341
  """
301
342
  Handles the submission process for claims based on detailed patient data.
302
343
  This function orchestrates the flow from user decision on endpoint suggestions to the actual submission of claims.
303
344
  """
304
- # TODO If we get here via a user decline we end up not displaying the patient summary data, but this doesn't happen in the first round. Can be de-tangled later.
305
-
345
+ insurance_edited = False # Flag to track if insurance types were edited
346
+
306
347
  # Ask the user if they want to edit insurance types
307
348
  edit_insurance = input("Do you want to edit insurance types? (y/n): ").strip().lower()
308
349
  if edit_insurance in ['y', 'yes', '']:
350
+ insurance_edited = True # User chose to edit insurance types
309
351
  while True:
310
352
  # Bulk edit insurance types
311
353
  MediLink_DataMgmt.bulk_edit_insurance_types(detailed_patient_data, insurance_options)
@@ -317,7 +359,7 @@ def handle_submission(detailed_patient_data, config):
317
359
  print("Returning to bulk edit insurance types.")
318
360
 
319
361
  # Initiate user interaction to confirm or adjust suggested endpoints.
320
- adjusted_data = user_decision_on_suggestions(detailed_patient_data, config)
362
+ adjusted_data = user_decision_on_suggestions(detailed_patient_data, config, insurance_edited)
321
363
 
322
364
  # Confirm all remaining suggested endpoints.
323
365
  confirmed_data = MediLink_DataMgmt.confirm_all_suggested_endpoints(adjusted_data)
@@ -328,7 +370,7 @@ def handle_submission(detailed_patient_data, config):
328
370
  if MediLink_Up.confirm_transmission(organized_data):
329
371
  if MediLink_Up.check_internet_connection():
330
372
  # Submit claims if internet connectivity is confirmed.
331
- _ = MediLink_Up.submit_claims(organized_data, config)
373
+ _ = MediLink_Up.submit_claims(organized_data, config, crosswalk)
332
374
  # TODO submit_claims will have a receipt return in the future.
333
375
  else:
334
376
  # Notify the user of an internet connection error.