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.

@@ -0,0 +1,174 @@
1
+ import time
2
+ import requests
3
+
4
+ # Importing configuration loader
5
+ try:
6
+ from MediLink import MediLink_ConfigLoader
7
+ except ImportError:
8
+ import MediLink_ConfigLoader
9
+
10
+ # Class for handling API calls
11
+ class APIClient:
12
+ def __init__(self):
13
+ # Load configuration
14
+ self.config, _ = MediLink_ConfigLoader.load_configuration()
15
+ # Initialize token cache
16
+ self.token_cache = {}
17
+
18
+ # Method to get access token
19
+ def get_access_token(self, endpoint_name):
20
+ # Retrieve endpoint configuration
21
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
22
+ current_time = time.time()
23
+
24
+ # Check if token is cached and still valid
25
+ if endpoint_name in self.token_cache:
26
+ cached_token = self.token_cache[endpoint_name]
27
+ if cached_token['expires_at'] > current_time:
28
+ return cached_token['access_token']
29
+
30
+ # Prepare data for token request
31
+ data = {
32
+ 'grant_type': 'client_credentials',
33
+ 'client_id': endpoint_config['client_id'],
34
+ 'client_secret': endpoint_config['client_secret'],
35
+ 'scope': 'hipaa'
36
+ }
37
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
38
+
39
+ # Request token
40
+ response = requests.post(endpoint_config['token_url'], headers=headers, data=data)
41
+ response.raise_for_status()
42
+ token_data = response.json()
43
+ access_token = token_data['access_token']
44
+ expires_in = token_data.get('expires_in', 3600)
45
+
46
+ # Cache token with expiration time adjusted
47
+ self.token_cache[endpoint_name] = {
48
+ 'access_token': access_token,
49
+ 'expires_at': current_time + expires_in - 120
50
+ }
51
+ return access_token
52
+
53
+ # Method for making API calls
54
+ def make_api_call(self, endpoint_name, call_type, url_extension="", params=None, data=None):
55
+ # Retrieve endpoint configuration
56
+ endpoint_config = self.config['MediLink_Config']['endpoints'][endpoint_name]
57
+ # Get access token
58
+ token = self.get_access_token(endpoint_name)
59
+ headers = {'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'}
60
+
61
+ # Construct full URL
62
+ url = endpoint_config['api_url'] + url_extension
63
+
64
+ # Make appropriate type of call
65
+ if call_type == 'GET':
66
+ response = requests.get(url, headers=headers, params=params)
67
+ elif call_type == 'POST':
68
+ headers['Content-Type'] = 'application/json'
69
+ response = requests.post(url, headers=headers, json=data)
70
+ elif call_type == 'DELETE':
71
+ response = requests.delete(url, headers=headers)
72
+ else:
73
+ raise ValueError("Unsupported call type")
74
+
75
+ if response.status_code >= 400:
76
+ print("Error {}: {}".format(response.status_code, response.text))
77
+ response.raise_for_status()
78
+
79
+ return response.json()
80
+
81
+ # Method for creating coverage
82
+ def create_coverage(self, endpoint_name, patient_info):
83
+ return self.make_api_call(endpoint_name, 'POST', url_extension="/coverages", data=patient_info)
84
+
85
+ # Method for getting all coverages
86
+ def get_coverages(self, endpoint_name, params=None):
87
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/coverages", params=params)
88
+
89
+ # Method for getting coverage by ID
90
+ def get_coverage_by_id(self, endpoint_name, coverage_id):
91
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/coverages/{}".format(coverage_id))
92
+
93
+ # Method for deleting coverage by ID
94
+ def delete_coverage_by_id(self, endpoint_name, coverage_id):
95
+ return self.make_api_call(endpoint_name, 'DELETE', url_extension="/coverages/{}".format(coverage_id))
96
+
97
+ # Method for getting payer list
98
+ def get_payer_list(self, endpoint_name, params=None):
99
+ return self.make_api_call(endpoint_name, 'GET', url_extension="/availity-payer-list", params=params)
100
+
101
+ # Function to fetch payer name from API
102
+ def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
103
+ client = APIClient()
104
+
105
+ # Step 1: Reload configuration for safety (This should be able to be replaced by APIClient())
106
+ config, _ = MediLink_ConfigLoader.load_configuration()
107
+
108
+ # Step 2: Check if the primary endpoint is specified and is valid
109
+ # (these validity checks will need to be incorporated into the main functionality)
110
+ endpoints = config['MediLink_Config']['endpoints']
111
+ if primary_endpoint and primary_endpoint in endpoints:
112
+ endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
113
+ else:
114
+ endpoint_order = list(endpoints.keys())
115
+
116
+ # Step 3: Iterate through available endpoints in specified order
117
+ for endpoint_name in endpoint_order:
118
+ endpoint_config = endpoints[endpoint_name]
119
+ if not all(key in endpoint_config for key in ['token_url', 'client_id', 'client_secret']):
120
+ MediLink_ConfigLoader.log("Skipping {} due to missing API keys.".format(endpoint_name), config, level="WARNING")
121
+ continue
122
+
123
+ try:
124
+ response = client.get_payer_list(endpoint_name, params={'payerId': payer_id})
125
+ if 'payers' in response and response['payers']:
126
+ payer = response['payers'][0]
127
+ payer_name = payer.get('displayName') or payer.get('name')
128
+
129
+ MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), config, level="INFO")
130
+ return payer_name
131
+ else:
132
+ MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), config, level="INFO")
133
+ except requests.RequestException as e:
134
+ MediLink_ConfigLoader.log("API call failed at {} for Payer ID '{}': {}".format(endpoint_name, payer_id, str(e)), config, level="ERROR")
135
+
136
+ error_message = "All endpoints exhausted for Payer ID {}.".format(payer_id)
137
+ MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
138
+ raise ValueError(error_message)
139
+
140
+ # Example usage
141
+ if __name__ == "__main__":
142
+ client = APIClient()
143
+ try:
144
+ # Fetch and print payer name
145
+ payer_name = fetch_payer_name_from_api("11347", 'config.yaml')
146
+ print("Payer Name: {}".format(payer_name))
147
+
148
+ # Example patient info
149
+ patient_info = {
150
+ "policyNumber": "12345",
151
+ "name": "John Doe",
152
+ "dob": "1980-01-01"
153
+ }
154
+ # Create coverage and print response
155
+ response = client.create_coverage('AVAILITY', patient_info)
156
+ print("Create Coverage Response: {}".format(response))
157
+
158
+ # Get all coverages and print response
159
+ response = client.get_coverages('AVAILITY')
160
+ print("All Coverages: {}".format(response))
161
+
162
+ # Example coverage ID
163
+ coverage_id = "some-coverage-id"
164
+ # Get coverage by ID and print response
165
+ response = client.get_coverage_by_id('AVAILITY', coverage_id)
166
+ print("Coverage by ID: {}".format(response))
167
+
168
+ # Delete coverage by ID and print response
169
+ response = client.delete_coverage_by_id('AVAILITY', coverage_id)
170
+ print("Delete Coverage Response: {}".format(response))
171
+
172
+ except Exception as e:
173
+ # Print error if any
174
+ print("Error: {}".format(e))
@@ -0,0 +1,137 @@
1
+ import time
2
+ import requests
3
+ try:
4
+ from MediLink import MediLink_ConfigLoader
5
+ except ImportError:
6
+ import MediLink_ConfigLoader
7
+
8
+ # Fetches the payer name from API based on the payer ID.
9
+ def fetch_payer_name_from_api(payer_id, config, primary_endpoint='AVAILITY'):
10
+ """
11
+ Fetches the payer name from the API using the provided payer ID.
12
+
13
+ Args:
14
+ payer_id (str): The ID of the payer.
15
+ config (dict): Configuration settings.
16
+ primary_endpoint (str): The primary endpoint for resolving payer information.
17
+
18
+ Raises:
19
+ ValueError: If all endpoints are exhausted without finding the payer.
20
+
21
+ Returns:
22
+ str: The fetched payer name.
23
+ """
24
+ # Reload for safety
25
+ config, _ = MediLink_ConfigLoader.load_configuration()
26
+
27
+ # Step 1: Retrieve endpoint configurations
28
+ endpoints = config['MediLink_Config']['endpoints']
29
+ tried_endpoints = []
30
+
31
+ # Step 2: Check if the primary endpoint is specified and is valid
32
+ if primary_endpoint and primary_endpoint in endpoints:
33
+ endpoint_order = [primary_endpoint] + [endpoint for endpoint in endpoints if endpoint != primary_endpoint]
34
+ else:
35
+ endpoint_order = list(endpoints.keys())
36
+
37
+ # Step 3: Iterate through available endpoints in specified order
38
+ for endpoint_name in endpoint_order:
39
+ endpoint_config = endpoints[endpoint_name]
40
+ if not all(key in endpoint_config for key in ['token_url', 'client_id', 'client_secret']):
41
+ MediLink_ConfigLoader.log("Skipping {} due to missing API keys.".format(endpoint_name), config, level="WARNING")
42
+ continue
43
+
44
+ # Step 4: Get access token for the endpoint
45
+ token = get_access_token(endpoint_config)
46
+ api_url = endpoint_config.get("api_url", "")
47
+ headers = {'Authorization': 'Bearer {}'.format(token), 'Accept': 'application/json'}
48
+ params = {'payerId': payer_id}
49
+
50
+ try:
51
+ # Step 5: Make API call to fetch payer name
52
+ response = requests.get(api_url, headers=headers, params=params)
53
+ response.raise_for_status()
54
+ data = response.json()
55
+ if 'payers' in data and data['payers']:
56
+ payer = data['payers'][0]
57
+ payer_name = payer.get('displayName') or payer.get('name')
58
+
59
+ # Log successful match
60
+ MediLink_ConfigLoader.log("Successfully found payer at {} for ID {}: {}".format(endpoint_name, payer_id, payer_name), config, level="INFO")
61
+
62
+ return payer_name
63
+ else:
64
+ MediLink_ConfigLoader.log("No payer found at {} for ID: {}. Trying next available endpoint.".format(endpoint_name, payer_id), config, level="INFO")
65
+ except requests.RequestException as e:
66
+ # Step 6: Log API call failure
67
+ MediLink_ConfigLoader.log("API call failed at {} for Payer ID '{}': {}".format(endpoint_name, payer_id, str(e)), config, level="ERROR")
68
+ tried_endpoints.append(endpoint_name)
69
+
70
+ # Step 7: Log all endpoints exhaustion and raise error
71
+ error_message = "All endpoints exhausted for Payer ID {}. Endpoints tried: {}".format(payer_id, ', '.join(tried_endpoints))
72
+ MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
73
+ raise ValueError(error_message)
74
+
75
+ # Test Case for API fetch
76
+ #payer_id = "11347"
77
+ #config = load_configuration()
78
+ #payer_name = fetch_payer_name_from_api(payer_id, config, endpoint='AVAILITY')
79
+ #print(payer_id, payer_name)
80
+
81
+ # Initialize a global dictionary to store the access token and its expiry time
82
+ # TODO (Low API) This will need to get setup for each endpoint separately.
83
+ token_cache = {
84
+ 'access_token': None,
85
+ 'expires_at': 0 # Timestamp of when the token expires
86
+ }
87
+
88
+ def get_access_token(endpoint_config):
89
+ current_time = time.time()
90
+
91
+ # Check if the cached token is still valid
92
+ if token_cache['access_token'] and token_cache['expires_at'] > current_time:
93
+ return token_cache['access_token']
94
+
95
+ # Validate endpoint configuration
96
+ if not endpoint_config or not all(k in endpoint_config for k in ['client_id', 'client_secret', 'token_url']):
97
+ raise ValueError("Endpoint configuration is incomplete or missing necessary fields.")
98
+
99
+ # Extract credentials and URL from the config
100
+ CLIENT_ID = endpoint_config.get("client_id")
101
+ CLIENT_SECRET = endpoint_config.get("client_secret")
102
+ url = endpoint_config.get("token_url")
103
+
104
+ # Setup the data payload and headers for the HTTP request
105
+ data = {
106
+ 'grant_type': 'client_credentials',
107
+ 'client_id': CLIENT_ID,
108
+ 'client_secret': CLIENT_SECRET,
109
+ 'scope': 'hipaa'
110
+ }
111
+ headers = {
112
+ 'Content-Type': 'application/x-www-form-urlencoded'
113
+ }
114
+
115
+ try:
116
+ # Perform the HTTP request to get the access token
117
+ response = requests.post(url, headers=headers, data=data)
118
+ response.raise_for_status() # This will raise an exception for HTTP error statuses
119
+ json_response = response.json()
120
+ access_token = json_response.get('access_token')
121
+ expires_in = json_response.get('expires_in', 3600) # Default to 3600 seconds if not provided
122
+
123
+ if not access_token:
124
+ raise ValueError("No access token returned by the server.")
125
+
126
+ # Store the access token and calculate the expiry time
127
+ token_cache['access_token'] = access_token
128
+ token_cache['expires_at'] = current_time + expires_in - 120 # Subtracting 120 seconds to provide buffer
129
+
130
+ return access_token
131
+ except requests.RequestException as e:
132
+ # Handle HTTP errors (e.g., network problems, invalid response)
133
+ error_msg = "Failed to retrieve access token: {0}. Response status: {1}".format(str(e), response.status_code if response else 'No response')
134
+ raise Exception(error_msg)
135
+ except ValueError as e:
136
+ # Handle specific errors like missing access token
137
+ raise Exception("Configuration or server response error: {0}".format(str(e)))
@@ -4,45 +4,28 @@ import logging
4
4
  from datetime import datetime
