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.

@@ -1,288 +1,590 @@
1
- import json
2
- import sys
3
- import os
4
-
5
- # Add parent directory of the project to the Python path
6
- import sys
1
+ import json, sys, os
7
2
 
3
+ # Set the project directory to the parent directory of the current file
8
4
  project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
9
- sys.path.append(project_dir)
5
+ if project_dir not in sys.path:
6
+ sys.path.append(project_dir)
10
7
 
11
- try:
8
+ # Attempt to import the MediLink_ConfigLoader module, falling back to an alternative import if necessary
9
+ try:
12
10
  import MediLink_ConfigLoader
13
- except ImportError:
11
+ except ImportError:
14
12
  from MediLink import MediLink_ConfigLoader
15
13
 
16
- try:
14
+ # Attempt to import the fetch_payer_name_from_api function from MediLink_API_v3, with a fallback
15
+ try:
17
16
  from MediLink_API_v3 import fetch_payer_name_from_api
18
- except ImportError:
17
+ except ImportError:
19
18
  from MediLink import MediLink_API_v3
20
19
  fetch_payer_name_from_api = MediLink_API_v3.fetch_payer_name_from_api
21
20
 
22
- try:
21
+ # Attempt to import the MediBot_Preprocessor_lib module, with a fallback
22
+ try:
23
23
  from MediBot import MediBot_Preprocessor_lib
24
- except ImportError:
24
+ except ImportError:
25
25
  import MediBot_Preprocessor_lib
26
26
 
27
- def check_and_initialize_crosswalk(config):
28
- """
29
- Checks if the 'payer_id' key exists in the crosswalk. If not, prompts the user
30
- to initialize the crosswalk.
27
+ """
28
+ # TODO This has a bunch of issues that need to be fixed. Notice, the log has repetitive calls to the API that are redundant.
29
+
30
+ PS G:\My Drive\Codes\MediCafe> & C:/Python34/python.exe "g:/My Drive/Codes/MediCafe/MediBot/MediBot_Preprocessor.py" --update-crosswalk
31
+ Updating the crosswalk...
32
+ The 'payer_id' list is empty or missing. Would you like to initialize the crosswalk? (yes/no): yes
33
+ No payer found at AVAILITY for ID 60054M. Response: {'limit': 50, 'offset': 0, 'totalCount': 0, 'payers': [], 'count': 0}
34
+ All endpoints exhausted for Payer ID 60054M.
35
+ WARNING: Invalid Payer ID 60054M (Unknown).
36
+ Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: 60054
37
+ csv_replacements updated: '60054M' -> '60054'.
38
+ No payer found at AVAILITY for ID MCRFL. Response: {'limit': 50, 'offset': 0, 'totalCount': 0, 'payers': [], 'count': 0}
39
+ All endpoints exhausted for Payer ID MCRFL.
40
+ No payer found at AVAILITY for ID BCSFL. Response: {'limit': 50, 'offset': 0, 'totalCount': 0, 'payers': [], 'count': 0}
41
+ All endpoints exhausted for Payer ID BCSFL.
42
+ Payer ID '60054M' has been successfully replaced with '60054'.
43
+ No payer found at AVAILITY for ID MCRFL. Response: {'limit': 50, 'offset': 0, 'totalCount': 0, 'payers': [], 'count': 0}
44
+ All endpoints exhausted for Payer ID MCRFL.
45
+ WARNING: Invalid Payer ID MCRFL (Unknown).
46
+ Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: force
47
+ Payer ID 'MCRFL' has been marked as 'Unknown'.
48
+ No payer found at AVAILITY for ID BCSFL. Response: {'limit': 50, 'offset': 0, 'totalCount': 0, 'payers': [], 'count': 0}
49
+ All endpoints exhausted for Payer ID BCSFL.
50
+ WARNING: Invalid Payer ID BCSFL (Unknown).
51
+ Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: 00590
52
+ csv_replacements updated: 'BCSFL' -> '00590'.
53
+ Payer ID 'BCSFL' has been successfully replaced with '00590'.
54
+ Crosswalk initialized with mappings for 5 payers.
55
+
56
+ """
31
57
 
58
+
59
+ def fetch_and_store_payer_name(client, payer_id, crosswalk, config):
60
+ """
61
+ Fetches the payer name for a given payer ID and stores it in the crosswalk.
62
+
32
63
  Args:
33
- config (dict): Configuration settings.
64
+ payer_id (str): The ID of the payer to fetch.
65
+ crosswalk (dict): The crosswalk dictionary to store the payer name.
66
+ config (dict): Configuration settings for logging.
34
67
 
35
68
  Returns:
36
- boolean: True if succeeded.
69
+ bool: True if the payer name was fetched and stored successfully, False otherwise.
37
70
  """
38
- # Reload for safety
39
- config, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
40
-
71
+ MediLink_ConfigLoader.log("Attempting to fetch payer name for Payer ID: {}".format(payer_id), config, level="DEBUG")
41
72
  try:
42
- # Attempt to access the 'payer_id' key to ensure it exists
43
- if 'payer_id' not in crosswalk:
44
- raise KeyError("Missing 'payer_id' key in crosswalk.")
45
- except KeyError:
46
- error_message = "The 'payer_id' key does not exist in the crosswalk configuration. \n" \
47
- "This could be because the crosswalk is not initialized. \n" \
48
- "Consider running the Crosswalk initializer."
49
- print(error_message)
50
- MediLink_ConfigLoader.log(error_message, config, level="ERROR")
73
+ # Fetch the payer name from the API
74
+ payer_name = fetch_payer_name_from_api(client, payer_id, config, primary_endpoint=None)
75
+ MediLink_ConfigLoader.log("Fetched payer name: {} for Payer ID: {}".format(payer_name, payer_id), config, level="DEBUG")
51
76
 
52
- # Prompt user to initialize crosswalk
53
- initialize_choice = input("\nADVANCED OPTION: The crosswalk may not be initialized. \nType 'yes' to initialize it now: ").strip().lower()
54
- if initialize_choice == 'yes':
55
- initialize_crosswalk_from_mapat()
56
- _, crosswalk = MediLink_ConfigLoader.load_configuration() # Reload crosswalk
57
- MediLink_ConfigLoader.log("Crosswalk reloaded successfully.", config, level="INFO")
58
- else:
59
- raise KeyError(error_message)
77
+ # Ensure the 'payer_id' key exists in the crosswalk
78
+ if 'payer_id' not in crosswalk:
79
+ crosswalk['payer_id'] = {}
80
+ MediLink_ConfigLoader.log("Initialized 'payer_id' in crosswalk.", config, level="DEBUG")
81
+
82
+ # Initialize the payer ID entry if it doesn't exist
83
+ if payer_id not in crosswalk['payer_id']:
84
+ crosswalk['payer_id'][payer_id] = {} # Initialize the entry
85
+ MediLink_ConfigLoader.log("Initialized entry for Payer ID: {}".format(payer_id), config, level="DEBUG")
86
+
87
+ # Store the fetched payer name in the crosswalk
88
+ crosswalk['payer_id'][payer_id]['name'] = payer_name
89
+ message = "Payer ID {} ({}) fetched and stored successfully.".format(payer_id, payer_name)
90
+ MediLink_ConfigLoader.log(message, config, level="INFO")
91
+ print(message)
92
+ return True
93
+ except Exception as e:
94
+ # Log any errors encountered during the fetching process
95
+ MediLink_ConfigLoader.log("Failed to fetch name for Payer ID {}: {}".format(payer_id, e), config, level="WARNING")
96
+ crosswalk['payer_id'][payer_id]['name'] = "Unknown"
97
+ return False
98
+
99
+ def validate_and_correct_payer_ids(client, crosswalk, config, auto_correct=False):
100
+ """
101
+ Validates and corrects payer IDs in the crosswalk. If a payer ID is invalid, it prompts the user for correction.
102
+
103
+ Args:
104
+ crosswalk (dict): The crosswalk dictionary containing payer IDs.
105
+ config (dict): Configuration settings for logging.
106
+ auto_correct (bool): If True, automatically corrects invalid payer IDs to 'Unknown'.
107
+ """
108
+ processed_payer_ids = set() # Track processed payer IDs
109
+ payer_ids = list(crosswalk.get('payer_id', {}).keys()) # Static list to prevent modification issues
60
110
 
61
- return True
62
-
63
- def validate_and_correct_payer_ids(crosswalk, config):
64
- """Validates payer IDs via API and handles invalid IDs through user intervention."""
65
- for payer_id in list(crosswalk['payer_id'].keys()):
66
- try:
67
- fetch_payer_name_from_api(payer_id, config, primary_endpoint=None)
68
- MediLink_ConfigLoader.log("Payer ID {} validated successfully.".format(payer_id), config, level="INFO")
69
- except Exception as e:
70
- MediLink_ConfigLoader.log("Payer ID validation failed for {}: {}".format(payer_id, e), config, level="WARNING")
111
+ for payer_id in payer_ids:
112
+ if payer_id in processed_payer_ids:
113
+ continue # Skip already processed payer IDs
114
+
115
+ # Validate the payer ID by fetching its name
116
+ is_valid = fetch_and_store_payer_name(client, payer_id, crosswalk, config)
117
+
118
+ if not is_valid:
119
+ if auto_correct:
120
+ # Automatically correct invalid payer IDs to 'Unknown'
121
+ crosswalk['payer_id'][payer_id]['name'] = "Unknown"
122
+ MediLink_ConfigLoader.log(
123
+ "Auto-corrected Payer ID {} to 'Unknown'.".format(payer_id),
124
+ config,
125
+ level="WARNING"
126
+ )
127
+ print("Auto-corrected Payer ID '{}' to 'Unknown'.".format(payer_id))
128
+ processed_payer_ids.add(payer_id)
129
+ continue
130
+
131
+ # Prompt the user for a corrected payer ID
132
+ current_name = crosswalk['payer_id'].get(payer_id, {}).get('name', 'Unknown')
133
+ corrected_payer_id = input(
134
+ "WARNING: Invalid Payer ID {} ({}).\n"
135
+ "Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: ".format(
136
+ payer_id, current_name)
137
+ ).strip()
138
+
139
+ if corrected_payer_id.lower() == 'force':
140
+ # Assign "Unknown" and log the action
141
+ crosswalk['payer_id'][payer_id]['name'] = "Unknown"
142
+ MediLink_ConfigLoader.log(
143
+ "User forced unresolved Payer ID {} to remain as 'Unknown'.".format(payer_id),
144
+ config,
145
+ level="WARNING"
146
+ )
147
+ print("Payer ID '{}' has been marked as 'Unknown'.".format(payer_id))
148
+ processed_payer_ids.add(payer_id)
149
+ continue
71
150
 
72
- while True:
73
- corrected_payer_id = input("WWARNING: Invalid Payer ID {}. Enter the correct Payer ID for replacement or type 'FORCE' to continue with the unresolved Payer ID: ".format(payer_id))
74
-
75
- if corrected_payer_id.strip().upper() == 'FORCE':
76
- MediLink_ConfigLoader.log("User opted to force-continue with unresolved Payer ID {}. Warning: This may indicate an underlying issue.".format(payer_id), config, level="WARNING")
77
- break
78
-
79
- if corrected_payer_id:
80
- try:
81
- fetch_payer_name_from_api(corrected_payer_id, config, primary_endpoint=None)
82
- MediLink_ConfigLoader.log("Corrected Payer ID {} validated successfully.".format(corrected_payer_id), config, level="INFO")
83
-
84
- if update_crosswalk_with_corrected_payer_id(payer_id, corrected_payer_id, config, crosswalk):
85
- if 'csv_replacements' not in crosswalk:
86
- crosswalk['csv_replacements'] = {}
87
- crosswalk['csv_replacements'][payer_id] = corrected_payer_id
88
- MediLink_ConfigLoader.log("Added replacement filter: {} -> {}".format(payer_id, corrected_payer_id), config, level="INFO")
89
- else:
90
- print("Failed to update crosswalk with the corrected Payer ID {}.".format(corrected_payer_id))
91
- MediLink_ConfigLoader.log("Failed to update crosswalk with the corrected Payer ID {}.".format(corrected_payer_id), config, level="ERROR")
92
- break
93
- except Exception as e:
94
- print("Corrected Payer ID {} validation failed: {}".format(corrected_payer_id, e))
95
- MediLink_ConfigLoader.log("Corrected Payer ID {} validation failed: {}".format(corrected_payer_id, e), config, level="ERROR")
151
+ if corrected_payer_id:
152
+ # Validate the corrected payer ID
153
+ if fetch_and_store_payer_name(client, corrected_payer_id, crosswalk, config):
154
+ # Replace the old payer ID with the corrected one in the crosswalk
155
+ success = update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk)
156
+ if success:
157
+ print("Payer ID '{}' has been successfully replaced with '{}'.".format(
158
+ payer_id, corrected_payer_id))
159
+ processed_payer_ids.add(corrected_payer_id)
96
160
  else:
97
- print("Exiting initialization. Please correct the Payer ID and retry.")
98
- sys.exit(1)
161
+ # Only set to "Unknown" if the corrected payer ID is not valid
162
+ crosswalk['payer_id'][corrected_payer_id] = {'name': "Unknown"}
163
+ MediLink_ConfigLoader.log(
164
+ "Failed to validate corrected Payer ID {}. Set to 'Unknown'.".format(corrected_payer_id),
165
+ config,
166
+ level="ERROR"
167
+ )
168
+ print("Payer ID '{}' has been added with name 'Unknown'.".format(corrected_payer_id))
169
+ processed_payer_ids.add(corrected_payer_id)
170
+ else:
171
+ MediLink_ConfigLoader.log(
172
+ "No correction provided for Payer ID {}. Skipping.".format(payer_id),
173
+ config,
174
+ level="WARNING"
175
+ )
176
+ print("No correction provided for Payer ID '{}'. Skipping.".format(payer_id))
99
177
 
100
- def initialize_crosswalk_from_mapat():
178
+ def initialize_crosswalk_from_mapat(client):
101
179
  """
