medicafe 0.250711.1__py3-none-any.whl → 0.250720.1__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.

@@ -4,6 +4,8 @@ from datetime import datetime
4
4
  import MediLink_ConfigLoader
5
5
  from MediLink_DataMgmt import parse_fixed_width_data, read_fixed_width_data
6
6
  import MediLink_837p_encoder_library
7
+ # TODO (COB ENHANCEMENT): Import COB library when implementing Medicare and secondary claim support
8
+ # import MediLink_837p_cob_library
7
9
  #from tqdm import tqdm
8
10
 
9
11
  def format_single_claim(patient_data, config, endpoint, transaction_set_control_number, crosswalk, client):
@@ -46,7 +48,18 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
46
48
 
47
49
  # Subscriber information, possibly including endpoint-specific logic
48
50
  segments.extend(MediLink_837p_encoder_library.create_hl_subscriber_segment())
51
+
52
+ # TODO (COB ENHANCEMENT): Enhanced SBR segment creation for COB scenarios
53
+ # Replace the standard SBR segment creation with enhanced COB-aware version
54
+ # Current: segments.append(MediLink_837p_encoder_library.create_sbr_segment(config, patient_data, endpoint))
55
+ # Enhanced:
56
+ # if patient_data.get('claim_type') == 'secondary':
57
+ # enhanced_sbr = MediLink_837p_cob_library.create_enhanced_sbr_segment(patient_data, config, crosswalk)
58
+ # segments.append(enhanced_sbr)
59
+ # else:
60
+ # segments.append(MediLink_837p_encoder_library.create_sbr_segment(config, patient_data, endpoint))
49
61
  segments.append(MediLink_837p_encoder_library.create_sbr_segment(config, patient_data, endpoint))
62
+
50
63
  segments.append(MediLink_837p_encoder_library.create_nm1_subscriber_segment(config, patient_data, endpoint))
51
64
  segments.extend(MediLink_837p_encoder_library.create_subscriber_address_segments(patient_data))
52
65
  segments.append(MediLink_837p_encoder_library.create_dmg_segment(patient_data))
@@ -58,9 +71,36 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
58
71
  segments.extend([MediLink_837p_encoder_library.create_2010BB_payer_information_segment(patient_data)])
59
72
  #segments.extend(MediLink_837p_encoder_library.create_payer_address_segments(config)) OMITTED
60
73
 
74
+ # TODO (COB ENHANCEMENT): COB processing for secondary claims and Medicare adjudication
75
+ # See MediLink_837p_cob_library.process_cob_claim() for complete COB implementation
76
+ # This would include:
77
+ # - Enhanced SBR segments with Medicare payer types (MB/MA)
78
+ # - 2320 loop for other subscriber information
79
+ # - 2330B loop for prior payer (Medicare) information
80
+ # - 2330C loop when patient != subscriber
81
+ # - 2430 loop for service-level COB adjudication
82
+ # - PWK segments for attachment references
83
+ #
84
+ # Integration point for COB processing:
85
+ # if patient_data.get('claim_type') == 'secondary' or patient_data.get('requires_cob_processing'):
86
+ # cob_segments = MediLink_837p_cob_library.process_cob_claim(patient_data, config, crosswalk, client)
87
+ # segments.extend(cob_segments)
88
+
61
89
  # Rendering Provider (2310B Loop)
62
90
  segments.extend(MediLink_837p_encoder_library.create_nm1_rendering_provider_segment(config))
63
91
 
92
+ # TODO (COB ENHANCEMENT): Enhanced CLM segment creation for COB claims
93
+ # Replace standard CLM creation with COB-aware version
94
+ # Current: segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config, crosswalk))
95
+ # Enhanced:
96
+ # if patient_data.get('claim_type') == 'secondary':
97
+ # enhanced_clm = MediLink_837p_cob_library.create_enhanced_clm_segment(patient_data, config, crosswalk)
98
+ # segments.append(enhanced_clm)
99
+ # # Add service line segments separately for COB
100
+ # segments.extend(MediLink_837p_encoder_library.create_service_line_segments(patient_data, config, crosswalk))
101
+ # else:
102
+ # segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config, crosswalk))
103
+
64
104
  # Claim information 2300, 2310C Service Facility and 2400 loop segments
