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,257 @@
|
|
|
1
|
+
# MediLink_PatientProcessor.py
|
|
2
|
+
# Patient data processing and endpoint determination functions
|
|
3
|
+
# Extracted from MediLink.py for better modularity and maintainability
|
|
4
|
+
|
|
5
|
+
import os, sys
|
|
6
|
+
|
|
7
|
+
# Use core utilities for standardized imports
|
|
8
|
+
from MediCafe.core_utils import get_shared_config_loader
|
|
9
|
+
MediLink_ConfigLoader = get_shared_config_loader()
|
|
10
|
+
|
|
11
|
+
import MediLink_DataMgmt
|
|
12
|
+
import MediLink_Display_Utils
|
|
13
|
+
|
|
14
|
+
# Add parent directory access for MediBot import
|
|
15
|
+
project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
16
|
+
if project_dir not in sys.path:
|
|
17
|
+
sys.path.append(project_dir)
|
|
18
|
+
|
|
19
|
+
# Use dynamic import to avoid circular dependencies
|
|
20
|
+
def _get_load_insurance_function():
|
|
21
|
+
"""Dynamically import load_insurance_data_from_mains to avoid circular imports."""
|
|
22
|
+
try:
|
|
23
|
+
import MediBot_Preprocessor_lib
|
|
24
|
+
return MediBot_Preprocessor_lib.load_insurance_data_from_mains
|
|
25
|
+
except (ImportError, AttributeError) as e:
|
|
26
|
+
MediLink_ConfigLoader.log("Failed to import load_insurance_data_from_mains: {}".format(e), level="WARNING")
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
load_insurance_data_from_mains = _get_load_insurance_function()
|
|
30
|
+
|
|
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
|
+
|
|
48
|
+
# Retrieve insurance options with codes and descriptions
|
|
49
|
+
insurance_options = config['MediLink_Config'].get('insurance_options')
|
|
50
|
+
|
|
51
|
+
for file_path in selected_files:
|
|
52
|
+
# IMPORTANT: extract_and_suggest_endpoint creates data with 'patient_id' field
|
|
53
|
+
# sourced from the 'CHART' field in fixed-width files
|
|
54
|
+
detailed_data = extract_and_suggest_endpoint(file_path, config, crosswalk)
|
|
55
|
+
detailed_patient_data.extend(detailed_data) # Accumulate detailed data for processing
|
|
56
|
+
|
|
57
|
+
# Enrich the detailed patient data with insurance type
|
|
58
|
+
# NOTE: This receives data from extract_and_suggest_endpoint with 'patient_id' field
|
|
59
|
+
detailed_patient_data = enrich_with_insurance_type(detailed_patient_data, insurance_options)
|
|
60
|
+
|
|
61
|
+
# Display summaries and provide an option for bulk edit
|
|
62
|
+
MediLink_Display_Utils.display_patient_summaries(detailed_patient_data)
|
|
63
|
+
|
|
64
|
+
return detailed_patient_data
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def enrich_with_insurance_type(detailed_patient_data, patient_insurance_type_mapping=None):
|
|
68
|
+
"""
|
|
69
|
+
Enriches the detailed patient data with insurance type based on patient ID.
|
|
70
|
+
Enhanced with optional API integration and comprehensive logging.
|
|
71
|
+
|
|
72
|
+
DATA FLOW CLARIFICATION:
|
|
73
|
+
This function receives data from collect_detailed_patient_data() -> extract_and_suggest_endpoint().
|
|
74
|
+
The patient ID field is 'patient_id' (NOT 'PATID').
|
|
75
|
+
|
|
76
|
+
IMPORTANT: Do not confuse with MediBot's parse_z_dat() flow which uses 'PATID'.
|
|
77
|
+
MediLink flow: fixed-width files -> extract_and_suggest_endpoint() -> 'patient_id' field (from 'CHART')
|
|
78
|
+
MediBot flow: Z.dat files -> parse_z_dat() -> 'PATID' field
|
|
79
|
+
|
|
80
|
+
Parameters:
|
|
81
|
+
- detailed_patient_data: List of dictionaries containing detailed patient data with 'patient_id' field.
|
|
82
|
+
- patient_insurance_mapping: Dictionary mapping patient IDs to their insurance types.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
- Enriched detailed patient data with insurance type added.
|
|
86
|
+
|
|
87
|
+
TODO: Implement a function to provide `patient_insurance_mapping` from a reliable source.
|
|
88
|
+
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.
|
|
89
|
+
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.
|
|
90
|
+
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
|
|
91
|
+
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
|
|
92
|
+
patient data that is already pulled from UHC so that the user can see which ones are already using the enriched data.
|
|
93
|
+
"""
|
|
94
|
+
# Enhanced mode check with graceful degradation
|
|
95
|
+
enhanced_mode = False
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
from MediLink_insurance_utils import (
|
|
99
|
+
get_feature_flag,
|
|
100
|
+
validate_insurance_type_from_config
|
|
101
|
+
)
|
|
102
|
+
enhanced_mode = get_feature_flag('enhanced_insurance_enrichment', default=False)
|
|
103
|
+
MediLink_ConfigLoader.log("Insurance enhancement utilities loaded successfully", level="DEBUG")
|
|
104
|
+
except ImportError as e:
|
|
105
|
+
MediLink_ConfigLoader.log("Insurance utils not available: {}. Using legacy mode.".format(str(e)), level="INFO")
|
|
106
|
+
enhanced_mode = False
|
|
107
|
+
except Exception as e:
|
|
108
|
+
MediLink_ConfigLoader.log("Error initializing insurance enhancements: {}. Using legacy mode.".format(str(e)), level="ERROR")
|
|
109
|
+
enhanced_mode = False
|
|
110
|
+
|
|
111
|
+
if patient_insurance_type_mapping is None:
|
|
112
|
+
MediLink_ConfigLoader.log("No Patient:Insurance-Type mapping available.", level="INFO")
|
|
113
|
+
patient_insurance_type_mapping = {}
|
|
114
|
+
|
|
115
|
+
# Enhanced mode with validation
|
|
116
|
+
if enhanced_mode:
|
|
117
|
+
MediLink_ConfigLoader.log("Using enhanced insurance type enrichment", level="INFO")
|
|
118
|
+
|
|
119
|
+
for data in detailed_patient_data:
|
|
120
|
+
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
|
121
|
+
# This field contains the value from the 'CHART' field in the original fixed-width file
|
|
122
|
+
patient_id = data.get('patient_id')
|
|
123
|
+
if patient_id:
|
|
124
|
+
raw_insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
|
125
|
+
validated_insurance_type = validate_insurance_type_from_config(raw_insurance_type, patient_id)
|
|
126
|
+
data['insurance_type'] = validated_insurance_type
|
|
127
|
+
data['insurance_type_source'] = 'MANUAL' if patient_id in patient_insurance_type_mapping else 'DEFAULT'
|
|
128
|
+
else:
|
|
129
|
+
# Handle case where patient_id is missing or empty
|
|
130
|
+
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
|
131
|
+
data['insurance_type'] = '12' # SBR09 default PPO
|
|
132
|
+
data['insurance_type_source'] = 'DEFAULT_FALLBACK'
|
|
133
|
+
|
|
134
|
+
else:
|
|
135
|
+
# Legacy mode (preserve existing behavior exactly)
|
|
136
|
+
MediLink_ConfigLoader.log("Using legacy insurance type enrichment", level="INFO")
|
|
137
|
+
for data in detailed_patient_data:
|
|
138
|
+
# FIELD NAME CLARIFICATION: Use 'patient_id' field created by extract_and_suggest_endpoint()
|
|
139
|
+
# This field contains the value from the 'CHART' field in the original fixed-width file
|
|
140
|
+
patient_id = data.get('patient_id')
|
|
141
|
+
if patient_id:
|
|
142
|
+
insurance_type = patient_insurance_type_mapping.get(patient_id, '12') # Default to '12' (PPO/SBR09)
|
|
143
|
+
else:
|
|
144
|
+
# Handle case where patient_id is missing or empty
|
|
145
|
+
MediLink_ConfigLoader.log("No patient_id found in data record", level="WARNING")
|
|
146
|
+
insurance_type = '12' # Default when no patient ID available
|
|
147
|
+
|
|
148
|
+
data['insurance_type'] = insurance_type
|
|
149
|
+
|
|
150
|
+
return detailed_patient_data
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def extract_and_suggest_endpoint(file_path, config, crosswalk):
|
|
154
|
+
"""
|
|
155
|
+
Reads a fixed-width file, extracts file details including surgery date, patient ID,
|
|
156
|
+
patient name, primary insurance, and other necessary details for each record. It suggests
|
|
157
|
+
an endpoint based on insurance provider information found in the crosswalk and prepares
|
|
158
|
+
detailed patient data for processing.
|
|
159
|
+
|
|
160
|
+
DATA FLOW CLARIFICATION:
|
|
161
|
+
This function is the PRIMARY data source for MediLink patient processing.
|
|
162
|
+
It creates the 'patient_id' field by extracting the 'CHART' field from fixed-width files.
|
|
163
|
+
|
|
164
|
+
IMPORTANT: This is DIFFERENT from MediBot's parse_z_dat() which extracts 'PATID'.
|
|
165
|
+
|
|
166
|
+
Field mapping for MediLink flow:
|
|
167
|
+
- Fixed-width file 'CHART' field -> detailed_data['patient_id']
|
|
168
|
+
- This 'patient_id' is then used by enrich_with_insurance_type()
|
|
169
|
+
|
|
170
|
+
Parameters:
|
|
171
|
+
- file_path: Path to the fixed-width file.
|
|
172
|
+
- crosswalk: Crosswalk dictionary loaded from a JSON file.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
- A comprehensive data structure retaining detailed patient claim details needed for processing,
|
|
176
|
+
including new key-value pairs for file path, surgery date, patient name, and primary insurance.
|
|
177
|
+
"""
|
|
178
|
+
detailed_patient_data = []
|
|
179
|
+
|
|
180
|
+
# Load insurance data from MAINS to create a mapping from insurance names to their respective IDs
|
|
181
|
+
if load_insurance_data_from_mains is None:
|
|
182
|
+
MediLink_ConfigLoader.log("load_insurance_data_from_mains function not available. Using empty insurance mapping.", level="WARNING")
|
|
183
|
+
insurance_to_id = {}
|
|
184
|
+
else:
|
|
185
|
+
insurance_to_id = load_insurance_data_from_mains(config)
|
|
186
|
+
MediLink_ConfigLoader.log("Insurance data loaded from MAINS. {} insurance providers found.".format(len(insurance_to_id)), level="INFO")
|
|
187
|
+
|
|
188
|
+
for personal_info, insurance_info, service_info, service_info_2, service_info_3 in MediLink_DataMgmt.read_fixed_width_data(file_path):
|
|
189
|
+
# Parse reserved 5-line record: 3 active lines + 2 reserved for future expansion
|
|
190
|
+
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))
|
|
191
|
+
|
|
192
|
+
primary_insurance = parsed_data.get('INAME')
|
|
193
|
+
|
|
194
|
+
# Retrieve the insurance ID associated with the primary insurance
|
|
195
|
+
insurance_id = insurance_to_id.get(primary_insurance)
|
|
196
|
+
MediLink_ConfigLoader.log("Primary insurance ID retrieved for '{}': {}".format(primary_insurance, insurance_id))
|
|
197
|
+
|
|
198
|
+
# Use insurance ID to retrieve the payer ID(s) associated with the insurance
|
|
199
|
+
payer_ids = []
|
|
200
|
+
if insurance_id:
|
|
201
|
+
for payer_id, payer_data in crosswalk.get('payer_id', {}).items():
|
|
202
|
+
medisoft_ids = [str(id) for id in payer_data.get('medisoft_id', [])]
|
|
203
|
+
# MediLink_ConfigLoader.log("Payer ID: {}, Medisoft IDs: {}".format(payer_id, medisoft_ids))
|
|
204
|
+
if str(insurance_id) in medisoft_ids:
|
|
205
|
+
payer_ids.append(payer_id)
|
|
206
|
+
if payer_ids:
|
|
207
|
+
MediLink_ConfigLoader.log("Payer IDs retrieved for insurance '{}': {}".format(primary_insurance, payer_ids))
|
|
208
|
+
else:
|
|
209
|
+
MediLink_ConfigLoader.log("No payer IDs found for insurance '{}'".format(primary_insurance))
|
|
210
|
+
|
|
211
|
+
# Find the suggested endpoint from the crosswalk based on the payer IDs
|
|
212
|
+
suggested_endpoint = 'AVAILITY' # Default endpoint if no matching payer IDs found
|
|
213
|
+
if payer_ids:
|
|
214
|
+
payer_id = payer_ids[0] # Select the first payer ID
|
|
215
|
+
suggested_endpoint = crosswalk['payer_id'].get(payer_id, {}).get('endpoint', 'AVAILITY')
|
|
216
|
+
MediLink_ConfigLoader.log("Suggested endpoint for payer ID '{}': {}".format(payer_id, suggested_endpoint))
|
|
217
|
+
|
|
218
|
+
# Validate suggested endpoint against the config
|
|
219
|
+
if suggested_endpoint not in config['MediLink_Config'].get('endpoints', {}):
|
|
220
|
+
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")
|
|
221
|
+
raise ValueError("Invalid suggested endpoint: '{}' for payer ID '{}'. Please correct the configuration.".format(suggested_endpoint, payer_id))
|
|
222
|
+
else:
|
|
223
|
+
MediLink_ConfigLoader.log("No suggested endpoint found for payer IDs: {}".format(payer_ids))
|
|
224
|
+
|
|
225
|
+
# Enrich detailed patient data with additional information and suggested endpoint
|
|
226
|
+
detailed_data = parsed_data.copy() # Copy parsed_data to avoid modifying the original dictionary
|
|
227
|
+
detailed_data.update({
|
|
228
|
+
'file_path': file_path,
|
|
229
|
+
# CRITICAL FIELD MAPPING: 'CHART' field from fixed-width file becomes 'patient_id'
|
|
230
|
+
# This is the field that enrich_with_insurance_type() will use
|
|
231
|
+
'patient_id': parsed_data.get('CHART'), # <- This is the key field mapping for MediLink flow
|
|
232
|
+
'surgery_date': parsed_data.get('DATE'),
|
|
233
|
+
'patient_name': ' '.join([parsed_data.get(key, '') for key in ['FIRST', 'MIDDLE', 'LAST']]),
|
|
234
|
+
'amount': parsed_data.get('AMOUNT'),
|
|
235
|
+
'primary_insurance': primary_insurance,
|
|
236
|
+
'suggested_endpoint': suggested_endpoint
|
|
237
|
+
})
|
|
238
|
+
detailed_patient_data.append(detailed_data)
|
|
239
|
+
|
|
240
|
+
# Return only the enriched detailed patient data, eliminating the need for a separate summary list
|
|
241
|
+
return detailed_patient_data
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def get_effective_endpoint(patient_data):
|
|
245
|
+
"""
|
|
246
|
+
Returns the most appropriate endpoint for a patient based on the hierarchy:
|
|
247
|
+
1. Confirmed endpoint (final decision)
|
|
248
|
+
2. User preferred endpoint (if user made a change)
|
|
249
|
+
3. Original suggested endpoint
|
|
250
|
+
4. Default (AVAILITY)
|
|
251
|
+
|
|
252
|
+
:param patient_data: Individual patient data dictionary
|
|
253
|
+
:return: The effective endpoint to use for this patient
|
|
254
|
+
"""
|
|
255
|
+
return (patient_data.get('confirmed_endpoint') or
|
|
256
|
+
patient_data.get('user_preferred_endpoint') or
|
|
257
|
+
patient_data.get('suggested_endpoint', 'AVAILITY'))
|
MediLink/MediLink_UI.py
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
import os, sys
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
# Set up paths using core utilities
|
|
6
|
+
from MediCafe.core_utils import setup_module_paths
|
|
7
|
+
setup_module_paths(__file__)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
from MediCafe.core_utils import get_shared_config_loader
|
|
10
|
+
MediLink_ConfigLoader = get_shared_config_loader()
|
|
11
|
+
import MediLink_DataMgmt
|
|
12
|
+
import MediLink_PatientProcessor
|
|
13
|
+
import MediLink_Display_Utils
|
|
13
14
|
|
|
14
15
|
def display_welcome():
|
|
15
16
|
print("\n" + "-" * 60)
|
|
@@ -100,69 +101,7 @@ def get_new_endpoint_choice():
|
|
|
100
101
|
print("Invalid input. I understood your request as: '{}'. Please enter valid endpoint numbers separated by commas.".format(choice))
|
|
101
102
|
print("Tip: Ensure there are no extra spaces or periods in your input.")
|
|
102
103
|
|
|
103
|
-
#
|
|
104
|
-
def display_insurance_options(insurance_options=None):
|
|
105
|
-
"""Display insurance options, loading from config if not provided"""
|
|
106
|
-
|
|
107
|
-
if insurance_options is None:
|
|
108
|
-
config, _ = MediLink_ConfigLoader.load_configuration()
|
|
109
|
-
insurance_options = config.get('MediLink_Config', {}).get('insurance_options', {})
|
|
110
|
-
|
|
111
|
-
print("\nInsurance Type Options (SBR09 Codes):")
|
|
112
|
-
print("-" * 50)
|
|
113
|
-
for code, description in sorted(insurance_options.items()):
|
|
114
|
-
print("{:>3}: {}".format(code, description))
|
|
115
|
-
print("-" * 50)
|
|
116
|
-
print("Note: '12' (PPO) is the default if no selection is made.")
|
|
117
|
-
print() # Add a blank line for better readability
|
|
118
|
-
|
|
119
|
-
def display_patient_summaries(detailed_patient_data):
|
|
120
|
-
"""
|
|
121
|
-
Displays summaries of all patients and their suggested endpoints.
|
|
122
|
-
"""
|
|
123
|
-
print("\nSummary of patient details and suggested endpoint:")
|
|
124
|
-
for index, summary in enumerate(detailed_patient_data, start=1):
|
|
125
|
-
try:
|
|
126
|
-
display_file_summary(index, summary)
|
|
127
|
-
except KeyError as e:
|
|
128
|
-
print("Summary at index {} is missing key: {}".format(index, e))
|
|
129
|
-
print() # add blank line for improved readability.
|
|
130
|
-
|
|
131
|
-
def display_file_summary(index, summary):
|
|
132
|
-
# Ensure surgery_date is converted to a datetime object
|
|
133
|
-
surgery_date = datetime.strptime(summary['surgery_date'], "%m-%d-%y")
|
|
134
|
-
|
|
135
|
-
# Add header row if it's the first index
|
|
136
|
-
if index == 1:
|
|
137
|
-
print("{:<3} {:5} {:<10} {:20} {:15} {:3} {:20}".format(
|
|
138
|
-
"No.", "Date", "ID", "Name", "Primary Ins.", "IT", "Current Endpoint"
|
|
139
|
-
))
|
|
140
|
-
print("-"*82)
|
|
141
|
-
|
|
142
|
-
# Check if insurance_type is available; if not, set a default placeholder (this should already be '12' at this point)
|
|
143
|
-
insurance_type = summary.get('insurance_type', '--')
|
|
144
|
-
|
|
145
|
-
# Get the effective endpoint (confirmed > user preference > suggestion > default)
|
|
146
|
-
effective_endpoint = (summary.get('confirmed_endpoint') or
|
|
147
|
-
summary.get('user_preferred_endpoint') or
|
|
148
|
-
summary.get('suggested_endpoint', 'AVAILITY'))
|
|
149
|
-
|
|
150
|
-
# Format insurance type for display - handle both 2 and 3 character codes
|
|
151
|
-
if insurance_type and len(insurance_type) <= 3:
|
|
152
|
-
insurance_display = insurance_type
|
|
153
|
-
else:
|
|
154
|
-
insurance_display = insurance_type[:3] if insurance_type else '--'
|
|
155
|
-
|
|
156
|
-
# Displays the summary of a file.
|
|
157
|
-
print("{:02d}. {:5} ({:<8}) {:20} {:15} {:3} {:20}".format(
|
|
158
|
-
index,
|
|
159
|
-
surgery_date.strftime("%m-%d"),
|
|
160
|
-
summary['patient_id'],
|
|
161
|
-
summary['patient_name'][:20],
|
|
162
|
-
summary['primary_insurance'][:15],
|
|
163
|
-
insurance_display,
|
|
164
|
-
effective_endpoint[:20])
|
|
165
|
-
)
|
|
104
|
+
# Display functions moved to MediLink_Display_Utils.py to eliminate circular dependencies
|
|
166
105
|
|
|
167
106
|
def user_select_files(file_list):
|
|
168
107
|
# Sort files by creation time in descending order
|
|
@@ -196,4 +135,79 @@ def user_select_files(file_list):
|
|
|
196
135
|
selected_indices = [int(i.strip()) - 1 for i in selected_indices.split(',')]
|
|
197
136
|
selected_files = [formatted_files[i][1] for i in selected_indices]
|
|
198
137
|
|
|
199
|
-
return selected_files
|
|
138
|
+
return selected_files
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def user_decision_on_suggestions(detailed_patient_data, config, insurance_edited, crosswalk):
|
|
142
|
+
"""
|
|
143
|
+
Presents the user with all patient summaries and suggested endpoints,
|
|
144
|
+
then asks for confirmation to proceed with all or specify adjustments manually.
|
|
145
|
+
|
|
146
|
+
FIXED: Display now properly shows effective endpoints (user preferences over original suggestions)
|
|
147
|
+
"""
|
|
148
|
+
if insurance_edited:
|
|
149
|
+
# Display summaries only if insurance types were edited
|
|
150
|
+
MediLink_Display_Utils.display_patient_summaries(detailed_patient_data)
|
|
151
|
+
|
|
152
|
+
while True:
|
|
153
|
+
proceed_input = input("Do you want to proceed with all suggested endpoints? (Y/N): ").strip().lower()
|
|
154
|
+
if proceed_input in ['y', 'yes']:
|
|
155
|
+
proceed = True
|
|
156
|
+
break
|
|
157
|
+
elif proceed_input in ['n', 'no']:
|
|
158
|
+
proceed = False
|
|
159
|
+
break
|
|
160
|
+
else:
|
|
161
|
+
print("Invalid input. Please enter 'Y' for yes or 'N' for no.")
|
|
162
|
+
|
|
163
|
+
# If the user agrees to proceed with all suggested endpoints, confirm them.
|
|
164
|
+
if proceed:
|
|
165
|
+
return MediLink_DataMgmt.confirm_all_suggested_endpoints(detailed_patient_data), crosswalk
|
|
166
|
+
# Otherwise, allow the user to adjust the endpoints manually.
|
|
167
|
+
else:
|
|
168
|
+
return select_and_adjust_files(detailed_patient_data, config, crosswalk)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def select_and_adjust_files(detailed_patient_data, config, crosswalk):
|
|
172
|
+
"""
|
|
173
|
+
Allows users to select patients and adjust their endpoints by interfacing with UI functions.
|
|
174
|
+
|
|
175
|
+
FIXED: Now properly updates suggested_endpoint and persists user preferences to crosswalk.
|
|
176
|
+
"""
|
|
177
|
+
# Display options for patients
|
|
178
|
+
display_patient_options(detailed_patient_data)
|
|
179
|
+
|
|
180
|
+
# Get user-selected indices for adjustment
|
|
181
|
+
selected_indices = get_selected_indices(len(detailed_patient_data))
|
|
182
|
+
|
|
183
|
+
# Get an ordered list of endpoint keys
|
|
184
|
+
endpoint_keys = list(config['MediLink_Config']['endpoints'].keys())
|
|
185
|
+
|
|
186
|
+
# Iterate over each selected index and process endpoint changes
|
|
187
|
+
for i in selected_indices:
|
|
188
|
+
data = detailed_patient_data[i]
|
|
189
|
+
current_effective_endpoint = MediLink_PatientProcessor.get_effective_endpoint(data)
|
|
190
|
+
display_patient_for_adjustment(data['patient_name'], current_effective_endpoint)
|
|
191
|
+
|
|
192
|
+
endpoint_change = get_endpoint_decision()
|
|
193
|
+
if endpoint_change == 'y':
|
|
194
|
+
display_endpoint_options(config['MediLink_Config']['endpoints'])
|
|
195
|
+
endpoint_index = int(get_new_endpoint_choice()) - 1 # Adjusting for zero-based index
|
|
196
|
+
|
|
197
|
+
if 0 <= endpoint_index < len(endpoint_keys):
|
|
198
|
+
selected_endpoint_key = endpoint_keys[endpoint_index]
|
|
199
|
+
print("Endpoint changed to {0} for patient {1}.".format(config['MediLink_Config']['endpoints'][selected_endpoint_key]['name'], data['patient_name']))
|
|
200
|
+
|
|
201
|
+
# Use the new endpoint management system
|
|
202
|
+
updated_crosswalk = MediLink_DataMgmt.update_suggested_endpoint_with_user_preference(
|
|
203
|
+
detailed_patient_data, i, selected_endpoint_key, config, crosswalk
|
|
204
|
+
)
|
|
205
|
+
if updated_crosswalk:
|
|
206
|
+
crosswalk = updated_crosswalk
|
|
207
|
+
else:
|
|
208
|
+
print("Invalid selection. Keeping the current endpoint.")
|
|
209
|
+
data['confirmed_endpoint'] = current_effective_endpoint
|
|
210
|
+
else:
|
|
211
|
+
data['confirmed_endpoint'] = current_effective_endpoint
|
|
212
|
+
|
|
213
|
+
return detailed_patient_data, crosswalk
|
MediLink/MediLink_Up.py
CHANGED
|
@@ -8,9 +8,19 @@ except ImportError:
|
|
|
8
8
|
def tqdm(iterable, **kwargs):
|
|
9
9
|
return iterable
|
|
10
10
|
import MediLink_837p_encoder
|
|
11
|
-
from MediLink_ConfigLoader import log, load_configuration
|
|
12
11
|
from MediLink_DataMgmt import operate_winscp
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
# Use core utilities for standardized imports
|
|
14
|
+
from MediCafe.core_utils import get_shared_config_loader, get_api_client
|
|
15
|
+
MediLink_ConfigLoader = get_shared_config_loader()
|
|
16
|
+
log = MediLink_ConfigLoader.log
|
|
17
|
+
load_configuration = MediLink_ConfigLoader.load_configuration
|
|
18
|
+
|
|
19
|
+
# Import api_core for claim submission
|
|
20
|
+
try:
|
|
21
|
+
from MediCafe import api_core
|
|
22
|
+
except ImportError:
|
|
23
|
+
api_core = None
|
|
14
24
|
|
|
15
25
|
# Pre-compile regex patterns for better performance
|
|
16
26
|
GS_PATTERN = re.compile(r'GS\*HC\*[^*]*\*[^*]*\*([0-9]{8})\*([0-9]{4})')
|
|
@@ -75,7 +85,16 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
|
|
|
75
85
|
# Attempt submission to each endpoint
|
|
76
86
|
if True: #confirm_transmission({endpoint: patients_data}): # Confirm transmission to each endpoint with detailed overview
|
|
77
87
|
if check_internet_connection():
|
|
78
|
-
client =
|
|
88
|
+
client = get_api_client()
|
|
89
|
+
if client is None:
|
|
90
|
+
print("Warning: API client not available via factory")
|
|
91
|
+
# Fallback to direct instantiation
|
|
92
|
+
try:
|
|
93
|
+
from MediCafe import api_core
|
|
94
|
+
client = api_core.APIClient()
|
|
95
|
+
except ImportError:
|
|
96
|
+
print("Error: Unable to create API client")
|
|
97
|
+
continue
|
|
79
98
|
# Process files per endpoint
|
|
80
99
|
converted_files = MediLink_837p_encoder.convert_files_for_submission(patients_data, config, crosswalk, client)
|
|
81
100
|
if converted_files:
|
|
@@ -103,7 +122,12 @@ def submit_claims(detailed_patient_data_grouped_by_endpoint, config, crosswalk):
|
|
|
103
122
|
with open(file_path, 'r') as file:
|
|
104
123
|
# Optimize string operations by doing replacements in one pass
|
|
105
124
|
x12_request_data = file.read().replace('\n', '').replace('\r', '').strip()
|
|
106
|
-
|
|
125
|
+
try:
|
|
126
|
+
from MediCafe import api_core
|
|
127
|
+
response = api_core.submit_uhc_claim(client, x12_request_data)
|
|
128
|
+
except ImportError:
|
|
129
|
+
print("Error: Unable to import api_core for claim submission")
|
|
130
|
+
response = {"error": "API module not available"}
|
|
107
131
|
api_responses.append((file_path, response))
|
|
108
132
|
success_dict = handle_transmission_result(api_responses, config, "api", method)
|
|
109
133
|
submission_results[endpoint] = success_dict
|