medicafe 0.250725.8__tar.gz → 0.250725.9__tar.gz

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 (59) hide show
  1. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_UI.py +419 -408
  2. {medicafe-0.250725.8 → medicafe-0.250725.9}/PKG-INFO +1 -1
  3. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/PKG-INFO +1 -1
  4. {medicafe-0.250725.8 → medicafe-0.250725.9}/setup.py +1 -1
  5. {medicafe-0.250725.8 → medicafe-0.250725.9}/LICENSE +0 -0
  6. {medicafe-0.250725.8 → medicafe-0.250725.9}/MANIFEST.in +0 -0
  7. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot.bat +0 -0
  8. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot.py +0 -0
  9. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_Charges.py +0 -0
  10. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_Crosswalk_Library.py +0 -0
  11. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_Post.py +0 -0
  12. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_Preprocessor.py +0 -0
  13. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_Preprocessor_lib.py +0 -0
  14. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_dataformat_library.py +0 -0
  15. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/MediBot_docx_decoder.py +0 -0
  16. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/PDF_to_CSV_Cleaner.py +0 -0
  17. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/__init__.py +0 -0
  18. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/update_json.py +0 -0
  19. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediBot/update_medicafe.py +0 -0
  20. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink.py +0 -0
  21. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_837p_cob_library.py +0 -0
  22. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_837p_encoder.py +0 -0
  23. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_837p_encoder_library.py +0 -0
  24. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_837p_utilities.py +0 -0
  25. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_API_Generator.py +0 -0
  26. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_API_v2.py +0 -0
  27. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_API_v3.py +0 -0
  28. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_APIs.py +0 -0
  29. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Azure.py +0 -0
  30. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_ClaimStatus.py +0 -0
  31. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_ConfigLoader.py +0 -0
  32. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_DataMgmt.py +0 -0
  33. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Decoder.py +0 -0
  34. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Deductible.py +0 -0
  35. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Deductible_Validator.py +0 -0
  36. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Down.py +0 -0
  37. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Gmail.py +0 -0
  38. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_GraphQL.py +0 -0
  39. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Mailer.py +0 -0
  40. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Parser.py +0 -0
  41. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Scan.py +0 -0
  42. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Scheduler.py +0 -0
  43. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_UI.py +0 -0
  44. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_Up.py +0 -0
  45. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/MediLink_batch.bat +0 -0
  46. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/Soumit_api.py +0 -0
  47. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/__init__.py +0 -0
  48. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/openssl.cnf +0 -0
  49. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/test.py +0 -0
  50. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/test_cob_library.py +0 -0
  51. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/test_validation.py +0 -0
  52. {medicafe-0.250725.8 → medicafe-0.250725.9}/MediLink/webapp.html +0 -0
  53. {medicafe-0.250725.8 → medicafe-0.250725.9}/README.md +0 -0
  54. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/SOURCES.txt +0 -0
  55. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/dependency_links.txt +0 -0
  56. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/not-zip-safe +0 -0
  57. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/requires.txt +0 -0
  58. {medicafe-0.250725.8 → medicafe-0.250725.9}/medicafe.egg-info/top_level.txt +0 -0
  59. {medicafe-0.250725.8 → medicafe-0.250725.9}/setup.cfg +0 -0
@@ -1,409 +1,420 @@
1
- #MediBot_UI.py
2
- import ctypes, time, re, os, sys, msvcrt
3
- from ctypes import wintypes
4
- from sys import exit
5
- project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
6
- if project_dir not in sys.path:
7
- sys.path.append(project_dir)
8
-
9
- try:
10
- from MediLink import MediLink_ConfigLoader
11
- except ImportError:
12
- import MediLink_ConfigLoader
13
-
14
- # Load configuration
15
- config, crosswalk = MediLink_ConfigLoader.load_configuration()
16
-
17
- # Function to check if a specific key is pressed
18
- VK_END = int(config.get('VK_END', ""), 16) # Try F12 (7B). Virtual key code for 'End' (23)
19
- VK_PAUSE = int(config.get('VK_PAUSE', ""), 16) # Try F11 (7A). Virtual-key code for 'Home' (24)
20
-
21
- def flush_console_input_buffer():
22
- """
23
- Remove any keystrokes already sitting in the console input buffer.
24
- Necessary on Windows XP where a stray CR/LF can remain after the
25
- script is launched.
26
- """
27
- if sys.platform != "win32":
28
- return # no-op on non-Windows
29
-
30
- print("DEBUG_FLUSH: Starting flush_console_input_buffer()")
31
-
32
- # Check what's in the buffer before flushing
33
- chars_in_buffer = []
34
- while msvcrt.kbhit():
35
- ch = msvcrt.getch()
36
- chars_in_buffer.append(ch)
37
-
38
- if chars_in_buffer:
39
- print("DEBUG_FLUSH: Found {} chars in buffer before flush: {}".format(len(chars_in_buffer), chars_in_buffer))
40
- else:
41
- print("DEBUG_FLUSH: No chars found in buffer before flush")
42
-
43
- try:
44
- kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
45
- STD_INPUT_HANDLE = -10
46
- h_stdin = kernel32.GetStdHandle(STD_INPUT_HANDLE)
47
- result = kernel32.FlushConsoleInputBuffer(h_stdin)
48
- print("DEBUG_FLUSH: FlushConsoleInputBuffer API call result: {}".format(result))
49
- except Exception as e:
50
- print("DEBUG_FLUSH: FlushConsoleInputBuffer API failed: {}".format(e))
51
- # Fallback: drain any leading newlines manually
52
- fallback_chars = []
53
- while msvcrt.kbhit():
54
- ch = msvcrt.getch()
55
- fallback_chars.append(ch)
56
- if fallback_chars:
57
- print("DEBUG_FLUSH: Fallback removed {} chars: {}".format(len(fallback_chars), fallback_chars))
58
- else:
59
- print("DEBUG_FLUSH: Fallback found no chars to remove")
60
-
61
- print("DEBUG_FLUSH: flush_console_input_buffer() completed")
62
-
63
- def robust_input(prompt="", max_retries=3):
64
- """
65
- Robust input function that handles Windows XP console timing issues.
66
- Automatically retries if the first input returns empty.
67
- """
68
- print("DEBUG_ROBUST: Starting robust_input with prompt: '{}'".format(prompt))
69
-
70
- for attempt in range(max_retries):
71
- if attempt > 0:
72
- print("DEBUG_ROBUST: Retry attempt {} for input".format(attempt + 1))
73
- flush_console_input_buffer()
74
-
75
- # Print prompt only on first attempt, or if it's a retry
76
- if attempt == 0:
77
- print("DEBUG_ROBUST: First attempt - calling input(prompt)")
78
- response = input(prompt).strip()
79
- else:
80
- print("DEBUG_ROBUST: Retry attempt - calling input() without prompt")
81
- response = input().strip()
82
-
83
- print("DEBUG_ROBUST: Attempt {} got: '{}' (length: {})".format(attempt + 1, response, len(response)))
84
-
85
- if response:
86
- print("DEBUG_ROBUST: Success! Returning: '{}'".format(response))
87
- return response
88
-
89
- # If all attempts failed, return empty string
90
- print("DEBUG_ROBUST: All {} attempts failed, returning empty string".format(max_retries))
91
- return ""
92
-
93
- def discard_leading_blank():
94
- """
95
- Alternative fallback that uses non-blocking msvcrt to drain any available input.
96
- """
97
- if sys.platform != "win32":
98
- return # no-op on non-Windows
99
-
100
- # Use non-blocking msvcrt approach to drain any available input
101
- while msvcrt.kbhit():
102
- msvcrt.getch()
103
-
104
- class AppControl:
105
- def __init__(self):
106
- self.script_paused = False
107
- self.mapat_med_path = ''
108
- self.medisoft_shortcut = ''
109
- # PERFORMANCE FIX: Add configuration caching to reduce lookup overhead
110
- self._config_cache = {} # Cache for Medicare vs Private configuration lookups
111
- # Load initial paths from config when instance is created
112
- self.load_paths_from_config()
113
-
114
- def get_pause_status(self):
115
- return self.script_paused
116
-
117
- def set_pause_status(self, status):
118
- self.script_paused = status
119
-
120
- def get_mapat_med_path(self):
121
- return self.mapat_med_path
122
-
123
- def set_mapat_med_path(self, path):
124
- self.mapat_med_path = path
125
-
126
- def get_medisoft_shortcut(self):
127
- return self.medisoft_shortcut
128
-
129
- def set_medisoft_shortcut(self, path):
130
- self.medisoft_shortcut = path
131
-
132
- def load_paths_from_config(self, medicare=False):
133
- # Assuming `config` is a module or a globally accessible configuration dictionary
134
- # TODO Is this where the MAINS paths should also be set?
135
-
136
- # PERFORMANCE FIX: Cache configuration lookups to reduce Medicare vs Private overhead
137
- cache_key = 'medicare' if medicare else 'private'
138
-
139
- if cache_key not in self._config_cache:
140
- # Build cache entry for this configuration type
141
- if medicare:
142
- cached_config = {
143
- 'mapat_path': config.get('MEDICARE_MAPAT_MED_PATH', ""),
144
- 'shortcut': config.get('MEDICARE_SHORTCUT', "")
145
- }
146
- else:
147
- cached_config = {
148
- 'mapat_path': config.get('MAPAT_MED_PATH', ""),
149
- 'shortcut': config.get('PRIVATE_SHORTCUT', "")
150
- }
151
- self._config_cache[cache_key] = cached_config
152
-
153
- # Use cached values to avoid repeated config lookups
154
- cached = self._config_cache[cache_key]
155
- self.mapat_med_path = cached['mapat_path']
156
- self.medisoft_shortcut = cached['shortcut']
157
-
158
- app_control = AppControl()
159
-
160
- def is_key_pressed(key_code):
161
- user32 = ctypes.WinDLL('user32', use_last_error=True)
162
- user32.GetAsyncKeyState.restype = wintypes.SHORT
163
- user32.GetAsyncKeyState.argtypes = [wintypes.INT]
164
- return user32.GetAsyncKeyState(key_code) & 0x8000 != 0
165
-
166
- def manage_script_pause(csv_data, error_message, reverse_mapping):
167
- user_action = 0 # initialize as 'continue'
168
-
169
- if not app_control.get_pause_status() and is_key_pressed(VK_PAUSE):
170
- app_control.set_pause_status(True)
171
- print("Script paused. Opening menu...")
172
- interaction_mode = 'normal' # Assuming normal interaction mode for script pause
173
- user_action = user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
174
-
175
- while app_control.get_pause_status():
176
- if is_key_pressed(VK_END):
177
- app_control.set_pause_status(False)
178
- print("Continuing...")
179
- elif is_key_pressed(VK_PAUSE):
180
- user_action = user_interaction(csv_data, 'normal', error_message, reverse_mapping)
181
- time.sleep(0.1)
182
-
183
- return user_action
184
-
185
- # Menu Display & User Interaction
186
- def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicare):
187
- selected_patient_ids = []
188
- selected_indices = []
189
-
190
- def display_menu_header(title):
191
- print("\n" + "-" * 60)
192
- print(title)
193
- print("-" * 60)
194
-
195
- def display_patient_list(csv_data, reverse_mapping, medicare_filter=False, exclude_medicare=False):
196
- medicare_policy_pattern = r"^[a-zA-Z0-9]{11}$" # Regex pattern for 11 alpha-numeric characters
197
- primary_policy_number_header = reverse_mapping.get('Primary Policy Number', 'Primary Policy Number')
198
- primary_insurance_header = reverse_mapping.get('Primary Insurance', 'Primary Insurance') # Adjust field name as needed
199
-
200
- displayed_indices = []
201
- displayed_patient_ids = []
202
-
203
- for index, row in enumerate(csv_data):
204
- policy_number = row.get(primary_policy_number_header, "")
205
- primary_insurance = row.get(primary_insurance_header, "").upper()
206
-
207
- if medicare_filter and (not re.match(medicare_policy_pattern, policy_number) or "MEDICARE" not in primary_insurance):
208
- continue
209
- if exclude_medicare and re.match(medicare_policy_pattern, policy_number) and "MEDICARE" in primary_insurance:
210
- continue
211
-
212
- patient_id_header = reverse_mapping['Patient ID #2']
213
- patient_name_header = reverse_mapping['Patient Name']
214
- patient_id = row.get(patient_id_header, "N/A")
215
- patient_name = row.get(patient_name_header, "Unknown")
216
- surgery_date = row.get('Surgery Date', "Unknown Date") # Access 'Surgery Date' as string directly from the row
217
-
218
- print("{0:03d}: {3:%m-%d} (ID: {2}) {1} ".format(index+1, patient_name, patient_id, surgery_date))
219
-
220
- displayed_indices.append(index)
221
- displayed_patient_ids.append(patient_id)
222
-
223
- return displayed_indices, displayed_patient_ids
224
-
225
- if proceed_as_medicare:
226
- display_menu_header("MEDICARE Patient Selection for Today's Data Entry")
227
- selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, medicare_filter=True)
228
- else:
229
- display_menu_header("PRIVATE Patient Selection for Today's Data Entry")
230
- selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, exclude_medicare=True)
231
-
232
- print("-" * 60)
233
- print("\nDo you want to proceed with the selected patients? (yes/no): ", end='', flush=True)
234
- # Force flush and wait for Windows XP console buffer synchronization
235
- sys.stdout.flush()
236
- time.sleep(0.2) # Increased delay for Windows XP
237
-
238
- # Flush console input buffer to remove any stray CR/LF
239
- flush_console_input_buffer()
240
-
241
- # Use input() for more reliable input on Windows XP
242
- proceed = input().lower().strip() in ['yes', 'y']
243
-
244
- if not proceed:
245
- display_menu_header("Patient Selection for Today's Data Entry")
246
- selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping)
247
- print("-" * 60)
248
-
249
- while True:
250
- while True:
251
- print("\nEnter the number(s) of the patients you wish to proceed with \n(e.g., 1,3,5): ", end='', flush=True)
252
- # Force flush and wait for Windows XP console buffer synchronization
253
- sys.stdout.flush()
254
- time.sleep(0.2) # Increased delay for Windows XP
255
-
256
- # Flush console input buffer to remove any stray CR/LF
257
- flush_console_input_buffer()
258
-
259
- # Use input() for more reliable input on Windows XP
260
- selection = input().strip()
261
- if not selection:
262
- print("Invalid entry. Please provide at least one number.")
263
- continue
264
-
265
- selection = selection.replace('.', ',') # Replace '.' with ',' in the user input just in case
266
- selected_indices = [int(x.strip()) - 1 for x in selection.split(',') if x.strip().isdigit()]
267
-
268
- if not selected_indices:
269
- print("Invalid entry. Please provide at least one integer.")
270
- continue
271
-
272
- proceed = True
273
- break
274
-
275
- if not selection:
276
- print("Invalid entry. Please provide at least one number.")
277
- continue
278
-
279
- selection = selection.replace('.', ',') # Replace '.' with ',' in the user input just in case
280
- selected_indices = [int(x.strip()) - 1 for x in selection.split(',') if x.strip().isdigit()]
281
-
282
- if not selected_indices:
283
- print("Invalid entry. Please provide at least one integer.")
284
- continue
285
-
286
- proceed = True
287
- break
288
-
289
- patient_id_header = reverse_mapping['Patient ID #2']
290
- selected_patient_ids = [csv_data[i][patient_id_header] for i in selected_indices if i < len(csv_data)]
291
-
292
- return proceed, selected_patient_ids, selected_indices
293
-
294
- def display_menu_header(title):
295
- print("\n" + "-" * 60)
296
- print(title)
297
- print("-" * 60)
298
- # Force flush for Windows XP compatibility
299
- sys.stdout.flush()
300
-
301
- def handle_user_interaction(interaction_mode, error_message):
302
- # Import here to avoid circular imports
303
- try:
304
- from MediBot import current_patient_context
305
- except ImportError:
306
- current_patient_context = None
307
-
308
- while True:
309
- # If interaction_mode is neither 'triage' nor 'error', then it's normal mode.
310
- title = "Error Occurred" if interaction_mode == 'error' else "Data Entry Options"
311
- display_menu_header(title)
312
-
313
- if interaction_mode == 'error':
314
- print("\nERROR: ", error_message)
315
-
316
- # PERFORMANCE FIX: Display patient context to address "won't be obvious anymore" issue
317
- # Show user which patient and field they're working with for better F11 menu usability
318
- if current_patient_context:
319
- patient_name = current_patient_context.get('patient_name', 'Unknown Patient')
320
- surgery_date = current_patient_context.get('surgery_date', 'Unknown Date')
321
- last_field = current_patient_context.get('last_field', 'Unknown Field')
322
- print("\nCurrent Context:")
323
- print(" Patient: {}".format(patient_name))
324
- print(" Surgery Date: {}".format(surgery_date))
325
- print(" Last Field: {}".format(last_field))
326
- print("")
327
-
328
- # Menu options with improved context
329
- print("1: Retry last entry")
330
- print("2: Skip to next patient and continue")
331
- print("3: Go back two patients and redo")
332
- print("4: Exit script")
333
- print("-" * 60)
334
- print("Enter your choice (1/2/3/4): ", end='', flush=True)
335
- # Force flush and wait for Windows XP console buffer synchronization
336
- sys.stdout.flush()
337
- time.sleep(0.2) # Increased delay for Windows XP
338
-
339
- # Flush console input buffer to remove any stray CR/LF
340
- flush_console_input_buffer()
341
-
342
- # Use input() for more reliable input on Windows XP
343
- choice = input().strip()
344
-
345
- if choice == '1':
346
- print("Selected: 'Retry last entry'. Please press 'F12' to continue.")
347
- return -1
348
- elif choice == '2':
349
- print("Selected: 'Skip to next patient and continue'. Please press 'F12' to continue.")
350
- return 1
351
- elif choice == '3':
352
- print("Selected: 'Go back two patients and redo'. Please press 'F12' to continue.")
353
- # Returning a specific value to indicate the action of going back two patients
354
- # but we might run into a problem if we stop mid-run on the first row?
355
- return -2
356
- elif choice == '4':
357
- print("Exiting the script.")
358
- exit()
359
- else:
360
- print("Invalid choice. Please enter a valid number.")
361
-
362
- def user_interaction(csv_data, interaction_mode, error_message, reverse_mapping):
363
- global app_control # Use the instance of AppControl
364
- selected_patient_ids = []
365
- selected_indices = []
366
-
367
- if interaction_mode == 'triage':
368
- display_menu_header(" =(^.^)= Welcome to MediBot! =(^.^)=")
369
-
370
- # Force flush for Windows XP compatibility
371
- sys.stdout.flush()
372
-
373
- # Flush console input buffer to remove any stray CR/LF from script launch
374
- flush_console_input_buffer()
375
-
376
- while True:
377
- try:
378
- print("DEBUG_MAIN: About to call robust_input() - waiting for user...")
379
- # Use robust_input() for more reliable input on Windows XP
380
- response = robust_input("\nAm I processing Medicare patients? (yes/no): ").lower()
381
-
382
- # Debug: Print what we actually got
383
- print("DEBUG_MAIN: Final response: '{}' (length: {})".format(response, len(response)))
384
- print("DEBUG_MAIN: Response type: {}, repr: {}".format(type(response), repr(response)))
385
-
386
- if response:
387
- if response in ['yes', 'y']:
388
- app_control.load_paths_from_config(medicare=True)
389
- break
390
- elif response in ['no', 'n']:
391
- app_control.load_paths_from_config(medicare=False)
392
- break
393
- else:
394
- print("Invalid entry. Please enter 'yes' or 'no'.")
395
- else:
396
- print("A response is required. Please try again.")
397
- except KeyboardInterrupt:
398
- print("\nOperation cancelled by user. Exiting script.")
399
- exit()
400
-
401
- fixed_values = config.get('fixed_values', {}) # Get fixed values from config json
402
- if response in ['yes', 'y']:
403
- medicare_added_fixed_values = config.get('medicare_added_fixed_values', {})
404
- fixed_values.update(medicare_added_fixed_values) # Add any medicare-specific fixed values from config
405
-
406
- proceed, selected_patient_ids, selected_indices = display_patient_selection_menu(csv_data, reverse_mapping, response in ['yes', 'y'])
407
- return proceed, selected_patient_ids, selected_indices, fixed_values
408
-
1
+ #MediBot_UI.py
2
+ import ctypes, time, re, os, sys, msvcrt
3
+ from ctypes import wintypes
4
+ from sys import exit
5
+ project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
6
+ if project_dir not in sys.path:
7
+ sys.path.append(project_dir)
8
+
9
+ try:
10
+ from MediLink import MediLink_ConfigLoader
11
+ except ImportError:
12
+ import MediLink_ConfigLoader
13
+
14
+ # Load configuration
15
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
16
+
17
+ # Function to check if a specific key is pressed
18
+ VK_END = int(config.get('VK_END', ""), 16) # Try F12 (7B). Virtual key code for 'End' (23)
19
+ VK_PAUSE = int(config.get('VK_PAUSE', ""), 16) # Try F11 (7A). Virtual-key code for 'Home' (24)
20
+
21
+ def flush_console_input_buffer():
22
+ """
23
+ Remove any keystrokes already sitting in the console input buffer.
24
+ Necessary on Windows XP where a stray CR/LF can remain after the
25
+ script is launched.
26
+ """
27
+ if sys.platform != "win32":
28
+ return # no-op on non-Windows
29
+
30
+ print("DEBUG_FLUSH: Starting flush_console_input_buffer()")
31
+
32
+ # Check what's in the buffer before flushing
33
+ chars_in_buffer = []
34
+ while msvcrt.kbhit():
35
+ ch = msvcrt.getch()
36
+ chars_in_buffer.append(ch)
37
+
38
+ if chars_in_buffer:
39
+ print("DEBUG_FLUSH: Found {} chars in buffer before flush: {}".format(len(chars_in_buffer), chars_in_buffer))
40
+ else:
41
+ print("DEBUG_FLUSH: No chars found in buffer before flush")
42
+
43
+ try:
44
+ kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
45
+ STD_INPUT_HANDLE = -10
46
+ h_stdin = kernel32.GetStdHandle(STD_INPUT_HANDLE)
47
+ result = kernel32.FlushConsoleInputBuffer(h_stdin)
48
+ print("DEBUG_FLUSH: FlushConsoleInputBuffer API call result: {}".format(result))
49
+ except Exception as e:
50
+ print("DEBUG_FLUSH: FlushConsoleInputBuffer API failed: {}".format(e))
51
+ # Fallback: drain any leading newlines manually
52
+ fallback_chars = []
53
+ while msvcrt.kbhit():
54
+ ch = msvcrt.getch()
55
+ fallback_chars.append(ch)
56
+ if fallback_chars:
57
+ print("DEBUG_FLUSH: Fallback removed {} chars: {}".format(len(fallback_chars), fallback_chars))
58
+ else:
59
+ print("DEBUG_FLUSH: Fallback found no chars to remove")
60
+
61
+ print("DEBUG_FLUSH: flush_console_input_buffer() completed")
62
+
63
+ def robust_input(prompt="", max_retries=3):
64
+ """
65
+ Robust input function that handles Windows XP console timing issues.
66
+ Automatically retries if the first input returns empty.
67
+ """
68
+ print("DEBUG_ROBUST: Starting robust_input with prompt: '{}'".format(prompt))
69
+
70
+ for attempt in range(max_retries):
71
+ if attempt > 0:
72
+ print("DEBUG_ROBUST: Retry attempt {} for input".format(attempt + 1))
73
+ flush_console_input_buffer()
74
+
75
+ # Print prompt only on first attempt, or if it's a retry
76
+ if attempt == 0:
77
+ print("DEBUG_ROBUST: First attempt - calling input(prompt)")
78
+ response = input(prompt).strip()
79
+ else:
80
+ print("DEBUG_ROBUST: Retry attempt - calling input() without prompt")
81
+ response = input().strip()
82
+
83
+ print("DEBUG_ROBUST: Attempt {} got: '{}' (length: {})".format(attempt + 1, response, len(response)))
84
+
85
+ if response:
86
+ print("DEBUG_ROBUST: Success! Returning: '{}'".format(response))
87
+ return response
88
+
89
+ # If all attempts failed, return empty string
90
+ print("DEBUG_ROBUST: All {} attempts failed, returning empty string".format(max_retries))
91
+ return ""
92
+
93
+ def discard_leading_blank():
94
+ """
95
+ Alternative fallback that uses non-blocking msvcrt to drain any available input.
96
+ """
97
+ if sys.platform != "win32":
98
+ return # no-op on non-Windows
99
+
100
+ # Use non-blocking msvcrt approach to drain any available input
101
+ while msvcrt.kbhit():
102
+ msvcrt.getch()
103
+
104
+ class AppControl:
105
+ def __init__(self):
106
+ self.script_paused = False
107
+ self.mapat_med_path = ''
108
+ self.medisoft_shortcut = ''
109
+ # PERFORMANCE FIX: Add configuration caching to reduce lookup overhead
110
+ self._config_cache = {} # Cache for Medicare vs Private configuration lookups
111
+ # Load initial paths from config when instance is created
112
+ self.load_paths_from_config()
113
+
114
+ def get_pause_status(self):
115
+ return self.script_paused
116
+
117
+ def set_pause_status(self, status):
118
+ self.script_paused = status
119
+
120
+ def get_mapat_med_path(self):
121
+ return self.mapat_med_path
122
+
123
+ def set_mapat_med_path(self, path):
124
+ self.mapat_med_path = path
125
+
126
+ def get_medisoft_shortcut(self):
127
+ return self.medisoft_shortcut
128
+
129
+ def set_medisoft_shortcut(self, path):
130
+ self.medisoft_shortcut = path
131
+
132
+ def load_paths_from_config(self, medicare=False):
133
+ # Assuming `config` is a module or a globally accessible configuration dictionary
134
+ # TODO Is this where the MAINS paths should also be set?
135
+
136
+ # PERFORMANCE FIX: Cache configuration lookups to reduce Medicare vs Private overhead
137
+ cache_key = 'medicare' if medicare else 'private'
138
+
139
+ if cache_key not in self._config_cache:
140
+ # Build cache entry for this configuration type
141
+ if medicare:
142
+ cached_config = {
143
+ 'mapat_path': config.get('MEDICARE_MAPAT_MED_PATH', ""),
144
+ 'shortcut': config.get('MEDICARE_SHORTCUT', "")
145
+ }
146
+ else:
147
+ cached_config = {
148
+ 'mapat_path': config.get('MAPAT_MED_PATH', ""),
149
+ 'shortcut': config.get('PRIVATE_SHORTCUT', "")
150
+ }
151
+ self._config_cache[cache_key] = cached_config
152
+
153
+ # Use cached values to avoid repeated config lookups
154
+ cached = self._config_cache[cache_key]
155
+ self.mapat_med_path = cached['mapat_path']
156
+ self.medisoft_shortcut = cached['shortcut']
157
+
158
+ app_control = AppControl()
159
+
160
+ def is_key_pressed(key_code):
161
+ user32 = ctypes.WinDLL('user32', use_last_error=True)
162
+ user32.GetAsyncKeyState.restype = wintypes.SHORT
163
+ user32.GetAsyncKeyState.argtypes = [wintypes.INT]
164
+ return user32.GetAsyncKeyState(key_code) & 0x8000 != 0
165
+
166
+ def manage_script_pause(csv_data, error_message, reverse_mapping):
167
+ user_action = 0 # initialize as 'continue'
168
+
169
+ if not app_control.get_pause_status() and is_key_pressed(VK_PAUSE):
170
+ app_control.set_pause_status(True)
171
+ print("Script paused. Opening menu...")
172
+ interaction_mode = 'normal' # Assuming normal interaction mode for script pause
173
+ user_action = user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
174
+
175
+ while app_control.get_pause_status():
176
+ if is_key_pressed(VK_END):
177
+ app_control.set_pause_status(False)
178
+ print("Continuing...")
179
+ elif is_key_pressed(VK_PAUSE):
180
+ user_action = user_interaction(csv_data, 'normal', error_message, reverse_mapping)
181
+ time.sleep(0.1)
182
+
183
+ return user_action
184
+
185
+ # Menu Display & User Interaction
186
+ def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicare):
187
+ selected_patient_ids = []
188
+ selected_indices = []
189
+
190
+ def display_menu_header(title):
191
+ print("\n" + "-" * 60)
192
+ print(title)
193
+ print("-" * 60)
194
+
195
+ def display_patient_list(csv_data, reverse_mapping, medicare_filter=False, exclude_medicare=False):
196
+ medicare_policy_pattern = r"^[a-zA-Z0-9]{11}$" # Regex pattern for 11 alpha-numeric characters
197
+ primary_policy_number_header = reverse_mapping.get('Primary Policy Number', 'Primary Policy Number')
198
+ primary_insurance_header = reverse_mapping.get('Primary Insurance', 'Primary Insurance') # Adjust field name as needed
199
+
200
+ displayed_indices = []
201
+ displayed_patient_ids = []
202
+
203
+ for index, row in enumerate(csv_data):
204
+ policy_number = row.get(primary_policy_number_header, "")
205
+ primary_insurance = row.get(primary_insurance_header, "").upper()
206
+
207
+ if medicare_filter and (not re.match(medicare_policy_pattern, policy_number) or "MEDICARE" not in primary_insurance):
208
+ continue
209
+ if exclude_medicare and re.match(medicare_policy_pattern, policy_number) and "MEDICARE" in primary_insurance:
210
+ continue
211
+
212
+ patient_id_header = reverse_mapping['Patient ID #2']
213
+ patient_name_header = reverse_mapping['Patient Name']
214
+ patient_id = row.get(patient_id_header, "N/A")
215
+ patient_name = row.get(patient_name_header, "Unknown")
216
+ surgery_date = row.get('Surgery Date', "Unknown Date") # Access 'Surgery Date' as string directly from the row
217
+
218
+ print("{0:03d}: {3:%m-%d} (ID: {2}) {1} ".format(index+1, patient_name, patient_id, surgery_date))
219
+
220
+ displayed_indices.append(index)
221
+ displayed_patient_ids.append(patient_id)
222
+
223
+ return displayed_indices, displayed_patient_ids
224
+
225
+ if proceed_as_medicare:
226
+ display_menu_header("MEDICARE Patient Selection for Today's Data Entry")
227
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, medicare_filter=True)
228
+ else:
229
+ display_menu_header("PRIVATE Patient Selection for Today's Data Entry")
230
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping, exclude_medicare=True)
231
+
232
+ print("-" * 60)
233
+ print("\nDo you want to proceed with the selected patients? (yes/no): ", end='', flush=True)
234
+ # Force flush and wait for Windows XP console buffer synchronization
235
+ sys.stdout.flush()
236
+ time.sleep(0.2) # Increased delay for Windows XP
237
+
238
+ # Flush console input buffer to remove any stray CR/LF
239
+ flush_console_input_buffer()
240
+
241
+ # Use input() for more reliable input on Windows XP
242
+ proceed = input().lower().strip() in ['yes', 'y']
243
+
244
+ if not proceed:
245
+ display_menu_header("Patient Selection for Today's Data Entry")
246
+ selected_indices, selected_patient_ids = display_patient_list(csv_data, reverse_mapping)
247
+ print("-" * 60)
248
+
249
+ while True:
250
+ while True:
251
+ print("\nEnter the number(s) of the patients you wish to proceed with \n(e.g., 1,3,5): ", end='', flush=True)
252
+ # Force flush and wait for Windows XP console buffer synchronization
253
+ sys.stdout.flush()
254
+ time.sleep(0.2) # Increased delay for Windows XP
255
+
256
+ # Flush console input buffer to remove any stray CR/LF
257
+ flush_console_input_buffer()
258
+
259
+ # Use input() for more reliable input on Windows XP
260
+ selection = input().strip()
261
+ if not selection:
262
+ print("Invalid entry. Please provide at least one number.")
263
+ continue
264
+
265
+ selection = selection.replace('.', ',') # Replace '.' with ',' in the user input just in case
266
+ selected_indices = [int(x.strip()) - 1 for x in selection.split(',') if x.strip().isdigit()]
267
+
268
+ if not selected_indices:
269
+ print("Invalid entry. Please provide at least one integer.")
270
+ continue
271
+
272
+ proceed = True
273
+ break
274
+
275
+ if not selection:
276
+ print("Invalid entry. Please provide at least one number.")
277
+ continue
278
+
279
+ selection = selection.replace('.', ',') # Replace '.' with ',' in the user input just in case
280
+ selected_indices = [int(x.strip()) - 1 for x in selection.split(',') if x.strip().isdigit()]
281
+
282
+ if not selected_indices:
283
+ print("Invalid entry. Please provide at least one integer.")
284
+ continue
285
+
286
+ proceed = True
287
+ break
288
+
289
+ patient_id_header = reverse_mapping['Patient ID #2']
290
+ selected_patient_ids = [csv_data[i][patient_id_header] for i in selected_indices if i < len(csv_data)]
291
+
292
+ return proceed, selected_patient_ids, selected_indices
293
+
294
+ def display_menu_header(title):
295
+ print("\n" + "-" * 60)
296
+ print(title)
297
+ print("-" * 60)
298
+ # Force flush for Windows XP compatibility
299
+ sys.stdout.flush()
300
+
301
+ def handle_user_interaction(interaction_mode, error_message):
302
+ # Import here to avoid circular imports
303
+ try:
304
+ from MediBot import current_patient_context
305
+ except ImportError:
306
+ current_patient_context = None
307
+
308
+ while True:
309
+ # If interaction_mode is neither 'triage' nor 'error', then it's normal mode.
310
+ title = "Error Occurred" if interaction_mode == 'error' else "Data Entry Options"
311
+ display_menu_header(title)
312
+
313
+ if interaction_mode == 'error':
314
+ print("\nERROR: ", error_message)
315
+
316
+ # PERFORMANCE FIX: Display patient context to address "won't be obvious anymore" issue
317
+ # Show user which patient and field they're working with for better F11 menu usability
318
+ if current_patient_context:
319
+ patient_name = current_patient_context.get('patient_name', 'Unknown Patient')
320
+ surgery_date = current_patient_context.get('surgery_date', 'Unknown Date')
321
+ last_field = current_patient_context.get('last_field', 'Unknown Field')
322
+ print("\nCurrent Context:")
323
+ print(" Patient: {}".format(patient_name))
324
+ print(" Surgery Date: {}".format(surgery_date))
325
+ print(" Last Field: {}".format(last_field))
326
+ print("")
327
+
328
+ # Menu options with improved context
329
+ print("1: Retry last entry")
330
+ print("2: Skip to next patient and continue")
331
+ print("3: Go back two patients and redo")
332
+ print("4: Exit script")
333
+ print("-" * 60)
334
+ print("Enter your choice (1/2/3/4): ", end='', flush=True)
335
+ # Force flush and wait for Windows XP console buffer synchronization
336
+ sys.stdout.flush()
337
+ time.sleep(0.2) # Increased delay for Windows XP
338
+
339
+ # Flush console input buffer to remove any stray CR/LF
340
+ flush_console_input_buffer()
341
+
342
+ # Use input() for more reliable input on Windows XP
343
+ choice = input().strip()
344
+
345
+ if choice == '1':
346
+ print("Selected: 'Retry last entry'. Please press 'F12' to continue.")
347
+ return -1
348
+ elif choice == '2':
349
+ print("Selected: 'Skip to next patient and continue'. Please press 'F12' to continue.")
350
+ return 1
351
+ elif choice == '3':
352
+ print("Selected: 'Go back two patients and redo'. Please press 'F12' to continue.")
353
+ # Returning a specific value to indicate the action of going back two patients
354
+ # but we might run into a problem if we stop mid-run on the first row?
355
+ return -2
356
+ elif choice == '4':
357
+ print("Exiting the script.")
358
+ exit()
359
+ else:
360
+ print("Invalid choice. Please enter a valid number.")
361
+
362
+ def user_interaction(csv_data, interaction_mode, error_message, reverse_mapping):
363
+ global app_control # Use the instance of AppControl
364
+ selected_patient_ids = []
365
+ selected_indices = []
366
+
367
+ if interaction_mode == 'triage':
368
+ display_menu_header(" =(^.^)= Welcome to MediBot! =(^.^)=")
369
+
370
+ # Force flush for Windows XP compatibility
371
+ sys.stdout.flush()
372
+
373
+ # Flush console input buffer to remove any stray CR/LF from script launch
374
+ flush_console_input_buffer()
375
+
376
+ while True:
377
+ try:
378
+ print("DEBUG_MAIN: About to call robust_input() - waiting for user...")
379
+ # Use robust_input() for more reliable input on Windows XP
380
+ response = robust_input("\nAm I processing Medicare patients? (yes/no): ").lower()
381
+
382
+ print("DEBUG_MAIN: robust_input() returned, response = '{}'".format(response))
383
+ # Debug: Print what we actually got
384
+ print("DEBUG_MAIN: Final response: '{}' (length: {})".format(response, len(response)))
385
+ print("DEBUG_MAIN: Response type: {}, repr: {}".format(type(response), repr(response)))
386
+ print("DEBUG_MAIN: About to check response validity...")
387
+
388
+ if response:
389
+ print("DEBUG_MAIN: Response is not empty, checking if valid...")
390
+ if response in ['yes', 'y']:
391
+ print("DEBUG_MAIN: Valid response '{}', loading Medicare config".format(response))
392
+ app_control.load_paths_from_config(medicare=True)
393
+ break
394
+ elif response in ['no', 'n']:
395
+ print("DEBUG_MAIN: Valid response '{}', loading Private config".format(response))
396
+ app_control.load_paths_from_config(medicare=False)
397
+ break
398
+ else:
399
+ print("DEBUG_MAIN: Invalid response '{}', asking again".format(response))
400
+ print("Invalid entry. Please enter 'yes' or 'no'.")
401
+ else:
402
+ print("DEBUG_MAIN: Empty response, asking again")
403
+ print("A response is required. Please try again.")
404
+ except KeyboardInterrupt:
405
+ print("\nOperation cancelled by user. Exiting script.")
406
+ exit()
407
+ except Exception as e:
408
+ print("DEBUG_MAIN: Exception occurred: {}".format(e))
409
+ import traceback
410
+ traceback.print_exc()
411
+
412
+ fixed_values = config.get('fixed_values', {}) # Get fixed values from config json
413
+ if response in ['yes', 'y']:
414
+ medicare_added_fixed_values = config.get('medicare_added_fixed_values', {})
415
+ fixed_values.update(medicare_added_fixed_values) # Add any medicare-specific fixed values from config
416
+
417
+ proceed, selected_patient_ids, selected_indices = display_patient_selection_menu(csv_data, reverse_mapping, response in ['yes', 'y'])
418
+ return proceed, selected_patient_ids, selected_indices, fixed_values
419
+
409
420
  return handle_user_interaction(interaction_mode, error_message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250725.8
3
+ Version: 0.250725.9
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2
6
6
  Author: Daniel Vidaud
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: medicafe
3
- Version: 0.250725.8
3
+ Version: 0.250725.9
4
4
  Summary: MediCafe
5
5
  Home-page: https://github.com/katanada2
6
6
  Author: Daniel Vidaud
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='medicafe',
5
- version="0.250725.8",
5
+ version="0.250725.9",
6
6
  description='MediCafe',
7
7
  long_description="""
8
8
  # Project Overview: MediCafe
File without changes
File without changes
File without changes
File without changes