102
- Input: Historical Carol's CSVs and MAPAT data.
103
-
104
- Process:
105
- Extract mappings from Carol's old CSVs to identify Payer IDs and associated Patient IDs.
106
- Use MAPAT to correlate these Patient IDs with Insurance IDs.
107
- Compile these mappings into the crosswalk, setting Payer IDs as keys and corresponding Insurance IDs as values.
108
-
109
- Output: A fully populated crosswalk.json file that serves as a baseline for future updates.
180
+ Initializes the crosswalk from the MAPAT data source. Loads configuration and data sources,
181
+ validates payer IDs, and saves the crosswalk.
182
+
183
+ Returns:
184
+ dict: The payer ID mappings from the initialized crosswalk.
110
185
  """
111
186
  config, crosswalk = MediLink_ConfigLoader.load_configuration()
112
-
113
- # Load historical mappings
114
187
  try:
188
+ # Load data sources for patient and payer IDs
115
189
  patient_id_to_insurance_id, payer_id_to_patient_ids = MediBot_Preprocessor_lib.load_data_sources(config, crosswalk)
116
190
  except ValueError as e:
117
191
  print(e)
118
192
  sys.exit(1)
119
193
 
120
- # Map Payer IDs to Insurance IDs
194
+ # Map payer IDs to insurance IDs
121
195
  payer_id_to_details = MediBot_Preprocessor_lib.map_payer_ids_to_insurance_ids(patient_id_to_insurance_id, payer_id_to_patient_ids)
122
-
123
- # Update the crosswalk for payer IDs only, retaining other mappings
124
196
  crosswalk['payer_id'] = payer_id_to_details
125
197
 
126
- # Validate payer IDs via API and handle invalid IDs
127
- validate_and_correct_payer_ids(crosswalk, config)
198
+ # Validate and correct payer IDs in the crosswalk
199
+ validate_and_correct_payer_ids(client, crosswalk, config)
128
200
 
129
- # Save the initial crosswalk
130
- if save_crosswalk(config, crosswalk):
201
+ # Save the crosswalk and log the result
202
+ if save_crosswalk(client, config, crosswalk):
131
203
  message = "Crosswalk initialized with mappings for {} payers.".format(len(crosswalk.get('payer_id', {})))
132
204
  print(message)
133
205
  MediLink_ConfigLoader.log(message, config, level="INFO")
134
206
  else:
135
207
  print("Failed to save the crosswalk.")
136
208
  sys.exit(1)
137
- return payer_id_to_details
209
+
210
+ return crosswalk['payer_id']
138
211
 
139
- def crosswalk_update(config, crosswalk):
212
+ def load_and_parse_z_data(config):
213
+ """
214
+ Loads and parses Z data for patient to insurance name mappings from the specified directory.
215
+
216
+ Args:
217
+ config (dict): Configuration settings for logging.
218
+
219
+ Returns:
220
+ dict: A mapping of patient IDs to insurance names.
140
221
  """
141
- Updates the `crosswalk.json` file using mappings from MAINS, Z.dat, and Carol's CSV. This function integrates
142
- user-defined insurance mappings from Z.dat with existing payer-to-insurance mappings in the crosswalk,
143
- and validates these mappings using MAINS.
222
+ patient_id_to_insurance_name = {}
223
+ try:
224
+ z_dat_path = config['MediLink_Config']['Z_DAT_PATH']
225
+ MediLink_ConfigLoader.log("Z_DAT_PATH is set to: {}".format(z_dat_path), config, level="DEBUG")
226
+
227
+ # Get the directory of the Z_DAT_PATH
228
+ directory = os.path.dirname(z_dat_path)
229
+ MediLink_ConfigLoader.log("Looking for .DAT files in directory: {}".format(directory), config, level="DEBUG")
230
+
231
+ # List all .DAT files in the directory, case insensitive
232
+ dat_files = [f for f in os.listdir(directory) if f.lower().endswith('.dat')]
233
+ MediLink_ConfigLoader.log("Found {} .DAT files in the directory.".format(len(dat_files)), config, level="DEBUG")
234
+
235
+ # Load processed files tracking
236
+ processed_files_path = os.path.join(directory, 'processed_files.txt')
237
+ if os.path.exists(processed_files_path):
238
+ with open(processed_files_path, 'r') as f:
239
+ processed_files = set(line.strip() for line in f)
240
+ MediLink_ConfigLoader.log("Loaded processed files: {}.".format(processed_files), config, level="DEBUG")
241
+ else:
242
+ processed_files = set()
243
+ MediLink_ConfigLoader.log("No processed files found, starting fresh.", config, level="DEBUG")
144
244
 
145
- Steps:
146
- 1. Load mappings from MAINS for translating insurance names to IDs.
147
- 2. Load mappings from the latest Carol's CSV for new patient entries mapping Patient IDs to Payer IDs.
148
- 3. Parse incremental data from Z.dat which contains recent user interactions mapping Patient IDs to Insurance Names.
149
- 4. Update the crosswalk using the loaded and parsed data, ensuring each Payer ID maps to the correct Insurance IDs.
150
- 5. Persist the updated mappings back to the crosswalk file.
245
+ # Filter for new .DAT files that haven't been processed yet, but always include Z.DAT and ZM.DAT
246
+ new_dat_files = [f for f in dat_files if f not in processed_files or f.lower() in ['z.dat', 'zm.dat']]
247
+ MediLink_ConfigLoader.log("Identified {} new .DAT files to process.".format(len(new_dat_files)), config, level="INFO")
151
248
 
152
- Args:
153
- config (dict): Configuration dictionary containing paths and other settings.
154
- crosswalk (dict): Existing crosswalk mapping Payer IDs to sets of Insurance IDs.
249
+ for dat_file in new_dat_files:
250
+ file_path = os.path.join(directory, dat_file)
251
+ MediLink_ConfigLoader.log("Parsing .DAT file: {}".format(file_path), config, level="DEBUG")
252
+ # Parse each .DAT file and accumulate results
253
+ insurance_name_mapping = MediBot_Preprocessor_lib.parse_z_dat(file_path, config['MediLink_Config'])
254
+ if insurance_name_mapping: # Ensure insurance_name_mapping is not empty
255
+ patient_id_to_insurance_name.update(insurance_name_mapping)
155
256
 
156
- Returns:
157
- bool: True if the crosswalk was successfully updated and saved, False otherwise.
158
- """
159
- # Load insurance mappings from MAINS (Insurance Name to Insurance ID)
160
- insurance_name_to_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config)
161
- MediLink_ConfigLoader.log("Loaded insurance data from MAINS...")
257
+ # Mark this file as processed
258
+ with open(processed_files_path, 'a') as f:
259
+ f.write(dat_file + '\n')
260
+ MediLink_ConfigLoader.log("Marked file as processed: {}".format(dat_file), config, level="DEBUG")
261
+
262
+ if not patient_id_to_insurance_name: # Check if the result is empty
263
+ raise ValueError("Parsed Z data is empty, possibly indicating an error in parsing or all files already processed.") # TODO Add differentiator here because this is dumb.
264
+ MediLink_ConfigLoader.log("Successfully parsed Z data with {} mappings found.".format(len(patient_id_to_insurance_name)), config, level="INFO")
265
+ return patient_id_to_insurance_name # Ensure the function returns the mapping
266
+ except Exception as e:
267
+ MediLink_ConfigLoader.log("Error loading and parsing Z data: {}".format(e), config, level="ERROR")
268
+ return {}
269
+
270
+ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstream of this is only MediBot_Preprocessor.py and MediBot.py
271
+ """
272
+ Updates the crosswalk with insurance data and historical mappings.
273
+ It loads insurance data, historical payer mappings, and updates the crosswalk accordingly.
274
+
275
+ Args:
276
+ config (dict): Configuration settings for logging.
277
+ crosswalk (dict): The crosswalk dictionary to update.
278
+ skip_known_payers (bool): If True, skips records with 'name' not equal to 'Unknown'.
162
279
 
163
- # Load new Patient ID to Payer ID mappings from Carol's CSV (if necessary)
164
- # TODO This is a low performance strategy.
165
- patient_id_to_payer_id = MediBot_Preprocessor_lib.load_historical_payer_to_patient_mappings(config)
166
- MediLink_ConfigLoader.log("Loaded historical mappings...")
167
-
168
- # Load incremental mapping data from Z.dat (Patient ID to Insurance Name)
169
- # TODO This may be a redundant approach?
170
- # This is a singular path. This is fine though because any time we process a Z.DAT we'd have the crosswalk incremented.
171
- patient_id_to_insurance_name = MediBot_Preprocessor_lib.parse_z_dat(config['MediLink_Config']['Z_DAT_PATH'], config['MediLink_Config'])
172
- MediLink_ConfigLoader.log("Parsed Z data...")
173
-
174
- # Update the crosswalk with new or revised mappings
280
+ Returns:
281
+ bool: True if the crosswalk was updated successfully, False otherwise.
282
+ """
283
+ MediLink_ConfigLoader.log("Starting crosswalk update process...", config, level="INFO")
284
+
285
+ # Load insurance data from MAINS
286
+ try:
287
+ MediLink_ConfigLoader.log("Attempting to load insurance data from MAINS...", config, level="DEBUG")
288
+ insurance_name_to_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config)
289
+ MediLink_ConfigLoader.log("Loaded insurance data from MAINS with {} entries.".format(len(insurance_name_to_id)), config, level="INFO")
290
+ except Exception as e:
291
+ MediLink_ConfigLoader.log("Error loading insurance data from MAINS: {}".format(e), config, level="ERROR")
292
+ return False
293
+
294
+ # Load historical payer to patient mappings
295
+ try:
296
+ MediLink_ConfigLoader.log("Attempting to load historical payer to patient mappings...", config, level="DEBUG")
297
+ patient_id_to_payer_id = MediBot_Preprocessor_lib.load_historical_payer_to_patient_mappings(config)
298
+ MediLink_ConfigLoader.log("Loaded historical mappings with {} entries.".format(len(patient_id_to_payer_id)), config, level="INFO")
299
+ except Exception as e:
300
+ MediLink_ConfigLoader.log("Error loading historical mappings: {}".format(e), config, level="ERROR")
301
+ return False
302
+
303
+ # Parse Z data for patient to insurance name mappings
304
+ try:
305
+ patient_id_to_insurance_name = load_and_parse_z_data(config)
306
+ mapping_count = len(patient_id_to_insurance_name) if patient_id_to_insurance_name is not None else 0
307
+ MediLink_ConfigLoader.log("Parsed Z data with {} mappings found.".format(mapping_count), config, level="INFO")
308
+ except Exception as e:
309
+ MediLink_ConfigLoader.log("Error parsing Z data in crosswalk update: {}".format(e), config, level="ERROR")
310
+ return False
311
+
312
+ # Check if 'payer_id' key exists and is not empty
313
+ MediLink_ConfigLoader.log("Checking for 'payer_id' key in crosswalk...", config, level="DEBUG")
314
+ if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
315
+ MediLink_ConfigLoader.log("The 'payer_id' list is empty or missing.", config, level="WARNING")
316
+ user_input = input(
317
+ "The 'payer_id' list is empty or missing. Would you like to initialize the crosswalk? (yes/no): "
318
+ ).strip().lower()
319
+ if user_input in ['yes', 'y']:
320
+ MediLink_ConfigLoader.log("User chose to initialize the crosswalk.", config, level="INFO")
321
+ initialize_crosswalk_from_mapat(client)
322
+ return True # Indicate that the crosswalk was initialized
323
+ else:
324
+ MediLink_ConfigLoader.log("User opted not to initialize the crosswalk.", config, level="WARNING")
325
+ return False # Indicate that the update was not completed
326
+
327
+ # Update the crosswalk with new payer IDs and insurance IDs
175
328
  for patient_id, payer_id in patient_id_to_payer_id.items():