65
105
  segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config, crosswalk))
66
106
 
@@ -10,27 +10,22 @@ if project_dir not in sys.path:
10
10
  from MediBot import MediBot_Preprocessor_lib
11
11
  load_insurance_data_from_mains = MediBot_Preprocessor_lib.load_insurance_data_from_mains
12
12
  from MediBot import MediBot_Crosswalk_Library
13
- from MediLink_API_v3 import fetch_payer_name_from_api
14
-
15
- # Converts date format from one format to another.
16
- def convert_date_format(date_str):
17
- # Parse the input date string into a datetime object using the input format
18
- # Determine the input date format based on the length of the input string
19
- input_format = "%m-%d-%Y" if len(date_str) == 10 else "%m-%d-%y"
20
- date_obj = datetime.strptime(date_str, input_format)
21
- # Format the datetime object into the desired output format and return
22
- return date_obj.strftime("%Y%m%d")
23
-
24
- # Formats date and time according to the specified format.
25
- def format_datetime(dt=None, format_type='date'):
26
- if dt is None:
27
- dt = datetime.now()
28
- if format_type == 'date':
29
- return dt.strftime('%Y%m%d')
30
- elif format_type == 'isa':
31
- return dt.strftime('%y%m%d')
32
- elif format_type == 'time':
33
- return dt.strftime('%H%M')
13
+ from .MediLink_API_v3 import fetch_payer_name_from_api
14
+
15
+ # Import utility functions from utilities module
16
+ from .MediLink_837p_utilities import (
17
+ convert_date_format,
18
+ format_datetime,
19
+ get_user_confirmation,
20
+ prompt_user_for_payer_id,
21
+ format_claim_number,
22
+ generate_segment_counts,
23
+ handle_validation_errors,
24
+ get_output_directory,
25
+ winscp_validate_output_directory
26
+ )
27
+
28
+
34
29
 
35
30
  # Constructs the ST segment for transaction set.
36
31
  def create_st_segment(transaction_set_control_number):
@@ -152,14 +147,18 @@ def create_1000A_submitter_name_segment(patient_data, config, endpoint):
152
147
  # Submitter contact details
153
148
  contact_name = config.get('submitter_name', 'NONE')
154
149
  contact_telephone_number = config.get('submitter_tel', 'NONE')
155
- entity_type_qualifier = '2' # if contact_name else '1' BUG - 1 if submitter_first_name is not empty. Reference: billing_entity_type_qualifier = '1' if billing_provider_firstname else '2'
150
+
151
+ # Get submitter first name to determine entity type qualifier
152
+ submitter_first_name = config.get('submitter_first_name', '')
153
+ # Determine entity_type_qualifier: '1' for individual (with first name), '2' for organization
154
+ entity_type_qualifier = '1' if submitter_first_name else '2' # Make sure that this is correct. Original default was 2.
156
155
 
157
156
  # Construct NM1 segment for the submitter
158
157
  nm1_segment = "NM1*41*{}*{}*****{}*{}~".format(entity_type_qualifier, submitter_name, submitter_id_qualifier, submitter_id) # BUG - need to check submitter_name because this is written as fixed ****** which implies a single entry and not a first and last name. This is weird.
159
158
 
160
159
  # Construct PER segment for the submitter's contact information
161
160
  per_segment = "PER*IC*{}*TE*{}~".format(contact_name, contact_telephone_number)
162
-
161
+
163
162
  return [nm1_segment, per_segment]
164
163
 
165
164
  # Constructs the NM1 segment for the receiver (1000B).
@@ -237,15 +236,7 @@ def create_2010BB_payer_information_segment(parsed_data):
237
236
  # Build NM1 segment using provided payer name and payer ID
238
237
  return build_nm1_segment(payer_name, payer_id)
239
238
 