5
5
  from collections import OrderedDict
6
6
  import sys
7
+ import platform
7
8
 
8
9
  """
9
10
  This function should be generalizable to have a initialization script over all the Medi* functions
10
11
  """
11
-
12
- # Setup basic logging.
13
- # BUG Consolidate this with MediLink_837p_encoder_library.log
14
- def setup_logger(local_storage_path):
15
- # Define a reasonable name for the log file, e.g., "MediLink_Down_Process.log"
16
- log_filename = datetime.now().strftime("MediLink_Down_Process_%m%d%Y.log")
17
- log_filepath = os.path.join(local_storage_path, log_filename)
18
-
19
- for handler in logging.root.handlers[:]:
20
- logging.root.removeHandler(handler)
21
-
22
- # Setup logging to file
23
- logging.basicConfig(level=logging.INFO,
24
- format='%(asctime)s - %(levelname)s - %(message)s',
25
- filename=log_filepath, # Direct logging to a file in local_storage_path
26
- filemode='a') # Append mode
27
-
28
- # If you also want to see the logs in the console, add a StreamHandler
29
- #console_handler = logging.StreamHandler()
30
- #console_handler.setLevel(logging.INFO)
31
- #formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
32
- #console_handler.setFormatter(formatter)
33
- #logging.getLogger('').addHandler(console_handler)
34
-
35
12
  def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'config.json'), crosswalk_path=os.path.join(os.path.dirname(__file__), '..', 'json', 'crosswalk.json')):
