medicafe 0.240809.0__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.

MediBot/update_json.py CHANGED
@@ -1,7 +1,25 @@
1
+ # update_json.py
1
2
  import json
2
3
  import sys
3
4
  from collections import OrderedDict
4
5
 
6
+ def get_current_csv_path(json_file):
7
+ try:
8
+ with open(json_file, 'r', encoding='utf-8') as file:
9
+ try:
10
+ data = json.load(file, object_pairs_hook=OrderedDict)
11
+ return data.get('CSV_FILE_PATH', None)
12
+ except ValueError as decode_err:
13
+ print("Error decoding JSON file '{}': {}".format(json_file, decode_err))
14
+ sys.exit(1)
15
+ except IOError as io_err:
16
+ print("Error accessing file '{}': {}".format(json_file, io_err))
17
+ sys.exit(1)
18
+ except Exception as e:
19
+ print("An unexpected error occurred: {}".format(e))
20
+ sys.exit(1)
21
+ return None
22
+
5
23
  def update_csv_path(json_file, new_path):
6
24
  try:
7
25
  with open(json_file, 'r', encoding='utf-8') as file:
@@ -38,6 +56,13 @@ if __name__ == "__main__":
38
56
  json_path = sys.argv[1]
39
57
  new_csv_path = sys.argv[2]
40
58
  update_csv_path(json_path, new_csv_path)
59
+ elif len(sys.argv) == 2:
60
+ json_path = sys.argv[1]
61
+ current_csv_path = get_current_csv_path(json_path)
62
+ if current_csv_path:
63
+ print(current_csv_path)
64
+ else:
65
+ print("No CSV path found in config.")
41
66
  else:
42
- print("Usage: update_json.py <path_to_json_file> <new_csv_path>")
67
+ print("Usage: update_json.py <path_to_json_file> [<new_csv_path>]")
43
68
  sys.exit(1)
@@ -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()
@@ -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), level="DEBUG")
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']), level="WARNING")
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.
@@ -1,13 +1,12 @@
1
- import re
1
+ # MediLink_837p_encoder.py
2
+ import re, argparse, os
2
3
  from datetime import datetime
3
- import argparse
4
- import os
5
4
  import MediLink_ConfigLoader
6
5
  from MediLink_DataMgmt import parse_fixed_width_data, read_fixed_width_data
7
6
  import MediLink_837p_encoder_library
8
7
  #from tqdm import tqdm
9
8
 
10
- def format_single_claim(patient_data, config, endpoint, transaction_set_control_number):
9
+ def format_single_claim(patient_data, config, endpoint, transaction_set_control_number, crosswalk, client):
11
10
  """
12
11
  Formats a single claim into 837P segments based on the provided patient data and endpoint.
13
12
 
@@ -21,7 +20,7 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
21
20
  - String representation of the formatted 837P claim.
22
21
  """
23
22
  # Pre-resolve and enrich with Payer Name and ID for special case handling like Florida Blue.
24
- patient_data = MediLink_837p_encoder_library.payer_id_to_payer_name(patient_data, config, endpoint)
23
+ patient_data = MediLink_837p_encoder_library.payer_id_to_payer_name(patient_data, config, endpoint, crosswalk, client)
25
24
 
26
25
  segments = []
27
26
 
@@ -63,7 +62,7 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
63
62
  segments.extend(MediLink_837p_encoder_library.create_nm1_rendering_provider_segment(config))
64
63
 
65
64
  # Claim information 2300, 2310C Service Facility and 2400 loop segments
66
- segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config))
65
+ segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config, crosswalk))
67
66
 
68
67
  # Placeholder for the SE segment to be updated with actual segment count later
69
68
  segments.append("SE**{transaction_set_control_number:04d}~")
