medicafe 0.250728.9__py3-none-any.whl → 0.250805.2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (57) hide show
  1. MediBot/MediBot.bat +233 -19
  2. MediBot/MediBot.py +138 -46
  3. MediBot/MediBot_Crosswalk_Library.py +127 -623
  4. MediBot/MediBot_Crosswalk_Utils.py +618 -0
  5. MediBot/MediBot_Preprocessor.py +72 -17
  6. MediBot/MediBot_Preprocessor_lib.py +470 -76
  7. MediBot/MediBot_UI.py +32 -17
  8. MediBot/MediBot_dataformat_library.py +68 -20
  9. MediBot/MediBot_docx_decoder.py +120 -19
  10. MediBot/MediBot_smart_import.py +180 -0
  11. MediBot/__init__.py +89 -0
  12. MediBot/get_medicafe_version.py +25 -0
  13. MediBot/update_json.py +35 -6
  14. MediBot/update_medicafe.py +19 -1
  15. MediCafe/MediLink_ConfigLoader.py +160 -0
  16. MediCafe/__init__.py +171 -0
  17. MediCafe/__main__.py +314 -0
  18. MediCafe/api_core.py +1098 -0
  19. MediCafe/api_core_backup.py +427 -0
  20. MediCafe/api_factory.py +306 -0
  21. MediCafe/api_utils.py +356 -0
  22. MediCafe/core_utils.py +450 -0
  23. MediCafe/graphql_utils.py +445 -0
  24. MediCafe/logging_config.py +123 -0
  25. MediCafe/logging_demo.py +61 -0
  26. MediCafe/migration_helpers.py +463 -0
  27. MediCafe/smart_import.py +436 -0
  28. MediLink/MediLink_837p_cob_library.py +28 -28
  29. MediLink/MediLink_837p_encoder.py +33 -34
  30. MediLink/MediLink_837p_encoder_library.py +226 -150
  31. MediLink/MediLink_837p_utilities.py +129 -5
  32. MediLink/MediLink_API_Generator.py +83 -60
  33. MediLink/MediLink_API_v3.py +1 -1
  34. MediLink/MediLink_ClaimStatus.py +177 -31
  35. MediLink/MediLink_DataMgmt.py +378 -63
  36. MediLink/MediLink_Decoder.py +20 -1
  37. MediLink/MediLink_Deductible.py +155 -28
  38. MediLink/MediLink_Display_Utils.py +72 -0
  39. MediLink/MediLink_Down.py +127 -5
  40. MediLink/MediLink_Gmail.py +720 -653
  41. MediLink/MediLink_PatientProcessor.py +257 -0
  42. MediLink/MediLink_UI.py +85 -71
  43. MediLink/MediLink_Up.py +28 -4
  44. MediLink/MediLink_insurance_utils.py +227 -230
  45. MediLink/MediLink_main.py +248 -0
  46. MediLink/MediLink_smart_import.py +264 -0
  47. MediLink/__init__.py +93 -1
  48. MediLink/insurance_type_integration_test.py +13 -3
  49. MediLink/test.py +1 -1
  50. MediLink/test_timing.py +59 -0
  51. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/METADATA +1 -1
  52. medicafe-0.250805.2.dist-info/RECORD +81 -0
  53. medicafe-0.250805.2.dist-info/entry_points.txt +2 -0
  54. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/top_level.txt +1 -0
  55. medicafe-0.250728.9.dist-info/RECORD +0 -59
  56. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/LICENSE +0 -0
  57. {medicafe-0.250728.9.dist-info → medicafe-0.250805.2.dist-info}/WHEEL +0 -0
@@ -1,64 +1,101 @@
1
1
  # MediLink_837p_encoder_library.py
2
2
  from datetime import datetime
3
3
  import sys, os
4
- from MediLink import MediLink_ConfigLoader
4
+ # Set up paths and use core utilities
5
+ import os, sys
6
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
7
+ from MediCafe.core_utils import get_shared_config_loader
8
+ MediLink_ConfigLoader = get_shared_config_loader()
9
+ import re
5
10
 
6
11
  project_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
7
12
  if project_dir not in sys.path:
8
13
  sys.path.append(project_dir)
9
14
 