36
13
  """
37
14
  Loads endpoint configuration, credentials, and other settings from JSON files.
38
15
 
39
16
  Returns: A tuple containing dictionaries with configuration settings for the main config and crosswalk.
40
17
  """
41
- # BUG HARDCODE FOR NOW
42
- config_path="G:\\My Drive\\Codes\\MediCafe\\json\\config.json"
43
- # "F:\\Medibot\\json\\config.json"
44
- crosswalk_path="G:\\My Drive\\Codes\\MediCafe\\json\\crosswalk.json"
45
- # "F:\\Medibot\\json\\crosswalk.json"
18
+ # TODO (Low Config Upgrade) The Medicare / Private differentiator flag probably needs to be pulled or passed to this.
19
+ # BUG (HARDCODE) FOR NOW:
20
+ # Detect the operating system
21
+ if platform.system() == 'Windows' and platform.release() == 'XP':
22
+ # Use F: paths for Windows XP
23
+ config_path = "F:\\Medibot\\json\\config.json"
24
+ crosswalk_path = "F:\\Medibot\\json\\crosswalk.json"
25
+ else:
26
+ # Use G: paths for other versions of Windows
27
+ config_path = "G:\\My Drive\\Codes\\MediCafe\\json\\config.json"
28
+ crosswalk_path = "G:\\My Drive\\Codes\\MediCafe\\json\\crosswalk.json"
46
29
 