@@ -124,7 +123,7 @@ def write_output_file(document_segments, output_directory, endpoint_key, input_f
124
123
  MediLink_ConfigLoader.log("Error: Failed to write output file. {}".format(e), config, level="ERROR")
125
124
  return None
126
125
 
127
- def process_file(file_path, config, endpoint_key, transaction_set_control_number):
126
+ def process_single_file(file_path, config, endpoint_key, transaction_set_control_number, crosswalk): # BUG Duplicate function name??
128
127
  """
129
128
  Process the claim data from a file into the 837P format.
130
129
 
@@ -145,7 +144,7 @@ def process_file(file_path, config, endpoint_key, transaction_set_control_number
145
144
  return None, transaction_set_control_number # Halt processing if the user chooses
146
145
 
147
146
  # Process each valid claim
148
- formatted_claims, transaction_set_control_number = format_claims(valid_claims, config, endpoint_key, transaction_set_control_number)
147
+ formatted_claims, transaction_set_control_number = format_claims(valid_claims, config, endpoint_key, transaction_set_control_number, crosswalk)
149
148
 
150
149
  formatted_claims_str = '\n'.join(formatted_claims) # Join formatted claims into a single string
151
150
  return formatted_claims_str, transaction_set_control_number
@@ -179,7 +178,7 @@ def read_and_validate_claims(file_path, config):
179
178
 
180
179
  return valid_claims, validation_errors
181
180
 
182
- def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_control_number):
181
+ def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_control_number, crosswalk):
183
182
  """
184
183
  Formats a list of parsed claim data into 837P segments.
185
184
 
@@ -196,7 +195,7 @@ def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_c
196
195
  transaction_set_control_number = starting_transaction_set_control_number
197
196
 
198
197
  for parsed_data in parsed_data_list:
199
- formatted_claim = format_single_claim(parsed_data, config, endpoint, transaction_set_control_number)
198
+ formatted_claim = format_single_claim(parsed_data, config, endpoint, transaction_set_control_number, crosswalk)
200
199
  formatted_claims.append(formatted_claim)
201
200
  transaction_set_control_number += 1 # Increment for each successfully processed claim
202
201
 
@@ -274,7 +273,7 @@ def validate_claim_data(parsed_data, config, required_fields=[]):
274
273
 
275
274
  return True, []
276
275
 
277
- def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
276
+ def process_and_write_file(file_path, config, endpoint, crosswalk, starting_tscn=1):
278
277
  """
279
278
  Process a single file, create complete 837P document with headers and trailers, and write to output file.
280
279
 
@@ -286,7 +285,7 @@ def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
286
285
  """
287
286
  print("Processing: {}".format(file_path))
288
287
  MediLink_ConfigLoader.log("Processing: {}".format(file_path))
289
- formatted_data, transaction_set_control_number = process_file(file_path, config, endpoint, starting_tscn)
288
+ formatted_data, transaction_set_control_number = process_single_file(file_path, config, endpoint, starting_tscn, crosswalk)
290
289
  isa_header, gs_header, ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
291
290
 
292
291
  # Combine everything into a single document
@@ -303,6 +302,7 @@ def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
303
302
  print("File processed. Output saved to: {}".format(output_file_path))
304
303
 
305
304
  def main():
305
+ # BUG (MAJOR) THIS NEEDS THE API CLIENT TO BE PASSED INTO THE FUNCTION FOR SINGLE FILE PROCESSING.
306
306
  """
307
307
  Converts fixed-width files to 837P format for health claim submissions.
308
308
 
@@ -329,7 +329,7 @@ def main():
329
329
  parser.add_argument(
330
330
  "-e", "--endpoint",
331
331
  required=True,
332
- choices=["AVAILITY", "OPTUMEDI", "PNT_DATA", "UHCAPI"],
332
+ choices=["AVAILITY", "OPTUMEDI", "PNT_DATA", "UHCAPI", "CLAIMSHUTTLE"], # This should read from the config?
333
333
  help="Specify the endpoint for which the conversion is intended."
334
334
  )
335
335
  parser.add_argument(
@@ -346,13 +346,13 @@ def main():
346
346
 
347
347
  print("Starting the conversion process for {}. Processing {} at '{}'.".format(args.endpoint, 'directory' if args.is_directory else 'file', args.path))
348
348
 
349
- config, _ = MediLink_ConfigLoader.load_configuration()
349
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
350
350
  config = config.get('MediLink_Config', config)
351
351
 
352
- process_files(args.path, config, args.endpoint, args.is_directory)
352
+ process_dat_files(args.path, config, args.endpoint, args.is_directory, crosswalk)
353
353
  print("Conversion complete.")
354
354
 
355
- def process_files(path, config, endpoint, is_directory):
355
+ def process_dat_files(path, config, endpoint, is_directory, crosswalk):
356
356
  """
357
357
  Processes either a single file or all files within a directory.
358
358
 
@@ -370,10 +370,10 @@ def process_files(path, config, endpoint, is_directory):
370
370
  for file_name in os.listdir(path):
371
371
  if file_name.endswith(".DAT"):
372
372
  file_path = os.path.join(path, file_name)
373
- process_and_write_file(file_path, config, endpoint)
373
+ process_and_write_file(file_path, config, endpoint, crosswalk)
374
374
  else:
375
375
  MediLink_ConfigLoader.log("Processing the single file: {}".format(path))
376
- process_and_write_file(path, config, endpoint)
376
+ process_and_write_file(path, config, endpoint, crosswalk)
377
377
 
378
378
  if __name__ == "__main__":
379
379
  main()
@@ -381,7 +381,7 @@ if __name__ == "__main__":
381
381
  # The functions below are the ones that are used as non-main library by outside scripts.
382
382
  #######################################################################################
383
383
 
384
- def convert_files_for_submission(detailed_patient_data, config):
384
+ def convert_files_for_submission(detailed_patient_data, config, crosswalk, client):
385
385
  """
386
386
  Processes detailed patient data for submission based on their confirmed endpoints,
387
387
  generating separate 837P files for each endpoint according to the configured submission type.
@@ -424,18 +424,18 @@ def convert_files_for_submission(detailed_patient_data, config):
424
424
  chart_number = patient_data.get('CHART', 'UNKNOWN')#[:5] truncation might cause collisions.
425
425
  suffix = "_{}".format(chart_number)
426
426
  # Process and convert each patient's data to a separate file
427
- converted_path = process_claim(config, endpoint, [patient_data], suffix)
427
+ converted_path = process_claim(config, endpoint, [patient_data], crosswalk, client, suffix)
428
428
  if converted_path:
429
429
  converted_files_paths.append(converted_path)
430
430
  else:
431
431
  # Process all patient data together for batch submissions
432
- converted_path = process_claim(config, endpoint, patient_data_list)
432
+ converted_path = process_claim(config, endpoint, patient_data_list, crosswalk, client)
433
433
  if converted_path:
434
434
  converted_files_paths.append(converted_path)
435
435
 
436
436
  return converted_files_paths
437
437
 
438
- def process_claim(config, endpoint, patient_data_list, suffix=""):
438
+ def process_claim(config, endpoint, patient_data_list, crosswalk, client, suffix=""):
439
439
  """
440
440
  Processes patient data for a specified endpoint, converting it into the 837P format.
441
441
  Can handle both batch and single-patient submissions.
@@ -465,7 +465,7 @@ def process_claim(config, endpoint, patient_data_list, suffix=""):
465
465
  is_valid, validation_errors = validate_claim_data(patient_data, config)
466
466
  if is_valid:
467
467
  # Format the claim into 837P segments
468
- formatted_claim = format_single_claim(patient_data, config, endpoint, transaction_set_control_number)
468
+ formatted_claim = format_single_claim(patient_data, config, endpoint, transaction_set_control_number, crosswalk, client)
469
469
  document_segments.append(formatted_claim)
470
470
  transaction_set_control_number += 1
471
471
  else: