medicafe 0.250813.2__py3-none-any.whl → 0.250814.3__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.
- MediBot/MediBot.bat +1 -1
- MediBot/MediBot.py +1 -1
- MediBot/MediBot_Preprocessor_lib.py +21 -5
- MediBot/MediBot_UI.py +13 -1
- MediBot/__init__.py +1 -1
- MediBot/update_medicafe.py +69 -7
- MediCafe/__init__.py +1 -1
- MediLink/__init__.py +1 -1
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/METADATA +1 -1
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/RECORD +14 -27
- MediBot/MediPost.py +0 -5
- MediBot/PDF_to_CSV_Cleaner.py +0 -211
- MediLink/MediLink.py +0 -623
- MediLink/MediLink_277_decoder.py +0 -92
- MediLink/MediLink_API_v2.py +0 -176
- MediLink/MediLink_API_v3.py +0 -910
- MediLink/MediLink_APIs.py +0 -138
- MediLink/MediLink_ConfigLoader.py +0 -87
- MediLink/MediLink_ERA_decoder.py +0 -192
- MediLink/MediLink_GraphQL.py +0 -445
- MediLink/MediLink_StatusCheck.py +0 -0
- MediLink/MediLink_api_utils.py +0 -305
- MediLink/MediLink_batch.bat +0 -7
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/LICENSE +0 -0
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/WHEEL +0 -0
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/entry_points.txt +0 -0
- {medicafe-0.250813.2.dist-info → medicafe-0.250814.3.dist-info}/top_level.txt +0 -0
MediLink/MediLink.py
DELETED
@@ -1,623 +0,0 @@
|
|
1
|
-
# MediLink.py
|
2
|
-
import os, sys, time
|
3
|
-
import MediLink_Down
|
4
|
-
import MediLink_Up
|
5
|
-
import MediLink_ConfigLoader
|
6
|
-
import MediLink_DataMgmt
|
7
|
-
|
8
|
-
# For UI Functions
|
9
|
-
import MediLink_UI # Import UI module for handling all user interfaces
|
10
|
-
try:
|
11
|
-
from tqdm import tqdm
|
12
|
-
except ImportError:
|
13
|
-
# Fallback for when tqdm is not available
|
14
|
-
def tqdm(iterable, **kwargs):
|
15
|
-
return iterable
|
16
|
-
|
17
|
-
# Add parent directory of the project to the Python path
|
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
|
-
from MediBot import MediBot_Preprocessor_lib
|
23
|
-
load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
|
24
|
-
|
25
|
-
# Retrieve insurance options with codes and descriptions
|
26
|
-
config, _ = MediLink_ConfigLoader.load_configuration()
|
27
|
-
insurance_options = config['MediLink_Config'].get('insurance_options')
|
28
|
-
|
29
|
-
# TODO There needs to be a crosswalk auditing feature right alongside where all the names get fetched during initial startup maybe?
|
30
|
-
# This already happens when MediLink is opened.
|
31
|
-
|
32
|
-
def collect_detailed_patient_data(selected_files, config, crosswalk):
|
33
|
-
"""
|
34
|
-
Collects detailed patient data from the selected files.
|
35
|
-
|
36
|
-
DATA FLOW CLARIFICATION:
|
37
|
-
This function processes fixed-width files through extract_and_suggest_endpoint(),
|
38
|
-
which creates data structures with 'patient_id' field (sourced from 'CHART' field).
|
39
|
-
This is DIFFERENT from MediBot's parse_z_dat() flow which uses 'PATID' field.
|
40
|
-
|
41
|
-
:param selected_files: List of selected file paths.
|
42
|
-
:param config: Configuration settings loaded from a JSON file.
|
43
|
-
:param crosswalk: Crosswalk data for mapping purposes.
|
44
|
-
:return: A list of detailed patient data with 'patient_id' field populated.
|
45
|
-
"""
|
46
|
-
detailed_patient_data = []
|
47
|
-
for file_path in selected_files:
|
48
|
-
# IMPORTANT: extract_and_suggest_endpoint creates data with 'patient_id' field
|
49
|
-
# sourced from the 'CHART' field in fixed-width files
|
50
|
-
detailed_data = extract_and_suggest_endpoint(file_path, config, crosswalk)
|
51
|
-
detailed_patient_data.extend(detailed_data) # Accumulate detailed data for processing
|
52
|
-
|
53
|
-
# Enrich the detailed patient data with insurance type
|
54
|
-
# NOTE: This receives data from extract_and_suggest_endpoint with 'patient_id' field
|
55
|
-
detailed_patient_data = enrich_with_insurance_type(detailed_patient_data, insurance_options)
|
56
|
-
|
57
|
-
# Display summaries and provide an option for bulk edit
|
58
|
-
MediLink_UI.display_patient_summaries(detailed_patient_data)
|
59
|
-
|
60
|
-
return detailed_patient_data
|
61
|
-
|
62
|
-
def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_mapping=None):
|
63
|
-
"""
|
64
|
-
Enriches the detailed patient data with insurance type based on patient ID.
|
65
|
-
Enhanced with optional API integration and comprehensive logging.
|
66
|
-
|
67
|
-
DATA FLOW CLARIFICATION:
|
68
|
-
This function receives data from collect_detailed_patient_data() -> extract_and_suggest_endpoint().
|
69
|
-
The patient ID field is 'patient_id' (NOT 'PATID').
|
70
|
-
|
71
|
-
IMPORTANT: Do not confuse with MediBot's parse_z_dat() flow which uses 'PATID'.
|
72
|
-
MediLink flow: fixed-width files -> extract_and_suggest_endpoint() -> 'patient_id' field (from 'CHART')
|
73
|
-
MediBot flow: Z.dat files -> parse_z_dat() -> 'PATID' field
|
74
|
-
|
75
|
-
Parameters:
|
76
|
-
- detailed_patient_data: List of dictionaries containing detailed patient data with 'patient_id' field.
|
77
|
-
- patient_insurance_mapping: Dictionary mapping patient IDs to their insurance types.
|
78
|
-
|
79
|
-
Returns:
|
80
|
-
- Enriched detailed patient data with insurance type added.
|
81
|
-
|
82
|
-
TODO: Implement a function to provide `patient_insurance_mapping` from a reliable source.
|
83
|
-
This is going to be coming soon as an API feature from United Healthcare. We'll be able to get insurance types directly via their API.
|
84
|
-
So, while we won't be able to do it for all payerIDs, we'll be able to do it for the ones that are supported by UHC.
|
85
|
-
So, we'll need a way to augment the associated menu here so that the user is aware of which insurance types are already pulled from
|
86
|
-
UHC and which ones are not yet supported so they know which ones they need to edit. It is possible that we may want to isolate the
|
87
|
-
patient data that is already pulled from UHC so that the user can see which ones are already using the enriched data.
|
88
|
-
"""
|
89
|
-
# Enhanced mode check with graceful degradation
|
90
|
-
enhanced_mode = False
|
91
|
-
|
92
|
-
try:
|
93
|
-
from MediLink_insurance_utils import (
|
94
|
-
get_feature_flag,
|
95
|
-
validate_insurance_type_from_config
|
96
|
-
)
|
97
|
-
enhanced_mode = get_feature_flag('enhanced_insurance_enrichment', default=False)
|
98
|
-
MediLink_ConfigLoader.log("Insurance enhancement utilities loaded successfully", level="DEBUG")
|
99
|
-
except ImportError as e:
|
100
|
-
MediLink_ConfigLoader.log("Insurance utils not available: {}. Using legacy mode.".format(str(e)), level="INFO")
|
101
|
-
enhanced_mode = False
|
102
|
-
except Exception as e:
|
103
|
-
MediLink_ConfigLoader.log("Error initializing insurance enhancements: {}. Using legacy mode.".format(str(e)), level="ERROR")
|
104
|
-
enhanced_mode = False
|
105
|
-
|
106
|
-
if patient_insurance_type_mapping is None:
|
107
|
-
MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.", level="INFO")
|
108
|
-
patient_insurance_type_mapping = {}
|
109
|
-
|
110
|
-
# Enhanced mode with validation
|
111
|
-
if enhanced_mode:
|
112
|
-
MediLink_ConfigLoader.log("Using enhanced insurance type enrichment", level="INFO")
|
113
|
-
|
114
|
-
for data in detailed_patient_data:
|
115
|
-
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
116
|
-
# This field contains the value from the 'CHART' field in the original fixed-width file
|
117
|
-
patient_id = data.get('patient_id')
|
118
|
-
if patient_id:
|
119
|
-
raw_insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
120
|
-
validated_insurance_type = validate_insurance_type_from_config(raw_insurance_type, patient_id)
|
121
|
-
data['insurance_type'] = validated_insurance_type
|
122
|
-
data['insurance_type_source'] = 'MANUAL' if patient_id in patient_insurance_type_mapping else 'DEFAULT'
|
123
|
-
else:
|
124
|
-
# Handle case where patient_id is missing or empty
|
125
|
-
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
126
|
-
data['insurance_type'] = '12' # SBR09 default PPO
|
127
|
-
data['insurance_type_source'] = 'DEFAULT_FALLBACK'
|
128
|
-
|
129
|
-
else:
|
130
|
-
# Legacy mode (preserve existing behavior exactly)
|
131
|
-
MediLink_ConfigLoader.log("Using legacy insurance type enrichment", level="INFO")
|
132
|
-
for data in detailed_patient_data:
|
133
|
-
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
134
|
-
# This field contains the value from the 'CHART' field in the original fixed-width file
|
135
|
-
patient_id = data.get('patient_id')
|
136
|
-
if patient_id:
|
137
|
-
insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
138
|
-
else:
|
139
|
-
# Handle case where patient_id is missing or empty
|
140
|
-
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
141
|
-
insurance_type = '12' # Default when no patient ID available
|
142
|
-
|
143
|
-
data['insurance_type'] = insurance_type
|
144
|
-
|
145
|
-
return detailed_patient_data
|
146
|
-
|
147
|
-
def extract_and_suggest_endpoint(file_path, config, crosswalk):
|
148
|
-
"""
|
149
|
-
Reads a fixed-width file, extracts file details including surgery date, patient ID,
|
150
|
-
patient name, primary insurance, and other necessary details for each record. It suggests
|
151
|
-
an endpoint based on insurance provider information found in the crosswalk and prepares
|
152
|
-
detailed patient data for processing.
|
153
|
-
|
154
|
-
DATA FLOW CLARIFICATION:
|
155
|
-
This function is the PRIMARY data source for MediLink patient processing.
|
156
|
-
It creates the 'patient_id' field by extracting the 'CHART' field from fixed-width files.
|
157
|
-
|
158
|
-
IMPORTANT: This is DIFFERENT from MediBot's parse_z_dat() which extracts 'PATID'.
|
159
|
-
|
160
|
-
Field mapping for MediLink flow:
|
161
|
-
- Fixed-width file 'CHART' field -> detailed_data['patient_id']
|
162
|
-
- This 'patient_id' is then used by enrich_with_insurance_type()
|
163
|
-
|
164
|
-
Parameters:
|
165
|
-
- file_path: Path to the fixed-width file.
|
166
|
-
- crosswalk: Crosswalk dictionary loaded from a JSON file.
|
167
|
-
|
168
|
-
Returns:
|
169
|
-
- A comprehensive data structure retaining detailed patient claim details needed for processing,
|
170
|
-
including new key-value pairs for file path, surgery date, patient name, and primary insurance.
|
171
|
-
"""
|
172
|
-
detailed_patient_data = []
|
173
|
-
|
174
|
-
# Load insurance data from MAINS to create a mapping from insurance names to their respective IDs
|
175
|
-
insurance_to_id = load_insurance_data_from_mains(config)
|
176
|
-
MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)), level="INFO")
|
177
|
-
|
178
|
-
for personal_info, insurance_info, service_info, service_info_2, service_info_3 in MediLink_DataMgmt.read_fixed_width_data(file_path):
|
179
|
-
parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, service_info_2, service_info_3, config.get('MediLink_Config', config))
|
180
|
-
|
181
|
-
primary_insurance = parsed_data.get('INAME')
|
182
|
-
|
183
|
-
# Retrieve the insurance ID associated with the primary insurance
|
184
|
-
insurance_id = insurance_to_id.get(primary_insurance)
|
185
|
-
MediLink_ConfigLoader.log("Primary insurance ID retrieved for '{}': {}".format(primary_insurance, insurance_id))
|
186
|
-
|
187
|
-
# Use insurance ID to retrieve the payer ID(s) associated with the insurance
|
188
|
-
payer_ids = []
|
189
|
-
if insurance_id:
|
190
|
-
for payer_id, payer_data in crosswalk.get('payer_id', {}).items():
|
191
|
-
medisoft_ids = [str(id) for id in payer_data.get('medisoft_id', [])]
|
192
|
-
# MediLink_ConfigLoader.log("Payer ID: {}, Medisoft IDs: {}".format(payer_id, medisoft_ids))
|
193
|
-
if str(insurance_id) in medisoft_ids:
|
194
|
-
payer_ids.append(payer_id)
|
195
|
-
if payer_ids:
|
196
|
-
MediLink_ConfigLoader.log("Payer IDs retrieved for insurance '{}': {}".format(primary_insurance, payer_ids))
|
197
|
-
else:
|
198
|
-
MediLink_ConfigLoader.log("No payer IDs found for insurance '{}'".format(primary_insurance))
|
199
|
-
|
200
|
-
# Find the suggested endpoint from the crosswalk based on the payer IDs
|
201
|
-
suggested_endpoint = 'AVAILITY' # Default endpoint if no matching payer IDs found
|
202
|
-
if payer_ids:
|
203
|
-
payer_id = payer_ids[0] # Select the first payer ID
|
204
|
-
suggested_endpoint = crosswalk['payer_id'].get(payer_id, {}).get('endpoint', 'AVAILITY')
|
205
|
-
MediLink_ConfigLoader.log("Suggested endpoint for payer ID '{}': {}".format(payer_id, suggested_endpoint))
|
206
|
-
|
207
|
-
# Validate suggested endpoint against the config
|
208
|
-
if suggested_endpoint not in config['MediLink_Config'].get('endpoints', {}):
|
209
|
-
MediLink_ConfigLoader.log("Warning: Suggested endpoint '{}' is not defined in the configuration. Please Run MediBot. If this persists, check the crosswalk and config file.".format(suggested_endpoint), level="ERROR")
|
210
|
-
raise ValueError("Invalid suggested endpoint: '{}' for payer ID '{}'. Please correct the configuration.".format(suggested_endpoint, payer_id))
|
211
|
-
else:
|
212
|
-
MediLink_ConfigLoader.log("No suggested endpoint found for payer IDs: {}".format(payer_ids))
|
213
|
-
|
214
|
-
# Enrich detailed patient data with additional information and suggested endpoint
|
215
|
-
detailed_data = parsed_data.copy() # Copy parsed_data to avoid modifying the original dictionary
|
216
|
-
detailed_data.update({
|
217
|
-
'file_path': file_path,
|
218
|
-
# CRITICAL FIELD MAPPING: 'CHART' field from fixed-width file becomes 'patient_id'
|
219
|
-
# This is the field that enrich_with_insurance_type() will use
|
220
|
-
'patient_id': parsed_data.get('CHART'), # ← This is the key field mapping for MediLink flow
|
221
|
-
'surgery_date': parsed_data.get('DATE'),
|
222
|
-
'patient_name': ' '.join([parsed_data.get(key, '') for key in ['FIRST', 'MIDDLE', 'LAST']]),
|
223
|
-
'amount': parsed_data.get('AMOUNT'),
|
224
|
-
'primary_insurance': primary_insurance,
|
225
|
-
'suggested_endpoint': suggested_endpoint
|
226
|
-
})
|
227
|
-
detailed_patient_data.append(detailed_data)
|
228
|
-
|
229
|
-
# Return only the enriched detailed patient data, eliminating the need for a separate summary list
|
230
|
-
return detailed_patient_data
|
231
|
-
|
232
|
-
def check_for_new_remittances(config=None):
|
233
|
-
"""
|
234
|
-
Function to check for new remittance files across all configured endpoints.
|
235
|
-
Loads the configuration, validates it, and processes each endpoint to download and handle files.
|
236
|
-
Accumulates results from all endpoints and processes them together at the end.
|
237
|
-
"""
|
238
|
-
# Start the process and log the initiation
|
239
|
-
MediLink_ConfigLoader.log("Starting check_for_new_remittances function")
|
240
|
-
print("\nChecking for new files across all endpoints...")
|
241
|
-
MediLink_ConfigLoader.log("Checking for new files across all endpoints...")
|
242
|
-
|
243
|
-
# Step 1: Load and validate the configuration
|
244
|
-
if config is None:
|
245
|
-
config, _ = MediLink_ConfigLoader.load_configuration()
|
246
|
-
|
247
|
-
if not config or 'MediLink_Config' not in config or 'endpoints' not in config['MediLink_Config']:
|
248
|
-
MediLink_ConfigLoader.log("Error: Config is missing necessary sections. Aborting...", level="ERROR")
|
249
|
-
return
|
250
|
-
|
251
|
-
endpoints = config['MediLink_Config'].get('endpoints')
|
252
|
-
if not isinstance(endpoints, dict):
|
253
|
-
MediLink_ConfigLoader.log("Error: 'endpoints' is not a dictionary. Aborting...", level="ERROR")
|
254
|
-
return
|
255
|
-
|
256
|
-
# Lists to accumulate all consolidated records and translated files across all endpoints
|
257
|
-
all_consolidated_records = []
|
258
|
-
all_translated_files = []
|
259
|
-
|
260
|
-
# Step 2: Process each endpoint and accumulate results
|
261
|
-
for endpoint_key, endpoint_info in tqdm(endpoints.items(), desc="Processing endpoints"):
|
262
|
-
# Validate endpoint structure
|
263
|
-
if not endpoint_info or not isinstance(endpoint_info, dict):
|
264
|
-
MediLink_ConfigLoader.log("Error: Invalid endpoint structure for {}. Skipping...".format(endpoint_key), level="ERROR")
|
265
|
-
continue
|
266
|
-
|
267
|
-
if 'remote_directory_down' in endpoint_info:
|
268
|
-
# Process the endpoint and handle the files
|
269
|
-
MediLink_ConfigLoader.log("Processing endpoint: {}".format(endpoint_key))
|
270
|
-
consolidated_records, translated_files = process_endpoint(endpoint_key, endpoint_info, config)
|
271
|
-
|
272
|
-
# Accumulate the results for later processing
|
273
|
-
if consolidated_records:
|
274
|
-
all_consolidated_records.extend(consolidated_records)
|
275
|
-
if translated_files:
|
276
|
-
all_translated_files.extend(translated_files)
|
277
|
-
else:
|
278
|
-
MediLink_ConfigLoader.log("Skipping endpoint '{}'. 'remote_directory_down' not configured.".format(endpoint_info.get('name', 'Unknown')), level="WARNING")
|
279
|
-
|
280
|
-
# Step 3: After processing all endpoints, handle the accumulated results
|
281
|
-
if all_consolidated_records:
|
282
|
-
MediLink_Down.display_consolidated_records(all_consolidated_records) # Ensure this is called only once
|
283
|
-
MediLink_Down.prompt_csv_export(all_consolidated_records, config['MediLink_Config']['local_storage_path'])
|
284
|
-
else:
|
285
|
-
MediLink_ConfigLoader.log("No records to display after processing all endpoints.", level="WARNING")
|
286
|
-
print("No records to display after processing all endpoints.")
|
287
|
-
|
288
|
-
def process_endpoint(endpoint_key, endpoint_info, config):
|
289
|
-
"""
|
290
|
-
Helper function to process a single endpoint.
|
291
|
-
Downloads files from the endpoint, processes them, and returns the consolidated records and translated files.
|
292
|
-
"""
|
293
|
-
try:
|
294
|
-
# Process the files for the given endpoint
|
295
|
-
local_storage_path = config['MediLink_Config']['local_storage_path']
|
296
|
-
MediLink_ConfigLoader.log("[Process Endpoint] Local storage path set to {}".format(local_storage_path))
|
297
|
-
downloaded_files = MediLink_Down.operate_winscp("download", None, endpoint_info, local_storage_path, config)
|
298
|
-
|
299
|
-
if downloaded_files:
|
300
|
-
MediLink_ConfigLoader.log("[Process Endpoint] WinSCP Downloaded the following files: \n{}".format(downloaded_files))
|
301
|
-
return MediLink_Down.handle_files(local_storage_path, downloaded_files)
|
302
|
-
else:
|
303
|
-
MediLink_ConfigLoader.log("[Process Endpoint]No files were downloaded for endpoint: {}.".format(endpoint_key), level="WARNING")
|
304
|
-
return [], []
|
305
|
-
|
306
|
-
except Exception as e:
|
307
|
-
# Handle any exceptions that occur during the processing
|
308
|
-
MediLink_ConfigLoader.log("Error processing endpoint {}: {}".format(endpoint_key, e), level="ERROR")
|
309
|
-
return [], []
|
310
|
-
|
311
|
-
def user_decision_on_suggestions(detailed_patient_data, config, insurance_edited, crosswalk):
|
312
|
-
"""
|
313
|
-
Presents the user with all patient summaries and suggested endpoints,
|
314
|
-
then asks for confirmation to proceed with all or specify adjustments manually.
|
315
|
-
|
316
|
-
FIXED: Display now properly shows effective endpoints (user preferences over original suggestions)
|
317
|
-
"""
|
318
|
-
if insurance_edited:
|
319
|
-
# Display summaries only if insurance types were edited
|
320
|
-
MediLink_UI.display_patient_summaries(detailed_patient_data)
|
321
|
-
|
322
|
-
while True:
|
323
|
-
proceed_input = input("Do you want to proceed with all suggested endpoints? (Y/N): ").strip().lower()
|
324
|
-
if proceed_input in ['y', 'yes']:
|
325
|
-
proceed = True
|
326
|
-
break
|
327
|
-
elif proceed_input in ['n', 'no']:
|
328
|
-
proceed = False
|
329
|
-
break
|
330
|
-
else:
|
331
|
-
print("Invalid input. Please enter 'Y' for yes or 'N' for no.")
|
332
|
-
|
333
|
-
# If the user agrees to proceed with all suggested endpoints, confirm them.
|
334
|
-
if proceed:
|
335
|
-
return MediLink_DataMgmt.confirm_all_suggested_endpoints(detailed_patient_data), crosswalk
|
336
|
-
# Otherwise, allow the user to adjust the endpoints manually.
|
337
|
-
else:
|
338
|
-
return select_and_adjust_files(detailed_patient_data, config, crosswalk)
|
339
|
-
|
340
|
-
def select_and_adjust_files(detailed_patient_data, config, crosswalk):
|
341
|
-
"""
|
342
|
-
Allows users to select patients and adjust their endpoints by interfacing with UI functions.
|
343
|
-
|
344
|
-
FIXED: Now properly updates suggested_endpoint and persists user preferences to crosswalk.
|
345
|
-
"""
|
346
|
-
# Display options for patients
|
347
|
-
MediLink_UI.display_patient_options(detailed_patient_data)
|
348
|
-
|
349
|
-
# Get user-selected indices for adjustment
|
350
|
-
selected_indices = MediLink_UI.get_selected_indices(len(detailed_patient_data))
|
351
|
-
|
352
|
-
# Get an ordered list of endpoint keys
|
353
|
-
endpoint_keys = list(config['MediLink_Config']['endpoints'].keys())
|
354
|
-
|
355
|
-
# Iterate over each selected index and process endpoint changes
|
356
|
-
for i in selected_indices:
|
357
|
-
data = detailed_patient_data[i]
|
358
|
-
current_effective_endpoint = get_effective_endpoint(data)
|
359
|
-
MediLink_UI.display_patient_for_adjustment(data['patient_name'], current_effective_endpoint)
|
360
|
-
|
361
|
-
endpoint_change = MediLink_UI.get_endpoint_decision()
|
362
|
-
if endpoint_change == 'y':
|
363
|
-
MediLink_UI.display_endpoint_options(config['MediLink_Config']['endpoints'])
|
364
|
-
endpoint_index = int(MediLink_UI.get_new_endpoint_choice()) - 1 # Adjusting for zero-based index
|
365
|
-
|
366
|
-
if 0 <= endpoint_index < len(endpoint_keys):
|
367
|
-
selected_endpoint_key = endpoint_keys[endpoint_index]
|
368
|
-
print("Endpoint changed to {0} for patient {1}.".format(config['MediLink_Config']['endpoints'][selected_endpoint_key]['name'], data['patient_name']))
|
369
|
-
|
370
|
-
# Use the new endpoint management system
|
371
|
-
updated_crosswalk = update_suggested_endpoint_with_user_preference(
|
372
|
-
detailed_patient_data, i, selected_endpoint_key, config, crosswalk
|
373
|
-
)
|
374
|
-
if updated_crosswalk:
|
375
|
-
crosswalk = updated_crosswalk
|
376
|
-
else:
|
377
|
-
print("Invalid selection. Keeping the current endpoint.")
|
378
|
-
data['confirmed_endpoint'] = current_effective_endpoint
|
379
|
-
else:
|
380
|
-
data['confirmed_endpoint'] = current_effective_endpoint
|
381
|
-
|
382
|
-
return detailed_patient_data, crosswalk
|
383
|
-
|
384
|
-
def update_suggested_endpoint_with_user_preference(detailed_patient_data, patient_index, new_endpoint, config, crosswalk):
|
385
|
-
"""
|
386
|
-
Updates the suggested endpoint for a patient and optionally updates the crosswalk
|
387
|
-
for future patients with the same insurance.
|
388
|
-
|
389
|
-
:param detailed_patient_data: List of patient data dictionaries
|
390
|
-
:param patient_index: Index of the patient being updated
|
391
|
-
:param new_endpoint: The new endpoint selected by the user
|
392
|
-
:param config: Configuration settings
|
393
|
-
:param crosswalk: Crosswalk data for in-memory updates
|
394
|
-
:return: Updated crosswalk if changes were made, None otherwise
|
395
|
-
"""
|
396
|
-
data = detailed_patient_data[patient_index]
|
397
|
-
original_suggested = data.get('suggested_endpoint')
|
398
|
-
|
399
|
-
# Update the patient's endpoint preference
|
400
|
-
data['user_preferred_endpoint'] = new_endpoint
|
401
|
-
data['confirmed_endpoint'] = new_endpoint
|
402
|
-
|
403
|
-
# If user changed from the original suggestion, offer to update crosswalk
|
404
|
-
if original_suggested != new_endpoint:
|
405
|
-
primary_insurance = data.get('primary_insurance')
|
406
|
-
patient_name = data.get('patient_name')
|
407
|
-
|
408
|
-
print("\nYou changed the endpoint for {} from {} to {}.".format(patient_name, original_suggested, new_endpoint))
|
409
|
-
update_future = input("Would you like to use {} as the default endpoint for future patients with {}? (Y/N): ".format(new_endpoint, primary_insurance)).strip().lower()
|
410
|
-
|
411
|
-
if update_future in ['y', 'yes']:
|
412
|
-
# Find the payer ID associated with this insurance
|
413
|
-
insurance_to_id = load_insurance_data_from_mains(config)
|
414
|
-
insurance_id = insurance_to_id.get(primary_insurance)
|
415
|
-
|
416
|
-
if insurance_id:
|
417
|
-
# Find the payer ID in crosswalk and update it
|
418
|
-
updated = False
|
419
|
-
for payer_id, payer_data in crosswalk.get('payer_id', {}).items():
|
420
|
-
medisoft_ids = [str(id) for id in payer_data.get('medisoft_id', [])]
|
421
|
-
if str(insurance_id) in medisoft_ids:
|
422
|
-
# Update the crosswalk in memory
|
423
|
-
crosswalk['payer_id'][payer_id]['endpoint'] = new_endpoint
|
424
|
-
MediLink_ConfigLoader.log("Updated crosswalk in memory: Payer ID {} ({}) now defaults to {}".format(payer_id, primary_insurance, new_endpoint), level="INFO")
|
425
|
-
|
426
|
-
# Update suggested_endpoint for other patients with same insurance in current batch
|
427
|
-
for other_data in detailed_patient_data:
|
428
|
-
if (other_data.get('primary_insurance') == primary_insurance and
|
429
|
-
'user_preferred_endpoint' not in other_data):
|
430
|
-
other_data['suggested_endpoint'] = new_endpoint
|
431
|
-
|
432
|
-
updated = True
|
433
|
-
break
|
434
|
-
|
435
|
-
if updated:
|
436
|
-
# Save the updated crosswalk to disk immediately using API bypass mode
|
437
|
-
if save_crosswalk_immediately(config, crosswalk):
|
438
|
-
print("Updated default endpoint for {} to {}".format(primary_insurance, new_endpoint))
|
439
|
-
else:
|
440
|
-
print("Updated endpoint preference (will be saved during next crosswalk update)")
|
441
|
-
return crosswalk
|
442
|
-
else:
|
443
|
-
MediLink_ConfigLoader.log("Could not find payer ID in crosswalk for insurance {}".format(primary_insurance), level="WARNING")
|
444
|
-
else:
|
445
|
-
MediLink_ConfigLoader.log("Could not find insurance ID for {} to update crosswalk".format(primary_insurance), level="WARNING")
|
446
|
-
|
447
|
-
return None
|
448
|
-
|
449
|
-
def save_crosswalk_immediately(config, crosswalk):
|
450
|
-
"""
|
451
|
-
Saves the crosswalk to disk immediately using API bypass mode.
|
452
|
-
|
453
|
-
:param config: Configuration settings
|
454
|
-
:param crosswalk: Crosswalk data to save
|
455
|
-
:return: True if saved successfully, False otherwise
|
456
|
-
"""
|
457
|
-
try:
|
458
|
-
# Import the crosswalk library
|
459
|
-
from MediBot import MediBot_Crosswalk_Library
|
460
|
-
|
461
|
-
# Save using API bypass mode (no client needed, skip API operations)
|
462
|
-
success = MediBot_Crosswalk_Library.save_crosswalk(None, config, crosswalk, skip_api_operations=True)
|
463
|
-
|
464
|
-
if success:
|
465
|
-
MediLink_ConfigLoader.log("Successfully saved crosswalk with updated endpoint preferences", level="INFO")
|
466
|
-
else:
|
467
|
-
MediLink_ConfigLoader.log("Failed to save crosswalk - preferences will be saved during next crosswalk update", level="WARNING")
|
468
|
-
|
469
|
-
return success
|
470
|
-
|
471
|
-
except ImportError:
|
472
|
-
MediLink_ConfigLoader.log("Could not import MediBot_Crosswalk_Library for saving crosswalk", level="ERROR")
|
473
|
-
return False
|
474
|
-
except Exception as e:
|
475
|
-
MediLink_ConfigLoader.log("Error saving crosswalk: {}".format(e), level="ERROR")
|
476
|
-
return False
|
477
|
-
|
478
|
-
def get_effective_endpoint(patient_data):
|
479
|
-
"""
|
480
|
-
Returns the most appropriate endpoint for a patient based on the hierarchy:
|
481
|
-
1. Confirmed endpoint (final decision)
|
482
|
-
2. User preferred endpoint (if user made a change)
|
483
|
-
3. Original suggested endpoint
|
484
|
-
4. Default (AVAILITY)
|
485
|
-
|
486
|
-
:param patient_data: Individual patient data dictionary
|
487
|
-
:return: The effective endpoint to use for this patient
|
488
|
-
"""
|
489
|
-
return (patient_data.get('confirmed_endpoint') or
|
490
|
-
patient_data.get('user_preferred_endpoint') or
|
491
|
-
patient_data.get('suggested_endpoint', 'AVAILITY'))
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
def main_menu():
|
496
|
-
"""
|
497
|
-
Initializes the main menu loop and handles the overall program flow,
|
498
|
-
including loading configurations and managing user input for menu selections.
|
499
|
-
"""
|
500
|
-
# Load configuration settings and display the initial welcome message.
|
501
|
-
config, crosswalk = MediLink_ConfigLoader.load_configuration()
|
502
|
-
|
503
|
-
# Check to make sure payer_id key is available in crosswalk, otherwise, go through that crosswalk initialization flow
|
504
|
-
if 'payer_id' not in crosswalk:
|
505
|
-
print("\n" + "="*60)
|
506
|
-
print("SETUP REQUIRED: Payer Information Database Missing")
|
507
|
-
print("="*60)
|
508
|
-
print("\nThe system needs to build a database of insurance company information")
|
509
|
-
print("before it can process claims. This is a one-time setup requirement.")
|
510
|
-
print("\nThis typically happens when:")
|
511
|
-
print("• You're running MediLink for the first time")
|
512
|
-
print("• The payer database was accidentally deleted or corrupted")
|
513
|
-
print("• You're using a new installation of the system")
|
514
|
-
print("\nTO FIX THIS:")
|
515
|
-
print("1. Open a command prompt/terminal")
|
516
|
-
print("2. Navigate to the MediCafe directory")
|
517
|
-
print("3. Run: python MediBot/MediBot_Preprocessor.py --update-crosswalk")
|
518
|
-
print("4. Wait for the process to complete (this may take a few minutes)")
|
519
|
-
print("5. Return here and restart MediLink")
|
520
|
-
print("\nThis will download and build the insurance company database.")
|
521
|
-
print("="*60)
|
522
|
-
print("\nPress Enter to exit...")
|
523
|
-
input()
|
524
|
-
return # Graceful exit instead of abrupt halt
|
525
|
-
|
526
|
-
# Check if the application is in test mode
|
527
|
-
if config.get("MediLink_Config", {}).get("TestMode", False):
|
528
|
-
print("\n--- MEDILINK TEST MODE --- \nTo enable full functionality, please update the config file \nand set 'TestMode' to 'false'.")
|
529
|
-
|
530
|
-
# Display Welcome Message
|
531
|
-
MediLink_UI.display_welcome()
|
532
|
-
|
533
|
-
# Normalize the directory path for file operations.
|
534
|
-
directory_path = os.path.normpath(config['MediLink_Config']['inputFilePath'])
|
535
|
-
|
536
|
-
# Detect files and determine if a new file is flagged.
|
537
|
-
all_files, file_flagged = MediLink_DataMgmt.detect_new_files(directory_path)
|
538
|
-
|
539
|
-
while True:
|
540
|
-
# Define the menu options. Base options include checking remittances and exiting the program.
|
541
|
-
options = ["Check for new remittances", "Exit"]
|
542
|
-
# If any files are detected, add the option to submit claims.
|
543
|
-
if all_files:
|
544
|
-
options.insert(1, "Submit claims")
|
545
|
-
|
546
|
-
# Display the dynamically adjusted menu options.
|
547
|
-
MediLink_UI.display_menu(options)
|
548
|
-
# Retrieve user choice and handle it.
|
549
|
-
choice = MediLink_UI.get_user_choice()
|
550
|
-
|
551
|
-
if choice == '1':
|
552
|
-
# Handle remittance checking.
|
553
|
-
check_for_new_remittances(config)
|
554
|
-
elif choice == '2' and all_files:
|
555
|
-
# Handle the claims submission flow if any files are present.
|
556
|
-
if file_flagged:
|
557
|
-
# Extract the newest single latest file from the list if a new file is flagged.
|
558
|
-
selected_files = [max(all_files, key=os.path.getctime)]
|
559
|
-
else:
|
560
|
-
# Prompt the user to select files if no new file is flagged.
|
561
|
-
selected_files = MediLink_UI.user_select_files(all_files)
|
562
|
-
|
563
|
-
# Collect detailed patient data for selected files.
|
564
|
-
detailed_patient_data = collect_detailed_patient_data(selected_files, config, crosswalk)
|
565
|
-
|
566
|
-
# Process the claims submission.
|
567
|
-
handle_submission(detailed_patient_data, config, crosswalk)
|
568
|
-
elif choice == '3' or (choice == '2' and not all_files):
|
569
|
-
# Exit the program if the user chooses to exit or if no files are present.
|
570
|
-
MediLink_UI.display_exit_message()
|
571
|
-
break
|
572
|
-
else:
|
573
|
-
# Display an error message if the user's choice does not match any valid option.
|
574
|
-
MediLink_UI.display_invalid_choice()
|
575
|
-
|
576
|
-
def handle_submission(detailed_patient_data, config, crosswalk):
|
577
|
-
"""
|
578
|
-
Handles the submission process for claims based on detailed patient data.
|
579
|
-
This function orchestrates the flow from user decision on endpoint suggestions to the actual submission of claims.
|
580
|
-
"""
|
581
|
-
insurance_edited = False # Flag to track if insurance types were edited
|
582
|
-
|
583
|
-
# Ask the user if they want to edit insurance types
|
584
|
-
edit_insurance = input("Do you want to edit insurance types? (y/n): ").strip().lower()
|
585
|
-
if edit_insurance in ['y', 'yes', '']:
|
586
|
-
insurance_edited = True # User chose to edit insurance types
|
587
|
-
while True:
|
588
|
-
# Bulk edit insurance types
|
589
|
-
MediLink_DataMgmt.bulk_edit_insurance_types(detailed_patient_data, insurance_options)
|
590
|
-
|
591
|
-
# Review and confirm changes
|
592
|
-
if MediLink_DataMgmt.review_and_confirm_changes(detailed_patient_data, insurance_options):
|
593
|
-
break # Exit the loop if changes are confirmed
|
594
|
-
else:
|
595
|
-
print("Returning to bulk edit insurance types.")
|
596
|
-
|
597
|
-
# Initiate user interaction to confirm or adjust suggested endpoints.
|
598
|
-
adjusted_data, updated_crosswalk = user_decision_on_suggestions(detailed_patient_data, config, insurance_edited, crosswalk)
|
599
|
-
|
600
|
-
# Update crosswalk reference if it was modified
|
601
|
-
if updated_crosswalk:
|
602
|
-
crosswalk = updated_crosswalk
|
603
|
-
|
604
|
-
# Confirm all remaining suggested endpoints.
|
605
|
-
confirmed_data = MediLink_DataMgmt.confirm_all_suggested_endpoints(adjusted_data)
|
606
|
-
if confirmed_data: # Proceed if there are confirmed data entries.
|
607
|
-
# Organize data by confirmed endpoints for submission.
|
608
|
-
organized_data = MediLink_DataMgmt.organize_patient_data_by_endpoint(confirmed_data)
|
609
|
-
# Confirm transmission with the user and check for internet connectivity.
|
610
|
-
if MediLink_Up.confirm_transmission(organized_data):
|
611
|
-
if MediLink_Up.check_internet_connection():
|
612
|
-
# Submit claims if internet connectivity is confirmed.
|
613
|
-
_ = MediLink_Up.submit_claims(organized_data, config, crosswalk)
|
614
|
-
# TODO submit_claims will have a receipt return in the future.
|
615
|
-
else:
|
616
|
-
# Notify the user of an internet connection error.
|
617
|
-
print("Internet connection error. Please ensure you're connected and try again.")
|
618
|
-
else:
|
619
|
-
# Notify the user if the submission is cancelled.
|
620
|
-
print("Submission cancelled. No changes were made.")
|
621
|
-
|
622
|
-
if __name__ == "__main__":
|
623
|
-
main_menu()
|