medicafe 0.250722.0__tar.gz → 0.250723.1__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 (60) hide show
  1. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot.py +82 -2
  2. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_Crosswalk_Library.py +53 -21
  3. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_Preprocessor_lib.py +795 -779
  4. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_UI.py +43 -9
  5. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_API_v3.py +3 -0
  6. medicafe-0.250723.1/MediLink/MediLink_ClaimStatus.py +273 -0
  7. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Deductible.py +120 -70
  8. {medicafe-0.250722.0 → medicafe-0.250723.1}/PKG-INFO +1 -1
  9. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/PKG-INFO +1 -1
  10. {medicafe-0.250722.0 → medicafe-0.250723.1}/setup.py +1 -1
  11. medicafe-0.250722.0/MediLink/MediLink_ClaimStatus.py +0 -160
  12. {medicafe-0.250722.0 → medicafe-0.250723.1}/LICENSE +0 -0
  13. {medicafe-0.250722.0 → medicafe-0.250723.1}/MANIFEST.in +0 -0
  14. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot.bat +0 -0
  15. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_Charges.py +0 -0
  16. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_Post.py +0 -0
  17. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_Preprocessor.py +0 -0
  18. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_dataformat_library.py +0 -0
  19. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/MediBot_docx_decoder.py +0 -0
  20. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/PDF_to_CSV_Cleaner.py +0 -0
  21. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/__init__.py +0 -0
  22. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/update_json.py +0 -0
  23. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediBot/update_medicafe.py +0 -0
  24. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink.py +0 -0
  25. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_837p_cob_library.py +0 -0
  26. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_837p_encoder.py +0 -0
  27. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_837p_encoder_library.py +0 -0
  28. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_837p_utilities.py +0 -0
  29. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_API_Generator.py +0 -0
  30. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_API_v2.py +0 -0
  31. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_APIs.py +0 -0
  32. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Azure.py +0 -0
  33. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_ConfigLoader.py +0 -0
  34. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_DataMgmt.py +0 -0
  35. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Decoder.py +0 -0
  36. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Deductible_Validator.py +0 -0
  37. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Down.py +0 -0
  38. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Gmail.py +0 -0
  39. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_GraphQL.py +0 -0
  40. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Mailer.py +0 -0
  41. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Parser.py +0 -0
  42. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Scan.py +0 -0
  43. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Scheduler.py +0 -0
  44. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_UI.py +0 -0
  45. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_Up.py +0 -0
  46. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/MediLink_batch.bat +0 -0
  47. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/Soumit_api.py +0 -0
  48. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/__init__.py +0 -0
  49. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/openssl.cnf +0 -0
  50. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/test.py +0 -0
  51. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/test_cob_library.py +0 -0
  52. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/test_validation.py +0 -0
  53. {medicafe-0.250722.0 → medicafe-0.250723.1}/MediLink/webapp.html +0 -0
  54. {medicafe-0.250722.0 → medicafe-0.250723.1}/README.md +0 -0
  55. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/SOURCES.txt +0 -0
  56. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/dependency_links.txt +0 -0
  57. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/not-zip-safe +0 -0
  58. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/requires.txt +0 -0
  59. {medicafe-0.250722.0 → medicafe-0.250723.1}/medicafe.egg-info/top_level.txt +0 -0
  60. {medicafe-0.250722.0 → medicafe-0.250723.1}/setup.cfg +0 -0
@@ -40,8 +40,45 @@ def identify_field(header, field_mapping):
40
40
  # Add this print to a function that is calling identify_field
41
41
  #print("Warning: No matching field found for CSV header '{}'".format(header))
42
42
 
43
+ # Global flag to control AHK execution method - set to True to use optimized stdin method
44
+ USE_AHK_STDIN_OPTIMIZATION = True
45
+
43
46
  # Function to execute an AutoHotkey script
44
47
  def run_ahk_script(script_content):
48
+ """
49
+ Execute an AutoHotkey script using either optimized stdin method or traditional file method.
50
+ Automatically falls back to file method if stdin method fails.
51
+ """
52
+ if USE_AHK_STDIN_OPTIMIZATION:
53
+ try:
54
+ # Optimized method: Execute AHK script via stdin pipe - eliminates temporary file creation
55
+ # Compatible with Windows XP and AutoHotkey v1.0.48+
56
+ process = subprocess.Popen(
57
+ [AHK_EXECUTABLE, '/f', '*'], # '/f *' tells AHK to read from stdin
58
+ stdin=subprocess.PIPE,
59
+ stdout=subprocess.PIPE,
60
+ stderr=subprocess.PIPE,
61
+ shell=False
62
+ )
63
+
64
+ # Send script content via stdin
65
+ stdout, stderr = process.communicate(input=script_content.encode('utf-8'))
66
+
67
+ if process.returncode != 0:
68
+ print("AHK script failed with exit status: {}".format(process.returncode)) # Log the exit status of the failed script
69
+ MediLink_ConfigLoader.log("AHK script failed with exit status: {}".format(process.returncode), level="ERROR")
70
+ if stderr:
71
+ print("AHK Error: {}".format(stderr.decode('utf-8', errors='ignore')))
72
+ MediLink_ConfigLoader.log("AHK Error: {}".format(stderr.decode('utf-8', errors='ignore')), level="ERROR")
73
+ return # Success - no file cleanup needed
74
+
75
+ except Exception as e:
76
+ # If stdin method fails, fall back to traditional file method
77
+ print("AHK stdin execution failed, falling back to file method: {}".format(e))
78
+ MediLink_ConfigLoader.log("AHK stdin execution failed, falling back to file method: {}".format(e), level="ERROR")
79
+ # Continue to fallback implementation below
80
+
81
+ # Traditional file-based method (fallback or when optimization disabled)
45
82
  temp_script_name = None # Initialize variable to hold the name of the temporary script file
46
83
  try:
47
84
  # Create a temporary AHK script file
@@ -53,9 +90,12 @@ def run_ahk_script(script_content):
53
90
  subprocess.check_call([AHK_EXECUTABLE, temp_script_name]) # Execute the AHK script using the AutoHotkey executable
54
91
  except subprocess.CalledProcessError as e:
55
92
  print("AHK script failed with exit status: {}".format(e.returncode)) # Log the exit status of the failed script
93
+ MediLink_ConfigLoader.log("AHK script failed with exit status: {}".format(e.returncode), level="ERROR")
56
94
  print("Output from AHK script: {}".format(e.output)) # Log the output from the failed script
95
+ MediLink_ConfigLoader.log("Output from AHK script: {}".format(e.output), level="ERROR")
57
96
  except Exception as e:
58
97
  print("An unexpected error occurred while running the AHK script: {}".format(e)) # Log any unexpected errors
98
+ MediLink_ConfigLoader.log("An unexpected error occurred while running the AHK script: {}".format(e), level="ERROR")
59
99
  traceback.print_exc() # Print the full traceback for debugging purposes
60
100
  finally:
61
101
  # Delete the temporary script file
@@ -64,12 +104,15 @@ def run_ahk_script(script_content):
64
104
  os.unlink(temp_script_name) # Attempt to delete the temporary script file
65
105
  except OSError as e:
66
106
  print("Error deleting temporary script file: {}".format(e)) # Log any errors encountered while deleting the file
107
+ MediLink_ConfigLoader.log("Error deleting temporary script file: {}".format(e), level="ERROR")
67
108
  # Future Improvement: Implement a cleanup mechanism to handle orphaned temporary files
68
109
 
69
110
  # Global variable to store the last processed entry
70
111
  last_processed_entry = None
71
112
  # Global variable to store temporarily parsed address components
72
113
  parsed_address_components = {}
114
+ # Global variable to store patient context for F11 menu (preserved across patients)
115
+ current_patient_context = None
73
116
 
74
117
  def process_field(medisoft_field, csv_row, parsed_address_components, reverse_mapping, csv_data, fixed_values):
75
118
  global last_processed_entry
