medicafe 0.250723.0__tar.gz → 0.250723.2__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.0 → medicafe-0.250723.2}/MediBot/MediBot.py +7 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_Crosswalk_Library.py +27 -9
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_Preprocessor_lib.py +29 -4
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_837p_encoder_library.py +142 -20
- {medicafe-0.250723.0 → medicafe-0.250723.2}/PKG-INFO +1 -1
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/PKG-INFO +1 -1
- {medicafe-0.250723.0 → medicafe-0.250723.2}/setup.py +1 -1
- {medicafe-0.250723.0 → medicafe-0.250723.2}/LICENSE +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MANIFEST.in +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot.bat +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_Charges.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_Post.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_Preprocessor.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_UI.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_dataformat_library.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/MediBot_docx_decoder.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/PDF_to_CSV_Cleaner.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/__init__.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/update_json.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediBot/update_medicafe.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_837p_cob_library.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_837p_encoder.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_837p_utilities.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_API_Generator.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_API_v2.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_API_v3.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_APIs.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Azure.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_ClaimStatus.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_ConfigLoader.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_DataMgmt.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Decoder.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Deductible.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Deductible_Validator.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Down.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Gmail.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_GraphQL.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Mailer.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Parser.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Scan.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Scheduler.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_UI.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_Up.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/MediLink_batch.bat +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/Soumit_api.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/__init__.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/openssl.cnf +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/test.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/test_cob_library.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/test_validation.py +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/MediLink/webapp.html +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/README.md +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/SOURCES.txt +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/dependency_links.txt +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/not-zip-safe +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/requires.txt +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/medicafe.egg-info/top_level.txt +0 -0
- {medicafe-0.250723.0 → medicafe-0.250723.2}/setup.cfg +0 -0
|
@@ -66,13 +66,16 @@ def run_ahk_script(script_content):
|
|
|
66
66
|
|
|
67
67
|
if process.returncode != 0:
|
|
68
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")
|
|
69
70
|
if stderr:
|
|
70
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")
|
|
71
73
|
return # Success - no file cleanup needed
|
|
72
74
|
|
|
73
75
|
except Exception as e:
|
|
74
76
|
# If stdin method fails, fall back to traditional file method
|
|
75
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")
|
|
76
79
|
# Continue to fallback implementation below
|
|
77
80
|
|
|
78
81
|
# Traditional file-based method (fallback or when optimization disabled)
|
|
@@ -87,9 +90,12 @@ def run_ahk_script(script_content):
|
|
|
87
90
|
subprocess.check_call([AHK_EXECUTABLE, temp_script_name]) # Execute the AHK script using the AutoHotkey executable
|
|
88
91
|
except subprocess.CalledProcessError as e:
|
|
89
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")
|
|
90
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")
|
|
91
96
|
except Exception as e:
|
|
92
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")
|
|
93
99
|
traceback.print_exc() # Print the full traceback for debugging purposes
|
|
94
100
|
finally:
|
|
95
101
|
# Delete the temporary script file
|
|
@@ -98,6 +104,7 @@ def run_ahk_script(script_content):
|
|
|
98
104
|
os.unlink(temp_script_name) # Attempt to delete the temporary script file
|
|
99
105
|
except OSError as e:
|
|
100
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")
|
|
101
108
|
# Future Improvement: Implement a cleanup mechanism to handle orphaned temporary files
|
|
102
109
|
|
|
103
110
|
# Global variable to store the last processed entry
|
|
@@ -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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
"""
|
|
@@ -434,28 +434,50 @@ def update_procedure_codes(csv_data, crosswalk):
|
|
|
434
434
|
for diagnosis_code in diagnosis_codes
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
# Initialize
|
|
437
|
+
# Initialize counters for tracking
|
|
438
438
|
updated_count = 0
|
|
439
|
+
missing_medisoft_codes = set()
|
|
440
|
+
missing_procedure_mappings = set()
|
|
439
441
|
|
|
440
442
|
# Update the "Procedure Code" column in the CSV data
|
|
441
443
|
for row_num, row in enumerate(csv_data, start=1):
|
|
442
444
|
try:
|
|
443
445
|
medisoft_code = row.get('Default Diagnosis #1', '').strip()
|
|
444
446
|
diagnosis_code = medisoft_to_diagnosis.get(medisoft_code)
|
|
447
|
+
|
|
445
448
|
if diagnosis_code:
|
|
446
449
|
procedure_code = diagnosis_to_procedure.get(diagnosis_code)
|
|
447
450
|
if procedure_code:
|
|
448
451
|
row['Procedure Code'] = procedure_code
|
|
449
452
|
updated_count += 1
|
|
450
453
|
else:
|
|
451
|
-
|
|
454
|
+
# Track missing procedure mapping
|
|
455
|
+
missing_procedure_mappings.add(diagnosis_code)
|
|
456
|
+
row['Procedure Code'] = "Unknown" # Will be handled by 837p encoder
|
|
457
|
+
MediLink_ConfigLoader.log("Missing procedure mapping for diagnosis code '{}' (Medisoft code: '{}') in row {}".format(
|
|
458
|
+
diagnosis_code, medisoft_code, row_num), level="WARNING")
|
|
452
459
|
else:
|
|
453
|
-
|
|
460
|
+
# Track missing Medisoft code mapping
|
|
461
|
+
if medisoft_code: # Only track if there's actually a code
|
|
462
|
+
missing_medisoft_codes.add(medisoft_code)
|
|
463
|
+
row['Procedure Code'] = "Unknown" # Will be handled by 837p encoder
|
|
464
|
+
MediLink_ConfigLoader.log("Missing Medisoft code mapping for '{}' in row {}".format(
|
|
465
|
+
medisoft_code, row_num), level="WARNING")
|
|
454
466
|
except Exception as e:
|
|
455
467
|
MediLink_ConfigLoader.log("In update_procedure_codes, Error processing row {}: {}".format(row_num, e), level="ERROR")
|
|
456
468
|
|
|
457
|
-
# Log
|
|
469
|
+
# Log summary statistics
|
|
458
470
|
MediLink_ConfigLoader.log("Total {} 'Procedure Code' rows updated.".format(updated_count), level="INFO")
|
|
471
|
+
|
|
472
|
+
if missing_medisoft_codes:
|
|
473
|
+
MediLink_ConfigLoader.log("Missing Medisoft code mappings: {}".format(sorted(missing_medisoft_codes)), level="WARNING")
|
|
474
|
+
print("WARNING: {} Medisoft codes need to be added to diagnosis_to_medisoft mapping: {}".format(
|
|
475
|
+
len(missing_medisoft_codes), sorted(missing_medisoft_codes)))
|
|
476
|
+
|
|
477
|
+
if missing_procedure_mappings:
|
|
478
|
+
MediLink_ConfigLoader.log("Missing procedure mappings for diagnosis codes: {}".format(sorted(missing_procedure_mappings)), level="WARNING")
|
|
479
|
+
print("WARNING: {} diagnosis codes need to be added to procedure_to_diagnosis mapping: {}".format(
|
|
480
|
+
len(missing_procedure_mappings), sorted(missing_procedure_mappings)))
|
|
459
481
|
|
|
460
482
|
return True
|
|
461
483
|
|
|
@@ -573,8 +595,11 @@ def update_diagnosis_codes(csv_data):
|
|
|
573
595
|
# Convert diagnosis code to Medisoft shorthand format.
|
|
574
596
|
medisoft_shorthand = diagnosis_to_medisoft.get(diagnosis_code, None)
|
|
575
597
|
if medisoft_shorthand is None and diagnosis_code:
|
|
598
|
+
# Use fallback logic for missing mapping
|
|
576
599
|
defaulted_code = diagnosis_code.lstrip('H').lstrip('T8').replace('.', '')[-5:]
|
|
577
600
|
medisoft_shorthand = defaulted_code
|
|
601
|
+
MediLink_ConfigLoader.log("Missing diagnosis mapping for '{}', using fallback code '{}'".format(
|
|
602
|
+
diagnosis_code, medisoft_shorthand), level="WARNING")
|
|
578
603
|
MediLink_ConfigLoader.log("Converted diagnosis code to Medisoft shorthand: {}".format(medisoft_shorthand), level="DEBUG")
|
|
579
604
|
|
|
580
605
|
row['Default Diagnosis #1'] = medisoft_shorthand
|
|
@@ -733,41 +733,37 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
|
|
|
733
733
|
See MediLink_837p_cob_library.create_enhanced_clm_segment() for enhanced implementation.
|
|
734
734
|
"""
|
|
735
735
|
|
|
736
|
+
# FINAL LINE OF DEFENSE: Validate all claim data before creating segments
|
|
737
|
+
validated_data = validate_claim_data_for_837p(parsed_data, config, crosswalk)
|
|
738
|
+
|
|
736
739
|
segments = []
|
|
737
740
|
|
|
738
741
|
# Format the claim number
|
|
739
|
-
chart_number =
|
|
740
|
-
date_of_service =
|
|
742
|
+
chart_number = validated_data.get('CHART', '')
|
|
743
|
+
date_of_service = validated_data.get('DATE', '')
|
|
741
744
|
formatted_claim_number = format_claim_number(chart_number, date_of_service)
|
|
742
745
|
|
|
743
746
|
# CLM - Claim Information
|
|
744
747
|
# TODO (COB ENHANCEMENT): Enhanced claim frequency handling
|
|
745
748
|
# For COB claims, CLM05-3 should be "1" (original) unless replacement logic applies
|
|
746
749
|
claim_frequency = "1" # Default to original
|
|
747
|
-
# if
|
|
750
|
+
# if validated_data.get('claim_type') == 'secondary':
|
|
748
751
|
# claim_frequency = "1" # Original for secondary claims
|
|
749
752
|
|
|
750
753
|
segments.append("CLM*{}*{}***{}:B:{}*Y*A*Y*Y~".format(
|
|
751
754
|
formatted_claim_number,
|
|
752
|
-
|
|
753
|
-
|
|
755
|
+
validated_data['AMOUNT'],
|
|
756
|
+
validated_data['TOS'],
|
|
754
757
|
claim_frequency))
|
|
755
758
|
|
|
756
759
|
# HI - Health Care Diagnosis Code
|
|
757
760
|
# Hardcoding "ABK" for ICD-10 codes as they are the only ones used now.
|
|
758
|
-
medisoft_code = ''.join(filter(str.isalnum,
|
|
761
|
+
medisoft_code = ''.join(filter(str.isalnum, validated_data['DIAG']))
|
|
759
762
|
diagnosis_code = next((key for key, value in crosswalk.get('diagnosis_to_medisoft', {}).items() if value == medisoft_code), None)
|
|
760
763
|
|
|
764
|
+
# This should never be None now due to validation, but keeping as safety check
|
|
761
765
|
if diagnosis_code is None:
|
|
762
|
-
|
|
763
|
-
MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
|
|
764
|
-
print(error_message)
|
|
765
|
-
diagnosis_code = input("Enter the complete diagnosis code: ")
|
|
766
|
-
# Update the crosswalk dictionary with the new pairing of diagnosis_code and medisoft_code.
|
|
767
|
-
crosswalk['diagnosis_to_medisoft'][diagnosis_code] = medisoft_code
|
|
768
|
-
MediLink_ConfigLoader.log("Updated crosswalk with new diagnosis code: {}, for Medisoft code {}".format(diagnosis_code, medisoft_code), config, level="INFO")
|
|
769
|
-
# TODO This needs to actually save the .json though which right now I'd like to route through the dedicated function for updating the crosswalk.
|
|
770
|
-
# TODO This should have been a validation exercise upstream and not a last minute check like this.
|
|
766
|
+
raise ValueError("Diagnosis code mapping failed for patient {} with medisoft code {}".format(chart_number, medisoft_code))
|
|
771
767
|
|
|
772
768
|
cleaned_diagnosis_code = ''.join(char for char in diagnosis_code if char.isalnum())
|
|
773
769
|
segments.append("HI*ABK:{}~".format(cleaned_diagnosis_code))
|
|
@@ -795,13 +791,13 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
|
|
|
795
791
|
|
|
796
792
|
# SV1 - Professional Service
|
|
797
793
|
segments.append("SV1*HC:{}:{}*{}*MJ*{}***1~".format(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
794
|
+
validated_data['CODEA'],
|
|
795
|
+
validated_data['POS'],
|
|
796
|
+
validated_data['AMOUNT'],
|
|
797
|
+
validated_data['MINTUES']))
|
|
802
798
|
|
|
803
799
|
# DTP - Date
|
|
804
|
-
segments.append("DTP*472*D8*{}~".format(convert_date_format(
|
|
800
|
+
segments.append("DTP*472*D8*{}~".format(convert_date_format(validated_data['DATE'])))
|
|
805
801
|
|
|
806
802
|
# Is there REF - Line Item Control Number missing here? Private insurance doesn't need it, but Medicare does?
|
|
807
803
|
# segments.append("REF*6R*1~") # REF01, Reference Identification Qualifier; REF02, Line Item Control Number.
|
|
@@ -946,5 +942,131 @@ def create_interchange_trailer(config, num_transactions, isa13, num_functional_g
|
|
|
946
942
|
|
|
947
943
|
return ge_segment, iea_segment
|
|
948
944
|
|
|
945
|
+
def validate_claim_data_for_837p(parsed_data, config, crosswalk):
|
|
946
|
+
"""
|
|
947
|
+
Final line of defense validation for 837P claim data.
|
|
948
|
+
|
|
949
|
+
This function validates that all required fields have valid values before creating
|
|
950
|
+
the 837P claim. If invalid values are found, it provides detailed user guidance
|
|
951
|
+
and allows manual input to correct the data.
|
|
952
|
+
|
|
953
|
+
Parameters:
|
|
954
|
+
- parsed_data: Dictionary containing claim data
|
|
955
|
+
- config: Configuration settings
|
|
956
|
+
- crosswalk: Crosswalk data for mappings
|
|
957
|
+
|
|
958
|
+
Returns:
|
|
959
|
+
- Dictionary with validated claim data, or raises ValueError if validation fails
|
|
960
|
+
"""
|
|
961
|
+
validated_data = parsed_data.copy()
|
|
962
|
+
chart_number = parsed_data.get('CHART', 'UNKNOWN')
|
|
963
|
+
|
|
964
|
+
# Validate diagnosis code
|
|
965
|
+
medisoft_code = ''.join(filter(str.isalnum, parsed_data.get('DIAG', '')))
|
|
966
|
+
diagnosis_code = next((key for key, value in crosswalk.get('diagnosis_to_medisoft', {}).items() if value == medisoft_code), None)
|
|
967
|
+
|
|
968
|
+
if not diagnosis_code:
|
|
969
|
+
# Log the error condition with detailed context
|
|
970
|
+
error_message = "Diagnosis code is empty for chart number: {}. Please verify. Medisoft code is {}".format(chart_number, medisoft_code)
|
|
971
|
+
MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
|
|
972
|
+
|
|
973
|
+
print("\n{}".format("="*80))
|
|
974
|
+
print("CRITICAL: Missing diagnosis code mapping for patient {}".format(chart_number))
|
|
975
|
+
print("{}".format("="*80))
|
|
976
|
+
print("Medisoft code: '{}'".format(medisoft_code))
|
|
977
|
+
print("Patient: {}, {}".format(parsed_data.get('LAST', 'Unknown'), parsed_data.get('FIRST', 'Unknown')))
|
|
978
|
+
print("Service Date: {}".format(parsed_data.get('DATE', 'Unknown')))
|
|
979
|
+
print("\nThis diagnosis code needs to be added to the crosswalk.json file.")
|
|
980
|
+
print("\nCurrent diagnosis_to_medisoft mapping format:")
|
|
981
|
+
|
|
982
|
+
# Show example entries from the crosswalk
|
|
983
|
+
diagnosis_examples = list(crosswalk.get('diagnosis_to_medisoft', {}).items())[:3]
|
|
984
|
+
for full_code, medisoft_short in diagnosis_examples:
|
|
985
|
+
print(" '{}': '{}'".format(full_code, medisoft_short))
|
|
986
|
+
|
|
987
|
+
print("\nPlease enter the complete ICD-10 diagnosis code (e.g., H25.10):")
|
|
988
|
+
diagnosis_code = input("> ").strip()
|
|
989
|
+
|
|
990
|
+
if not diagnosis_code:
|
|
991
|
+
raise ValueError("Cannot proceed without diagnosis code for patient {}".format(chart_number))
|
|
992
|
+
|
|
993
|
+
# Update the crosswalk dictionary with the new pairing of diagnosis_code and medisoft_code
|
|
994
|
+
crosswalk['diagnosis_to_medisoft'][diagnosis_code] = medisoft_code
|
|
995
|
+
MediLink_ConfigLoader.log("Updated crosswalk with new diagnosis code: {}, for Medisoft code {}".format(diagnosis_code, medisoft_code), config, level="INFO")
|
|
996
|
+
print("\n✓ Added '{}' -> '{}' to crosswalk".format(diagnosis_code, medisoft_code))
|
|
997
|
+
print(" IMPORTANT: You must manually save this to crosswalk.json to persist the change!")
|
|
998
|
+
# TODO This needs to actually save the .json though which right now I'd like to route through the dedicated function for updating the crosswalk.
|
|
999
|
+
# TODO This should have been a validation exercise upstream and not a last minute check like this.
|
|
1000
|
+
|
|
1001
|
+
# Validate procedure code
|
|
1002
|
+
procedure_code = parsed_data.get('CODEA', '').strip()
|
|
1003
|
+
if not procedure_code or procedure_code.lower() in ['unknown', 'none', '']:
|
|
1004
|
+
# Log the error condition with detailed context
|
|
1005
|
+
error_message = "Procedure code is empty for chart number: {}. Please verify. Diagnosis code is {}".format(chart_number, diagnosis_code)
|
|
1006
|
+
MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
|
|
1007
|
+
|
|
1008
|
+
print("\n{}".format("="*80))
|
|
1009
|
+
print("CRITICAL: Missing procedure code for patient {}".format(chart_number))
|
|
1010
|
+
print("{}".format("="*80))
|
|
1011
|
+
print("Diagnosis: {}".format(diagnosis_code))
|
|
1012
|
+
print("Patient: {}, {}".format(parsed_data.get('LAST', 'Unknown'), parsed_data.get('FIRST', 'Unknown')))
|
|
1013
|
+
print("Service Date: {}".format(parsed_data.get('DATE', 'Unknown')))
|
|
1014
|
+
print("\nThis procedure code needs to be added to the crosswalk.json file.")
|
|
1015
|
+
print("\nCurrent procedure_to_diagnosis mapping format:")
|
|
1016
|
+
|
|
1017
|
+
# Show example entries from the crosswalk
|
|
1018
|
+
procedure_examples = list(crosswalk.get('procedure_to_diagnosis', {}).items())[:3]
|
|
1019
|
+
for proc_code, diagnosis_list in procedure_examples:
|
|
1020
|
+
print(" '{}': {}".format(proc_code, diagnosis_list))
|
|
1021
|
+
|
|
1022
|
+
print("\nPlease enter the CPT procedure code (e.g., 66984):")
|
|
1023
|
+
procedure_code = input("> ").strip()
|
|
1024
|
+
|
|
1025
|
+
if not procedure_code:
|
|
1026
|
+
raise ValueError("Cannot proceed without procedure code for patient {}".format(chart_number))
|
|
1027
|
+
|
|
1028
|
+
# Update the crosswalk dictionary with the new pairing of procedure_code and diagnosis_code
|
|
1029
|
+
if procedure_code not in crosswalk.get('procedure_to_diagnosis', {}):
|
|
1030
|
+
crosswalk.setdefault('procedure_to_diagnosis', {})[procedure_code] = []
|
|
1031
|
+
crosswalk['procedure_to_diagnosis'][procedure_code].append(diagnosis_code)
|
|
1032
|
+
MediLink_ConfigLoader.log("Updated crosswalk with new procedure code: {}, for diagnosis code {}".format(procedure_code, diagnosis_code), config, level="INFO")
|
|
1033
|
+
print("\n✓ Added '{}' -> ['{}'] to crosswalk".format(procedure_code, diagnosis_code))
|
|
1034
|
+
print(" IMPORTANT: You must manually save this to crosswalk.json to persist the change!")
|
|
1035
|
+
# TODO This needs to actually save the .json though which right now I'd like to route through the dedicated function for updating the crosswalk.
|
|
1036
|
+
# TODO This should have been a validation exercise upstream and not a last minute check like this.
|
|
1037
|
+
|
|
1038
|
+
# Update the validated data
|
|
1039
|
+
validated_data['DIAG'] = medisoft_code
|
|
1040
|
+
validated_data['CODEA'] = procedure_code
|
|
1041
|
+
|
|
1042
|
+
# Validate other critical fields
|
|
1043
|
+
critical_fields = {
|
|
1044
|
+
'AMOUNT': 'claim amount',
|
|
1045
|
+
'TOS': 'type of service',
|
|
1046
|
+
'POS': 'place of service',
|
|
1047
|
+
'MINTUES': 'service minutes'
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
for field, description in critical_fields.items():
|
|
1051
|
+
value = validated_data.get(field, '').strip()
|
|
1052
|
+
if not value or value.lower() in ['unknown', 'none', '']:
|
|
1053
|
+
print("\n{}".format("="*80))
|
|
1054
|
+
print("CRITICAL: Missing {} for patient {}".format(description, chart_number))
|
|
1055
|
+
print("{}".format("="*80))
|
|
1056
|
+
print("Field: {}".format(field))
|
|
1057
|
+
print("Patient: {}, {}".format(parsed_data.get('LAST', 'Unknown'), parsed_data.get('FIRST', 'Unknown')))
|
|
1058
|
+
print("Service Date: {}".format(parsed_data.get('DATE', 'Unknown')))
|
|
1059
|
+
print("\nPlease enter the {}:".format(description))
|
|
1060
|
+
new_value = input("> ").strip()
|
|
1061
|
+
|
|
1062
|
+
if not new_value:
|
|
1063
|
+
raise ValueError("Cannot proceed without {} for patient {}".format(description, chart_number))
|
|
1064
|
+
|
|
1065
|
+
validated_data[field] = new_value
|
|
1066
|
+
print("\n✓ Updated {} to '{}'".format(field, new_value))
|
|
1067
|
+
|
|
1068
|
+
print("\n✓ All claim data validated for patient {}".format(chart_number))
|
|
1069
|
+
return validated_data
|
|
1070
|
+
|
|
949
1071
|
|
|
950
1072
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|