medicafe 0.250723.5__tar.gz → 0.250725.0__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.
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot.py +8 -2
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_Preprocessor_lib.py +57 -32
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_UI.py +8 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_dataformat_library.py +5 -2
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_docx_decoder.py +5 -3
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Decoder.py +38 -18
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Deductible.py +23 -17
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Parser.py +0 -2
- {medicafe-0.250723.5 → medicafe-0.250725.0}/PKG-INFO +1 -1
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/PKG-INFO +1 -1
- {medicafe-0.250723.5 → medicafe-0.250725.0}/setup.py +1 -1
- {medicafe-0.250723.5 → medicafe-0.250725.0}/LICENSE +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MANIFEST.in +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot.bat +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_Crosswalk_Library.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/PDF_to_CSV_Cleaner.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/__init__.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/update_json.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_837p_encoder_library.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_API_v2.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_API_v3.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_APIs.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_ClaimStatus.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_ConfigLoader.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_DataMgmt.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Deductible_Validator.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Gmail.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_GraphQL.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_UI.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_Up.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/MediLink_batch.bat +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/__init__.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/test.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/test_cob_library.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/test_validation.py +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/MediLink/webapp.html +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/README.md +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/SOURCES.txt +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/medicafe.egg-info/top_level.txt +0 -0
- {medicafe-0.250723.5 → medicafe-0.250725.0}/setup.cfg +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#MediBot.py
|
|
2
|
-
import subprocess, os, tempfile, traceback, re, sys
|
|
2
|
+
import subprocess, os, tempfile, traceback, re, sys, time
|
|
3
3
|
from collections import OrderedDict
|
|
4
4
|
import MediBot_dataformat_library
|
|
5
5
|
import MediBot_Preprocessor
|
|
@@ -187,8 +187,10 @@ def data_entry_loop(csv_data, field_mapping, reverse_mapping, fixed_values):
|
|
|
187
187
|
# last_processed_entry, parsed_address_components = None, {} // BUG should this just be this line rather than the global line above?
|
|
188
188
|
error_message = '' # Initialize error_message once
|
|
189
189
|
current_row_index = 0
|
|
190
|
+
# PERFORMANCE FIX: Cache list length to avoid repeated len() calls
|
|
191
|
+
csv_data_length = len(csv_data)
|
|
190
192
|
|
|
191
|
-
while current_row_index <
|
|
193
|
+
while current_row_index < csv_data_length:
|
|
192
194
|
row = csv_data[current_row_index]
|
|
193
195
|
|
|
194
196
|
# PERFORMANCE FIX: Clear accumulating memory while preserving F11 menu context
|
|
@@ -394,9 +396,13 @@ if __name__ == "__main__":
|
|
|
394
396
|
# Check if there are patients left to process
|
|
395
397
|
if len(patients_to_process) == 0:
|
|
396
398
|
print("\nAll patients have been processed. Continue anyway?: ", end='', flush=True)
|
|
399
|
+
# Small delay for Windows XP console buffer synchronization
|
|
400
|
+
time.sleep(0.1)
|
|
397
401
|
proceed = input().lower().strip() in ['yes', 'y']
|
|
398
402
|
else:
|
|
399
403
|
print("\nDo you want to proceed with the {} remaining patient(s)? (yes/no): ".format(len(patients_to_process)), end='', flush=True)
|
|
404
|
+
# Small delay for Windows XP console buffer synchronization
|
|
405
|
+
time.sleep(0.1)
|
|
400
406
|
proceed = input().lower().strip() in ['yes', 'y']
|
|
401
407
|
|
|
402
408
|
# TODO: Here is where we need to add the step where we move to MediBot_Charges.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#MediBot_Preprocessor_lib.py
|
|
2
2
|
from collections import OrderedDict, defaultdict
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
|
-
import os, csv, sys
|
|
4
|
+
import os, csv, sys, time
|
|
5
5
|
import chardet # Ensure chardet is imported
|
|
6
6
|
|
|
7
7
|
# Add the parent directory of the project to the Python path
|
|
@@ -120,8 +120,8 @@ def load_csv_data(csv_file_path):
|
|
|
120
120
|
# Clean the headers
|
|
121
121
|
cleaned_headers = clean_header(reader.fieldnames)
|
|
122
122
|
|
|
123
|
-
#
|
|
124
|
-
header_mapping = {
|
|
123
|
+
# PERFORMANCE FIX: Use zip() instead of range(len()) for header mapping
|
|
124
|
+
header_mapping = {clean: orig for clean, orig in zip(cleaned_headers, reader.fieldnames)}
|
|
125
125
|
|
|
126
126
|
# Process the remaining rows - optimize by pre-allocating the list
|
|
127
127
|
csv_data = []
|
|
@@ -129,9 +129,8 @@ def load_csv_data(csv_file_path):
|
|
|
129
129
|
# csv_data = [None] * estimated_size # if we had row count
|
|
130
130
|
|
|
131
131
|
for row in reader:
|
|
132
|
-
# Use
|
|
133
|
-
cleaned_row =
|
|
134
|
-
for i in range(len(cleaned_headers)))
|
|
132
|
+
# PERFORMANCE FIX: Use zip() instead of range(len()) for row processing
|
|
133
|
+
cleaned_row = {clean: row[header_mapping[clean]] for clean in cleaned_headers}
|
|
135
134
|
csv_data.append(cleaned_row)
|
|
136
135
|
|
|
137
136
|
return csv_data # Return a list of dictionaries
|
|
@@ -161,9 +160,10 @@ def add_columns(csv_data, column_headers):
|
|
|
161
160
|
elif not isinstance(column_headers, list):
|
|
162
161
|
raise ValueError("column_headers should be a list or a string")
|
|
163
162
|
|
|
163
|
+
# PERFORMANCE FIX: Optimize column initialization to avoid nested loop
|
|
164
164
|
for row in csv_data:
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
# Use dict.update() to set multiple columns at once
|
|
166
|
+
row.update({header: '' for header in column_headers})
|
|
167
167
|
|
|
168
168
|
# Extracting the list to a variable for future refactoring:
|
|
169
169
|
def filter_rows(csv_data):
|
|
@@ -304,6 +304,17 @@ def NEW_update_insurance_ids(csv_data, config, crosswalk):
|
|
|
304
304
|
processed_payer_ids = set() # Track processed Payer IDs
|
|
305
305
|
MediLink_ConfigLoader.log("Starting update of insurance IDs.", level="INFO")
|
|
306
306
|
|
|
307
|
+
# PERFORMANCE FIX: Pre-build flattened payer lookup cache to avoid nested dictionary access
|
|
308
|
+
payer_cache = {}
|
|
309
|
+
crosswalk_payers = crosswalk.get('payer_id', {})
|
|
310
|
+
for payer_id, details in crosswalk_payers.items():
|
|
311
|
+
payer_cache[payer_id] = {
|
|
312
|
+
'medisoft_id': details.get('medisoft_id', []),
|
|
313
|
+
'medisoft_medicare_id': details.get('medisoft_medicare_id', []),
|
|
314
|
+
'endpoint': details.get('endpoint', None)
|
|
315
|
+
}
|
|
316
|
+
MediLink_ConfigLoader.log("Built payer cache for {} payers".format(len(payer_cache)), level="DEBUG")
|
|
317
|
+
|
|
307
318
|
# Load MAINS data to get mapping from Medisoft ID to MAINS names
|
|
308
319
|
insurance_to_id = load_insurance_data_from_mains(config) # Assuming it returns a dict mapping insurance names to IDs
|
|
309
320
|
MediLink_ConfigLoader.log("Loaded MAINS data for insurance to ID mapping.", level="DEBUG")
|
|
@@ -313,7 +324,9 @@ def NEW_update_insurance_ids(csv_data, config, crosswalk):
|
|
|
313
324
|
for insurance_name, medisoft_id in insurance_to_id.items():
|
|
314
325
|
medisoft_to_mains_names[medisoft_id].append(insurance_name)
|
|
315
326
|
|
|
316
|
-
for row in csv_data:
|
|
327
|
+
for row_idx, row in enumerate(csv_data, 1):
|
|
328
|
+
# PERFORMANCE FIX: Store row index to avoid O(n) csv_data.index() calls later
|
|
329
|
+
row['_row_index'] = row_idx
|
|
317
330
|
ins1_payer_id = row.get('Ins1 Payer ID', '').strip()
|
|
318
331
|
MediLink_ConfigLoader.log("Processing row with Ins1 Payer ID: '{}'.".format(ins1_payer_id), level="DEBUG")
|
|
319
332
|
|
|
@@ -323,20 +336,24 @@ def NEW_update_insurance_ids(csv_data, config, crosswalk):
|
|
|
323
336
|
processed_payer_ids.add(ins1_payer_id) # Add to set
|
|
324
337
|
MediLink_ConfigLoader.log("Marked Payer ID '{}' as processed.".format(ins1_payer_id), level="DEBUG")
|
|
325
338
|
|
|
326
|
-
#
|
|
327
|
-
|
|
339
|
+
# PERFORMANCE FIX: Use flattened cache instead of nested dictionary lookups
|
|
340
|
+
payer_info = payer_cache.get(ins1_payer_id, {})
|
|
341
|
+
medisoft_ids = payer_info.get('medisoft_id', [])
|
|
328
342
|
MediLink_ConfigLoader.log("Retrieved Medisoft IDs for Payer ID '{}': {}".format(ins1_payer_id, medisoft_ids), level="DEBUG")
|
|
329
343
|
|
|
330
344
|
if not medisoft_ids:
|
|
331
345
|
MediLink_ConfigLoader.log("No Medisoft IDs available for Payer ID '{}', creating placeholder entry.".format(ins1_payer_id), level="WARNING")
|
|
332
|
-
# Create a placeholder entry in the crosswalk
|
|
333
|
-
|
|
334
|
-
crosswalk['payer_id'] = {}
|
|
335
|
-
crosswalk['payer_id'][ins1_payer_id] = {
|
|
346
|
+
# Create a placeholder entry in the crosswalk and cache
|
|
347
|
+
placeholder_entry = {
|
|
336
348
|
'medisoft_id': [], # Placeholder for future Medisoft IDs
|
|
337
349
|
'medisoft_medicare_id': [], # Placeholder for future Medicare IDs
|
|
338
350
|
'endpoint': None # Placeholder for future endpoint
|
|
339
351
|
}
|
|
352
|
+
if 'payer_id' not in crosswalk:
|
|
353
|
+
crosswalk['payer_id'] = {}
|
|
354
|
+
crosswalk['payer_id'][ins1_payer_id] = placeholder_entry
|
|
355
|
+
# PERFORMANCE FIX: Update cache with placeholder entry
|
|
356
|
+
payer_cache[ins1_payer_id] = placeholder_entry
|
|
340
357
|
continue # Skip further processing for this Payer ID
|
|
341
358
|
|
|
342
359
|
# If only one Medisoft ID is associated, assign it directly
|
|
@@ -344,7 +361,9 @@ def NEW_update_insurance_ids(csv_data, config, crosswalk):
|
|
|
344
361
|
try:
|
|
345
362
|
medisoft_id = int(medisoft_ids[0])
|
|
346
363
|
row['Ins1 Insurance ID'] = medisoft_id
|
|
347
|
-
|
|
364
|
+
# PERFORMANCE FIX: Use enumerate index instead of csv_data.index() which is O(n)
|
|
365
|
+
row_number = getattr(row, '_row_index', 'Unknown')
|
|
366
|
+
MediLink_ConfigLoader.log("Assigned Medisoft ID '{}' to row number {} with Payer ID '{}'.".format(medisoft_id, row_number, ins1_payer_id), level="DEBUG")
|
|
348
367
|
except ValueError as e:
|
|
349
368
|
MediLink_ConfigLoader.log("Error converting Medisoft ID '{}' to integer for Payer ID '{}': {}".format(medisoft_ids[0], ins1_payer_id, e), level="ERROR")
|
|
350
369
|
row['Ins1 Insurance ID'] = None
|
|
@@ -398,9 +417,10 @@ def update_insurance_ids(csv_data, config, crosswalk):
|
|
|
398
417
|
payer_id, medisoft_ids, medicare_ids), level="DEBUG")
|
|
399
418
|
|
|
400
419
|
# PERFORMANCE FIX: Single pass through CSV data with optimized Medicare ID resolution
|
|
401
|
-
for row in csv_data:
|
|
420
|
+
for row_idx, row in enumerate(csv_data, 1):
|
|
402
421
|
ins1_payer_id = row.get('Ins1 Payer ID', '').strip()
|
|
403
|
-
|
|
422
|
+
# PERFORMANCE FIX: Use enumerate index instead of csv_data.index() which is O(n)
|
|
423
|
+
MediLink_ConfigLoader.log("Processing row #{} with Ins1 Payer ID '{}'.".format(row_idx, ins1_payer_id), level="DEBUG")
|
|
404
424
|
|
|
405
425
|
# Try Medicare ID first, then fall back to regular ID (optimized Medicare processing)
|
|
406
426
|
insurance_id = (payer_id_to_medicare.get(ins1_payer_id) or
|
|
@@ -517,19 +537,22 @@ def update_diagnosis_codes(csv_data):
|
|
|
517
537
|
|
|
518
538
|
MediLink_ConfigLoader.log("BAD IDEA: Processing DOCX files modified between {} and {}.".format(threshold_start, threshold_end), level="INFO")
|
|
519
539
|
|
|
520
|
-
# PERFORMANCE OPTIMIZATION:
|
|
521
|
-
#
|
|
540
|
+
# PERFORMANCE OPTIMIZATION: Batch file system operations with caching
|
|
541
|
+
# Pre-convert threshold timestamps for efficient comparison (Windows XP compatible)
|
|
542
|
+
threshold_start_ts = threshold_start.timestamp() if hasattr(threshold_start, 'timestamp') else time.mktime(threshold_start.timetuple())
|
|
543
|
+
threshold_end_ts = threshold_end.timestamp() if hasattr(threshold_end, 'timestamp') else time.mktime(threshold_end.timetuple())
|
|
544
|
+
|
|
522
545
|
valid_files = []
|
|
523
546
|
try:
|
|
524
|
-
# Use os.scandir()
|
|
547
|
+
# Use os.scandir() with optimized timestamp comparison (XP/3.4.4 compatible)
|
|
525
548
|
with os.scandir(local_storage_path) as entries:
|
|
526
549
|
for entry in entries:
|
|
527
550
|
if entry.name.endswith('.docx'):
|
|
528
551
|
# Get file modification time in single operation
|
|
529
552
|
try:
|
|
530
553
|
stat_info = entry.stat()
|
|
531
|
-
|
|
532
|
-
if
|
|
554
|
+
# Direct timestamp comparison avoids datetime conversion overhead
|
|
555
|
+
if threshold_start_ts <= stat_info.st_mtime <= threshold_end_ts:
|
|
533
556
|
valid_files.append(entry.path)
|
|
534
557
|
except (OSError, ValueError):
|
|
535
558
|
# Skip files with invalid modification times
|
|
@@ -537,6 +560,9 @@ def update_diagnosis_codes(csv_data):
|
|
|
537
560
|
except OSError:
|
|
538
561
|
MediLink_ConfigLoader.log("Error accessing directory: {}".format(local_storage_path), level="ERROR")
|
|
539
562
|
return
|
|
563
|
+
|
|
564
|
+
# PERFORMANCE OPTIMIZATION: Log file count for debugging without processing overhead
|
|
565
|
+
MediLink_ConfigLoader.log("Found {} DOCX files within date threshold".format(len(valid_files)), level="INFO")
|
|
540
566
|
|
|
541
567
|
# PERFORMANCE OPTIMIZATION: Pre-process patient IDs for efficient lookup
|
|
542
568
|
# Create a set of patient IDs from CSV data for faster lookups
|
|
@@ -821,13 +847,12 @@ def capitalize_all_fields(csv_data):
|
|
|
821
847
|
Returns:
|
|
822
848
|
None: The function modifies the csv_data in place.
|
|
823
849
|
"""
|
|
850
|
+
# PERFORMANCE FIX: Optimize uppercase conversion using dict comprehension
|
|
824
851
|
for row in csv_data:
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
# Convert any other non-None values to string and then uppercase
|
|
833
|
-
row[key] = str(value).upper()
|
|
852
|
+
# Single-pass update using dict comprehension
|
|
853
|
+
row.update({
|
|
854
|
+
key: (value.upper() if isinstance(value, str)
|
|
855
|
+
else str(value).upper() if value is not None and not isinstance(value, datetime)
|
|
856
|
+
else value)
|
|
857
|
+
for key, value in row.items()
|
|
858
|
+
})
|
|
@@ -148,6 +148,8 @@ def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicar
|
|
|
148
148
|
|
|
149
149
|
print("-" * 60)
|
|
150
150
|
print("\nDo you want to proceed with the selected patients? (yes/no): ", end='', flush=True)
|
|
151
|
+
# Small delay for Windows XP console buffer synchronization
|
|
152
|
+
time.sleep(0.1)
|
|
151
153
|
proceed = input().lower().strip() in ['yes', 'y']
|
|
152
154
|
|
|
153
155
|
if not proceed:
|
|
@@ -158,6 +160,8 @@ def display_patient_selection_menu(csv_data, reverse_mapping, proceed_as_medicar
|
|
|
158
160
|
while True:
|
|
159
161
|
while True:
|
|
160
162
|
print("\nEnter the number(s) of the patients you wish to proceed with \n(e.g., 1,3,5): ", end='', flush=True)
|
|
163
|
+
# Small delay for Windows XP console buffer synchronization
|
|
164
|
+
time.sleep(0.1)
|
|
161
165
|
selection = input().strip()
|
|
162
166
|
if not selection:
|
|
163
167
|
print("Invalid entry. Please provide at least one number.")
|
|
@@ -233,6 +237,8 @@ def handle_user_interaction(interaction_mode, error_message):
|
|
|
233
237
|
print("4: Exit script")
|
|
234
238
|
print("-" * 60)
|
|
235
239
|
print("Enter your choice (1/2/3/4): ", end='', flush=True)
|
|
240
|
+
# Small delay for Windows XP console buffer synchronization
|
|
241
|
+
time.sleep(0.1)
|
|
236
242
|
choice = input().strip()
|
|
237
243
|
|
|
238
244
|
if choice == '1':
|
|
@@ -267,6 +273,8 @@ def user_interaction(csv_data, interaction_mode, error_message, reverse_mapping)
|
|
|
267
273
|
try:
|
|
268
274
|
# Use a more explicit prompt format for Windows XP
|
|
269
275
|
print("\nAm I processing Medicare patients? (yes/no): ", end='', flush=True)
|
|
276
|
+
# Small delay for Windows XP console buffer synchronization
|
|
277
|
+
time.sleep(0.1)
|
|
270
278
|
response = input().lower().strip()
|
|
271
279
|
if response:
|
|
272
280
|
if response in ['yes', 'y']:
|
|
@@ -78,14 +78,17 @@ def enforce_significant_length(output):
|
|
|
78
78
|
# First line of defense: Replace ' APT ' or ' UNIT ' with ' #' if the original length is longer than 30 characters.
|
|
79
79
|
temp_output = temp_output.replace(' APT ', ' #').replace(' UNIT ', ' #')
|
|
80
80
|
|
|
81
|
-
# Remove spaces in a controlled manner from right to left if still too long
|
|
82
|
-
|
|
81
|
+
# PERFORMANCE FIX: Remove spaces in a controlled manner from right to left if still too long
|
|
82
|
+
# Cache length calculation to avoid repeated calls
|
|
83
|
+
temp_length = len(temp_output)
|
|
84
|
+
while temp_length > 30:
|
|
83
85
|
# Find the last space
|
|
84
86
|
last_space_index = temp_output.rfind(' ')
|
|
85
87
|
if last_space_index == -1:
|
|
86
88
|
break
|
|
87
89
|
# Remove the last space
|
|
88
90
|
temp_output = temp_output[:last_space_index] + temp_output[last_space_index+7:]
|
|
91
|
+
temp_length = len(temp_output) # Update cached length
|
|
89
92
|
|
|
90
93
|
# If still greater than 30, truncate to 30 characters
|
|
91
94
|
if len(temp_output) > 30:
|
|
@@ -407,9 +407,11 @@ def reassemble_year(text):
|
|
|
407
407
|
|
|
408
408
|
# Handle the less common cases where the year might be split as (1,1,2) or (2,1,1) or (1,2,1)
|
|
409
409
|
parts = _DIGIT_PARTS_PATTERN.findall(text)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
parts_len = len(parts)
|
|
411
|
+
if parts_len >= 4:
|
|
412
|
+
# PERFORMANCE FIX: Use direct indexing instead of range(len()) pattern
|
|
413
|
+
max_index = parts_len - 3
|
|
414
|
+
for i in range(max_index):
|
|
413
415
|
candidate = ''.join(parts[i:i + 4])
|
|
414
416
|
if len(candidate) == 4 and candidate.isdigit():
|
|
415
417
|
# More efficient pattern construction
|
|
@@ -204,20 +204,30 @@ def display_table(records):
|
|
|
204
204
|
print("No records to display.")
|
|
205
205
|
return
|
|
206
206
|
|
|
207
|
-
#
|
|
208
|
-
used_fields = [
|
|
207
|
+
# PERFORMANCE FIX: Single-pass optimization - determine used fields and calculate widths in one pass
|
|
208
|
+
used_fields = []
|
|
209
|
+
col_widths = {}
|
|
210
|
+
|
|
211
|
+
# First pass: identify used fields and initialize widths
|
|
212
|
+
for field in new_fieldnames:
|
|
213
|
+
col_widths[field] = len(field) # Header width
|
|
214
|
+
|
|
215
|
+
# Second pass: check for used fields and calculate max widths
|
|
216
|
+
for record in records:
|
|
217
|
+
for field in new_fieldnames:
|
|
218
|
+
value_str = str(record.get(field, ''))
|
|
219
|
+
if value_str.strip() and field not in used_fields:
|
|
220
|
+
used_fields.append(field)
|
|
221
|
+
if field in col_widths:
|
|
222
|
+
col_widths[field] = max(col_widths[field], len(value_str))
|
|
223
|
+
|
|
224
|
+
# Filter col_widths to only used fields
|
|
225
|
+
col_widths = {field: col_widths[field] for field in used_fields}
|
|
209
226
|
|
|
210
227
|
if not used_fields:
|
|
211
228
|
print("No data to display.")
|
|
212
229
|
return
|
|
213
230
|
|
|
214
|
-
# Calculate column widths based on the longest item in each used column
|
|
215
|
-
col_widths = {field: len(field) for field in used_fields}
|
|
216
|
-
|
|
217
|
-
for record in records:
|
|
218
|
-
for field in used_fields:
|
|
219
|
-
col_widths[field] = max(col_widths[field], len(str(record.get(field, ''))))
|
|
220
|
-
|
|
221
231
|
# Create table header
|
|
222
232
|
header = " | ".join("{:<{}}".format(field, col_widths[field]) for field in used_fields)
|
|
223
233
|
print(header)
|
|
@@ -256,20 +266,30 @@ def display_consolidated_records(records):
|
|
|
256
266
|
print("No valid records to display after filtering empty rows.")
|
|
257
267
|
return
|
|
258
268
|
|
|
259
|
-
#
|
|
260
|
-
used_fields = [
|
|
269
|
+
# PERFORMANCE FIX: Single-pass optimization - determine used fields and calculate widths in one pass
|
|
270
|
+
used_fields = []
|
|
271
|
+
col_widths = {}
|
|
272
|
+
|
|
273
|
+
# First pass: initialize column widths with header lengths
|
|
274
|
+
for field in new_fieldnames:
|
|
275
|
+
col_widths[field] = len(field)
|
|
276
|
+
|
|
277
|
+
# Second pass: check for used fields and calculate max widths
|
|
278
|
+
for record in filtered_records:
|
|
279
|
+
for field in new_fieldnames:
|
|
280
|
+
value_str = str(record.get(field, ''))
|
|
281
|
+
if value_str.strip() and field not in used_fields:
|
|
282
|
+
used_fields.append(field)
|
|
283
|
+
if field in col_widths:
|
|
284
|
+
col_widths[field] = max(col_widths[field], len(value_str))
|
|
285
|
+
|
|
286
|
+
# Filter col_widths to only used fields
|
|
287
|
+
col_widths = {field: col_widths[field] for field in used_fields}
|
|
261
288
|
|
|
262
289
|
if not used_fields:
|
|
263
290
|
print("No data to display.")
|
|
264
291
|
return
|
|
265
292
|
|
|
266
|
-
# Calculate column widths based on the longest item in each used column
|
|
267
|
-
col_widths = {field: len(field) for field in used_fields}
|
|
268
|
-
|
|
269
|
-
for record in filtered_records:
|
|
270
|
-
for field in used_fields:
|
|
271
|
-
col_widths[field] = max(col_widths[field], len(str(record.get(field, ''))))
|
|
272
|
-
|
|
273
293
|
# Print header
|
|
274
294
|
header = " | ".join("{:<{}}".format(field, col_widths[field]) for field in used_fields)
|
|
275
295
|
print(header)
|
|
@@ -725,33 +725,39 @@ if __name__ == "__main__":
|
|
|
725
725
|
print(table_header)
|
|
726
726
|
print("-" * len(table_header))
|
|
727
727
|
|
|
728
|
-
#
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
#
|
|
728
|
+
# PERFORMANCE FIX: Optimize patient-payer processing to avoid O(P×N) complexity
|
|
729
|
+
# Instead of nested loops, process each patient once and try payer_ids until success
|
|
730
|
+
# TODO: We should be able to determine the correct payer_id for each patient ahead of time
|
|
731
|
+
# by looking up their insurance information from the CSV data or crosswalk mapping.
|
|
732
|
+
# This would eliminate the need to try multiple payer_ids per patient and make this O(N).
|
|
732
733
|
errors = []
|
|
733
734
|
validation_reports = []
|
|
734
|
-
total_patients = len(patients) * len(payer_ids)
|
|
735
735
|
processed_count = 0
|
|
736
736
|
|
|
737
|
-
for
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
737
|
+
for dob, member_id in patients:
|
|
738
|
+
processed_count += 1
|
|
739
|
+
print("Processing patient {}/{}: Member ID {}, DOB {}".format(
|
|
740
|
+
processed_count, len(patients), member_id, dob))
|
|
741
|
+
|
|
742
|
+
# Try each payer_id for this patient until we get a successful response
|
|
743
|
+
patient_processed = False
|
|
744
|
+
for payer_id in payer_ids:
|
|
742
745
|
try:
|
|
743
|
-
processed_count += 1
|
|
744
|
-
print("Processing patient {}/{}: Member ID {}, DOB {}".format(
|
|
745
|
-
processed_count, total_patients, member_id, dob))
|
|
746
|
-
|
|
747
746
|
# Run with validation enabled only in debug mode
|
|
748
747
|
run_validation = DEBUG_MODE
|
|
749
748
|
eligibility_data = get_eligibility_info(client, payer_id, provider_last_name, dob, member_id, npi, run_validation=run_validation)
|
|
750
749
|
if eligibility_data is not None:
|
|
751
|
-
display_eligibility_info(eligibility_data, dob, member_id, output_file)
|
|
752
|
-
|
|
750
|
+
display_eligibility_info(eligibility_data, dob, member_id, output_file)
|
|
751
|
+
patient_processed = True
|
|
752
|
+
break # Stop trying other payer_ids for this patient once we get a response
|
|
753
753
|
except Exception as e:
|
|
754
|
-
|
|
754
|
+
# Continue trying other payer_ids
|
|
755
|
+
continue
|
|
756
|
+
|
|
757
|
+
# If no payer_id worked for this patient, log the error
|
|
758
|
+
if not patient_processed:
|
|
759
|
+
error_msg = "No successful payer_id found for patient"
|
|
760
|
+
errors.append((dob, member_id, error_msg))
|
|
755
761
|
|
|
756
762
|
# Display errors if any
|
|
757
763
|
if errors:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|