medicafe 0.240415.1__py3-none-any.whl → 0.240517.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of medicafe might be problematic. Click here for more details.

Files changed (42) hide show
  1. MediBot/MediBot.bat +198 -0
  2. MediBot/MediBot.py +346 -0
  3. MediBot/MediBot_Charges.py +28 -0
  4. MediBot/MediBot_Crosswalk_Library.py +280 -0
  5. MediBot/MediBot_Preprocessor.py +247 -0
  6. MediBot/MediBot_Preprocessor_lib.py +357 -0
  7. MediBot/MediBot_UI.py +240 -0
  8. MediBot/MediBot_dataformat_library.py +198 -0
  9. MediBot/MediBot_docx_decoder.py +80 -0
  10. MediBot/MediPost.py +5 -0
  11. MediBot/PDF_to_CSV_Cleaner.py +211 -0
  12. MediBot/__init__.py +0 -0
  13. MediBot/update_json.py +43 -0
  14. MediBot/update_medicafe.py +57 -0
  15. MediLink/MediLink.py +381 -0
  16. MediLink/MediLink_277_decoder.py +92 -0
  17. MediLink/MediLink_837p_encoder.py +502 -0
  18. MediLink/MediLink_837p_encoder_library.py +890 -0
  19. MediLink/MediLink_API_v2.py +174 -0
  20. MediLink/MediLink_APIs.py +137 -0
  21. MediLink/MediLink_ConfigLoader.py +81 -0
  22. MediLink/MediLink_DataMgmt.py +258 -0
  23. MediLink/MediLink_Down.py +128 -0
  24. MediLink/MediLink_ERA_decoder.py +192 -0
  25. MediLink/MediLink_Gmail.py +100 -0
  26. MediLink/MediLink_Mailer.py +7 -0
  27. MediLink/MediLink_Scheduler.py +173 -0
  28. MediLink/MediLink_StatusCheck.py +4 -0
  29. MediLink/MediLink_UI.py +118 -0
  30. MediLink/MediLink_Up.py +383 -0
  31. MediLink/MediLink_batch.bat +7 -0
  32. MediLink/Soumit_api.py +22 -0
  33. MediLink/__init__.py +0 -0
  34. MediLink/test.py +74 -0
  35. medicafe-0.240517.0.dist-info/METADATA +53 -0
  36. medicafe-0.240517.0.dist-info/RECORD +39 -0
  37. {medicafe-0.240415.1.dist-info → medicafe-0.240517.0.dist-info}/WHEEL +1 -1
  38. medicafe-0.240517.0.dist-info/top_level.txt +2 -0
  39. medicafe-0.240415.1.dist-info/METADATA +0 -17
  40. medicafe-0.240415.1.dist-info/RECORD +0 -5
  41. medicafe-0.240415.1.dist-info/top_level.txt +0 -1
  42. {medicafe-0.240415.1.dist-info → medicafe-0.240517.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,357 @@
1
+ from collections import OrderedDict, defaultdict
2
+ from datetime import datetime
3
+ import os
4
+ import csv
5
+ import sys
6
+
7
+ # Add parent directory of the project to the Python path
8
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
9
+ sys.path.append(project_dir)
10
+
11
+ try:
12
+ import MediLink_ConfigLoader
13
+ import MediLink_DataMgmt
14
+ except ImportError:
15
+ from MediLink import MediLink_ConfigLoader
16
+ from MediLink import MediLink_DataMgmt
17
+
18
+ try:
19
+ from MediBot_UI import app_control
20
+ except ImportError:
21
+ from MediBot import MediBot_UI
22
+ app_control = MediBot_UI.app_control
23
+
24
+ """
25
+ Draft Docstring to move over from Preprocessor.
26
+
27
+ Data Integrity and Validation
28
+ Implement a mechanism to confirm the accuracy of entered data, potentially through a verification step or summary report.
29
+ Enhance CSV integrity checks to identify and report potential issues with data format, especially concerning insurance policy numbers and special character handling.
30
+
31
+ Development Roadmap for crosswalk_update()
32
+ Automation required for updating the crosswalk.json when new Medisoft insurance is discovered.
33
+ For open_csv_for_editing
34
+
35
+ Known Issues and Bugs
36
+ Address the handling of '.' and other special characters that may disrupt parsing, especially under Windows XP.
37
+
38
+ For load_csv_data
39
+
40
+ Preprocessing Enhancements
41
+ Optimize script startup and CSV loading to reduce initial latency.
42
+
43
+ Data Integrity and Validation
44
+ Conduct a thorough CSV integrity check before processing to flag potential issues upfront.
45
+
46
+ Future Work
47
+ Consolidate data from multiple sources (Provider_Notes.csv, Surgery_Schedule.csv, and Carols_CSV.csv) into a single table with Patient ID as the key, ensuring all data elements are aligned and duplicate entries are minimized.
48
+ Implement logic to verify and match Patient IDs across different files to ensure data integrity before consolidation. (Catching errors between source data)
49
+ Optimize the preprocessing of surgery dates and diagnosis codes for use in patient billing and scheduling systems.
50
+ """
51
+ class InitializationError(Exception):
52
+ def __init__(self, message):
53
+ self.message = message
54
+ super().__init__(self.message)
55
+
56
+ def initialize(config):
57
+ global AHK_EXECUTABLE, CSV_FILE_PATH, field_mapping, page_end_markers
58
+
59
+ try:
60
+ AHK_EXECUTABLE = config.get('AHK_EXECUTABLE', "")
61
+ except AttributeError:
62
+ raise InitializationError("Error: 'AHK_EXECUTABLE' not found in config.")
63
+
64
+ try:
65
+ CSV_FILE_PATH = config.get('CSV_FILE_PATH', "")
66
+ except AttributeError:
67
+ raise InitializationError("Error: 'CSV_FILE_PATH' not found in config.")
68
+
69
+ try:
70
+ field_mapping = OrderedDict(config.get('field_mapping', {}))
71
+ except AttributeError:
72
+ raise InitializationError("Error: 'field_mapping' not found in config.")
73
+
74
+ try:
75
+ page_end_markers = config.get('page_end_markers', [])
76
+ except AttributeError:
77
+ raise InitializationError("Error: 'page_end_markers' not found in config.")
78
+
79
+
80
+ def open_csv_for_editing(csv_file_path):
81
+ try:
82
+ # Open the CSV file with its associated application
83
+ os.system('start "" "{}"'.format(csv_file_path))
84
+ print("After saving the revised CSV, please re-run MediBot.")
85
+ except Exception as e:
86
+ print("Failed to open CSV file:", e)
87
+
88
+ # Function to load and process CSV data
89
+ def load_csv_data(csv_file_path):
90
+ try:
91
+ # Check if the file exists
92
+ if not os.path.exists(csv_file_path):
93
+ raise FileNotFoundError("***Error: CSV file '{}' not found.".format(csv_file_path))
94
+
95
+ with open(csv_file_path, 'r') as csvfile:
96
+ reader = csv.DictReader(csvfile)
97
+ return [row for row in reader] # Return a list of dictionaries
98
+ except FileNotFoundError as e:
99
+ print(e) # Print the informative error message
100
+ print("Hint: Check if CSV file is located in the expected directory or specify a different path in config file.")
101
+ print("Please correct the issue and re-run MediBot.")
102
+ sys.exit(1) # Halt the script
103
+ except IOError as e:
104
+ print("Error reading CSV file: {}. Please check the file path and permissions.".format(e))
105
+ sys.exit(1) # Halt the script in case of other IO errors
106
+
107
+ # CSV Pre-processor Helper functions
108
+ def add_insurance_id_column(csv_data):
109
+ for row in csv_data:
110
+ row['Ins1 Insurance ID'] = '' # Initialize the column with empty values
111
+
112
+ def filter_rows(csv_data):
113
+ csv_data[:] = [row for row in csv_data if row.get('Patient ID', '').strip()]
114
+ csv_data[:] = [row for row in csv_data if row.get('Primary Insurance', '').strip() not in ['AETNA', 'AETNA MEDICARE', 'HUMANA MED HMO']]
115
+
116
+ def convert_surgery_date(csv_data):
117
+ for row in csv_data:
118
+ try:
119
+ row['Surgery Date'] = datetime.strptime(row.get('Surgery Date', ''), '%m/%d/%Y')
120
+ except ValueError:
121
+ row['Surgery Date'] = datetime.min # Assign a minimum datetime value for sorting purposes
122
+
123
+ def sort_and_deduplicate(csv_data):
124
+ csv_data.sort(key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip()))
125
+ unique_patients = {}
126
+ for row in csv_data:
127
+ patient_id = row.get('Patient ID')
128
+ if patient_id not in unique_patients or row['Surgery Date'] < unique_patients[patient_id]['Surgery Date']:
129
+ unique_patients[patient_id] = row
130
+ csv_data[:] = list(unique_patients.values())
131
+ csv_data.sort(key=lambda x: (x['Surgery Date'], x.get('Patient Last', '').strip()))
132
+
133
+ def combine_fields(csv_data):
134
+ for row in csv_data:
135
+ row['Surgery Date'] = row['Surgery Date'].strftime('%m/%d/%Y')
136
+ first_name = row.get('Patient First', '').strip()
137
+ middle_name = row.get('Patient Middle', '').strip()
138
+ if len(middle_name) > 1:
139
+ middle_name = middle_name[0] # Take only the first character
140
+ last_name = row.get('Patient Last', '').strip()
141
+ row['Patient Name'] = "{}, {} {}".format(last_name, first_name, middle_name).strip()
142
+ address1 = row.get('Patient Address1', '').strip()
143
+ address2 = row.get('Patient Address2', '').strip()
144
+ row['Patient Street'] = "{} {}".format(address1, address2).strip()
145
+
146
+ def apply_replacements(csv_data, crosswalk):
147
+ replacements = crosswalk.get('csv_replacements', {})
148
+ for row in csv_data:
149
+ for old_value, new_value in replacements.items():
150
+ if row.get('Patient SSN', '') == old_value:
151
+ row['Patient SSN'] = new_value
152
+ elif row.get('Primary Insurance', '') == old_value:
153
+ row['Primary Insurance'] = new_value
154
+ elif row.get('Ins1 Payer ID') == old_value:
155
+ row['Ins1 Payer ID'] = new_value
156
+
157
+ def update_insurance_ids(csv_data, crosswalk):
158
+ for row in csv_data:
159
+ ins1_payer_id = row.get('Ins1 Payer ID', '').strip()
160
+ # MediLink_ConfigLoader.log("Ins1 Payer ID '{}' associated with Patient ID {}.".format(ins1_payer_id, row.get('Patient ID', "None")))
161
+ if ins1_payer_id:
162
+ if ins1_payer_id in crosswalk.get('payer_id', {}):
163
+ medisoft_ids = crosswalk['payer_id'][ins1_payer_id].get('medisoft_id', [])
164
+ if medisoft_ids:
165
+ medisoft_ids = [int(id) for id in medisoft_ids]
166
+ # TODO Try to match OpenPM's Insurance Name to get a better match
167
+ row['Ins1 Insurance ID'] = medisoft_ids[0]
168
+ # MediLink_ConfigLoader.log("Ins1 Insurance ID '{}' used for Payer ID {} in crosswalk.".format(row.get('Ins1 Insurance ID', ''), ins1_payer_id))
169
+ else:
170
+ MediLink_ConfigLoader.log("Ins1 Payer ID '{}' not found in the crosswalk.".format(ins1_payer_id))
171
+ # Create a placeholder entry in the crosswalk, need to consider the medisoft_medicare_id handling later.
172
+ if 'payer_id' not in crosswalk:
173
+ crosswalk['payer_id'] = {}
174
+ crosswalk['payer_id'][ins1_payer_id] = {
175
+ 'medisoft_id': [],
176
+ 'medisoft_medicare_id': [],
177
+ 'endpoint': 'OPTUMEDI' # Default probably should be a flag for the crosswalk update function to deal with. BUG HARDCODE THERE ARE 3 of these defaults
178
+ }
179
+
180
+ def load_data_sources(config, crosswalk):
181
+ """Loads historical mappings from MAPAT and Carol's CSVs."""
182
+ patient_id_to_insurance_id = load_insurance_data_from_mapat(config, crosswalk)
183
+ if not patient_id_to_insurance_id:
184
+ raise ValueError("Failed to load historical Patient ID to Insurance ID mappings from MAPAT.")
185
+
186
+ payer_id_to_patient_ids = load_historical_payer_to_patient_mappings(config)
187
+ if not payer_id_to_patient_ids:
188
+ raise ValueError("Failed to load historical Carol's CSVs.")
189
+
190
+ return patient_id_to_insurance_id, payer_id_to_patient_ids
191
+
192
+ def map_payer_ids_to_insurance_ids(patient_id_to_insurance_id, payer_id_to_patient_ids):
193
+ """Maps Payer IDs to Insurance IDs based on the historical mappings."""
194
+ payer_id_to_details = {}
195
+ for payer_id, patient_ids in payer_id_to_patient_ids.items():
196
+ medisoft_ids = set()
197
+ for patient_id in patient_ids:
198
+ if patient_id in patient_id_to_insurance_id:
199
+ medisoft_id = patient_id_to_insurance_id[patient_id]
200
+ medisoft_ids.add(medisoft_id)
201
+ MediLink_ConfigLoader.log("Added Medisoft ID {} for Patient ID {} and Payer ID {}".format(medisoft_id, patient_id, payer_id))
202
+ else:
203
+ MediLink_ConfigLoader.log("No matching Insurance ID found for Patient ID {}".format(patient_id))
204
+ if medisoft_ids:
205
+ payer_id_to_details[payer_id] = {
206
+ "endpoint": "OPTUMEDI", # TODO Default, to be refined via API poll. There are 2 of these defaults!
207
+ "medisoft_id": list(medisoft_ids),
208
+ "medisoft_medicare_id": [] # Placeholder for future implementation
209
+ }
210
+ return payer_id_to_details
211
+
212
+ def load_insurance_data_from_mains(config):
213
+ """
214
+ Loads insurance data from MAINS and creates a mapping from insurance names to their respective IDs.
215
+ This mapping is critical for the crosswalk update process to correctly associate payer IDs with insurance IDs.
216
+
217
+ Args:
218
+ config (dict): Configuration object containing necessary paths and parameters.
219
+
220
+ Returns:
221
+ dict: A dictionary mapping insurance names to insurance IDs.
222
+ """
223
+ # Reset config pull to make sure its not using the MediLink config key subset
224
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
225
+
226
+ # Retrieve MAINS path and slicing information from the configuration
227
+ # TODO (Low) For secondary insurance, this needs to be pulling from the correct MAINS (there are 2)
228
+ # TODO (Low) Performance: There probably needs to be a dictionary proxy for MAINS that gets updated.
229
+ mains_path = config['MAINS_MED_PATH']
230
+ mains_slices = crosswalk['mains_mapping']['slices']
231
+
232
+ # Initialize the dictionary to hold the insurance to insurance ID mappings
233
+ insurance_to_id = {}
234
+
235
+ # Read data from MAINS using a provided function to handle fixed-width data
236
+ for record, line_number in MediLink_DataMgmt.read_general_fixed_width_data(mains_path, mains_slices):
237
+ insurance_name = record['MAINSNAME']
238
+ # Assuming line_number gives the correct insurance ID without needing adjustment
239
+ insurance_to_id[insurance_name] = line_number
240
+
241
+ return insurance_to_id
242
+
243
+ def load_insurance_data_from_mapat(config, crosswalk):
244
+ """
245
+ Loads insurance data from MAPAT and creates a mapping from patient ID to insurance ID.
246
+
247
+ Args:
248
+ config (dict): Configuration object containing necessary paths and parameters.
249
+ crosswalk ... ADD HERE.
250
+
251
+ Returns:
252
+ dict: A dictionary mapping patient IDs to insurance IDs.
253
+ """
254
+ # Retrieve MAPAT path and slicing information from the configuration
255
+ mapat_path = app_control.get_mapat_med_path()
256
+ mapat_slices = crosswalk['mapat_mapping']['slices']
257
+
258
+ # Initialize the dictionary to hold the patient ID to insurance ID mappings
259
+ patient_id_to_insurance_id = {}
260
+
261
+ # Read data from MAPAT using a provided function to handle fixed-width data
262
+ for record, _ in MediLink_DataMgmt.read_general_fixed_width_data(mapat_path, mapat_slices):
263
+ patient_id = record['MAPATPXID']
264
+ insurance_id = record['MAPATINID']
265
+ patient_id_to_insurance_id[patient_id] = insurance_id
266
+
267
+ return patient_id_to_insurance_id
268
+
269
+ def parse_z_dat(z_dat_path, config):
270
+ """
271
+ Parses the Z.dat file to map Patient IDs to Insurance Names using the provided fixed-width file format.
272
+
273
+ Args:
274
+ z_dat_path (str): Path to the Z.dat file.
275
+ config (dict): Configuration object containing slicing information and other parameters.
276
+
277
+ Returns:
278
+ dict: A dictionary mapping Patient IDs to Insurance Names.
279
+ """
280
+ patient_id_to_insurance_name = {}
281
+
282
+ try:
283
+ # Reading blocks of fixed-width data (3 lines per record: personal, insurance, service)
284
+ for personal_info, insurance_info, service_info in MediLink_DataMgmt.read_fixed_width_data(z_dat_path):
285
+ # Parsing the data using slice definitions from the config
286
+ parsed_data = MediLink_DataMgmt.parse_fixed_width_data(personal_info, insurance_info, service_info, config.get('MediLink_Config', config))
287
+
288
+ # Extract Patient ID and Insurance Name from parsed data
289
+ patient_id = parsed_data.get('PATID')
290
+ insurance_name = parsed_data.get('INAME')
291
+
292
+ if patient_id and insurance_name:
293
+ patient_id_to_insurance_name[patient_id] = insurance_name
294
+ MediLink_ConfigLoader.log("Mapped Patient ID {} to Insurance Name {}".format(patient_id, insurance_name), config, level="INFO")
295
+
296
+ except FileNotFoundError:
297
+ MediLink_ConfigLoader.log("File not found: {}".format(z_dat_path), config, level="ERROR")
298
+ raise
299
+ except Exception as e:
300
+ MediLink_ConfigLoader.log("Failed to parse Z.dat: {}".format(str(e)), config, level="ERROR")
301
+ raise
302
+
303
+ return patient_id_to_insurance_name
304
+
305
+ def load_historical_payer_to_patient_mappings(config):
306
+ """
307
+ Loads historical mappings from multiple Carol's CSV files in a specified directory,
308
+ mapping Payer IDs to sets of Patient IDs.
309
+
310
+ Args:
311
+ config (dict): Configuration object containing the directory path for Carol's CSV files
312
+ and other necessary parameters.
313
+
314
+ Returns:
315
+ dict: A dictionary where each key is a Payer ID and the value is a set of Patient IDs.
316
+ """
317
+ directory_path = os.path.dirname(config['CSV_FILE_PATH'])
318
+ payer_to_patient_ids = defaultdict(set)
319
+
320
+ try:
321
+ # Check if the directory exists
322
+ if not os.path.isdir(directory_path):
323
+ raise FileNotFoundError("Directory '{}' not found.".format(directory_path))
324
+
325
+ # Loop through each file in the directory containing Carol's historical CSVs
326
+ for filename in os.listdir(directory_path):
327
+ file_path = os.path.join(directory_path, filename)
328
+ if filename.endswith('.csv'):
329
+ try:
330
+ with open(file_path, 'r', encoding='utf-8') as csvfile:
331
+ reader = csv.DictReader(csvfile)
332
+ patient_count = 0 # Counter for Patient IDs found in this CSV
333
+ for row in reader:
334
+ if 'Patient ID' not in row or 'Ins1 Payer ID' not in row:
335
+ continue # Skip this row if either key is missing
336
+ if not row.get('Patient ID').strip() or not row.get('Ins1 Payer ID').strip():
337
+ continue # Skip this row if either value is missing or empty
338
+
339
+ payer_id = row['Ins1 Payer ID'].strip()
340
+ patient_id = row['Patient ID'].strip()
341
+ payer_to_patient_ids[payer_id].add(patient_id)
342
+ patient_count += 1 # Increment the counter for each valid mapping
343
+
344
+ # Log the accumulated count for this CSV file
345
+ if patient_count > 0:
346
+ MediLink_ConfigLoader.log("CSV file '{}' has {} Patient IDs with Payer IDs.".format(filename, patient_count))
347
+ else:
348
+ MediLink_ConfigLoader.log("CSV file '{}' is empty or does not have valid Patient ID or Payer ID mappings.".format(filename))
349
+ except Exception as e:
350
+ print("Error processing file {}: {}".format(filename, e))
351
+ except FileNotFoundError as e:
352
+ print("Error: {}".format(e))
353
+
354
+ if not payer_to_patient_ids:
355
+ print("No historical mappings were generated.")
356
+
357
+ return dict(payer_to_patient_ids)
MediBot/MediBot_UI.py ADDED
@@ -0,0 +1,240 @@
1
+ from sys import exit
2
+ import ctypes
3
+ from ctypes import wintypes
4
+ import time
5
+ import re
6
+
7
+ # Add parent directory of the project to the Python path
8
+ import os
9
+ import sys
10
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
11
+ sys.path.append(project_dir)
12
+
13
+ try:
14
+ from MediLink import MediLink_ConfigLoader
15
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
16
+ except ImportError:
17
+ from MediLink_ConfigLoader import load_configuration
18
+ config, crosswalk = load_configuration()
19
+
20
+ """
21
+ User Interaction Refinements
22
+ - [ ] Refine the menu options for clearer user guidance during script pauses and errors.
23
+ - [ ] Add functionality for user to easily repeat or skip specific entries without script restart.
24
+ Develop more intuitive skip and retry mechanisms that are responsive to user input during data entry sessions.
25
+ """
26
+ # Function to check if a specific key is pressed
27
+ VK_END = int(config.get('VK_END', ""), 16) # Try F12 (7B). Virtual key code for 'End' (23)
28
+ VK_PAUSE = int(config.get('VK_PAUSE', ""), 16) # Try F11 (7A). Virtual-key code for 'Home' (24)
29
+
30
+ class AppControl:
31
+ def __init__(self):
32
+ self.script_paused = False
33
+ self.mapat_med_path = ''
34
+ self.medisoft_shortcut = ''
35
+ # Load initial paths from config when instance is created
36
+ self.load_paths_from_config()
37
+
38
+ def get_pause_status(self):
39
+ return self.script_paused
40
+
41
+ def set_pause_status(self, status):
42
+ self.script_paused = status
43
+
44
+ def get_mapat_med_path(self):
45
+ return self.mapat_med_path
46
+
47
+ def set_mapat_med_path(self, path):
48
+ self.mapat_med_path = path
49
+
50
+ def get_medisoft_shortcut(self):
51
+ return self.medisoft_shortcut
52
+
53
+ def set_medisoft_shortcut(self, path):
54
+ self.medisoft_shortcut = path
55
+
56
+ def load_paths_from_config(self, medicare=False):
57
+ # Assuming `config` is a module or a globally accessible configuration dictionary
58
+ if medicare:
59
+ self.mapat_med_path = config.get('MEDICARE_MAPAT_MED_PATH', "")
60
+ self.medisoft_shortcut = config.get('MEDICARE_SHORTCUT', "")
61
+ else:
62
+ self.mapat_med_path = config.get('MAPAT_MED_PATH', "")
63
+ self.medisoft_shortcut = config.get('PRIVATE_SHORTCUT', "")
64
+
65
+ app_control = AppControl()
66
+
67
+ def is_key_pressed(key_code):
68
+ user32 = ctypes.WinDLL('user32', use_last_error=True)
69
+ user32.GetAsyncKeyState.restype = wintypes.SHORT
70
+ user32.GetAsyncKeyState.argtypes = [wintypes.INT]
71
+ return user32.GetAsyncKeyState(key_code) & 0x8000 != 0
72
+
73
+ def manage_script_pause(csv_data, error_message, reverse_mapping):
74
+ user_action = 0 # initialize as 'continue'
75
+
76
+ if not app_control.get_pause_status() and is_key_pressed(VK_PAUSE):
77
+ app_control.set_pause_status(True)
78
+ print("Script paused. Opening menu...")
79
+ interaction_mode = 'normal' # Assuming normal interaction mode for script pause
80
+ user_action = user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
81
+
82
+ while app_control.get_pause_status():
83
+ if is_key_pressed(VK_END):
84
+ app_control.set_pause_status(False)
85
+ print("Continuing...")
86
+ elif is_key_pressed(VK_PAUSE):
87
+ user_action = user_interaction(csv_data, 'normal', error_message, reverse_mapping)
88
+ time.sleep(0.1)
89
+
90
+ return user_action
91
+
92
+ # Menu Display & User Interaction
93
+ def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicare):
94
+ selected_patient_ids = []
95
+ selected_indices = []
96
+
97
+ def display_menu_header(title):
98
+ print("\n" + "-" * 60)
99
+ print(title)
100
+ print("-" * 60)
101
+
102
+ def display_patient_list(csv_data, reverse_mapping, medicare_filter=False, exclude_medicare=False):
103
+ medicare_policy_pattern = r"^[a-zA-Z0-9]{11}$" # Regex pattern for 11 alpha-numeric characters
104
+ primary_policy_number_header = reverse_mapping.get('Primary Policy Number', 'Primary Policy Number')
105
+ primary_insurance_header = reverse_mapping.get('Primary Insurance', 'Primary Insurance') # Adjust field name as needed
106
+
107
+ displayed_indices = []
108
+ displayed_patient_ids = []
109
+
110
+ for index, row in enumerate(csv_data):
111
+ policy_number = row.get(primary_policy_number_header, "")
112
+ primary_insurance = row.get(primary_insurance_header, "").upper()
113
+
114
+ if medicare_filter and (not re.match(medicare_policy_pattern, policy_number) or "MEDICARE" not in primary_insurance):
115
+ continue
116
+ if exclude_medicare and re.match(medicare_policy_pattern, policy_number) and "MEDICARE" in primary_insurance:
117
+ continue
118
+
119
+ patient_id_header = reverse_mapping['Patient ID #2']
120
+ patient_name_header = reverse_mapping['Patient Name']
121
+ patient_id = row.get(patient_id_header, "N/A")
122
+ patient_name = row.get(patient_name_header, "Unknown")
123
+ surgery_date = row.get('Surgery Date', "Unknown Date") # Access 'Surgery Date' as string directly from the row
124
+
125
+ print("{0:03d}: {3:.5s} (ID: {2}) {1} ".format(index+1, patient_name, patient_id, surgery_date))
126
+
127
+ displayed_indices.append(index)
128
+ displayed_patient_ids.append(patient_id)
129
+
130
+ return displayed_indices, displayed_patient_ids
131
+
132
+ if proceed_as_medicare:
133
+ display_menu_header("MEDICARE Patient Selection for Today's Data Entry")
134
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, medicare_filter=True)
135
+ else:
136
+ display_menu_header("PRIVATE Patient Selection for Today's Data Entry")
137
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, exclude_medicare=True)
138
+
139
+ print("-" * 60)
140
+ proceed = input("\nDo you want to proceed with the selected patients? (yes/no): ").lower().strip() in ['yes', 'y']
141
+
142
+ if not proceed:
143
+ display_menu_header("Patient Selection for Today's Data Entry")
144
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping)
145
+ print("-" * 60)
146
+
147
+ while True:
148
+ selection = input("\nEnter the number(s) of the patients you wish to proceed with \n(e.g., 1,3,5): ").strip()
149
+
150
+ if not selection:
151
+ print("Invalid entry. Please provide at least one number.")
152
+ continue
153
+
154
+ selection = selection.replace('.', ',') # Replace '.' with ',' in the user input just in case
155
+ selected_indices = [int(x.strip()) - 1 for x in selection.split(',') if x.strip().isdigit()]
156
+
157
+ if not selected_indices:
158
+ print("Invalid entry. Please provide at least one integer.")
159
+ continue
160
+
161
+ proceed = True
162
+ break
163
+
164
+ patient_id_header = reverse_mapping['Patient ID #2']
165
+ selected_patient_ids = [csv_data[i][patient_id_header] for i in selected_indices if i < len(csv_data)]
166
+
167
+ return proceed, selected_patient_ids, selected_indices
168
+
169
+ def display_menu_header(title):
170
+ print("\n" + "-" * 60)
171
+ print(title)
172
+ print("-" * 60)
173
+
174
+ def handle_user_interaction(interaction_mode, error_message):
175
+ while True:
176
+ # If interaction_mode is neither 'triage' nor 'error', then it's normal mode.
177
+ title = "Error Occurred" if interaction_mode == 'error' else "Data Entry Options"
178
+ display_menu_header(title)
179
+
180
+ if interaction_mode == 'error':
181
+ print("\nERROR: ", error_message)
182
+
183
+ # Need to tell the user which patient we're talking about because it won't be obvious anymore.
184
+ # Also, this ERROR might be called from a location where the menu below isn't relevant like selecting patients
185
+ # -- need a better way to handle that.
186
+ print("1: Retry last entry")
187
+ print("2: Skip to next patient and continue")
188
+ print("3: Go back two patients and redo")
189
+ print("4: Exit script")
190
+ print("-" * 60)
191
+ choice = input("Enter your choice (1/2/3/4): ").strip()
192
+
193
+ if choice == '1':
194
+ print("Selected: 'Retry last entry'. Please press 'F12' to continue.")
195
+ return -1
196
+ elif choice == '2':
197
+ print("Selected: 'Skip to next patient and continue'. Please press 'F12' to continue.")
198
+ return 1
199
+ elif choice == '3':
200
+ print("Selected: 'Go back two patients and redo'. Please press 'F12' to continue.")
201
+ # Returning a specific value to indicate the action of going back two patients
202
+ # but we might run into a problem if we stop mid-run on the first row?
203
+ return -2
204
+ elif choice == '4':
205
+ print("Exiting the script.")
206
+ exit()
207
+ else:
208
+ print("Invalid choice. Please enter a valid number.")
209
+
210
+ def user_interaction(csv_data, interaction_mode, error_message, reverse_mapping):
211
+ global app_control # Use the instance of AppControl
212
+ selected_patient_ids = []
213
+ selected_indices = []
214
+
215
+ if interaction_mode == 'triage':
216
+ display_menu_header(" =(^.^)= Welcome to MediBot! =(^.^)=")
217
+
218
+ while True:
219
+ response = input("\nAm I processing Medicare patients? (yes/no): ").lower().strip()
220
+ if response:
221
+ if response in ['yes', 'y']:
222
+ app_control.load_paths_from_config(medicare=True)
223
+ break
224
+ elif response in ['no', 'n']:
225
+ app_control.load_paths_from_config(medicare=False)
226
+ break
227
+ else:
228
+ print("Invalid entry. Please enter 'yes' or 'no'.")
229
+ else:
230
+ print("A response is required. Please try again.")
231
+
232
+ fixed_values = config.get('fixed_values', {}) # Get fixed values from config json
233
+ if response in ['yes', 'y']:
234
+ medicare_added_fixed_values = config.get('medicare_added_fixed_values', {})
235
+ fixed_values.update(medicare_added_fixed_values) # Add any medicare-specific fixed values from config
236
+
237
+ proceed, selected_patient_ids, selected_indices = display_patient_selection_menu(csv_data, reverse_mapping, response in ['yes', 'y'])
238
+ return proceed, selected_patient_ids, selected_indices, fixed_values
239
+
240
+ return handle_user_interaction(interaction_mode, error_message)