10
- from MediBot import MediBot_Preprocessor_lib
11
- load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
12
- from MediBot import MediBot_Crosswalk_Library
15
+ # Import MediBot modules conditionally to avoid circular imports
16
+ MediBot_Preprocessor_lib = None
17
+ load_insurance_data_from_mains = None
18
+
19
+ try:
20
+ from MediBot import MediBot_Preprocessor_lib
21
+ if hasattr(MediBot_Preprocessor_lib, 'load_insurance_data_from_mains'):
22
+ load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
23
+ except ImportError:
24
+ pass
25
+
26
+ try:
27
+ from MediBot import MediBot_Crosswalk_Library
28
+ except ImportError:
29
+ MediBot_Crosswalk_Library = None
13
30
 
14
31
  # Safe import for API functions - works in multiple contexts
15
32
  try:
16
- from .MediLink_API_v3 import fetch_payer_name_from_api
33
+ from MediCafe import api_core as MediLink_API_v3 # [auto-migrated]
34
+ fetch_payer_name_from_api = getattr(MediLink_API_v3, 'fetch_payer_name_from_api', None)
35
+ except ImportError:
36
+ MediLink_API_v3 = None
37
+ fetch_payer_name_from_api = None
38
+
39
+ # Safe import for utility functions - works in multiple contexts
40
+ try:
41
+ import MediLink_837p_utilities
17
42
  except (ImportError, SystemError):
18
43
  try:
19
- from MediLink_API_v3 import fetch_payer_name_from_api
44
+ import MediLink_837p_utilities
20
45
  except ImportError:
21
- import MediLink_API_v3
22
- fetch_payer_name_from_api = MediLink_API_v3.fetch_payer_name_from_api
46
+ MediLink_837p_utilities = None
23
47
 
24
- # Safe import for utility functions - works in multiple contexts
48
+ # Safe import for UI functions - works in multiple contexts
25
49
  try:
26
- from .MediLink_837p_utilities import (
27
- convert_date_format,
28
- format_datetime,
29
- get_user_confirmation,
30
- prompt_user_for_payer_id,
31
- format_claim_number,
32
- generate_segment_counts,
33
- handle_validation_errors,
34
- get_output_directory,
35
- winscp_validate_output_directory
36
- )
50
+ import MediLink_UI
37
51
  except (ImportError, SystemError):
38
52
  try:
39
- from MediLink_837p_utilities import (
40
- convert_date_format,
41
- format_datetime,
42
- get_user_confirmation,
43
- prompt_user_for_payer_id,
44
- format_claim_number,
45
- generate_segment_counts,
46
- handle_validation_errors,
47
- get_output_directory,
48
- winscp_validate_output_directory
49
- )
53
+ import MediLink_UI
50
54
  except ImportError:
51
- import MediLink_837p_utilities
52
- convert_date_format = MediLink_837p_utilities.convert_date_format
53
- format_datetime = MediLink_837p_utilities.format_datetime
54
- get_user_confirmation = MediLink_837p_utilities.get_user_confirmation
55
- prompt_user_for_payer_id = MediLink_837p_utilities.prompt_user_for_payer_id
56
- format_claim_number = MediLink_837p_utilities.format_claim_number
57
- generate_segment_counts = MediLink_837p_utilities.generate_segment_counts
58
- handle_validation_errors = MediLink_837p_utilities.handle_validation_errors
59
- get_output_directory = MediLink_837p_utilities.get_output_directory
60
- winscp_validate_output_directory = MediLink_837p_utilities.winscp_validate_output_directory
55
+ MediLink_UI = None
61
56
 