329
+ """ TODO this needs to be implemented at some point so we can skip known entities.
330
+ # Skip known payers if the flag is set
331
+ if skip_known_payers:
332
+ payer_id_str = next(iter(payer_id)) # Extract the single payer_id from the set
333
+ MediLink_ConfigLoader.log("Checking if payer_id '{}' is known...".format(payer_id_str), config, level="DEBUG")
334
+ payer_info = crosswalk['payer_id'].get(payer_id_str, {})
335
+ payer_name = payer_info.get('name', "Unknown")
336
+ MediLink_ConfigLoader.log("Retrieved payer name: '{}' for payer_id '{}'.".format(payer_name, payer_id), config, level="DEBUG")
337
+
338
+ if payer_name != "Unknown":
339
+ MediLink_ConfigLoader.log("Skipping known payer_id: '{}' as it is already in the crosswalk.".format(payer_id), config, level="DEBUG")
340
+ continue # Skip this payer_id
341
+ MediLink_ConfigLoader.log("Skipping known payer_id: {} as it is already in the crosswalk.".format(payer_id), config, level="DEBUG")
342
+ continue # Skip this payer_id
343
+ """
344
+
176
345
  insurance_name = patient_id_to_insurance_name.get(patient_id)
177
346
  if insurance_name and insurance_name in insurance_name_to_id:
178
347
  insurance_id = insurance_name_to_id[insurance_name]
179
-
180
- # Ensure payer ID is in the crosswalk and initialize if not
181
- MediLink_ConfigLoader.log("Initializing payer_id key...")
348
+
349
+ # Log the assembly of data
350
+ MediLink_ConfigLoader.log("Assembling data for patient_id '{}': payer_id '{}', insurance_name '{}', insurance_id '{}'.".format(
351
+ patient_id, payer_id, insurance_name, insurance_id), config, level="INFO")
352
+ # Ensure the 'payer_id' key exists in the crosswalk
182
353
  if 'payer_id' not in crosswalk:
183
354
  crosswalk['payer_id'] = {}
355
+ MediLink_ConfigLoader.log("Initialized 'payer_id' in crosswalk.", config, level="DEBUG")
356
+
357
+ # Initialize the payer ID entry if it doesn't exist
184
358
  if payer_id not in crosswalk['payer_id']:
185
- # TODO The OPTUMEDI default here should be gathered via API and not just a default. There are 2 of these defaults!!
186
- crosswalk['payer_id'][payer_id] = {'endpoint': 'OPTUMEDI', 'medisoft_id': set(), 'medisoft_medicare_id': set()}
187
-
188
- # Update the medisoft_id set, temporarily using a set to avoid duplicates
189
- crosswalk['payer_id'][payer_id]['medisoft_id'].add(insurance_id)
190
- MediLink_ConfigLoader.log("Added new insurance ID {} to payer ID {}".format(insurance_id, payer_id))
191
-
192
- # Convert sets to lists just before saving
193
- for payer_id in crosswalk['payer_id']:
194
- if isinstance(crosswalk['payer_id'][payer_id]['medisoft_id'], set):
195
- crosswalk['payer_id'][payer_id]['medisoft_id'] = list(crosswalk['payer_id'][payer_id]['medisoft_id'])
196
- if isinstance(crosswalk['payer_id'][payer_id]['medisoft_medicare_id'], set):
197
- crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = list(crosswalk['payer_id'][payer_id]['medisoft_medicare_id'])
198
-
199
- # Save the updated crosswalk to the specified file
200
- return save_crosswalk(config, crosswalk)
359
+ # Prompt the user to select an endpoint name or use the default
360
+ endpoint_options = list(config['MediLink_Config']['endpoints'].keys())
361
+ print("Available endpoints:")
362
+ for idx, key in enumerate(endpoint_options):
363
+ print("{0}: {1}".format(idx + 1, config['MediLink_Config']['endpoints'][key]['name']))
364
+ user_choice = input("Select an endpoint by number (or press Enter to use the default): ").strip()
365
+
366
+ if user_choice.isdigit() and 1 <= int(user_choice) <= len(endpoint_options):
367
+ selected_endpoint = config['MediLink_Config']['endpoints'][endpoint_options[int(user_choice) - 1]]['name']
368
+ else:
369
+ selected_endpoint = config['MediLink_Config']['endpoints'][endpoint_options[0]]['name']
370
+ MediLink_ConfigLoader.log("User opted for default endpoint: {}".format(selected_endpoint), config, level="INFO")
371
+
372
+ crosswalk['payer_id'][payer_id] = {
373
+ 'endpoint': selected_endpoint,
374
+ 'medisoft_id': set(), # Use set to store multiple IDs
375
+ 'medisoft_medicare_id': set()
376
+ }
377
+ MediLink_ConfigLoader.log("Initialized payer ID {} in crosswalk with endpoint '{}'.".format(payer_id, selected_endpoint), config, level="DEBUG")
378
+
379
+ # Add the insurance ID to the payer ID entry
380
+ crosswalk['payer_id'][payer_id]['medisoft_id'].add(str(insurance_id)) # Ensure IDs are strings
381
+ MediLink_ConfigLoader.log(
382
+ "Added new insurance ID {} to payer ID {}.".format(insurance_id, payer_id),
383
+ config,
384
+ level="INFO"
385
+ )
386
+
387
+ # Log the update of the crosswalk
388
+ MediLink_ConfigLoader.log("Updated crosswalk for payer_id '{}': added insurance_id '{}'.".format(payer_id, insurance_id), config, level="DEBUG")
389
+
390
+ # Fetch and store the payer name
391
+ MediLink_ConfigLoader.log("Fetching and storing payer name for payer_id: {}".format(payer_id), config, level="DEBUG")
392
+ fetch_and_store_payer_name(client, payer_id, crosswalk, config)
393
+ MediLink_ConfigLoader.log("Successfully fetched and stored payer name for payer_id: {}".format(payer_id), config, level="INFO")
201
394
 