@@ -107,8 +150,19 @@ def process_field(medisoft_field, csv_row, parsed_address_components, reverse_ma
107
150
  return handle_error(e, medisoft_field, last_processed_entry, csv_data)
108
151
 
109
152
  def handle_error(error, medisoft_field, last_processed_entry, csv_data):
153
+ global current_patient_context
110
154
  MediLink_ConfigLoader.log("Error in process_field: ", e)
111
155
  print("An error occurred while processing {0}: {1}".format(medisoft_field, error))
156
+
157
+ # Update patient context with current error information for F11 menu
158
+ if current_patient_context is None:
159
+ current_patient_context = {}
160
+ current_patient_context.update({
161
+ 'last_field': medisoft_field,
162
+ 'error_occurred': True,
163
+ 'error_message': str(error)
164
+ })
165
+
112
166
  # Assuming the interaction mode is 'error' in this case
113
167
  interaction_mode = 'error'
114
168
  response = user_interaction(csv_data, interaction_mode, error, reverse_mapping)
@@ -129,7 +183,7 @@ def iterate_fields(csv_row, field_mapping, parsed_address_components, reverse_ma
129
183
  return 0 # Default action to continue
130
184
 
131
185
  def data_entry_loop(csv_data, field_mapping, reverse_mapping, fixed_values):
132
- global last_processed_entry, parsed_address_components
186
+ global last_processed_entry, parsed_address_components, current_patient_context
133
187
  # last_processed_entry, parsed_address_components = None, {} // BUG should this just be this line rather than the global line above?
134
188
  error_message = '' # Initialize error_message once
135
189
  current_row_index = 0
@@ -137,6 +191,23 @@ def data_entry_loop(csv_data, field_mapping, reverse_mapping, fixed_values):
137
191
  while current_row_index < len(csv_data):
138
192
  row = csv_data[current_row_index]
139
193
 
194
+ # PERFORMANCE FIX: Clear accumulating memory while preserving F11 menu context
195
+ # Store patient context before clearing last_processed_entry for F11 "Retry last entry" functionality
196
+ if last_processed_entry is not None:
197
+ patient_name = row.get(reverse_mapping.get('Patient Name', ''), 'Unknown Patient')
198
+ surgery_date = row.get('Surgery Date', 'Unknown Date')
199
+ current_patient_context = {
200
+ 'patient_name': patient_name,
201
+ 'surgery_date': surgery_date,
202
+ 'last_field': last_processed_entry[0] if last_processed_entry else None,
203
+ 'last_value': last_processed_entry[1] if last_processed_entry else None,
204
+ 'row_index': current_row_index
205
+ }
206
+
207
+ # Clear memory-accumulating structures while preserving F11 context above
208
+ last_processed_entry = None
209
+ parsed_address_components = {}
210
+
140
211
  # Handle script pause at the start of each row (patient record).
141
212
  manage_script_pause(csv_data, error_message, reverse_mapping)
142
213
  error_message = '' # Clear error message for the next iteration
@@ -146,8 +217,9 @@ def data_entry_loop(csv_data, field_mapping, reverse_mapping, fixed_values):
146
217
 
147
218
  # I feel like this is overwriting what would have already been idenfitied in the mapping.
148
219
  # This probably needs to be initialized differently.
220
+ # Note: parsed_address_components is now explicitly cleared above for performance
149
221
  # parsed_address_components = {'City': '', 'State': '', 'Zip Code': ''}
150
- parsed_address_components = {}
222
+ # parsed_address_components = {} # Moved to top of loop for memory management
151
223
 
152
224
  # Process each field in the row
153
225
  action = iterate_fields(row, field_mapping, parsed_address_components, reverse_mapping, csv_data, fixed_values)
@@ -170,6 +242,14 @@ def data_entry_loop(csv_data, field_mapping, reverse_mapping, fixed_values):
170
242
  # Code to handle the end of a patient record
171
243
  # TODO One day this can just not pause...
172
244
  app_control.set_pause_status(True) # Pause at the end of processing each patient record
245
+
246
+ # PERFORMANCE FIX: Explicit cleanup at end of patient processing
247
+ # Clear global state to prevent accumulation over processing sessions
248
+ # Note: current_patient_context is preserved for F11 menu functionality
249
+ if current_row_index != len(csv_data) - 1: # Not the last patient
250
+ last_processed_entry = None
251
+ parsed_address_components.clear()
252
+
173
253
  current_row_index += 1 # Move to the next row by default
174
254
 
175
255
  def open_medisoft(shortcut_path):
@@ -279,19 +279,22 @@ def check_crosswalk_health(crosswalk):
279
279
  crosswalk (dict): The crosswalk dictionary to check.
280
280
 
281
281
  Returns:
282
- tuple: (is_healthy, missing_names_count, missing_medisoft_ids_count)
282
+ tuple: (is_healthy, missing_names_count, missing_medisoft_ids_count, missing_names_list, missing_medisoft_ids_list)
283
283
  """
284
284
  if 'payer_id' not in crosswalk or not crosswalk['payer_id']:
285
- return False, 0, 0
285
+ return False, 0, 0, [], []
286
286
 
287
287
  missing_names = 0
288
288
  missing_medisoft_ids = 0
289
+ missing_names_list = []
290
+ missing_medisoft_ids_list = []
289
291
 
290
292
  for payer_id, details in crosswalk['payer_id'].items():
291
293
  # Check if name is missing or "Unknown"
292
294
  name = details.get('name', '')
293
295
  if not name or name == 'Unknown':
294
296
  missing_names += 1
297
+ missing_names_list.append(payer_id)
295
298
 
296
299
  # Check if at least one medisoft ID exists in either field
297
300
  medisoft_id = details.get('medisoft_id', [])
@@ -306,10 +309,11 @@ def check_crosswalk_health(crosswalk):
306
309
  # If both are empty, count as missing; if either has at least one, it's healthy
307
310
  if not medisoft_id and not medisoft_medicare_id:
308
311
  missing_medisoft_ids += 1
312
+ missing_medisoft_ids_list.append(payer_id)
309
313
 
310
314
  # Consider healthy if no missing names and no missing medisoft IDs
311
315
  is_healthy = (missing_names == 0 and missing_medisoft_ids == 0)
312
- return is_healthy, missing_names, missing_medisoft_ids
316
+ return is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list
313
317
 
314
318
  def prompt_user_for_api_calls(crosswalk, config):
315
319
  """
@@ -324,7 +328,7 @@ def prompt_user_for_api_calls(crosswalk, config):
324
328
  bool: True if should proceed with API calls, False if should skip
325
329
  """
326
330
 
327
- is_healthy, missing_names, missing_medisoft_ids = check_crosswalk_health(crosswalk)
331
+ is_healthy, missing_names, missing_medisoft_ids, missing_names_list, missing_medisoft_ids_list = check_crosswalk_health(crosswalk)
328
332
  total_payers = len(crosswalk.get('payer_id', {}))
329
333
 
330
334
  if is_healthy:
@@ -362,13 +366,27 @@ def prompt_user_for_api_calls(crosswalk, config):
362
366
  else:
363
367
  print("\nCrosswalk needs attention:")
364
368
  print(" - {} payers found".format(total_payers))
369
+
370
+ # Show detailed information about missing names
365
371
  if missing_names > 0:
366
- print(" - {} payers missing names".format(missing_names))
372
+ print(" - {} payers missing names: {}".format(missing_names, ", ".join(missing_names_list)))
373
+
374
+ # Show detailed information about missing medisoft IDs
367
375
  if missing_medisoft_ids > 0:
368
- print(" - {} payers missing medisoft IDs".format(missing_medisoft_ids))
369
- print("Proceeding with API validation calls...")
370
- MediLink_ConfigLoader.log("Crosswalk unhealthy - proceeding with API calls", config, level="INFO")
371
- return True
376
+ print(" - {} payers missing medisoft IDs: {}".format(missing_medisoft_ids, ", ".join(missing_medisoft_ids_list)))
377
+ # API validation CANNOT resolve missing medisoft IDs
378
+ print(" TODO: Need user interface to manually input medisoft IDs for these payers")
379
+
380
+ # Only proceed with API calls if there are missing names (API can help with those)
381
+ if missing_names > 0:
382
+ print("Proceeding with API validation calls to resolve missing names...")
383
+ MediLink_ConfigLoader.log("Crosswalk has missing names - proceeding with API calls", config, level="INFO")
384
+ return True
385
+ else:
386
+ print("No missing names to resolve via API. Skipping API validation calls.")
387
+ print("TODO: Manual intervention needed for missing medisoft IDs")
388
+ MediLink_ConfigLoader.log("Crosswalk has missing medisoft IDs but no missing names - skipping API calls", config, level="INFO")
389
+ return False
372
390
 
373
391
  def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstream of this is only MediBot_Preprocessor.py and MediBot.py
374
392
  """
@@ -481,13 +499,15 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
481
499
 
482
500
  crosswalk['payer_id'][payer_id] = {
483
501
  'endpoint': selected_endpoint,
484
- 'medisoft_id': set(), # Use set to store multiple IDs
485
- 'medisoft_medicare_id': set()
502
+ 'medisoft_id': [], # PERFORMANCE FIX: Use list instead of set to avoid conversions
503
+ 'medisoft_medicare_id': [] # PERFORMANCE FIX: Use list instead of set to avoid conversions
486
504
  }
487
505
  MediLink_ConfigLoader.log("Initialized payer ID {} in crosswalk with endpoint '{}'.".format(payer_id, selected_endpoint), config, level="DEBUG")
488
506
 
489
- # Add the insurance ID to the payer ID entry
490
- crosswalk['payer_id'][payer_id]['medisoft_id'].add(str(insurance_id)) # Ensure IDs are strings
507
+ # Add the insurance ID to the payer ID entry (PERFORMANCE FIX: Use list operations)
508
+ insurance_id_str = str(insurance_id) # Ensure ID is string
509
+ if insurance_id_str not in crosswalk['payer_id'][payer_id]['medisoft_id']:
510
+ crosswalk['payer_id'][payer_id]['medisoft_id'].append(insurance_id_str) # Avoid duplicates
491
511
  MediLink_ConfigLoader.log(
492
512
  "Added new insurance ID {} to payer ID {}.".format(insurance_id, payer_id),
493
513
  config,
@@ -517,14 +537,26 @@ def crosswalk_update(client, config, crosswalk, skip_known_payers=True): # Upstr
517
537
  fetch_and_store_payer_name(client, payer_id, crosswalk, config)
518
538
  MediLink_ConfigLoader.log("Successfully fetched and stored payer name for unknown payer_id: {}".format(payer_id), config, level="INFO")
519
539
 
520
- # Ensure multiple medisoft_id values are preserved by converting sets to sorted lists
540
+ # PERFORMANCE FIX: Optimized list management - avoid redundant set/list conversions
541
+ # Ensure multiple medisoft_id values are preserved and deduplicated efficiently
521
542
  for payer_id, details in crosswalk.get('payer_id', {}).items():
522
- if isinstance(details.get('medisoft_id'), set):
523
- crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(details['medisoft_id']))
524
- MediLink_ConfigLoader.log("Converted medisoft_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
525
- if isinstance(details.get('medisoft_medicare_id'), set):
526
- crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(details['medisoft_medicare_id']))
527
- MediLink_ConfigLoader.log("Converted medisoft_medicare_id for payer ID {} to sorted list.".format(payer_id), config, level="DEBUG")
543
+ # Handle medisoft_id - convert sets to lists or deduplicate existing lists
544
+ medisoft_id = details.get('medisoft_id', [])
545
+ if isinstance(medisoft_id, set):
546
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = sorted(list(medisoft_id))
547
+ MediLink_ConfigLoader.log("Converted medisoft_id set to sorted list for payer ID {}.".format(payer_id), config, level="DEBUG")
548
+ elif isinstance(medisoft_id, list) and medisoft_id:
549
+ # Remove duplicates using dict.fromkeys() - preserves order, O(n) performance
550
+ crosswalk['payer_id'][payer_id]['medisoft_id'] = list(dict.fromkeys(medisoft_id))
551
+
552
+ # Handle medisoft_medicare_id - convert sets to lists or deduplicate existing lists
553
+ medicare_id = details.get('medisoft_medicare_id', [])
554
+ if isinstance(medicare_id, set):
555
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = sorted(list(medicare_id))
556
+ MediLink_ConfigLoader.log("Converted medisoft_medicare_id set to sorted list for payer ID {}.".format(payer_id), config, level="DEBUG")
557
+ elif isinstance(medicare_id, list) and medicare_id:
558
+ # Remove duplicates using dict.fromkeys() - preserves order, O(n) performance
559
+ crosswalk['payer_id'][payer_id]['medisoft_medicare_id'] = list(dict.fromkeys(medicare_id))
528
560
 
529
561
  MediLink_ConfigLoader.log("Crosswalk update process completed. Processed {} payer IDs.".format(len(patient_id_to_payer_id)), config, level="INFO")
530
562
  return save_crosswalk(client, config, crosswalk)
@@ -635,7 +667,7 @@ def update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config,
635
667
  # Ensure the 'payer_id' key exists in the crosswalk
636
668
  crosswalk['payer_id'][payer_id] = {
637
669
  'endpoint': selected_endpoint,
638
- 'medisoft_id': set(),
670
+ 'medisoft_id': [], # PERFORMANCE FIX: Use list instead of set to avoid conversions
639
671
  'medisoft_medicare_id': []
640
672
  }
641
673
  MediLink_ConfigLoader.log("Initialized payer ID {} in crosswalk with endpoint '{}'.".format(payer_id, selected_endpoint), config, level="DEBUG")