57
+ # Import MediBot crosswalk utilities with fallback - using dynamic imports to avoid circular dependencies
58
+ try:
59
+ import MediBot_Crosswalk_Utils
60
+ update_crosswalk_with_corrected_payer_id = MediBot_Crosswalk_Utils.update_crosswalk_with_corrected_payer_id
61
+ update_crosswalk_with_new_payer_id = MediBot_Crosswalk_Utils.update_crosswalk_with_new_payer_id
62
+ except ImportError:
63
+ # Fallback to main library if utility import fails
64
+ try:
65
+ import MediBot_Crosswalk_Library
66
+ update_crosswalk_with_corrected_payer_id = MediBot_Crosswalk_Library.update_crosswalk_with_corrected_payer_id
67
+ update_crosswalk_with_new_payer_id = MediBot_Crosswalk_Library.update_crosswalk_with_new_payer_id
68
+ except (ImportError, AttributeError):
69
+ # If functions are not available, set to None for graceful handling
70
+ update_crosswalk_with_corrected_payer_id = None
71
+ update_crosswalk_with_new_payer_id = None
72
+
73
+ # Import enhanced insurance selection with fallback
74
+ try:
75
+ from MediLink_insurance_utils import safe_insurance_type_selection
76
+ except ImportError:
77
+ safe_insurance_type_selection = None
78
+
79
+ # Import display utilities
80
+ try:
81
+ import MediLink_Display_Utils
82
+ except ImportError:
83
+ MediLink_Display_Utils = None
84
+
85
+ # Re-export commonly used functions for backward compatibility
86
+ if MediLink_837p_utilities:
87
+ get_output_directory = MediLink_837p_utilities.get_output_directory
88
+ format_datetime = MediLink_837p_utilities.format_datetime
89
+ get_user_confirmation = MediLink_837p_utilities.get_user_confirmation
90
+ prompt_user_for_payer_id = MediLink_837p_utilities.prompt_user_for_payer_id
91
+ convert_date_format = MediLink_837p_utilities.convert_date_format
92
+ format_claim_number = MediLink_837p_utilities.format_claim_number
93
+ generate_segment_counts = MediLink_837p_utilities.generate_segment_counts
94
+ handle_validation_errors = MediLink_837p_utilities.handle_validation_errors
95
+ winscp_validate_output_directory = MediLink_837p_utilities.winscp_validate_output_directory
96
+ find_closest_insurance_matches = MediLink_837p_utilities.find_closest_insurance_matches
97
+ prompt_for_insurance_selection = MediLink_837p_utilities.prompt_for_insurance_selection
98
+ build_nm1_segment = MediLink_837p_utilities.build_nm1_segment
62
99
 
63
100
 
64
101
  # Constructs the ST segment for transaction set.
@@ -174,7 +211,7 @@ def create_1000A_submitter_name_segment(patient_data, config, endpoint):
174
211
 
175
212
  # Check if payer_id is Florida Blue (00590 or BCBSF) and assign submitter_id accordingly
176
213
  if payer_id in ['00590', 'BCBSF']:
177
- submitter_id = endpoint_config.get('nm_109_bcbsf', 'DEFAULT BCBSF ID')
214
+ submitter_id = endpoint_config.get('nm_109_bcbs', 'DEFAULT BCBSF ID')
178
215
  else:
179
216
  submitter_id = endpoint_config.get('nm_109_value', 'DEFAULT ID') # Default ID if not specified in endpoint
180
217
 
@@ -329,7 +366,9 @@ def resolve_payer_name(payer_id, config, primary_endpoint, insurance_name, parse
329
366
  exit(1)
330
367
 
331
368
  # Step 9: Update the crosswalk with the corrected Payer ID
332
- if MediBot_Crosswalk_Library.update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk):
369
+ # Note: update_crosswalk_with_corrected_payer_id is imported at module level
370
+
371
+ if update_crosswalk_with_corrected_payer_id(client, payer_id, corrected_payer_id, config, crosswalk):
333
372
  return resolved_name
334
373
  else:
335
374
  print("Failed to update crosswalk with the corrected Payer ID.")
