medicafe 0.250728.8__py3-none-any.whl → 0.250805.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.

Files changed (58) hide show
  1. MediBot/MediBot.bat +233 -19
  2. MediBot/MediBot.py +138 -46
  3. MediBot/MediBot_Crosswalk_Library.py +127 -623
  4. MediBot/MediBot_Crosswalk_Utils.py +618 -0
  5. MediBot/MediBot_Preprocessor.py +72 -17
  6. MediBot/MediBot_Preprocessor_lib.py +470 -76
  7. MediBot/MediBot_UI.py +32 -17
  8. MediBot/MediBot_dataformat_library.py +68 -20
  9. MediBot/MediBot_docx_decoder.py +120 -19
  10. MediBot/MediBot_smart_import.py +180 -0
  11. MediBot/__init__.py +89 -0
  12. MediBot/get_medicafe_version.py +25 -0
  13. MediBot/update_json.py +35 -6
  14. MediBot/update_medicafe.py +19 -1
  15. MediCafe/MediLink_ConfigLoader.py +160 -0
  16. MediCafe/__init__.py +171 -0
  17. MediCafe/__main__.py +222 -0
  18. MediCafe/api_core.py +1098 -0
  19. MediCafe/api_core_backup.py +427 -0
  20. MediCafe/api_factory.py +306 -0
  21. MediCafe/api_utils.py +356 -0
  22. MediCafe/core_utils.py +450 -0
  23. MediCafe/graphql_utils.py +445 -0
  24. MediCafe/logging_config.py +123 -0
  25. MediCafe/logging_demo.py +61 -0
  26. MediCafe/migration_helpers.py +463 -0
  27. MediCafe/smart_import.py +436 -0
  28. MediLink/MediLink.py +66 -26
  29. MediLink/MediLink_837p_cob_library.py +28 -28
  30. MediLink/MediLink_837p_encoder.py +33 -34
  31. MediLink/MediLink_837p_encoder_library.py +243 -151
  32. MediLink/MediLink_837p_utilities.py +129 -5
  33. MediLink/MediLink_API_Generator.py +83 -60
  34. MediLink/MediLink_API_v3.py +1 -1
  35. MediLink/MediLink_ClaimStatus.py +177 -31
  36. MediLink/MediLink_DataMgmt.py +405 -72
  37. MediLink/MediLink_Decoder.py +20 -1
  38. MediLink/MediLink_Deductible.py +155 -28
  39. MediLink/MediLink_Display_Utils.py +72 -0
  40. MediLink/MediLink_Down.py +127 -5
  41. MediLink/MediLink_Gmail.py +712 -653
  42. MediLink/MediLink_PatientProcessor.py +257 -0
  43. MediLink/MediLink_UI.py +85 -61
  44. MediLink/MediLink_Up.py +28 -4
  45. MediLink/MediLink_insurance_utils.py +227 -264
  46. MediLink/MediLink_main.py +248 -0
  47. MediLink/MediLink_smart_import.py +264 -0
  48. MediLink/__init__.py +93 -0
  49. MediLink/insurance_type_integration_test.py +66 -76
  50. MediLink/test.py +1 -1
  51. MediLink/test_timing.py +59 -0
  52. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/METADATA +1 -1
  53. medicafe-0.250805.0.dist-info/RECORD +81 -0
  54. medicafe-0.250805.0.dist-info/entry_points.txt +2 -0
  55. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/top_level.txt +1 -0
  56. medicafe-0.250728.8.dist-info/RECORD +0 -59
  57. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/LICENSE +0 -0
  58. {medicafe-0.250728.8.dist-info → medicafe-0.250805.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,618 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ MediBot_Crosswalk_Utils.py - Helper utilities for crosswalk operations
5
+
6
+ This module contains utility functions extracted from MediBot_Crosswalk_Library.py
7
+ to improve code organization and maintainability.
8
+
9
+ Compatible with Python 3.4.4 and Windows XP environments.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ import threading
16
+
17
+ # Set the project directory to the parent directory of the current file
18
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
19
+ if project_dir not in sys.path:
20
+ sys.path.append(project_dir)
21
+
22
+ # Use core utilities for standardized imports
23
+ from MediCafe.core_utils import (
24
+ import_medibot_module,
25
+ get_config_loader_with_fallback
26
+ )
27
+
28
+ # Initialize configuration loader with fallback
29
+ MediLink_ConfigLoader = get_config_loader_with_fallback()
30
+
31
+ # Import MediBot modules using centralized import functions
32
+ MediBot_Preprocessor_lib = import_medibot_module('MediBot_Preprocessor_lib')
33
+
34
+ # =============================================================================
35
+ # CROSSWALK HEALTH CHECKING AND USER INTERACTION
36
+ # =============================================================================
37
+ # Functions for assessing crosswalk health and providing user prompts to skip
38
+ # unnecessary API calls when the crosswalk appears healthy.
39
+
40
+ def check_crosswalk_health(crosswalk):
41
+ """
42
+ Simple health check for crosswalk - checks if payers have names and at least one medisoft ID.
43
+
44
+ Args:
45
+ crosswalk (dict): The crosswalk dictionary to check.
46
+
47
+ Returns:
48
+ tuple: (is_healthy, missing_names_count, missing_medisoft_ids_count, missing_names_list, missing_medisoft_ids_list)
49
+ """
50
+ if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
51
+ return False, 0, 0, [], []
52
+
53
+ missing_names = 0
54
+ missing_medisoft_ids = 0
55
+ missing_names_list = []
56
+ missing_medisoft_ids_list = []
57
+
58
+ for payer_id, details in crosswalk['payer_id'].items():
59
+ # Check if name is missing or "Unknown"
60
+ name = details.get('name', '')
61
+ if not name or name == 'Unknown':
62
+ missing_names += 1
63
+ missing_names_list.append(payer_id)
64
+
65
+ # Check if at least one medisoft ID exists in either field
66
+ medisoft_id = details.get('medisoft_id', [])
67
+ medisoft_medicare_id = details.get('medisoft_medicare_id', [])
68
+
69
+ # Convert to list if it's a set (for compatibility)
70
+ if isinstance(medisoft_id, set):
71
+ medisoft_id = list(medisoft_id)
72
+ if isinstance(medisoft_medicare_id, set):
73
+ medisoft_medicare_id = list(medisoft_medicare_id)
74
+
75
+ # If both are empty, count as missing; if either has at least one, it's healthy
76
+ if not medisoft_id and not medisoft_medicare_id:
77
+ missing_medisoft_ids += 1
78
+ missing_medisoft_ids_list.append(payer_id)
79
+
80
+ # Consider healthy if no missing names and no missing medisoft IDs
81
+ is_healthy = (missing_names == 0 and missing_medisoft_ids == 0)
82
+ return is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list
83
+
84
+ def prompt_user_for_api_calls(crosswalk, config):
85
+ """
86
+ Prompts user with a 3-second timeout to skip API calls if crosswalk looks healthy.
87
+ Windows XP compatible version using threading instead of select.
88
+
89
+ Args:
90
+ crosswalk (dict): The crosswalk dictionary to check.
91
+ config (dict): Configuration settings for logging.
92
+
93
+ Returns:
94
+ bool: True if should proceed with API calls, False if should skip
95
+ """
96
+
97
+ is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list = check_crosswalk_health(crosswalk)
98
+ total_payers = len(crosswalk.get('payer_id', {}))
99
+
100
+ if is_healthy:
101
+ print("\nCrosswalk appears healthy:")
102
+ print(" - {} payers found".format(total_payers))
103
+ print(" - All payers have names")
104
+ print(" - All payers have medisoft IDs")
105
+ print("\nPress ENTER to run API validation, or wait 2 seconds to skip...")
106
+
107
+ # Use threading for timeout on Windows
108
+ user_input = [None] # Use list to store result from thread
109
+
110
+ def get_input():
111
+ try:
112
+ user_input[0] = input()
113
+ except (EOFError, KeyboardInterrupt):
114
+ user_input[0] = ""
115
+
116
+ # Start input thread
117
+ input_thread = threading.Thread(target=get_input)
118
+ input_thread.daemon = True
119
+ input_thread.start()
120
+
121
+ # Wait for 2 seconds or until input is received
122
+ input_thread.join(timeout=2.0)
123
+
124
+ if user_input[0] is not None:
125
+ print("Running API validation calls...")
126
+ MediLink_ConfigLoader.log("User pressed ENTER - proceeding with API calls", config, level="INFO")
127
+ return True
128
+ else:
129
+ print("Timed out - skipping API calls")
130
+ MediLink_ConfigLoader.log("Timeout - skipping API calls", config, level="INFO")
131
+ return False
132
+ else:
133
+ print("\nCrosswalk needs attention:")
134
+ print(" - {} payers found".format(total_payers))
135
+
136
+ # Show detailed information about missing names
137
+ if missing_names > 0:
138
+ print(" - {} payers missing names: {}".format(missing_names, ", ".join(missing_names_list)))
139
+
140
+ # Show detailed information about missing medisoft IDs
141
+ if missing_medisoft_ids > 0:
142
+ print(" - {} payers missing medisoft IDs: {}".format(missing_medisoft_ids, ", ".join(missing_medisoft_ids_list)))
143
+ # API validation CANNOT resolve missing medisoft IDs
144
+ print(" TODO: Need user interface to manually input medisoft IDs for these payers")
145
+
146
+ # Only proceed with API calls if there are missing names (API can help with those)
147
+ if missing_names > 0:
148
+ print("Proceeding with API validation calls to resolve missing names...")
149
+ MediLink_ConfigLoader.log("Crosswalk has missing names - proceeding with API calls", config, level="INFO")
150
+ return True
151
+ else:
152
+ print("No missing names to resolve via API. Skipping API validation calls.")
153
+ print("TODO: Manual intervention needed for missing medisoft IDs")
154
+ MediLink_ConfigLoader.log("Crosswalk has missing medisoft IDs but no missing names - skipping API calls", config, level="INFO")
155
+ return False
156
+
157
+ # =============================================================================
158
+ # CONFIGURATION MANAGEMENT
159
+ # =============================================================================
160
+ # Functions for managing configuration settings, endpoint selection, and ensuring
161
+ # proper configuration loading across the crosswalk system.
162
+
163
+ def select_endpoint(config, current_endpoint=None):
164
+ """
165
+ Prompts the user to select an endpoint from the available options or returns the default endpoint.
166
+ Validates the current endpoint against the available options.
167
+
168
+ Args:
169
+ config (dict): Configuration settings for logging. Can be either the full config or config['MediLink_Config'].
170
+ current_endpoint (str, optional): The current endpoint to validate.
171
+
172
+ Returns:
173
+ str: The selected endpoint key.
174
+
175
+ Raises:
176
+ ValueError: If the config does not contain valid endpoint information.
177
+ """
178
+ # Determine the effective MediLink_Config
179
+ if 'MediLink_Config' in config:
180
+ medi_link_config = config['MediLink_Config']
181
+ MediLink_ConfigLoader.log("Using 'MediLink_Config' from the provided configuration.", config, level="DEBUG")
182
+ else:
183
+ medi_link_config = config
184
+ MediLink_ConfigLoader.log("Using the provided configuration directly as 'MediLink_Config'.", config, level="DEBUG")
185
+
186
+ # Attempt to retrieve endpoint options
187
+ try:
188
+ endpoint_options = list(medi_link_config['endpoints'].keys())
189
+ MediLink_ConfigLoader.log("Successfully retrieved endpoint options.", config, level="DEBUG")
190
+ except KeyError:
191
+ MediLink_ConfigLoader.log("Failed to retrieve endpoint options due to KeyError.", config, level="ERROR")
192
+ raise ValueError("Invalid configuration: 'endpoints' not found in config.")
193
+
194
+
195
+ # Ensure there are available endpoints
196
+ if not endpoint_options:
197
+ MediLink_ConfigLoader.log("No endpoints available in the configuration.", config, level="ERROR")
198
+ raise ValueError("No endpoints available in the configuration.")
199
+ else:
200
+ MediLink_ConfigLoader.log("Available endpoints found in the configuration.", config, level="DEBUG")
201
+
202
+ print("Available endpoints:")
203
+ for idx, key in enumerate(endpoint_options):
204
+ # Safely retrieve the endpoint name
205
+ endpoint_name = medi_link_config['endpoints'].get(key, {}).get('name', key)
206
+ print("{0}: {1}".format(idx + 1, endpoint_name))
207
+
208
+ # Validate the current endpoint if provided
209
+ if current_endpoint and current_endpoint not in endpoint_options:
210
+ print("WARNING: The current endpoint '{}' is not valid.".format(current_endpoint))
211
+ MediLink_ConfigLoader.log("Current endpoint '{}' is not valid. Prompting for selection.".format(current_endpoint), config, level="WARNING")
212
+
213
+ user_choice = input("Select an endpoint by number (or press Enter to use the default): ").strip()
214
+
215
+ if user_choice.isdigit() and 1 <= int(user_choice) <= len(endpoint_options):
216
+ selected_endpoint = endpoint_options[int(user_choice) - 1] # Use the key instead of the name
217
+ else:
218
+ selected_endpoint = endpoint_options[0] # Default to the first key
219
+ MediLink_ConfigLoader.log("User opted for default endpoint: " + selected_endpoint, config, level="INFO")
220
+
221
+ return selected_endpoint
222
+
223
+ def ensure_full_config_loaded(config=None, crosswalk=None):
224
+ """
225
+ Ensures that the full base configuration and crosswalk are loaded.
226
+ If the base config is not valid or the crosswalk is None, reloads them.
227
+
228
+ Args:
229
+ config (dict, optional): The current configuration.
230
+ crosswalk (dict, optional): The current crosswalk.
231
+
232
+ Returns:
233
+ tuple: The loaded base configuration and crosswalk.
234
+ """
235
+ MediLink_ConfigLoader.log("Ensuring full configuration and crosswalk are loaded.", level="DEBUG")
236
+
237
+ # Reload configuration if necessary
238
+ if config is None or 'MediLink_Config' not in config:
239
+ MediLink_ConfigLoader.log("Base config is missing or invalid. Reloading configuration.", level="WARNING")
240
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
241
+ MediLink_ConfigLoader.log("Base configuration and crosswalk reloaded.", level="INFO")
242
+ else:
243
+ MediLink_ConfigLoader.log("Base config was correctly passed.", level="DEBUG")
244
+
245
+ # Reload crosswalk if necessary
246
+ if crosswalk is None:
247
+ MediLink_ConfigLoader.log("Crosswalk is None. Reloading crosswalk.", level="WARNING")
248
+ _, crosswalk = MediLink_ConfigLoader.load_configuration() # Reloading to get the crosswalk
249
+ MediLink_ConfigLoader.log("Crosswalk reloaded.", level="INFO")
250
+
251
+ return config, crosswalk
252
+
253
+ # =============================================================================
254
+ # CROSSWALK PERSISTENCE AND STORAGE
255
+ # =============================================================================
256
+ # Functions for saving and managing crosswalk data persistence, including
257
+ # validation of crosswalk structure and proper JSON serialization.
258
+
259
+ def save_crosswalk(client, config, crosswalk, skip_api_operations=False, api_cache=None):
260
+ """
261
+ Saves the crosswalk to a JSON file. Ensures that all necessary keys are present and logs the outcome.
262
+
263
+ Args:
264
+ client (APIClient): API client for fetching payer names (ignored if skip_api_operations=True).
265
+ config (dict): Configuration settings for logging.
266
+ crosswalk (dict): The crosswalk dictionary to save.
267
+ skip_api_operations (bool): If True, skips API calls and user prompts for faster saves.
268
+ api_cache (dict, optional): Cache to prevent redundant API calls.
269
+
270
+ Returns:
271
+ bool: True if the crosswalk was saved successfully, False otherwise.
272
+ """
273
+ try:
274
+ # Determine the path to save the crosswalk
275
+ crosswalk_path = config['MediLink_Config']['crosswalkPath']
276
+ MediLink_ConfigLoader.log("Determined crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
277
+ except KeyError:
278
+ crosswalk_path = config.get('crosswalkPath', 'crosswalk.json')
279
+ MediLink_ConfigLoader.log("Using default crosswalk path: {}.".format(crosswalk_path), config, level="DEBUG")
280
+
281
+ # Validate endpoints for each payer ID in the crosswalk
282
+ for payer_id, details in crosswalk.get('payer_id', {}).items():
283
+ current_endpoint = details.get('endpoint', None)
284
+ if current_endpoint and current_endpoint not in config['MediLink_Config']['endpoints']:
285
+ if skip_api_operations:
286
+ # Log warning but don't prompt user during API-bypass mode
287
+ MediLink_ConfigLoader.log("WARNING: Invalid endpoint '{}' for payer ID '{}' - skipping correction due to API bypass mode".format(current_endpoint, payer_id), config, level="WARNING")
288
+ else:
289
+ print("WARNING: The current endpoint '{}' for payer ID '{}' is not valid.".format(current_endpoint, payer_id))
290
+ MediLink_ConfigLoader.log("Current endpoint '{}' for payer ID '{}' is not valid. Prompting for selection.".format(current_endpoint, payer_id), config, level="WARNING")
291
+ selected_endpoint = select_endpoint(config, current_endpoint) # Prompt user to select a valid endpoint
292
+ crosswalk['payer_id'][payer_id]['endpoint'] = selected_endpoint # Update the endpoint in the crosswalk
293
+ MediLink_ConfigLoader.log("Updated payer ID {} with new endpoint '{}'.".format(payer_id, selected_endpoint), config, level="INFO")
294
+
295
+ try:
296
+ # Log API bypass mode if enabled
297
+ if skip_api_operations:
298
+ MediLink_ConfigLoader.log("save_crosswalk running in API bypass mode - skipping API calls and user prompts", config, level="INFO")
299
+
300
+ # Initialize the 'payer_id' key if it doesn't exist
301
+ if 'payer_id' not in crosswalk:
302
+ print("save_crosswalk is initializing 'payer_id' key...")
303
+ crosswalk['payer_id'] = {}
304
+ MediLink_ConfigLoader.log("Initialized 'payer_id' key in crosswalk.", config, level="INFO")
305
+
306
+ # Ensure all payer IDs have a name and initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
307
+ for payer_id in crosswalk['payer_id']:
308
+ if 'name' not in crosswalk['payer_id'][payer_id]:
309
+ if skip_api_operations:
310
+ # Set placeholder name and log for MediBot to handle later
311
+ crosswalk['payer_id'][payer_id]['name'] = 'Unknown'
312
+ MediLink_ConfigLoader.log("Set placeholder name for payer ID {} - will be resolved by MediBot health check".format(payer_id), config, level="INFO")
313
+ else:
314
+ # Note: fetch_and_store_payer_name is in the main library to avoid circular imports
315
+ # This function will be called from the main library's crosswalk_update process
316
+ MediLink_ConfigLoader.log("Payer ID {} will be processed by main library's fetch_and_store_payer_name function.".format(payer_id), config, level="DEBUG")
317
+
318
+ # Check for the endpoint key
319
+ if 'endpoint' not in crosswalk['payer_id'][payer_id]:
320
+ if skip_api_operations:
321
+ # Set default endpoint and log
322
+ crosswalk['payer_id'][payer_id]['endpoint'] = 'AVAILITY'
323
+ MediLink_ConfigLoader.log("Set default endpoint for payer ID {} - can be adjusted via MediBot if needed".format(payer_id), config, level="INFO")
324
+ else:
325
+ crosswalk['payer_id'][payer_id]['endpoint'] = select_endpoint(config) # Use the helper function to set the endpoint
326
+ MediLink_ConfigLoader.log("Initialized 'endpoint' for payer ID {}.".format(payer_id), config, level="DEBUG")
327
+
328
+ # Initialize medisoft_id and medisoft_medicare_id as empty lists if they do not exist
329
+ crosswalk['payer_id'][payer_id].setdefault('medisoft_id', [])
330
+ crosswalk['payer_id'][payer_id].setdefault('medisoft_medicare_id', []) # does this work in 3.4.4?
331
+ MediLink_ConfigLoader.log("Ensured 'medisoft_id' and 'medisoft_medicare_id' for payer ID {} are initialized.".format(payer_id), config, level="DEBUG")
332
+
333
+ # Convert sets to sorted lists for JSON serialization
334
+ for payer_id, details in crosswalk.get('payer_id', {}).items():
335
+ if isinstance(details.get('medisoft_id'), set):
336
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(details['medisoft_id']))
337
+ MediLink_ConfigLoader.log("Converted medisoft_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
338
+ if isinstance(details.get('medisoft_medicare_id'), set):
339
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(details['medisoft_medicare_id']))
340
+ MediLink_ConfigLoader.log("Converted medisoft_medicare_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
341
+
342
+ # Write the crosswalk to the specified file
343
+ with open(crosswalk_path, 'w') as file:
344
+ json.dump(crosswalk, file, indent=4)
345
+
346
+ MediLink_ConfigLoader.log(
347
+ "Crosswalk saved successfully to {}.".format(crosswalk_path),
348
+ config,
349
+ level="INFO"
350
+ )
351
+ print("Crosswalk saved successfully to {}.".format(crosswalk_path))
352
+ return True
353
+ except KeyError as e:
354
+ print("Key Error: A required key is missing in the crosswalk data - {}.".format(e))
355
+ MediLink_ConfigLoader.log("Key Error while saving crosswalk: {}.".format(e), config, level="ERROR")
356
+ return False
357
+ except TypeError as e:
358
+ print("Type Error: There was a type issue with the data being saved in the crosswalk - {}.".format(e))
359
+ MediLink_ConfigLoader.log("Type Error while saving crosswalk: {}.".format(e), config, level="ERROR")
360
+ return False
361
+ except IOError as e:
362
+ print("I/O Error: An error occurred while writing to the crosswalk file - {}.".format(e))
363
+ MediLink_ConfigLoader.log("I/O Error while saving crosswalk: {}.".format(e), config, level="ERROR")
364
+ return False
365
+ except Exception as e:
366
+ print("Unexpected crosswalk error: {}.".format(e))
367
+ MediLink_ConfigLoader.log("Unexpected error while saving crosswalk: {}.".format(e), config, level="ERROR")
368
+ return False
369
+
370
+ # =============================================================================
371
+ # CROSSWALK UPDATE OPERATIONS
372
+ # =============================================================================
373
+ # Functions for updating crosswalk data with new or corrected payer information,
374
+ # including handling of payer ID corrections and new payer additions.
375
+
376
+ def update_crosswalk_with_corrected_payer_id(client, old_payer_id, corrected_payer_id, config=None, crosswalk=None, api_cache=None):
377
+ """
378
+ Updates the crosswalk by replacing an old payer ID with a corrected payer ID.
379
+
380
+ Args:
381
+ old_payer_id (str): The old payer ID to be replaced.
382
+ corrected_payer_id (str): The new payer ID to replace the old one.
383
+ config (dict, optional): Configuration settings for logging.
384
+ crosswalk (dict, optional): The crosswalk dictionary to update.
385
+ api_cache (dict, optional): Cache to prevent redundant API calls.
386
+
387
+ Returns:
388
+ bool: True if the crosswalk was updated successfully, False otherwise.
389
+ """
390
+ # Ensure full configuration and crosswalk are loaded
391
+ config, crosswalk = ensure_full_config_loaded(config, crosswalk)
392
+
393
+ # Convert to a regular dict if crosswalk['payer_id'] is an OrderedDict
394
+ if isinstance(crosswalk['payer_id'], dict) and hasattr(crosswalk['payer_id'], 'items'):
395
+ crosswalk['payer_id'] = dict(crosswalk['payer_id'])
396
+
397
+ MediLink_ConfigLoader.log("Checking if old Payer ID {} exists in crosswalk.".format(old_payer_id), config, level="DEBUG")
398
+
399
+ MediLink_ConfigLoader.log("Attempting to replace old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="DEBUG")
400
+
401
+ # Check if the old payer ID exists before attempting to replace
402
+ if old_payer_id in crosswalk['payer_id']:
403
+ MediLink_ConfigLoader.log("Old Payer ID {} found. Proceeding with replacement.".format(old_payer_id), config, level="DEBUG")
404
+
405
+ # Store the details of the old payer ID
406
+ old_payer_details = crosswalk['payer_id'][old_payer_id]
407
+ MediLink_ConfigLoader.log("Storing details of old Payer ID {}: {}".format(old_payer_id, old_payer_details), config, level="DEBUG")
408
+
409
+ # Replace the old payer ID with the corrected one
410
+ crosswalk['payer_id'][corrected_payer_id] = old_payer_details
411
+ MediLink_ConfigLoader.log("Replaced old Payer ID {} with corrected Payer ID {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
412
+
413
+ # Remove the old payer ID from the crosswalk
414
+ del crosswalk['payer_id'][old_payer_id]
415
+ MediLink_ConfigLoader.log("Removed old Payer ID {} from crosswalk.".format(old_payer_id), config, level="DEBUG")
416
+
417
+ # Note: fetch_and_store_payer_name is in the main library to avoid circular imports
418
+ # The payer name will be fetched during the next crosswalk update process
419
+ MediLink_ConfigLoader.log("Corrected Payer ID {} added to crosswalk - name will be fetched during next update.".format(corrected_payer_id), config, level="INFO")
420
+
421
+ # Update csv_replacements
422
+ crosswalk.setdefault('csv_replacements', {})[old_payer_id] = corrected_payer_id
423
+ MediLink_ConfigLoader.log("Updated csv_replacements: {} -> {}.".format(old_payer_id, corrected_payer_id), config, level="INFO")
424
+ print("csv_replacements updated: '{}' -> '{}'.".format(old_payer_id, corrected_payer_id))
425
+
426
+ return save_crosswalk(client, config, crosswalk, api_cache=api_cache)
427
+ else:
428
+ MediLink_ConfigLoader.log("Failed to update crosswalk: old Payer ID {} not found.".format(old_payer_id), config, level="ERROR")
429
+ print("Failed to update crosswalk: could not find old Payer ID '{}'.".format(old_payer_id))
430
+ return False
431
+
432
+ def update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config, crosswalk, api_cache=None):
433
+ """
434
+ Updates the crosswalk with a new payer ID for a given insurance name.
435
+
436
+ Args:
437
+ insurance_name (str): The name of the insurance to associate with the new payer ID.
438
+ payer_id (str): The new payer ID to be added.
439
+ config (dict): Configuration settings for logging.
440
+ crosswalk (dict): The crosswalk dictionary to update.
441
+ api_cache (dict, optional): Cache to prevent redundant API calls.
442
+ """
443
+ # Ensure full configuration and crosswalk are loaded
444
+ config, crosswalk = ensure_full_config_loaded(config, crosswalk)
445
+
446
+ try:
447
+ # Check if 'payer_id' is present in the crosswalk
448
+ if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
449
+ # Reload the crosswalk if 'payer_id' is missing or empty
450
+ _, crosswalk = MediLink_ConfigLoader.load_configuration(None, config.get('crosswalkPath', 'crosswalk.json'))
451
+ MediLink_ConfigLoader.log("Reloaded crosswalk configuration from {}.".format(config.get('crosswalkPath', 'crosswalk.json')), config, level="DEBUG")
452
+ except KeyError as e: # Handle KeyError for crosswalk
453
+ MediLink_ConfigLoader.log("KeyError while checking or reloading crosswalk: {}".format(e), config, level="ERROR")
454
+ print("KeyError while checking or reloading crosswalk in update_crosswalk_with_new_payer_id: {}".format(e))
455
+ return False
456
+ except Exception as e:
457
+ MediLink_ConfigLoader.log("Error while checking or reloading crosswalk: {}".format(e), config, level="ERROR")
458
+ print("Error while checking or reloading crosswalk in update_crosswalk_with_new_payer_id: {}".format(e))
459
+ return False
460
+
461
+ # Load the Medisoft ID for the given insurance name
462
+ try:
463
+ # Note: MediBot_Preprocessor_lib is imported at module level
464
+ medisoft_id = MediBot_Preprocessor_lib.load_insurance_data_from_mains(config).get(insurance_name)
465
+ except KeyError as e: # Handle KeyError for config
466
+ MediLink_ConfigLoader.log("KeyError while loading Medisoft ID: {}".format(e), config, level="ERROR")
467
+ print("KeyError while loading Medisoft ID for insurance name {}: {}".format(insurance_name, e))
468
+ return False
469
+
470
+ MediLink_ConfigLoader.log("Retrieved Medisoft ID for insurance name {}: {}.".format(insurance_name, medisoft_id), config, level="DEBUG")
471
+
472
+ if medisoft_id:
473
+ medisoft_id_str = str(medisoft_id)
474
+ MediLink_ConfigLoader.log("Processing to update crosswalk with new payer ID: {} for insurance name: {}.".format(payer_id, insurance_name), config, level="DEBUG")
475
+
476
+ # Initialize the payer ID entry if it doesn't exist
477
+ if payer_id not in crosswalk['payer_id']:
478
+ selected_endpoint = select_endpoint(config) # Use the helper function to select the endpoint
479
+
480
+ # Ensure the 'payer_id' key exists in the crosswalk
481
+ crosswalk['payer_id'][payer_id] = {
482
+ 'endpoint': selected_endpoint,
483
+ 'medisoft_id': [], # PERFORMANCE FIX: Use list instead of set to avoid conversions
484
+ 'medisoft_medicare_id': []
485
+ }
486
+ MediLink_ConfigLoader.log("Initialized payer ID {} in crosswalk with endpoint '{}'.".format(payer_id, selected_endpoint), config, level="DEBUG")
487
+ else:
488
+ # Check if the existing endpoint is valid
489
+ current_endpoint = crosswalk['payer_id'][payer_id].get('endpoint', None)
490
+ if current_endpoint and current_endpoint not in config['MediLink_Config']['endpoints']:
491
+ print("WARNING: The current endpoint '{}' for payer ID '{}' is not valid.".format(current_endpoint, payer_id))
492
+ MediLink_ConfigLoader.log("Current endpoint '{}' for payer ID '{}' is not valid. Prompting for selection.".format(current_endpoint, payer_id), config, level="WARNING")
493
+ selected_endpoint = select_endpoint(config, current_endpoint) # Prompt user to select a valid endpoint
494
+ crosswalk['payer_id'][payer_id]['endpoint'] = selected_endpoint # Update the endpoint in the crosswalk
495
+ MediLink_ConfigLoader.log("Updated payer ID {} with new endpoint '{}'.".format(payer_id, selected_endpoint), config, level="INFO")
496
+ else:
497
+ selected_endpoint = current_endpoint # Use the existing valid endpoint
498
+
499
+ # Add the insurance ID to the payer ID entry - with error handling for the .add() operation
500
+ try:
501
+ if not isinstance(crosswalk['payer_id'][payer_id]['medisoft_id'], set):
502
+ # Convert to set if it's not already one
503
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = set(crosswalk['payer_id'][payer_id]['medisoft_id'])
504
+ MediLink_ConfigLoader.log("Converted medisoft_id to set for payer ID {}.".format(payer_id), config, level="DEBUG")
505
+
506
+ crosswalk['payer_id'][payer_id]['medisoft_id'].add(str(medisoft_id_str)) # Ensure IDs are strings
507
+ MediLink_ConfigLoader.log(
508
+ "Added new insurance ID {} to payer ID {}.".format(medisoft_id_str, payer_id),
509
+ config,
510
+ level="INFO"
511
+ )
512
+ except AttributeError as e:
513
+ MediLink_ConfigLoader.log("AttributeError while adding medisoft_id: {}".format(e), config, level="ERROR")
514
+ print("Error adding medisoft_id for payer ID {}: {}".format(payer_id, e))
515
+ return False
516
+
517
+ # Note: fetch_and_store_payer_name is in the main library to avoid circular imports
518
+ # The payer name will be fetched during the next crosswalk update process
519
+ MediLink_ConfigLoader.log("Added new payer ID {} for insurance name {} - name will be fetched during next update.".format(payer_id, insurance_name), config, level="INFO")
520
+
521
+ # Save the updated crosswalk
522
+ save_crosswalk(client, config, crosswalk, api_cache=api_cache)
523
+ MediLink_ConfigLoader.log("Crosswalk saved successfully after updating payer ID {}.".format(payer_id), config, level="DEBUG")
524
+ else:
525
+ message = "Failed to update crosswalk: Medisoft ID not found for insurance name {}.".format(insurance_name)
526
+ print(message)
527
+ MediLink_ConfigLoader.log(message, config, level="ERROR")
528
+
529
+ # =============================================================================
530
+ # DATA PARSING AND PROCESSING
531
+ # =============================================================================
532
+ # Functions for parsing and processing external data sources, including
533
+ # Z data files and other data formats used by the crosswalk system.
534
+
535
+ def load_and_parse_z_data(config):
536
+ """
537
+ Loads and parses Z data for patient to insurance name mappings from the specified directory.
538
+
539
+ Args:
540
+ config (dict): Configuration settings for logging.
541
+
542
+ Returns:
543
+ tuple: (dict, str) - A tuple containing:
544
+ - dict: A mapping of patient IDs to insurance names
545
+ - str: Status indicating the result type:
546
+ - "success_with_data": Successfully parsed with data
547
+ - "success_no_new_files": Successfully processed but no new files found
548
+ - "success_empty_files": Successfully processed but files contained no valid data
549
+ - "error": An error occurred during processing
550
+ """
551
+ patient_id_to_insurance_name = {}
552
+ files_processed = 0
553
+ files_with_data = 0
554
+
555
+ try:
556
+ z_dat_path = config['MediLink_Config']['Z_DAT_PATH']
557
+ MediLink_ConfigLoader.log("Z_DAT_PATH is set to: {}".format(z_dat_path), config, level="DEBUG")
558
+
559
+ # Get the directory of the Z_DAT_PATH
560
+ directory = os.path.dirname(z_dat_path)
561
+ MediLink_ConfigLoader.log("Looking for .DAT files in directory: {}".format(directory), config, level="DEBUG")
562
+
563
+ # List all .DAT files in the directory, case insensitive
564
+ dat_files = [f for f in os.listdir(directory) if f.lower().endswith('.dat')]
565
+ MediLink_ConfigLoader.log("Found {} .DAT files in the directory.".format(len(dat_files)), config, level="DEBUG")
566
+
567
+ # Load processed files tracking
568
+ processed_files_path = os.path.join(directory, 'processed_files.txt')
569
+ if os.path.exists(processed_files_path):
570
+ with open(processed_files_path, 'r') as f:
571
+ processed_files = set(line.strip() for line in f)
572
+ MediLink_ConfigLoader.log("Loaded processed files: {}.".format(processed_files), config, level="DEBUG")
573
+ else:
574
+ processed_files = set()
575
+ MediLink_ConfigLoader.log("No processed files found, starting fresh.", config, level="DEBUG")
576
+
577
+ # Filter for new .DAT files that haven't been processed yet, but always include Z.DAT and ZM.DAT
578
+ new_dat_files = [f for f in dat_files if f not in processed_files or f.lower() in ['z.dat', 'zm.dat']]
579
+ MediLink_ConfigLoader.log("Identified {} new .DAT files to process.".format(len(new_dat_files)), config, level="INFO")
580
+
581
+ if not new_dat_files:
582
+ MediLink_ConfigLoader.log("No new .DAT files to process.", config, level="INFO")
583
+ return {}, "success_no_new_files"
584
+
585
+ for dat_file in new_dat_files:
586
+ file_path = os.path.join(directory, dat_file)
587
+ MediLink_ConfigLoader.log("Parsing .DAT file: {}".format(file_path), config, level="DEBUG")
588
+
589
+ # Parse each .DAT file and accumulate results
590
+ # Note: MediBot_Preprocessor_lib is imported at module level
591
+ insurance_name_mapping = MediBot_Preprocessor_lib.parse_z_dat(file_path, config['MediLink_Config'])
592
+ files_processed += 1
593
+
594
+ if insurance_name_mapping: # Ensure insurance_name_mapping is not empty
595
+ patient_id_to_insurance_name.update(insurance_name_mapping)
596
+ files_with_data += 1
597
+ MediLink_ConfigLoader.log("File {} contained {} mappings.".format(dat_file, len(insurance_name_mapping)), config, level="DEBUG")
598
+
599
+ # Mark this file as processed
600
+ with open(processed_files_path, 'a') as f:
601
+ f.write(dat_file + '\n')
602
+ MediLink_ConfigLoader.log("Marked file as processed: {}".format(dat_file), config, level="DEBUG")
603
+
604
+ # Determine the result status
605
+ if patient_id_to_insurance_name:
606
+ MediLink_ConfigLoader.log("Successfully parsed Z data with {} mappings found from {} files.".format(
607
+ len(patient_id_to_insurance_name), files_with_data), config, level="INFO")
608
+ return patient_id_to_insurance_name, "success_with_data"
609
+ elif files_processed > 0:
610
+ MediLink_ConfigLoader.log("Processed {} files but found no valid data mappings.".format(files_processed), config, level="INFO")
611
+ return {}, "success_empty_files"
612
+ else:
613
+ MediLink_ConfigLoader.log("No files were processed.", config, level="WARNING")
614
+ return {}, "error"
615
+
616
+ except Exception as e:
617
+ MediLink_ConfigLoader.log("Error loading and parsing Z data: {}".format(e), config, level="ERROR")
618
+ return {}, "error"