240
- def get_user_confirmation(prompt_message):
241
- while True:
242
- response = input(prompt_message).strip().lower()
243
- if response in ['yes', 'y']:
244
- return True
245
- elif response in ['no', 'n']:
246
- return False
247
- else:
248
- print("Please respond with 'yes' or 'no'.")
239
+
249
240
 
250
241
  def resolve_payer_name(payer_id, config, primary_endpoint, insurance_name, parsed_data, crosswalk, client):
251
242
  # Check if the payer_id is in the crosswalk with a name already attached to it.
@@ -405,18 +396,7 @@ def handle_missing_payer_id(insurance_name, config, crosswalk, client):
405
396
  MediLink_ConfigLoader.log("User did not confirm the standard insurance name. Manual intervention is required.", config, level="CRITICAL")
406
397
  return None
407
398
 
408
- def prompt_user_for_payer_id(insurance_name):
409
- """
410
- Prompts the user to input the payer ID manually and ensures that a valid alphanumeric ID is provided.
411
- """
412
- while True:
413
- print("Manual intervention required: No payer ID found for insurance name '{}'.".format(insurance_name))
414
- payer_id = input("Please enter the payer ID manually: ").strip()
415
-
416
- if payer_id.isalnum():
417
- return payer_id
418
- else:
419
- print("Error: Payer ID must be alphanumeric. Please try again.")
399
+
420
400
 
421
401
  def build_nm1_segment(payer_name, payer_id):
422
402
  # Step 1: Build NM1 segment using payer name and ID
@@ -539,6 +519,12 @@ def create_sbr_segment(config, parsed_data, endpoint):
539
519
  # Insurance Type Code
540
520
  insurance_type_code = insurance_type_selection(parsed_data)
541
521
 
522
+ # TODO (COB ENHANCEMENT): Enhanced SBR segment for Medicare and COB support
523
+ # For Medicare Part B: SBR09 = "MB"
524
+ # For Medicare Advantage: SBR09 = "MA"
525
+ # For secondary claims: SBR01 = "S"
526
+ # See MediLink_837p_cob_library.create_enhanced_sbr_segment() for implementation
527
+
542
528
  # Construct the SBR segment using the determined codes