@@ -358,8 +397,8 @@ def resolve_payer_name(payer_id, config, primary_endpoint, insurance_name, parse
358
397
 
359
398
  # Placeholder for future implementation:
360
399
  # corrected_name = input("Please enter the correct insurance name: ")
361
- # MediLink_ConfigLoader.log(f"User provided corrected insurance name: {corrected_name}", config, level="INFO")
362
- # print(f"Using manually entered insurance name: {corrected_name}")
400
+ # MediLink_ConfigLoader.log("User provided corrected insurance name: {}".format(corrected_name), config, level="INFO")
401
+ # print("Using manually entered insurance name: {}".format(corrected_name))
363
402
  # TODO: Implement update_crosswalk_with_corrected_payer_name(corrected_name, payer_id, config, crosswalk)
364
403
  # return corrected_name
365
404
 
@@ -416,9 +455,10 @@ def handle_missing_payer_id(insurance_name, config, crosswalk, client):
416
455
  confirmation_prompt = "Is the standard insurance name '{}' correct? (yes/no): ".format(standard_insurance_name)
417
456
  if get_user_confirmation(confirmation_prompt):
418
457
  # Step 7: Update the crosswalk with the new payer ID and insurance name mapping
458
+ # Note: update_crosswalk_with_new_payer_id is imported at module level
419
459
  try:
420
460
  MediLink_ConfigLoader.log("Updating crosswalk with payer ID: {} for insurance name: {}".format(payer_id, insurance_name), config, level="DEBUG")
421
- MediBot_Crosswalk_Library.update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config, crosswalk)
461
+ update_crosswalk_with_new_payer_id(client, insurance_name, payer_id, config, crosswalk)
422
462
  return payer_id # Return the payer_id after successful update
423
463
  except Exception as e:
424
464
  # Enhanced error message to include exception type and context
@@ -436,9 +476,7 @@ def handle_missing_payer_id(insurance_name, config, crosswalk, client):
436
476
 
437
477
 
438
478
 
439
- def build_nm1_segment(payer_name, payer_id):
440
- # Step 1: Build NM1 segment using payer name and ID
441
- return "NM1*PR*2*{}*****PI*{}~".format(payer_name, payer_id)
479
+
442
480
 
443
481
  def map_insurance_name_to_payer_id(insurance_name, config, client, crosswalk):