202
- def update_crosswalk_with_corrected_payer_id(old_payer_id, corrected_payer_id, config, crosswalk):
203
- """Updates the crosswalk with the corrected payer ID."""
204
- # Update the payer_id section
395
+ # Validate and correct payer IDs in the crosswalk
396
+ MediLink_ConfigLoader.log("Validating and correcting payer IDs in the crosswalk.", config, level="DEBUG")
397
+ validate_and_correct_payer_ids(client, crosswalk, config)
398
+
399
+ # Check for any entries marked as "Unknown" and validate them
400
+ unknown_payers = [
401
+ payer_id for payer_id, details in crosswalk.get('payer_id', {}).items()
402
+ if details.get('name') == "Unknown"
403
+ ]
404
+ MediLink_ConfigLoader.log("Found {} unknown payer(s) to validate.".format(len(unknown_payers)), config, level="INFO")
405
+ for payer_id in unknown_payers:
406
+ MediLink_ConfigLoader.log("Fetching and storing payer name for unknown payer_id: {}".format(payer_id), config, level="DEBUG")
407
+ fetch_and_store_payer_name(client, payer_id, crosswalk, config)
408
+ MediLink_ConfigLoader.log("Successfully fetched and stored payer name for unknown payer_id: {}".format(payer_id), config, level="INFO")
409
+
410
+ # Ensure multiple medisoft_id values are preserved by converting sets to sorted lists
411
+ for payer_id, details in crosswalk.get('payer_id', {}).items():
412
+ if isinstance(details.get('medisoft_id'), set):
413
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(details['medisoft_id']))
414
+ MediLink_ConfigLoader.log("Converted medisoft_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
415
+ if isinstance(details.get('medisoft_medicare_id'), set):
416
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(details['medisoft_medicare_id']))
417
+ MediLink_ConfigLoader.log("Converted medisoft_medicare_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
418
+
419
+ MediLink_ConfigLoader.log("Crosswalk update process completed. Processed {} payer IDs.".format(len(patient_id_to_payer_id)), config, level="INFO")
420
+ return save_crosswalk(client, config, crosswalk)
421
+
422
+ def update_crosswalk_with_corrected_payer_id(client, old_payer_id, corrected_payer_id, config=None, crosswalk=None):
423
+ """
424
+ Updates the crosswalk by replacing an old payer ID with a corrected payer ID.
425
+
426
+ Args:
427
+ old_payer_id (str): The old payer ID to be replaced.
428
+ corrected_payer_id (str): The new payer ID to replace the old one.
429
+ config (dict, optional): Configuration settings for logging.
430
+ crosswalk (dict, optional): The crosswalk dictionary to update.
431
+
432
+ Returns:
433
+ bool: True if the crosswalk was updated successfully, False otherwise.
434
+ """
435
+ if config is None or crosswalk is None:
436
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
437
+ MediLink_ConfigLoader.log("Loaded configuration and crosswalk.", config, level="DEBUG")
438
+
439
+ # Convert to a regular dict if crosswalk['payer_id'] is an OrderedDict
440
+ if isinstance(crosswalk['payer_id'], dict) and hasattr(crosswalk['payer_id'], 'items'):
441
+ crosswalk['payer_id'] = dict(crosswalk['payer_id'])
442
+
443
+ MediLink_ConfigLoader.log("Checking if old Payer ID {} exists in crosswalk.".format(old_payer_id), config, level="DEBUG")
444
+
445
+ MediLink_ConfigLoader.log("Attempting to replace old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="DEBUG")
446
+
447
+ # Check if the old payer ID exists before attempting to replace
205
448
  if old_payer_id in crosswalk['payer_id']:
206
- crosswalk['payer_id'][corrected_payer_id] = crosswalk['payer_id'].pop(old_payer_id)
207
- MediLink_ConfigLoader.log("Crosswalk updated: replaced Payer ID {} with {}".format(old_payer_id, corrected_payer_id), config, level="INFO")
449
+ MediLink_ConfigLoader.log("Old Payer ID {} found. Proceeding with replacement.".format(old_payer_id), config, level="DEBUG")
450
+
451
+ # Store the details of the old payer ID
452
+ old_payer_details = crosswalk['payer_id'][old_payer_id]
453
+ MediLink_ConfigLoader.log("Storing details of old Payer ID {}: {}".format(old_payer_id, old_payer_details), config, level="DEBUG")
454
+
455
+ # Replace the old payer ID with the corrected one
456
+ crosswalk['payer_id'][corrected_payer_id] = old_payer_details
457
+ MediLink_ConfigLoader.log("Replaced old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
458
+
459
+ # Remove the old payer ID from the crosswalk
460
+ del crosswalk['payer_id'][old_payer_id]
461
+ MediLink_ConfigLoader.log("Removed old Payer ID {} from crosswalk.".format(old_payer_id), config, level="DEBUG")
462
+
463
+ # Fetch and store the payer name for the corrected ID
464
+ if fetch_and_store_payer_name(client, corrected_payer_id, crosswalk, config):
465
+ MediLink_ConfigLoader.log("Successfully fetched and stored payer name for corrected Payer ID {}.".format(corrected_payer_id), config, level="INFO")
466
+ else:
467
+ MediLink_ConfigLoader.log("Corrected Payer ID {} updated without a valid name.".format(corrected_payer_id), config, level="WARNING")
468
+
469
+ # Update csv_replacements
470
+ crosswalk.setdefault('csv_replacements', {})[old_payer_id] = corrected_payer_id
471
+ MediLink_ConfigLoader.log("Updated csv_replacements: {} -> {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
472
+ print("csv_replacements updated: '{}' -> '{}'.".format(old_payer_id, corrected_payer_id))
473
+
474
+ return save_crosswalk(client, config, crosswalk)
208
475
  else:
209
- MediLink_ConfigLoader.log("Failed to update crosswalk: could not find old Payer ID {}".format(old_payer_id), config, level="ERROR")
476
+ MediLink_ConfigLoader.log("Failed to update crosswalk: old Payer ID {} not found.".format(old_payer_id), config, level="ERROR")
477
+ print("Failed to update crosswalk: could not find old Payer ID '{}'.".format(old_payer_id))
210
478
  return False
211
479
 
212
- # Update the csv_replacements section
213
- if 'csv_replacements' not in crosswalk:
214
- crosswalk['csv_replacements'] = {}
215
- crosswalk['csv_replacements'][old_payer_id] = corrected_payer_id
216
- MediLink_ConfigLoader.log("Crosswalk csv_replacements updated: added {} -> {}".format(old_payer_id, corrected_payer_id), config, level="INFO")
480
+ def update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config):
481
+ """
482
+ Updates the crosswalk with a new payer ID for a given insurance name.
217
483
 
218
- # Save the updated crosswalk
219
- return save_crosswalk(config, crosswalk)
220
-
221
- def update_crosswalk_with_new_payer_id(insurance_name, payer_id, config):
222
- """Updates the crosswalk with a new payer ID."""
484
+ Args:
485
+ insurance_name (str): The name of the insurance to associate with the new payer ID.
486
+ payer_id (str): The new payer ID to be added.
487
+ config (dict): Configuration settings for logging.
488
+ """
223
489
  _, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
490
+ MediLink_ConfigLoader.log("Loaded crosswalk configuration from {}.".format(config.get('crosswalkPath', 'crosswalk.json')), config, level="DEBUG")
491
+
492
+ # Load the Medisoft ID for the given insurance name
224
493
  medisoft_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config).get(insurance_name)
494
+ MediLink_ConfigLoader.log("Retrieved Medisoft ID for insurance name {}: {}.".format(insurance_name, medisoft_id), config, level="DEBUG")
225
495
 
226
496
  if medisoft_id:
227
497
  medisoft_id_str = str(medisoft_id)
498
+
499
+ # Initialize the payer ID entry if it doesn't exist
228
500
  if payer_id not in crosswalk['payer_id']:
229
501
  crosswalk['payer_id'][payer_id] = {"medisoft_id": [medisoft_id_str], "medisoft_medicare_id": []}
502
+ MediLink_ConfigLoader.log("Initialized new payer ID {} in crosswalk.".format(payer_id), config, level="INFO")
230
503
  else:
231
504
  crosswalk['payer_id'][payer_id]['medisoft_id'].append(medisoft_id_str)
232
- save_crosswalk(config, crosswalk)
233
- MediLink_ConfigLoader.log("Updated crosswalk with new payer ID {} for insurance name {}".format(payer_id, insurance_name), config, level="INFO")
505
+ MediLink_ConfigLoader.log("Appended Medisoft ID {} to existing payer ID {}.".format(medisoft_id_str, payer_id), config, level="INFO")
506
+
507
+ # Fetch and store the payer name for the new payer ID
508
+ if fetch_and_store_payer_name(client, payer_id, crosswalk, config):
509
+ MediLink_ConfigLoader.log("Updated crosswalk with new payer ID {} for insurance name {}.".format(payer_id, insurance_name), config, level="INFO")
510
+ else:
511
+ MediLink_ConfigLoader.log("Added new payer ID {} without a valid name for insurance name {}.".format(payer_id, insurance_name), config, level="WARNING")
512
+
513
+ # Save the updated crosswalk
514
+ save_crosswalk(client, config, crosswalk)
234
515
  else:
235
- message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}".format(insurance_name)
516
+ message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}.".format(insurance_name)
236
517
  print(message)
