medicafe 0.250728.9__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.
- MediBot/MediBot.bat +233 -19
- MediBot/MediBot.py +138 -46
- MediBot/MediBot_Crosswalk_Library.py +127 -623
- MediBot/MediBot_Crosswalk_Utils.py +618 -0
- MediBot/MediBot_Preprocessor.py +72 -17
- MediBot/MediBot_Preprocessor_lib.py +470 -76
- MediBot/MediBot_UI.py +32 -17
- MediBot/MediBot_dataformat_library.py +68 -20
- MediBot/MediBot_docx_decoder.py +120 -19
- MediBot/MediBot_smart_import.py +180 -0
- MediBot/__init__.py +89 -0
- MediBot/get_medicafe_version.py +25 -0
- MediBot/update_json.py +35 -6
- MediBot/update_medicafe.py +19 -1
- MediCafe/MediLink_ConfigLoader.py +160 -0
- MediCafe/__init__.py +171 -0
- MediCafe/__main__.py +222 -0
- MediCafe/api_core.py +1098 -0
- MediCafe/api_core_backup.py +427 -0
- MediCafe/api_factory.py +306 -0
- MediCafe/api_utils.py +356 -0
- MediCafe/core_utils.py +450 -0
- MediCafe/graphql_utils.py +445 -0
- MediCafe/logging_config.py +123 -0
- MediCafe/logging_demo.py +61 -0
- MediCafe/migration_helpers.py +463 -0
- MediCafe/smart_import.py +436 -0
- MediLink/MediLink_837p_cob_library.py +28 -28
- MediLink/MediLink_837p_encoder.py +33 -34
- MediLink/MediLink_837p_encoder_library.py +226 -150
- MediLink/MediLink_837p_utilities.py +129 -5
- MediLink/MediLink_API_Generator.py +83 -60
- MediLink/MediLink_API_v3.py +1 -1
- MediLink/MediLink_ClaimStatus.py +177 -31
- MediLink/MediLink_DataMgmt.py +378 -63
- MediLink/MediLink_Decoder.py +20 -1
- MediLink/MediLink_Deductible.py +155 -28
- MediLink/MediLink_Display_Utils.py +72 -0
- MediLink/MediLink_Down.py +127 -5
- MediLink/MediLink_Gmail.py +712 -653
- MediLink/MediLink_PatientProcessor.py +257 -0
- MediLink/MediLink_UI.py +85 -71
- MediLink/MediLink_Up.py +28 -4
- MediLink/MediLink_insurance_utils.py +227 -230
- MediLink/MediLink_main.py +248 -0
- MediLink/MediLink_smart_import.py +264 -0
- MediLink/__init__.py +93 -1
- MediLink/insurance_type_integration_test.py +13 -3
- MediLink/test.py +1 -1
- MediLink/test_timing.py +59 -0
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/METADATA +1 -1
- medicafe-0.250805.0.dist-info/RECORD +81 -0
- medicafe-0.250805.0.dist-info/entry_points.txt +2 -0
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/top_level.txt +1 -0
- medicafe-0.250728.9.dist-info/RECORD +0 -59
- {medicafe-0.250728.9.dist-info → medicafe-0.250805.0.dist-info}/LICENSE +0 -0
- {medicafe-0.250728.9.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"
|