444
482
  """
@@ -466,10 +504,58 @@ def map_insurance_name_to_payer_id(insurance_name, config, client, crosswalk):
466
504
  # Get medisoft ID corresponding to the insurance name
467
505
  medisoft_id = insurance_to_id.get(insurance_name)
468
506
  if medisoft_id is None:
469
- error_message = "No Medisoft ID found for insurance name: {}. Consider checking MAINS directly.".format(insurance_name)
470
- MediLink_ConfigLoader.log(error_message, config, level="ERROR")
471
- # Asking for payer ID here is not the right approach.
472
- raise ValueError(error_message)
507
+ # Find closest matches instead of immediately failing
508
+ closest_matches = find_closest_insurance_matches(insurance_name, insurance_to_id)
509
+
510
+ if closest_matches:
511
+ # Prompt user to select from closest matches
512
+ selected_insurance_name = prompt_for_insurance_selection(insurance_name, closest_matches, config)
513
+
514
+ if selected_insurance_name:
515
+ # Use the selected insurance name
516
+ medisoft_id = insurance_to_id.get(selected_insurance_name)
517
+ MediLink_ConfigLoader.log("Using selected insurance name '{}' for original '{}'".format(selected_insurance_name, insurance_name), config, level="INFO")
518
+ else:
519
+ # User chose manual intervention
520
+ error_message = "CLAIM SUBMISSION CANCELLED: Insurance name '{}' not found in MAINS and user chose manual intervention.".format(insurance_name)
521
+ MediLink_ConfigLoader.log(error_message, config, level="WARNING")
522
+ print("\n" + "="*80)
523
+ print("CLAIM SUBMISSION CANCELLED: MANUAL INTERVENTION REQUIRED")
524
+ print("="*80)
525
+ print("\nThe system cannot automatically process this claim because:")
526
+ print("- Insurance name '{}' was not found in the MAINS database".format(insurance_name))
527
+ print("- Without a valid insurance mapping, the claim cannot be submitted")
528
+ print("\nTo proceed with this claim, you need to:")
529
+ print("1. Verify the correct insurance company name")
530
+ print("2. Ensure the insurance company is in your Medisoft system")
531
+ print("3. Restart the claim submission process once the insurance is properly configured")
532
+ print("="*80)
533
+ raise ValueError(error_message)
534
+ else:
535
+ # No matches found in MAINS
536
+ error_message = "CLAIM SUBMISSION FAILED: Cannot find Medisoft ID for insurance name: '{}'. No similar matches found in MAINS database.".format(insurance_name)
537
+ MediLink_ConfigLoader.log(error_message, config, level="ERROR")
538
+ print("\n" + "="*80)
539
+ print("CLAIM SUBMISSION ERROR: INSURANCE MAPPING FAILED")
540
+ print("="*80)
541
+ print("\nThe system cannot process this claim because:")
542
+ print("- Insurance name '{}' was not found in the MAINS database".format(insurance_name))
543
+ print("- No similar insurance names were found for manual selection")
544
+ print("- Without a Medisoft ID, the system cannot:")
545
+ print(" - Convert the insurance name to a payer ID")
546
+ print(" - Generate the required 837p claim format")
547
+ print(" - Submit the claim to the insurance company")
548
+ print("\nThis typically happens when:")
549
+ print("- The insurance company name is misspelled or abbreviated")
550
+ print("- The insurance company is not in Medisoft")
551
+ print("- The MAINS database is missing or incomplete")
552
+ print("\nTO FIX THIS:")
553
+ print("1. Check the spelling of the insurance company name")
554
+ print("2. Verify the insurance company exists in Medisoft")
555
+ print("3. If the name is correct, llamar a Dani")
556
+ print("4. The insurance company may need to be added")
557
+ print("="*80)
558
+ raise ValueError(error_message)
473
559
 
474
560
  # Convert medisoft_id to string to match the JSON data type
475
561
  medisoft_id_str = str(medisoft_id)
@@ -604,14 +690,14 @@ def insurance_type_selection(parsed_data):
604
690
  MediLink_ConfigLoader.log("insurance_type_selection(parsed_data): {}".format(parsed_data), level="DEBUG")
605
691
 
606
692
  # Try enhanced selection with safe fallback
607
- try:
608
- from MediLink_insurance_utils import safe_insurance_type_selection
609
- return safe_insurance_type_selection(parsed_data, _original_insurance_type_selection_logic)
610
- except ImportError as e:
611
- MediLink_ConfigLoader.log("Enhanced insurance selection not available: {}. Using original logic".format(str(e)), level="INFO")
612
- return _original_insurance_type_selection_logic(parsed_data)
613
- except Exception as e:
614
- MediLink_ConfigLoader.log("Error in enhanced insurance selection: {}. Using original logic".format(str(e)), level="ERROR")
693
+ if safe_insurance_type_selection:
694
+ try:
695
+ return safe_insurance_type_selection(parsed_data, _original_insurance_type_selection_logic)
696
+ except Exception as e:
697
+ MediLink_ConfigLoader.log("Error in enhanced insurance selection: {}. Using original logic".format(str(e)), level="ERROR")
698
+ return _original_insurance_type_selection_logic(parsed_data)
699
+ else:
700
+ MediLink_ConfigLoader.log("Enhanced insurance selection not available. Using original logic", level="INFO")
615
701
  return _original_insurance_type_selection_logic(parsed_data)
616
702
 
617
703
  def _original_insurance_type_selection_logic(parsed_data):
@@ -644,8 +730,8 @@ def _original_insurance_type_selection_logic(parsed_data):
644
730
  display_full_list = input("Do you want to see the full list of insurance options? (yes/no): ").strip().lower()
645
731
 
646
732
  # Display full list if user confirms
647
- if display_full_list in ['yes', 'y']:
648
- MediLink_UI.display_insurance_options(insurance_options)
733
+ if display_full_list in ['yes', 'y'] and MediLink_Display_Utils:
734
+ MediLink_Display_Utils.display_insurance_options(insurance_options)
649
735
 
650
736
  # Horrible menu
651
737
  prompt_display_insurance_options()
@@ -892,8 +978,8 @@ def create_interchange_elements(config, endpoint, transaction_set_control_number
892
978
  isa13 = endpoint_config.get('isa_13_value', '000000001')
893
979
 
894
980
  # Create interchange header and trailer using provided library functions.
895
- isa_header, gs_header = create_interchange_header(config, endpoint, isa13)
896
- ge_trailer, iea_trailer = create_interchange_trailer(config, transaction_set_control_number, isa13)
981
+ isa_header, gs_header, gs06 = create_interchange_header(config, endpoint, isa13)
982
+ ge_trailer, iea_trailer = create_interchange_trailer(config, transaction_set_control_number, isa13, gs06)
897
983
 
898
984
  return isa_header, gs_header, ge_trailer, iea_trailer
899
985
 
@@ -909,7 +995,7 @@ def create_interchange_header(config, endpoint, isa13):
909
995
  - isa13: The ISA13 field value representing the current system time.
910
996
 
911
997
  Returns:
912
- - Tuple containing the ISA and GS segment strings.
998
+ - Tuple containing the ISA segment, GS segment, and group control number (gs06).
913
999
  """