237
518
  MediLink_ConfigLoader.log(message, config, level="ERROR")
238
-
239
- def save_crosswalk(config, crosswalk):
519
+
520
+ def save_crosswalk(client, config, crosswalk):
240
521
  """
241
- Saves the updated crosswalk to a JSON file.
522
+ Saves the crosswalk to a JSON file. Ensures that all necessary keys are present and logs the outcome.
523
+
242
524
  Args:
243
- crosswalk_path (str): Path to the crosswalk.json file.
244
- crosswalk (dict): The updated crosswalk data.
525
+ config (dict): Configuration settings for logging.
526
+ crosswalk (dict): The crosswalk dictionary to save.
527
+
245
528
  Returns:
246
- bool: True if the file was successfully saved, False otherwise.
529
+ bool: True if the crosswalk was saved successfully, False otherwise.
247
530
  """
248
- # Attempt to fetch crosswalkPath from MediLink_Config
249
531
  try:
532
+ # Determine the path to save the crosswalk
250
533
  crosswalk_path = config['MediLink_Config']['crosswalkPath']
534
+ MediLink_ConfigLoader.log("Determined crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
251
535
  except KeyError:
252
- # If KeyError occurs, fall back to fetching crosswalkPath directly
253
- crosswalk_path = config.get('crosswalkPath', None) # Replace None with a default value if needed
254
-
536
+ crosswalk_path = config.get('crosswalkPath', 'crosswalk.json')
537
+ MediLink_ConfigLoader.log("Using default crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
538
+
255
539
  try:
256
- # Initialize 'payer_id' key if not present
257
- if 'payer_id' not in crosswalk:
540
+ # Initialize the 'payer_id' key if it doesn't exist
541
+ if 'payer_id' not in crosswalk:
258
542
  print("save_crosswalk is initializing 'payer_id' key...")
259
543
  crosswalk['payer_id'] = {}
260
-
261
- # Convert all 'medisoft_id' fields from sets to lists if necessary
262
- for k, v in crosswalk.get('payer_id', {}).items():
263
- if isinstance(v.get('medisoft_id'), set):
264
- v['medisoft_id'] = list(v['medisoft_id'])
265
-
544
+ MediLink_ConfigLoader.log("Initialized 'payer_id' key in crosswalk.", config, level="INFO")
545
+
546
+ # Ensure all payer IDs have a name and initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
547
+ for payer_id in crosswalk['payer_id']:
548
+ if 'name' not in crosswalk['payer_id'][payer_id]:
549
+ fetch_and_store_payer_name(client, payer_id, crosswalk, config)
550
+ MediLink_ConfigLoader.log("Fetched and stored payer name for payer ID: {}.".format(payer_id), config, level="DEBUG")
551
+
552
+ # Initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
553
+ if 'medisoft_id' not in crosswalk['payer_id'][payer_id]:
554
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = []
555
+ MediLink_ConfigLoader.log("Initialized 'medisoft_id' for payer ID {} as an empty list.".format(payer_id), config, level="DEBUG")
556
+ if 'medisoft_medicare_id' not in crosswalk['payer_id'][payer_id]:
557
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = []
558
+ MediLink_ConfigLoader.log("Initialized 'medisoft_medicare_id' for payer ID {} as an empty list.".format(payer_id), config, level="DEBUG")
559
+
560
+ # Convert sets to sorted lists for JSON serialization
561
+ for payer_id, details in crosswalk.get('payer_id', {}).items():
562
+ if isinstance(details.get('medisoft_id'), set):
563
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(details['medisoft_id']))
564
+ MediLink_ConfigLoader.log("Converted medisoft_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
565
+ if isinstance(details.get('medisoft_medicare_id'), set):
566
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(details['medisoft_medicare_id']))
567
+ MediLink_ConfigLoader.log("Converted medisoft_medicare_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
568
+
569
+ # Write the crosswalk to the specified file
266
570
  with open(crosswalk_path, 'w') as file:
267
- json.dump(crosswalk, file, indent=4) # Save the entire dictionary
571
+ json.dump(crosswalk, file, indent=4)
572
+
573
+ MediLink_ConfigLoader.log("Crosswalk saved successfully to {}.".format(crosswalk_path), config, level="INFO")
268
574
  return True
269
-
270
575
  except KeyError as e:
271
- # Log the KeyError with specific information about what was missing
272
- print("Key Error: A required key is missing in the crosswalk data -", e)
576
+ print("Key Error: A required key is missing in the crosswalk data - {}.".format(e))
577
+ MediLink_ConfigLoader.log("Key Error while saving crosswalk: {}.".format(e), config, level="ERROR")
273
578
  return False
274
-
275
579
  except TypeError as e:
276
- # Handle data type errors (e.g., non-serializable types)
277
- print("Type Error: There was a type issue with the data being saved in the crosswalk -", e)
580
+ print("Type Error: There was a type issue with the data being saved in the crosswalk - {}.".format(e))
581
+ MediLink_ConfigLoader.log("Type Error while saving crosswalk: {}.".format(e), config, level="ERROR")
278
582
  return False
279
-
280
583
  except IOError as e:
281
- # Handle I/O errors related to file operations
282
- print("I/O Error: An error occurred while writing to the crosswalk file -", e)
584
+ print("I/O Error: An error occurred while writing to the crosswalk file - {}.".format(e))
585
+ MediLink_ConfigLoader.log("I/O Error while saving crosswalk: {}.".format(e), config, level="ERROR")
283
586
  return False
284
-
285
587
  except Exception as e:
286
- # A general exception catch to log any other exceptions that may not have been anticipated
287
- print("Unexpected crosswalk error:", e)
588
+ print("Unexpected crosswalk error: {}.".format(e))
589
+ MediLink_ConfigLoader.log("Unexpected error while saving crosswalk: {}.".format(e), config, level="ERROR")
288
590
  return False