47
30
  try:
48
31
  with open(config_path, 'r') as config_file:
@@ -55,8 +38,11 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
55
38
  crosswalk = json.load(crosswalk_file)
56
39
 
57
40
  return config, crosswalk
58
- except json.JSONDecodeError as e:
59
- print("Error parsing JSON file: {}".format(e))
41
+ except ValueError as e:
42
+ if isinstance(e, UnicodeDecodeError):
43
+ print("Error decoding JSON file: {}".format(e))
44
+ else:
45
+ print("Error parsing JSON file: {}".format(e))
60
46
  sys.exit(1) # Exit the script due to a critical error in configuration loading
61
47
  except FileNotFoundError:
62
48
  print("One or both JSON files not found. Config: {}, Crosswalk: {}".format(config_path, crosswalk_path))
@@ -66,4 +52,30 @@ def load_configuration(config_path=os.path.join(os.path.dirname(__file__), '..',
66
52
  sys.exit(1) # Exit the script due to a critical error in configuration loading
67
53
  except Exception as e:
68
54
  print("An unexpected error occurred while loading the configuration: {}".format(e))
69
- sys.exit(1) # Exit the script due to a critical error in configuration loading
55
+ sys.exit(1) # Exit the script due to a critical error in configuration loading
56
+
57
+ # Logs messages with optional error type and claim data.
58
+ def log(message, config=None, level="INFO", error_type=None, claim=None):
59
+
60
+ # If config is not provided, load it
61
+ if config is None:
62
+ config, _ = load_configuration()
63
+
64
+ # Setup logger if not already configured
65
+ if not logging.root.handlers:
66
+ local_storage_path = config['MediLink_Config'].get('local_storage_path', '.') if isinstance(config, dict) else '.'
67
+ log_filename = datetime.now().strftime("Log_%m%d%Y.log")
68
+ log_filepath = os.path.join(local_storage_path, log_filename)
69
+ logging.basicConfig(level=logging.INFO,
70
+ format='%(asctime)s - %(levelname)s - %(message)s',
71
+ filename=log_filepath,
72
+ filemode='a')
73
+
74
+ # Prepare log message
75
+ claim_data = " - Claim Data: {}".format(claim) if claim else ""
76
+ error_info = " - Error Type: {}".format(error_type) if error_type else ""
77
+ full_message = "{} {}{}".format(message, claim_data, error_info)
78
+
79
+ # Log the message
80
+ logger = logging.getLogger()
81
+ getattr(logger, level.lower())(full_message)
@@ -1,10 +1,13 @@
1
1
  import csv
2
2
  import os
3
3
  from datetime import datetime, timedelta
4
- import logging
5
- import MediLink_837p_encoder_library
6
- import subprocess # BUG Currently disabled for testing.
7
- import logging
4
+ import subprocess
5
+
6
+ # Need this for running Medibot and MediLink
7
+ try:
8
+ import MediLink_ConfigLoader
9
+ except ImportError:
10
+ from . import MediLink_ConfigLoader
8
11
 
9
12
  # Helper function to slice and strip values
10
13
  def slice_data(data, slices):
@@ -12,7 +15,15 @@ def slice_data(data, slices):
12
15
  return {key: data[slice(*slices[key])].strip() for key in slices}
13
16
 
14
17
  # Function to parse fixed-width Medisoft output and extract claim data
15
- def parse_fixed_width_data(personal_info, insurance_info, service_info, config):
18
+ def parse_fixed_width_data(personal_info, insurance_info, service_info, config=None):
19
+
20
+ # Make sure we have the right config
21
+ if not config: # Checks if config is None or an empty dictionary
22
+ MediLink_ConfigLoader.log("No config passed to parse_fixed_width_data. Re-loading config...", level="WARNING")
23
+ config, _ = MediLink_ConfigLoader.load_configuration()
24
+
25
+ config = config.get('MediLink_Config', config) # Safest config call.
26
+
16
27
  # Load slice definitions from config within the MediLink_Config section
17
28
  personal_slices = config['fixedWidthSlices']['personal_slices']
18
29
  insurance_slices = config['fixedWidthSlices']['insurance_slices']
@@ -24,14 +35,15 @@ def parse_fixed_width_data(personal_info, insurance_info, service_info, config):
24
35
  parsed_data.update(slice_data(insurance_info, insurance_slices))
25
36
  parsed_data.update(slice_data(service_info, service_slices))
26
37
 
27
- MediLink_837p_encoder_library.log("Successfully parsed data from segments", config, level="INFO")
38
+ MediLink_ConfigLoader.log("Successfully parsed data from segments", config, level="INFO")
28
39
 
29
40
  return parsed_data
30
41
 
31
42
  # Function to read fixed-width Medisoft output and extract claim data
32
- def read_fixed_width_data(file_path, config):
43
+ def read_fixed_width_data(file_path):
33
44
  # Reads the fixed width data from the file and yields each patient's
34
45
  # personal, insurance, and service information.
46
+ MediLink_ConfigLoader.log("Starting to read fixed width data...")
35
47
  with open(file_path, 'r') as file:
36
48
  lines_buffer = [] # Buffer to hold lines for current patient data
37
49
  for line in file:
@@ -41,12 +53,21 @@ def read_fixed_width_data(file_path, config):
41
53
  # Once we have 3 lines of data, yield them as a patient record
42
54
  if len(lines_buffer) == 3:
43
55
  personal_info, insurance_info, service_info = lines_buffer
44
- MediLink_837p_encoder_library.log("Successfully read data from file: {}".format(file_path), config, level="INFO")
56
+ MediLink_ConfigLoader.log("Successfully read data from file: {}".format(file_path), level="INFO")
45
57
  yield personal_info, insurance_info, service_info
46
58
  lines_buffer.clear() # Reset buffer for the next patient record
47
59
  # If the line is blank but we have already started collecting a patient record,
48
60
  # we continue without resetting the buffer, effectively skipping blank lines.
49
61
 
62
+ # TODO (Refactor) Consider consolidating with the other read_fixed_with_data
63
+ def read_general_fixed_width_data(file_path, slices):
64
+ # handle any fixed-width data based on provided slice definitions
65
+ with open(file_path, 'r', encoding='utf-8') as file:
66
+ next(file) # Skip the header
67
+ for line_number, line in enumerate(file, start=1):
68
+ insurance_name = {key: line[start:end].strip() for key, (start, end) in slices.items()}
69
+ yield insurance_name, line_number
70
+
50
71
  def consolidate_csvs(source_directory):
51
72
  """
52
73
  This default overwrites any existing CSV for the same day. We want this for the automated runs but want to switch through
@@ -61,7 +82,7 @@ def consolidate_csvs(source_directory):
61
82
 
62
83
  # Check if the file already exists and log the action
63
84
  if os.path.exists(consolidated_filepath):
64
- MediLink_837p_encoder_library.log("The file {} already exists. It will be overwritten.".format(consolidated_filename))
85
+ MediLink_ConfigLoader.log("The file {} already exists. It will be overwritten.".format(consolidated_filename))
65
86
 
66
87
  for filename in os.listdir(source_directory):
67
88
  filepath = os.path.join(source_directory, filename)
@@ -90,11 +111,11 @@ def consolidate_csvs(source_directory):
90
111
  writer = csv.writer(csvfile)
91
112
  writer.writerows(consolidated_data)
92
113
 
93
- MediLink_837p_encoder_library.log("Consolidated CSVs into {}".format(consolidated_filepath))
114
+ MediLink_ConfigLoader.log("Consolidated CSVs into {}".format(consolidated_filepath))
94
115
 
95
116
  return consolidated_filepath
96
117
 
97
- def operate_winscp(operation_type, files, endpoint_config, local_storage_path):
118
+ def operate_winscp(operation_type, files, endpoint_config, local_storage_path, config):
98
119
  """
99
120
  General function to operate WinSCP for uploading or downloading files.
100
121
 
@@ -110,7 +131,7 @@ def operate_winscp(operation_type, files, endpoint_config, local_storage_path):
110
131
  'remote_directory_up': '/remote/upload/path'
111
132
  }
112
133
 
113
- operate_winscp('upload', upload_files, upload_config, 'path/to/local/storage')
134
+ operate_winscp('upload', upload_files, upload_config, 'path/to/local/storage', config)
114
135
 
115
136
  # Example of how to call this function for downloads
116
137
  download_config = {
@@ -118,21 +139,22 @@ def operate_winscp(operation_type, files, endpoint_config, local_storage_path):
118
139
  'remote_directory_down': '/remote/download/path'
119
140
  }
120
141
 
121
- operate_winscp('download', None, download_config, 'path/to/local/storage')
142
+ operate_winscp('download', None, download_config, 'path/to/local/storage', config)
122
143
  """
123
144
  # Setup paths
124
145
  try:
125
- winscp_path = endpoint_config['winscp_path']
146
+ # TODO (Easy / Config) Get this updated. ??
147
+ winscp_path = config['winscp_path']
126
148
  except KeyError:
127
149
  winscp_path = os.path.join(os.getcwd(), "Installers", "WinSCP-Portable", "WinSCP.com")
128
150
  except Exception as e:
129
151
  # Handle any other exceptions here
130
- print("An error occurred:", e)
152
+ print("An error occurred while running WinSCP:", e)
131
153
  winscp_path = None
132
154
 
133
155
  if not os.path.isfile(winscp_path):
134
- logging.error("WinSCP.com not found at {}".format(winscp_path))
135
- return False
156
+ MediLink_ConfigLoader.log("WinSCP.com not found at {}".format(winscp_path))
157
+ return []
136
158
 
137
159
  # Setup logging
138
160
  log_filename = "winscp_upload.log" if operation_type == "upload" else "winscp_download.log"
@@ -154,7 +176,7 @@ def operate_winscp(operation_type, files, endpoint_config, local_storage_path):
154
176
  ]
155
177
 
156
178
  # Add commands to WinSCP script
157
- # BUG We really need to fix this path situation.
179
+ # BUG (Low SFTP) We really need to fix this path situation.
158
180
  # Unfortunately, this just needs to be a non-spaced path because WinSCP can't
159
181
  # handle the spaces. Also, Windows won't let me use shutil to move the files out of G:\ into C:\ and it it wants an administrator security
160
182
  # check or verification thing for me to even move the file by hand so that doesn't work either.
@@ -162,28 +184,57 @@ def operate_winscp(operation_type, files, endpoint_config, local_storage_path):
162
184
  if operation_type == "upload":
163
185
  for file_path in files:
164
186
  normalized_path = os.path.normpath(file_path)
165
- command.append("put \"{}\"".format(normalized_path))
187
+ command.append("put {}".format(normalized_path))
166
188
  else:
167
189
  command.append('get *') # Adjust pattern as needed
168
190
 
169
191
  command += ['close', 'exit']
170
192
 
171
- # Execute command
172
- # process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
173
- # stdout, stderr = process.communicate()
193
+ # Check if TestMode is enabled in the configuration
194
+ if config.get("MediLink_Config", {}).get("TestMode", True):
195
+ # TestMode is enabled, do not execute the command
196
+ print("Test Mode is enabled! WinSCP Command not executed.")
197
+ MediLink_ConfigLoader.log("Test Mode is enabled! WinSCP Command not executed.")
198
+ MediLink_ConfigLoader.log("TEST MODE: Simulating WinSCP Upload File List.")
199
+ uploaded_files = []
200
+ for file_path in files:
201
+ normalized_path = os.path.normpath(file_path)
202
+ if os.path.exists(normalized_path): # Check if the file exists before appending
203
+ uploaded_files.append(normalized_path)
204
+ else:
205
+ MediLink_ConfigLoader.log("TEST MODE: Failed to upload file: {} does not exist.".format(normalized_path))
206
+ return uploaded_files
207
+ else:
208
+ # TestMode is not enabled, execute the command
209
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)
210
+ stdout, stderr = process.communicate()
174
211
 
175
- if True: #process.returncode == 0:
176
- print("WINSCP IS CURRENTLY DISABLED FOR TESTING.") # BUG
177
- logging.info("Files {}ed successfully.".format(operation_type))
178
- # BUG This return code is a little trigger happy.
179
- # BUG If the WinSCP command specifies the correct download path, this might not be necessary
180
- # move_downloaded_files(local_storage_path)
181
- return True
212
+ if process.returncode == 0: # BUG Does this work as intended?
213
+ MediLink_ConfigLoader.log("WinSCP {} attempted.".format(operation_type))
214
+ # Construct a list of downloaded files if operation_type is 'download'
215
+ if operation_type == 'download':
216
+ downloaded_files = []
217
+ for root, dirs, files in os.walk(local_storage_path):
218
+ for file in files:
219
+ downloaded_files.append(os.path.join(root, file))
220
+ return downloaded_files
221
+
222
+ if operation_type == 'upload':
223
+ # Return a list of uploaded files
224
+ uploaded_files = []
225
+ for file_path in files:
226
+ normalized_path = os.path.normpath(file_path)
227
+ if os.path.exists(normalized_path): # Check if the file exists before appending
228
+ uploaded_files.append(normalized_path)
229
+ else:
230
+ MediLink_ConfigLoader.log("Failed to upload file: {} does not exist.".format(normalized_path))
231
+ return uploaded_files
182
232
  else:
183
- logging.error("Failed to {} files. Details: {}".format(operation_type, stderr.decode('utf-8')))
184
- return False
233
+ MediLink_ConfigLoader.log("Failed to {} files. Details: {}".format(operation_type, stderr.decode('utf-8')))
234
+ return [] # Return empty list to indicate failure. BUG check to make sure this doesn't break something else.
185
235
 
186
236
  # UNUSED CSV Functions
237
+ """
187
238
  def remove_blank_rows_from_csv(csv_file_path):
188
239
  with open(csv_file_path, 'r') as csv_file:
189
240
  # Read the CSV file and filter out any empty rows
@@ -195,7 +246,7 @@ def remove_blank_rows_from_csv(csv_file_path):
195
246
  writer.writerows(rows)
196
247
 
197
248
  def list_chart_numbers_in_existing_file(filepath):
198
- """Lists the Chart Numbers contained in an existing CSV file."""
249
+ # Lists the Chart Numbers contained in an existing CSV file.
199
250
  chart_numbers = []
200
251
  with open(filepath, 'r', newline='') as csvfile:
201
252
  reader = csv.reader(csvfile)
@@ -203,4 +254,5 @@ def list_chart_numbers_in_existing_file(filepath):
203
254
  for row in reader:
204
255
  if len(row) > 2: # Assuming Chart Number is in the 3rd column
205
256
  chart_numbers.append(row[2])
206
- return chart_numbers
257
+ return chart_numbers
258
+ """