914
1000
  endpoint_config = config['endpoints'].get(endpoint.upper(), {})
915
1001
 
@@ -935,19 +1021,22 @@ def create_interchange_header(config, endpoint, isa13):
935
1021
  # GS Segment
936
1022
  # GS04 YYYYMMDD
937
1023
  # GS06 Group Control Number, Field Length 1/9, must match GE02
938
- # BUG probably move up a function.
939
- gs06 = '1' # Placeholder for now?
1024
+ # FIXED: Generate group control number using similar logic to ISA13 for consistency
1025
+ current_time = datetime.now().strftime('%H%M%S')
1026
+ gs06 = current_time[-6:] # Use last 6 digits of time for uniqueness within reasonable bounds
1027
+ if not gs06 or len(gs06) < 1:
1028
+ gs06 = '1' # Fallback to simple increment
940
1029
 
941
1030
  gs_segment = "GS*HC*{}*{}*{}*{}*{}*X*005010X222A1~".format(
942
1031
  gs_sender_code, gs_receiver_code, format_datetime(), format_datetime(format_type='time'), gs06
943
1032
  )
944
1033
 
945
- MediLink_ConfigLoader.log("Created interchange header for endpoint: {}".format(endpoint), config, level="INFO")
1034
+ MediLink_ConfigLoader.log("Created interchange header for endpoint: {} with group control number: {}".format(endpoint, gs06), config, level="INFO")
946
1035
 
947
- return isa_segment, gs_segment
1036
+ return isa_segment, gs_segment, gs06
948
1037
 
949
1038
  # Generates the GE and IEA segments for the interchange trailer based on the number of transactions and functional groups.