543
529
  sbr_segment = "SBR*{responsibility_code}*18*******{insurance_type_code}~".format(
544
530
  responsibility_code=responsibility_code,
@@ -591,6 +577,12 @@ def insurance_type_selection(parsed_data):
591
577
  # Retrieve insurance options with codes and descriptions
592
578
  config, _ = MediLink_ConfigLoader.load_configuration()
593
579
  insurance_options = config['MediLink_Config'].get('insurance_options')
580
+
581
+ # TODO (COB ENHANCEMENT): Enhanced insurance options for Medicare support
582
+ # See MediLink_837p_cob_library.get_enhanced_insurance_options() for Medicare codes:
583
+ # - MB: Medicare Part B
584
+ # - MA: Medicare Advantage
585
+ # - MC: Medicare Part C
594
586
 
595
587
  def prompt_display_insurance_options():
596
588
  # Prompt to display full list
@@ -684,15 +676,7 @@ def create_nm1_rendering_provider_segment(config, is_rendering_provider_differen
684
676
  else:
685
677
  return []
686
678
 
687
- def format_claim_number(chart_number, date_of_service):
688
- # Remove any non-alphanumeric characters from chart number and date
689
- chart_number_alphanumeric = ''.join(filter(str.isalnum, chart_number))
690
- date_of_service_alphanumeric = ''.join(filter(str.isalnum, date_of_service))
691
-
692
- # Combine the alphanumeric components without spaces
693
- formatted_claim_number = chart_number_alphanumeric + date_of_service_alphanumeric
694
-
695
- return formatted_claim_number
679
+
696
680
 
697
681
  # Constructs the CLM and related segments based on parsed data and configuration.
698
682
  def create_clm_and_related_segments(parsed_data, config, crosswalk):
@@ -705,6 +689,14 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
705
689
  Service Line Information (2400 Loop):
706
690
  Verify that the service line number (LX), professional service (SV1), and service date (DTP) segments contain
707
691
  accurate information and are formatted according to the claim's details.
692
+
693
+ TODO (COB ENHANCEMENT): This function needs enhancement for COB scenarios:
694
+ 1. Enhanced CLM segment with proper claim frequency (CLM05-3)
695
+ 2. Support for service-level COB adjudication (2430 loop)
696
+ 3. Integration with 835-derived adjudication data
697
+ 4. PWK segment support for attachments
698
+
699
+ See MediLink_837p_cob_library.create_enhanced_clm_segment() for enhanced implementation.
708
700
  """
709
701
 
710
702
  segments = []
@@ -715,15 +707,22 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
715
707
  formatted_claim_number = format_claim_number(chart_number, date_of_service)
716
708
 
717
709
  # CLM - Claim Information
718
- segments.append("CLM*{}*{}***{}:B:1*Y*A*Y*Y~".format(
710
+ # TODO (COB ENHANCEMENT): Enhanced claim frequency handling
711
+ # For COB claims, CLM05-3 should be "1" (original) unless replacement logic applies
712
+ claim_frequency = "1" # Default to original
713
+ # if parsed_data.get('claim_type') == 'secondary':
714
+ # claim_frequency = "1" # Original for secondary claims
715
+
716
+ segments.append("CLM*{}*{}***{}:B:{}*Y*A*Y*Y~".format(
719
717
  formatted_claim_number,
720
718
  parsed_data['AMOUNT'],
721
- parsed_data['TOS']))
719
+ parsed_data['TOS'],
720
+ claim_frequency))
722
721
 
723
722
  # HI - Health Care Diagnosis Code
724
723
  # Hardcoding "ABK" for ICD-10 codes as they are the only ones used now.
725
724
  medisoft_code = ''.join(filter(str.isalnum, parsed_data['DIAG']))
726
- diagnosis_code = next((key for key, value in crosswalk.get('diagnosis_to_medisoft', {}).items() if value == medisoft_code), None)
725
+ diagnosis_code = next((key for key, value in crosswalk.get('diagnosis_to_medisoft', {}).items() if value == medisoft_code), None)
727
726
 
728
727
  if diagnosis_code is None:
729
728
  error_message = "Diagnosis code is empty for chart number: {}. Please verify. Medisoft code is {}".format(chart_number, medisoft_code)
@@ -743,8 +742,21 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
743
742
  segments.extend(create_service_facility_location_npi_segment(config))
744
743
 
745
744
  # For future reference, SBR - (Loop 2320: OI, NM1 (2330A), N3, N4, NM1 (2330B)) - Other Subscriber Information goes here.
746
-
747
- # LX - Service Line Number
745
+ # TODO (COB ENHANCEMENT): COB loops for secondary claims and Medicare adjudication
746
+ # See MediLink_837p_cob_library for implementation of:
747
+ # - create_2320_other_subscriber_segments() for secondary payer info
748
+ # - create_2330B_prior_payer_segments() for Medicare prior payer
749
+ # - create_2430_service_line_cob_segments() for service-level adjudication
750
+ # - create_2330C_other_subscriber_name_segments() when patient != subscriber
751
+ # TODO (COB ENHANCEMENT): Optional attachment references (PWK) for non-electronic EOB handling
752
+ # See MediLink_837p_cob_library.create_pwk_attachment_segment() for implementation
753
+ # Example: PWK*EB*FX*123456~ for attachment control number
754
+ # if parsed_data.get('requires_attachment'):
755
+ # pwk_segment = MediLink_837p_cob_library.create_pwk_attachment_segment(parsed_data, config)
756
+ # if pwk_segment:
757
+ # segments.append(pwk_segment)
758
+
759
+ # LX - Service Line Counter
748
760
  segments.append("LX*1~")
749
761
 
750
762
  # SV1 - Professional Service
@@ -762,6 +774,14 @@ def create_clm_and_related_segments(parsed_data, config, crosswalk):
762
774
  # 6R - Provider Control Number (Number assigned by information provider company for tracking and billing purposes)
763
775
  # 1 - Reference information as defined for a particular Transaction Set or as specified by the Reference Identification Qualifier
764
776
 
777
+
778
+ # TODO (COB ENHANCEMENT): Service-level COB adjudication (2430 loop)
779
+ # See MediLink_837p_cob_library.create_2430_service_line_cob_segments() for implementation
780
+ # This would include SVD, CAS, and DTP*573 segments for service-level adjudication
781
+ # if parsed_data.get('service_adjudications'):
782
+ # cob_segments = MediLink_837p_cob_library.create_2430_service_line_cob_segments(parsed_data, config, crosswalk)
783
+ # segments.extend(cob_segments)
784
+
765
785
  return segments
766
786
 
767
787
  def get_endpoint_config(config, endpoint):
@@ -892,70 +912,5 @@ def create_interchange_trailer(config, num_transactions, isa13, num_functional_g
892
912
 
893
913
  return ge_segment, iea_segment
894
914
 
895
- # Generates segment counts for the formatted 837P transaction and updates SE segment.
896
- def generate_segment_counts(compiled_segments, transaction_set_control_number):
897
- # Count the number of segments, not including the placeholder SE segment
898
- segment_count = compiled_segments.count('~') # + 1 Including SE segment itself, but seems to be giving errors.
899
-
900
- # Ensure transaction set control number is correctly formatted as a string
901
- formatted_control_number = str(transaction_set_control_number).zfill(4) # Pad to ensure minimum 4 characters
902
915
 
903
- # Construct the SE segment with the actual segment count and the formatted transaction set control_number
904
- se_segment = "SE*{0}*{1}~".format(segment_count, formatted_control_number)
905
916
 
906
- # Assuming the placeholder SE segment was the last segment added before compiling
907
- # This time, we directly replace the placeholder with the correct SE segment
908
- formatted_837p = compiled_segments.rsplit('SE**', 1)[0] + se_segment
909
-
910
- return formatted_837p
911
-
912
- def handle_validation_errors(transaction_set_control_number, validation_errors, config):
913
- for error in validation_errors:
914
- MediLink_ConfigLoader.log("Validation error for transaction set {}: {}".format(transaction_set_control_number, error), config, level="WARNING")
915
-
916
- print("Validation errors encountered for transaction set {}. Errors: {}".format(transaction_set_control_number, validation_errors))
917
- user_input = input("Skip this patient and continue without incrementing transaction set number? (yes/no): ")
918
- if user_input.lower() == 'yes':
919
- print("Skipping patient...")
920
- MediLink_ConfigLoader.log("Skipped processing of transaction set {} due to user decision.".format(transaction_set_control_number), config, level="INFO")
921
- return True # Skip the current patient
922
- else:
923
- print("Processing halted due to validation errors.")
924
- MediLink_ConfigLoader.log("HALT: Processing halted at transaction set {} due to unresolved validation errors.".format(transaction_set_control_number), config, level="ERROR")
925
- sys.exit() # Optionally halt further processing
926
-
927
- def winscp_validate_output_directory(output_directory):
928
- """
929
- Validates the output directory path to ensure it has no spaces.
930
- If spaces are found, prompts the user to input a new path.
931
- If the directory doesn't exist, creates it.
932
- """
933
- while ' ' in output_directory:
934
- print("\nWARNING: The output directory path contains spaces, which can cause issues with upload operations.")
935
- print(" Current proposed path: {}".format(output_directory))
936
- new_path = input("Please enter a new path for the output directory: ")
937
- output_directory = new_path.strip() # Remove leading/trailing spaces
938
-
939
- # Check if the directory exists, if not, create it
940
- if not os.path.exists(output_directory):
941
- os.makedirs(output_directory)
942
- print("INFO: Created output directory: {}".format(output_directory))
943
-
944
- return output_directory
945
-
946
- def get_output_directory(config):
947
- # Retrieve desired default output file path from config
948
- output_directory = config.get('outputFilePath', '').strip()
949
- # BUG (Low SFTP) Add WinSCP validation because of the mishandling of spaces in paths. (This shouldn't need to exist.)
950
- if not output_directory:
951
- print("Output file path is not specified in the configuration.")
952
- output_directory = input("Please enter a valid output directory path: ").strip()
953
-
954
- # Validate the directory path (checks for spaces and existence)
955
- output_directory = winscp_validate_output_directory(output_directory)
956
-
957
- if not os.path.isdir(output_directory):
958
- print("Output directory does not exist or is not accessible. Please check the configuration.")
959
- return None
960
-
961
- return output_directory
@@ -0,0 +1,264 @@
1
+ # MediLink_837p_utilities.py
2
+ """
3
+ 837P Encoder Utility Functions
4
+
5
+ This module contains utility functions extracted from MediLink_837p_encoder_library.py
6
+ to reduce the size and complexity of the main encoder library while avoiding circular imports.
7
+
8
+ Functions included:
9
+ - Date/time formatting utilities
10
+ - User interaction utilities
11
+ - File/path handling utilities
12
+ - Processing utilities
13
+ - Validation utilities
14
+
15
+ Import Strategy:
16
+ This module only imports base Python modules and MediLink_ConfigLoader to avoid
17
+ circular dependencies. Other modules import from this utilities module.
18
+ """
19
+
20
+ from datetime import datetime
21
+ import sys
22
+ import os
23
+ import re
24
+
25
+ # Import MediLink_ConfigLoader for logging functionality
26
+ try:
27
+ from MediLink import MediLink_ConfigLoader
28
+ except ImportError:
29
+ import MediLink_ConfigLoader
30
+
31
+ # =============================================================================
32
+ # DATE/TIME UTILITIES
33
+ # =============================================================================
34
+
35
+ def convert_date_format(date_str):
36
+ """
37
+ Converts date format from one format to another.
38
+
39
+ Parameters:
40
+ - date_str: Date string in MM-DD-YYYY or MM-DD-YY format
41
+
42
+ Returns:
43
+ - Date string in YYYYMMDD format
44
+ """
45
+ # Parse the input date string into a datetime object using the input format
46
+ # Determine the input date format based on the length of the input string
47
+ input_format = "%m-%d-%Y" if len(date_str) == 10 else "%m-%d-%y"
48
+ date_obj = datetime.strptime(date_str, input_format)
49
+ # Format the datetime object into the desired output format and return
50
+ return date_obj.strftime("%Y%m%d")
51
+
52
+ def format_datetime(dt=None, format_type='date'):
53
+ """
54
+ Formats date and time according to the specified format.
55
+
56
+ Parameters:
57
+ - dt: datetime object (defaults to current datetime if None)
58
+ - format_type: 'date', 'isa', or 'time'
59
+
60
+ Returns:
61
+ - Formatted date/time string
62
+ """
63
+ if dt is None:
64
+ dt = datetime.now()
65
+ if format_type == 'date':
66
+ return dt.strftime('%Y%m%d')
67
+ elif format_type == 'isa':
68
+ return dt.strftime('%y%m%d')
69
+ elif format_type == 'time':
70
+ return dt.strftime('%H%M')
71
+
72
+ # =============================================================================
73
+ # USER INTERACTION UTILITIES
74
+ # =============================================================================
75
+
76
+ def get_user_confirmation(prompt_message):
77
+ """
78
+ Prompts user for yes/no confirmation with validation.
79
+
80
+ Parameters:
81
+ - prompt_message: Message to display to user
82
+
83
+ Returns:
84
+ - Boolean: True for yes, False for no
85
+ """
86
+ while True:
87
+ response = input(prompt_message).strip().lower()
88
+ if response in ['yes', 'y']:
89
+ return True
90
+ elif response in ['no', 'n']:
91
+ return False
92
+ else:
93
+ print("Please respond with 'yes' or 'no'.")
94
+
95
+ def prompt_user_for_payer_id(insurance_name):
96
+ """
97
+ Prompts the user to input the payer ID manually and ensures that a valid alphanumeric ID is provided.
98
+
99
+ Parameters:
100
+ - insurance_name: Name of the insurance for context
101
+
102
+ Returns:
103
+ - Valid alphanumeric payer ID
104
+ """
105
+ while True:
106
+ print("Manual intervention required: No payer ID found for insurance name '{}'.".format(insurance_name))
107
+ payer_id = input("Please enter the payer ID manually: ").strip()
108
+
109
+ if payer_id.isalnum():
110
+ return payer_id
111
+ else:
112
+ print("Error: Payer ID must be alphanumeric. Please try again.")
113
+
114
+ # =============================================================================
115
+ # FILE/PATH UTILITIES
116
+ # =============================================================================
117
+
118
+ def format_claim_number(chart_number, date_of_service):
119
+ """
120
+ Formats claim number by combining chart number and date of service.
121
+
122
+ Parameters:
123
+ - chart_number: Patient chart number
124
+ - date_of_service: Date of service
125
+
126
+ Returns:
127
+ - Formatted claim number (alphanumeric only)
128
+ """
129
+ # Remove any non-alphanumeric characters from chart number and date
130
+ chart_number_alphanumeric = ''.join(filter(str.isalnum, chart_number))
131
+ date_of_service_alphanumeric = ''.join(filter(str.isalnum, date_of_service))
132
+
133
+ # Combine the alphanumeric components without spaces
134
+ formatted_claim_number = chart_number_alphanumeric + date_of_service_alphanumeric
135
+
136
+ return formatted_claim_number
137
+
138
+ def winscp_validate_output_directory(output_directory):
139
+ """
140
+ Validates the output directory path to ensure it has no spaces.
141
+ If spaces are found, prompts the user to input a new path.
142
+ If the directory doesn't exist, creates it.
143
+
144
+ Parameters:
145
+ - output_directory: Directory path to validate
146
+
147
+ Returns:
148
+ - Validated directory path
149
+ """
150
+ while ' ' in output_directory:
151
+ print("\nWARNING: The output directory path contains spaces, which can cause issues with upload operations.")
152
+ print(" Current proposed path: {}".format(output_directory))
153
+ new_path = input("Please enter a new path for the output directory: ")
154
+ output_directory = new_path.strip() # Remove leading/trailing spaces
155
+
156
+ # Check if the directory exists, if not, create it
157
+ if not os.path.exists(output_directory):
158
+ os.makedirs(output_directory)
159
+ print("INFO: Created output directory: {}".format(output_directory))
160
+
161
+ return output_directory
162
+
163
+ def get_output_directory(config):
164
+ """
165
+ Retrieves and validates output directory from configuration.
166
+
167
+ Parameters:
168
+ - config: Configuration dictionary
169
+
170
+ Returns:
171
+ - Valid output directory path or None if invalid
172
+ """
173
+ # Retrieve desired default output file path from config
174
+ output_directory = config.get('outputFilePath', '').strip()
175
+ # BUG (Low SFTP) Add WinSCP validation because of the mishandling of spaces in paths. (This shouldn't need to exist.)
176
+ if not output_directory:
177
+ print("Output file path is not specified in the configuration.")
178
+ output_directory = input("Please enter a valid output directory path: ").strip()
179
+
180
+ # Validate the directory path (checks for spaces and existence)
181
+ output_directory = winscp_validate_output_directory(output_directory)
182
+
183
+ if not os.path.isdir(output_directory):
184
+ print("Output directory does not exist or is not accessible. Please check the configuration.")
185
+ return None
186
+
187
+ return output_directory
188
+
189
+ # =============================================================================
190
+ # PROCESSING UTILITIES
191
+ # =============================================================================
192
+
193
+ def generate_segment_counts(compiled_segments, transaction_set_control_number):
194
+ """
195
+ Generates segment counts for the formatted 837P transaction and updates SE segment.
196
+
197
+ Parameters:
198
+ - compiled_segments: String containing compiled 837P segments
199
+ - transaction_set_control_number: Transaction set control number
200
+
201
+ Returns:
202
+ - Formatted 837P string with correct SE segment
203
+ """
204
+ # Count the number of segments, not including the placeholder SE segment
205
+ segment_count = compiled_segments.count('~') # + 1 Including SE segment itself, but seems to be giving errors.
206
+
207
+ # Ensure transaction set control number is correctly formatted as a string
208
+ formatted_control_number = str(transaction_set_control_number).zfill(4) # Pad to ensure minimum 4 characters
209
+
210
+ # Construct the SE segment with the actual segment count and the formatted transaction set control_number
211
+ se_segment = "SE*{0}*{1}~".format(segment_count, formatted_control_number)
212
+
213
+ # Assuming the placeholder SE segment was the last segment added before compiling
214
+ # This time, we directly replace the placeholder with the correct SE segment
215
+ formatted_837p = compiled_segments.rsplit('SE**', 1)[0] + se_segment
216
+
217
+ return formatted_837p
218
+
219
+ # =============================================================================
220
+ # VALIDATION UTILITIES
221
+ # =============================================================================
222
+
223
+ def handle_validation_errors(transaction_set_control_number, validation_errors, config):
224
+ """
225
+ Handles validation errors with user interaction for decision making.
226
+
227
+ Parameters:
228
+ - transaction_set_control_number: Current transaction set control number
229
+ - validation_errors: List of validation errors
230
+ - config: Configuration for logging
231
+
232
+ Returns:
233
+ - Boolean: True to skip patient, False to halt processing
234
+ """
235
+ for error in validation_errors:
236
+ MediLink_ConfigLoader.log("Validation error for transaction set {}: {}".format(transaction_set_control_number, error), config, level="WARNING")
237
+
238
+ print("Validation errors encountered for transaction set {}. Errors: {}".format(transaction_set_control_number, validation_errors))
239
+ user_input = input("Skip this patient and continue without incrementing transaction set number? (yes/no): ")
240
+ if user_input.lower() == 'yes':
241
+ print("Skipping patient...")
242
+ MediLink_ConfigLoader.log("Skipped processing of transaction set {} due to user decision.".format(transaction_set_control_number), config, level="INFO")
243
+ return True # Skip the current patient
244
+ else:
245
+ print("Processing halted due to validation errors.")
246
+ MediLink_ConfigLoader.log("HALT: Processing halted at transaction set {} due to unresolved validation errors.".format(transaction_set_control_number), config, level="ERROR")
247
+ sys.exit() # Optionally halt further processing
248
+
249
+ # =============================================================================
250
+ # UTILITY FUNCTION REGISTRY
251
+ # =============================================================================
252
+
253
+ # Export all utility functions for easy importing
254
+ __all__ = [
255
+ 'convert_date_format',
256
+ 'format_datetime',
257
+ 'get_user_confirmation',
258
+ 'prompt_user_for_payer_id',
259
+ 'format_claim_number',
260
+ 'winscp_validate_output_directory',
261
+ 'get_output_directory',
262
+ 'generate_segment_counts',
263
+ 'handle_validation_errors'
264
+ ]
@@ -21,9 +21,10 @@ start_date_str = start_date.strftime('%m/%d/%Y')
21
21
  billing_provider_tin = config['MediLink_Config'].get('billing_provider_tin')
22
22
 
23
23
  # Define the list of payer_id's to iterate over
24
- payer_ids = ['87726'] # Default value, getting a bad request error when trying to get all payer_ids.
25
- # Allowed payer id's for UHC
26
- # payer_ids = ['87726', '03432', '96385', '95467', '86050', '86047', '95378', '06111', '37602']
24
+ payer_ids = ['87726'] # Default Value
25
+ # Allowed payer id's for UHC 87726, 03432, 96385, 95467, 86050, 86047, 95378, 37602. This api does not support payerId 06111.
26
+ # payer_ids = ['87726', '03432', '96385', '95467', '86050', '86047', '95378', '37602']
27
+ # Oddly enough, the API is completely ignoring the payerId parameter and returning the exact same dataset for all payer IDs.
27
28
 
28
29
  # Initialize the API client
29
30
  client = MediLink_API_v3.APIClient()