950
- def create_interchange_trailer(config, num_transactions, isa13, num_functional_groups=1):
1039
+ def create_interchange_trailer(config, num_transactions, isa13, gs06, num_functional_groups=1):
951
1040
  """
952
1041
  Generate GE and IEA segments for the interchange trailer.
953
1042
 
@@ -955,6 +1044,7 @@ def create_interchange_trailer(config, num_transactions, isa13, num_functional_g
955
1044
  - config: Configuration dictionary with settings and identifiers.
956
1045
  - num_transactions: The number of transactions within the functional group.
957
1046
  - isa13: The ISA13 field value representing the current system time.
1047
+ - gs06: The group control number from GS segment (must match GE02).
958
1048
  - num_functional_groups: The number of functional groups within the interchange. Default is 1.
959
1049
 
960
1050
  Returns:
@@ -965,14 +1055,13 @@ def create_interchange_trailer(config, num_transactions, isa13, num_functional_g
965
1055
  # Indicates the end of a functional group and provides the count of the number of transactions within it.
966
1056
 
967
1057
  # GE02 Group Control Number, Field Length 1/9, must match GS06 (Header)
968
- # TODO This gs/ge matching should probably move up a function like isa13.
969
- ge02 = '1' #Placeholder for now?
970
- ge_segment = "GE*{}*{}~".format(num_transactions, ge02)
1058
+ # FIXED: Use the gs06 parameter passed from the header generation instead of hardcoded placeholder
1059
+ ge_segment = "GE*{}*{}~".format(num_transactions, gs06)
971
1060
 
972
1061
  # IEA Segment: Interchange Control Trailer (Note: IEA02 needs to equal ISA13)
973
1062
  iea_segment = "IEA*{}*{}~".format(num_functional_groups, isa13)
974
1063
 
975
- MediLink_ConfigLoader.log("Created interchange trailer", config, level="INFO")
1064
+ MediLink_ConfigLoader.log("Created interchange trailer with matching group control number: {}".format(gs06), config, level="INFO")
976
1065
 
977
1066
  return ge_segment, iea_segment
978
1067
 
@@ -1029,78 +1118,65 @@ def validate_claim_data_for_837p(parsed_data, config, crosswalk):
1029
1118
  MediLink_ConfigLoader.log("Updated crosswalk with new diagnosis code: {}, for Medisoft code {}".format(diagnosis_code, medisoft_code), config, level="INFO")
1030
1119
  print("\n[SUCCESS] Added '{}' -> '{}' to crosswalk".format(diagnosis_code, medisoft_code))
1031
1120
  print(" IMPORTANT: You must manually save this to crosswalk.json to persist the change!")
1032
- # 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.
1033
- # TODO This should have been a validation exercise upstream and not a last minute check like this.
1034
-
1035
- # Validate procedure code
1036
- procedure_code = parsed_data.get('CODEA', '').strip()
1037
- if not procedure_code or procedure_code.lower() in ['unknown', 'none', '']:
1038
- # Log the error condition with detailed context
1039
- error_message = "Procedure code is empty for chart number: {}. Please verify. Diagnosis code is {}".format(chart_number, diagnosis_code)
1040
- MediLink_ConfigLoader.log(error_message, config, level="CRITICAL")
1041
-
1042
- print("\n{}".format("="*80))
1043
- print("CRITICAL: Missing procedure code for patient {}".format(chart_number))
1044
- print("{}".format("="*80))
1045
- print("Diagnosis: {}".format(diagnosis_code))
1046
- print("Patient: {}, {}".format(parsed_data.get('LAST', 'Unknown'), parsed_data.get('FIRST', 'Unknown')))
1047
- print("Service Date: {}".format(parsed_data.get('DATE', 'Unknown')))
1048
- print("\nThis procedure code needs to be added to the crosswalk.json file.")
1049
- print("\nCurrent procedure_to_diagnosis mapping format:")
1050
-
1051
- # Show example entries from the crosswalk
1052
- procedure_examples = list(crosswalk.get('procedure_to_diagnosis', {}).items())[:3]
1053
- for proc_code, diagnosis_list in procedure_examples:
1054
- print(" '{}': {}".format(proc_code, diagnosis_list))
1055
-
1056
- print("\nPlease enter the CPT procedure code (e.g., 66984):")
1057
- procedure_code = input("> ").strip()
1058
-
1059
- if not procedure_code:
1060
- raise ValueError("Cannot proceed without procedure code for patient {}".format(chart_number))
1061
-
1062
- # Update the crosswalk dictionary with the new pairing of procedure_code and diagnosis_code
1063
- if procedure_code not in crosswalk.get('procedure_to_diagnosis', {}):
1064
- crosswalk.setdefault('procedure_to_diagnosis', {})[procedure_code] = []
1065
- crosswalk['procedure_to_diagnosis'][procedure_code].append(diagnosis_code)
1066
- MediLink_ConfigLoader.log("Updated crosswalk with new procedure code: {}, for diagnosis code {}".format(procedure_code, diagnosis_code), config, level="INFO")
1067
- print("\n[SUCCESS] Added '{}' -> ['{}'] to crosswalk".format(procedure_code, diagnosis_code))
1068
- print(" IMPORTANT: You must manually save this to crosswalk.json to persist the change!")
1069
- # 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.
1070
- # TODO This should have been a validation exercise upstream and not a last minute check like this.
1071
-
1072
- # Update the validated data
1073
- validated_data['DIAG'] = medisoft_code
1074
- validated_data['CODEA'] = procedure_code
1075
-
1076
- # Validate other critical fields
1077
- critical_fields = {
1078
- 'AMOUNT': 'claim amount',
1079
- 'TOS': 'type of service',
1080
- 'POS': 'place of service',
1081
- 'MINTUES': 'service minutes'
1082
- }
1083
-
1084
- for field, description in critical_fields.items():
1085
- value = validated_data.get(field, '').strip()
1086
- if not value or value.lower() in ['unknown', 'none', '']:
1087
- print("\n{}".format("="*80))
1088
- print("CRITICAL: Missing {} for patient {}".format(description, chart_number))
1089
- print("{}".format("="*80))
1090
- print("Field: {}".format(field))
1091
- print("Patient: {}, {}".format(parsed_data.get('LAST', 'Unknown'), parsed_data.get('FIRST', 'Unknown')))
1092
- print("Service Date: {}".format(parsed_data.get('DATE', 'Unknown')))
1093
- print("\nPlease enter the {}:".format(description))
1094
- new_value = input("> ").strip()
1095
-
1096
- if not new_value:
1097
- raise ValueError("Cannot proceed without {} for patient {}".format(description, chart_number))
1098
-
1099
- validated_data[field] = new_value
1100
- print("\n[SUCCESS] Updated {} to '{}'".format(field, new_value))
1101
-
1102
- print("\n[SUCCESS] All claim data validated for patient {}".format(chart_number))
1103
- return validated_data
1104
-
1105
-
1106
-
1121
+ # TODO (HIGH PRIORITY - Crosswalk Data Persistence and Validation):
1122
+ # PROBLEM: Diagnosis codes are added to crosswalk in memory but not persisted to file.
1123
+ # Changes are lost when application restarts, requiring manual intervention.
1124
+ # Additionally, validation should happen upstream rather than at encoding time.
1125
+ #
1126
+ # CURRENT WORKFLOW ISSUES:
1127
+ # 1. In-memory crosswalk changes are not saved to crosswalk.json automatically
1128
+ # 2. User must manually save changes (error-prone, often forgotten)
1129
+ # 3. Validation happens during encoding (too late in the process)
1130
+ # 4. No backup or rollback mechanism for crosswalk changes
1131
+ # 5. No validation of new mappings before they're added
1132
+ #
1133
+ # IMPLEMENTATION PLAN:
1134
+ #
1135
+ # Phase 1: Automatic Persistence
1136
+ # 1. Identify or create dedicated crosswalk update function:
1137
+ # - Look for existing save_crosswalk_to_file() or similar
1138
+ # - If doesn't exist, create update_crosswalk_mapping(section, key, value, config)
1139
+ # 2. Replace manual save requirement with automatic persistence:
1140
+ # - Call crosswalk persistence function immediately after in-memory update
1141
+ # - Add file locking to prevent concurrent modification conflicts
1142
+ # - Create backup before modification for rollback capability
1143
+ #
1144
+ # Phase 2: Upstream Validation (RECOMMENDED)
1145
+ # 1. Move validation to data preprocessing stage:
1146
+ # - Add validate_diagnosis_codes_in_csv() function in preprocessing
1147
+ # - Check all diagnosis codes against crosswalk before encoding starts
1148
+ # - Batch collect all missing codes and prompt user once
1149
+ # 2. Create interactive crosswalk update session:
1150
+ # - Present all missing codes to user in a single session
1151
+ # - Allow bulk updates with confirmation
1152
+ # - Save all changes at once rather than piecemeal
1153
+ #
1154
+ # Phase 3: Enhanced Validation (FUTURE)
1155
+ # 1. Add ICD-10 code validation against official code sets
1156
+ # 2. Suggest similar codes when exact matches aren't found
1157
+ # 3. Add crosswalk versioning and change tracking
1158
+ # 4. Implement crosswalk sharing across multiple users/systems
1159
+ #
1160
+ # IMMEDIATE IMPLEMENTATION:
1161
+ # 1. Find existing crosswalk persistence function or create one
1162
+ # 2. Add call here: save_crosswalk_to_file(crosswalk, config)
1163
+ # 3. Remove manual save message
1164
+ # 4. Add error handling for file save failures
1165
+ # 5. Add logging for successful crosswalk updates
1166
+ #
1167
+ # FUNCTIONS TO LOOK FOR:
1168
+ # - save_crosswalk_to_file()
1169
+ # - update_crosswalk_json()
1170
+ # - persist_crosswalk_changes()
1171
+ # - write_crosswalk_config()
1172
+ #
1173
+ # FILES TO CHECK:
1174
+ # - MediLink_main.py (likely location for config management)
1175
+ # - MediCafe/MediLink_ConfigLoader.py (configuration management)
1176
+ # - Current file (encoder_library.py) for existing patterns
1177
+ #
1178
+ # TESTING REQUIREMENTS:
1179
+ # - Verify crosswalk changes persist across application restarts
1180
+ # - Test concurrent access scenarios
1181
+ # - Verify backup/restore functionality
1182
+ # - Test with invalid JSON syntax in crosswalk file