medicafe 0.240419.2__py3-none-any.whl → 0.240613.0__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 (38) hide show
  1. MediBot/MediBot.bat +174 -38
  2. MediBot/MediBot.py +80 -77
  3. MediBot/MediBot_Charges.py +0 -28
  4. MediBot/MediBot_Crosswalk_Library.py +281 -0
  5. MediBot/MediBot_Post.py +0 -0
  6. MediBot/MediBot_Preprocessor.py +138 -211
  7. MediBot/MediBot_Preprocessor_lib.py +496 -0
  8. MediBot/MediBot_UI.py +80 -35
  9. MediBot/MediBot_dataformat_library.py +79 -35
  10. MediBot/MediBot_docx_decoder.py +295 -0
  11. MediBot/update_medicafe.py +46 -8
  12. MediLink/MediLink.py +207 -108
  13. MediLink/MediLink_837p_encoder.py +299 -214
  14. MediLink/MediLink_837p_encoder_library.py +445 -245
  15. MediLink/MediLink_API_v2.py +174 -0
  16. MediLink/MediLink_APIs.py +139 -0
  17. MediLink/MediLink_ConfigLoader.py +44 -32
  18. MediLink/MediLink_DataMgmt.py +297 -89
  19. MediLink/MediLink_Decoder.py +63 -0
  20. MediLink/MediLink_Down.py +73 -102
  21. MediLink/MediLink_ERA_decoder.py +4 -4
  22. MediLink/MediLink_Gmail.py +479 -4
  23. MediLink/MediLink_Mailer.py +0 -0
  24. MediLink/MediLink_Parser.py +111 -0
  25. MediLink/MediLink_Scan.py +0 -0
  26. MediLink/MediLink_Scheduler.py +2 -131
  27. MediLink/MediLink_StatusCheck.py +0 -4
  28. MediLink/MediLink_UI.py +87 -27
  29. MediLink/MediLink_Up.py +301 -45
  30. MediLink/MediLink_batch.bat +1 -1
  31. MediLink/test.py +74 -0
  32. medicafe-0.240613.0.dist-info/METADATA +55 -0
  33. medicafe-0.240613.0.dist-info/RECORD +43 -0
  34. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/WHEEL +5 -5
  35. medicafe-0.240419.2.dist-info/METADATA +0 -19
  36. medicafe-0.240419.2.dist-info/RECORD +0 -32
  37. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/LICENSE +0 -0
  38. {medicafe-0.240419.2.dist-info → medicafe-0.240613.0.dist-info}/top_level.txt +0 -0
@@ -2,37 +2,11 @@ import re
2
2
  from datetime import datetime
3
3
  import argparse
4
4
  import os
5
- import MediLink_837p_encoder_library
6
5
  import MediLink_ConfigLoader
7
6
  from MediLink_DataMgmt import parse_fixed_width_data, read_fixed_width_data
7
+ import MediLink_837p_encoder_library
8
8
  #from tqdm import tqdm
9
9
 
10
- """
11
- Development Task List:
12
-
13
- 1. File Path Management: Enhance the handling of input paths to efficiently manage both individual files and directories, accommodating a range of file processing scenarios.
14
- 2. User Interface Improvement: Advance the CLI for intuitive user interaction, offering clear options for file processing and real-time progress updates.
15
- 3. Validation and Logging: Strengthen validation processes for input data, incorporating thorough checks against business rules and enhanced detailed logging for improved traceability and troubleshooting.
16
- 4. Batch Processing and Output Handling: Enhance output file management to support efficient batch operations, including systematic naming and organization for output files.
17
- 5. Comprehensive Documentation: Maintain up-to-date and detailed documentation within the codebase, ensuring all functions and complex logic are clearly explained.
18
- 6. De-persisting Intermediate Files.
19
- """
20
- def create_interchange_elements(config, endpoint, transaction_set_control_number):
21
- """
22
- Create interchange headers and trailers for an 837P document.
23
-
24
- Parameters:
25
- - config: Configuration settings loaded from a JSON file.
26
- - endpoint_key: The endpoint for which the data is being processed.
27
- - transaction_set_control_number: The starting transaction set control number.
28
-
29
- Returns:
30
- - Tuple containing (ISA header, GS header, GE trailer, IEA trailer).
31
- """
32
- isa_header, gs_header = MediLink_837p_encoder_library.create_interchange_header(config, endpoint)
33
- ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_trailer(config, endpoint, transaction_set_control_number)
34
- return isa_header, gs_header, ge_trailer, iea_trailer
35
-
36
10
  def format_single_claim(patient_data, config, endpoint, transaction_set_control_number):
37
11
  """
38
12
  Formats a single claim into 837P segments based on the provided patient data and endpoint.
@@ -46,6 +20,9 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
46
20
  Returns:
47
21
  - String representation of the formatted 837P claim.
48
22
  """
23
+ # Pre-resolve and enrich with Payer Name and ID for special case handling like Florida Blue.
24
+ patient_data = MediLink_837p_encoder_library.payer_id_to_payer_name(patient_data, config, endpoint)
25
+
49
26
  segments = []
50
27
 
51
28
  # Initialize with standard segments for all claims
@@ -53,7 +30,7 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
53
30
  segments.append(MediLink_837p_encoder_library.create_bht_segment(patient_data))
54
31
 
55
32
  # Submitter Name Segment and PER Contact Information (1000A Loop)
56
- segments.extend(MediLink_837p_encoder_library.create_1000A_submitter_name_segment(config, endpoint))
33
+ segments.extend(MediLink_837p_encoder_library.create_1000A_submitter_name_segment(patient_data, config, endpoint))
57
34
 
58
35
  # Receiver Name Segment (1000B Loop)
59
36
  segments.extend([MediLink_837p_encoder_library.create_1000B_receiver_name_segment(config, endpoint)])
@@ -76,7 +53,10 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
76
53
  segments.append(MediLink_837p_encoder_library.create_dmg_segment(patient_data))
77
54
 
78
55
  # Payer information (2010BB loop)
79
- segments.extend([MediLink_837p_encoder_library.create_2010BB_payer_information_segment(patient_data, config, endpoint)])
56
+ # TODO This function now includes detailed outputs and potential user interactions with the new implementation.
57
+ # The new implementation introduces user inputs directly in the flow, which could disrupt automated batch processes.
58
+ # Ensure that there are mechanisms or workflows in place to handle such interruptions appropriately.
59
+ segments.extend([MediLink_837p_encoder_library.create_2010BB_payer_information_segment(patient_data)])
80
60
  #segments.extend(MediLink_837p_encoder_library.create_payer_address_segments(config)) OMITTED
81
61
 
82
62
  # Rendering Provider (2310B Loop)
@@ -92,8 +72,8 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
92
72
  formatted_837p = MediLink_837p_encoder_library.generate_segment_counts('\n'.join(filter(None, segments)), transaction_set_control_number)
93
73
 
94
74
  # Optionally, print or log the formatted 837P for debugging or verification
95
- # BUG Add chart number to this.
96
- MediLink_837p_encoder_library.log("Formatted 837P for endpoint {}.".format(endpoint), config, level="INFO")
75
+ # TODO (Low UI/usability) Add chart number to this.
76
+ MediLink_ConfigLoader.log("Formatted 837P for endpoint {}.".format(endpoint), config, level="INFO")
97
77
 
98
78
  return formatted_837p
99
79
 
@@ -118,194 +98,217 @@ def write_output_file(document_segments, output_directory, endpoint_key, input_f
118
98
  Returns:
119
99
  - String specifying the path to the successfully created output file. Consider returning a tuple (path, status) for enhanced error handling.
120
100
  """
121
- formatted_837p = '\n'.join(document_segments)
101
+ # Check if document segments are empty
102
+ if not document_segments:
103
+ MediLink_ConfigLoader.log("Error: Empty document segments provided. No output file created.", config, level="ERROR")
104
+ return None
105
+
106
+ # Check if the output directory exists and is writable
107
+ if not os.path.exists(output_directory):
108
+ try:
109
+ os.makedirs(output_directory)
110
+ except OSError as e:
111
+ MediLink_ConfigLoader.log("Error: Failed to create output directory. {}".format(e), config, level="ERROR")
112
+ return None
113
+ elif not os.access(output_directory, os.W_OK):
114
+ MediLink_ConfigLoader.log("Error: Output directory is not writable.", config, level="ERROR")
115
+ return None
116
+
117
+ # Generate new output file path
122
118
  base_name = os.path.splitext(os.path.basename(input_file_path))[0]
123
119
  timestamp = datetime.now().strftime("%m%d%H%M")
124
120
  new_output_file_name = "{}_{}_{}.txt".format(base_name, endpoint_key.lower(), timestamp)
125
121
  new_output_file_path = os.path.join(output_directory, new_output_file_name)
126
122
 
127
- with open(new_output_file_path, 'w') as output_file:
128
- output_file.write(formatted_837p)
129
-
130
- # need to pass config here. for what?
131
- # BUG Validate that the output file path has no spaces in it, Unless this is no issue (skip)
132
- MediLink_837p_encoder_library.log("Successfully converted and saved to {}".format(new_output_file_path), config, level="INFO")
133
-
134
- return new_output_file_path
123
+ # Write formatted 837P document to the output file
124
+ try:
125
+ with open(new_output_file_path, 'w') as output_file:
126
+ output_file.write('\n'.join(document_segments))
127
+ MediLink_ConfigLoader.log("Successfully converted and saved to {}".format(new_output_file_path), config, level="INFO")
128
+ return new_output_file_path
129
+ except Exception as e:
130
+ MediLink_ConfigLoader.log("Error: Failed to write output file. {}".format(e), config, level="ERROR")
131
+ return None
135
132
 
136
133
  def process_file(file_path, config, endpoint_key, transaction_set_control_number):
137
134
  """
138
- Reads, validates, and formats claim data from a given file into the 837P format.
135
+ Process the claim data from a file into the 837P format.
136
+
137
+ Args:
138
+ file_path (str): The path to the file containing the claim data.
139
+ config (dict): Configuration settings loaded from a JSON file.
140
+ endpoint_key (str): The key representing the endpoint for which the claim is being processed.
141
+ transaction_set_control_number (int): The starting transaction set control number for 837P segments.
139
142
 
140
- :param file_path: The path to the file containing claim data.
141
- :param config: Configuration settings loaded from a JSON file.
142
- :param endpoint_key: The key representing the endpoint for which the claim is being processed.
143
- :param transaction_set_control_number: The starting transaction set control number for 837P segments.
144
- :return: A tuple containing the formatted claim segments and the next transaction set control number.
143
+ Returns:
144
+ tuple: A tuple containing the formatted claim segments and the next transaction set control number.
145
145
  """
146
- formatted_claims = []
146
+ valid_claims, validation_errors = read_and_validate_claims(file_path, config)
147
147
 
148
- for personal_info, insurance_info, service_info in read_fixed_width_data(file_path, config):
149
- # Parse and validate claim data
150
- parsed_data = parse_fixed_width_data(personal_info, insurance_info, service_info, config)
151
-
152
- # Assume validate_claim_data is a function that validates the parsed data
153
- # DISABLED
154
- #if not validate_claim_data(parsed_data, config):
155
- # MediLink_837p_encoder_library.log("Validation failed for claim data in file: {}".format(file_path), config, level="ERROR")
156
- # continue # Skip invalid claims
157
-
158
- # Format the claim into 837P segments
159
- formatted_claim = format_single_claim(parsed_data, config, endpoint_key, transaction_set_control_number)
160
- formatted_claims.append(formatted_claim)
161
- transaction_set_control_number += 1 # Increment for each successfully processed claim
148
+ # Handle validation errors
149
+ if validation_errors:
150
+ if not MediLink_837p_encoder_library.handle_validation_errors(transaction_set_control_number, validation_errors, config):
151
+ return None, transaction_set_control_number # Halt processing if the user chooses
162
152
 
163
- # Combine all formatted claims into a single string to be appended to the document segments
164
- formatted_claims_str = '\n'.join(formatted_claims)
165
-
153
+ # Process each valid claim
154
+ formatted_claims, transaction_set_control_number = format_claims(valid_claims, config, endpoint_key, transaction_set_control_number)
155
+
156
+ formatted_claims_str = '\n'.join(formatted_claims) # Join formatted claims into a single string
166
157
  return formatted_claims_str, transaction_set_control_number
167
158
 
168
- def winscp_validate_output_directory(output_directory):
169
- """
170
- Validates the output directory path to ensure it has no spaces.
171
- If spaces are found, prompts the user to input a new path.
172
- If the directory doesn't exist, creates it.
159
+ def read_and_validate_claims(file_path, config):
173
160
  """
174
- while ' ' in output_directory:
175
- print("\nWARNING: The output directory path contains spaces, which can cause issues with upload operations.")
176
- print(" Current proposed path: {}".format(output_directory))
177
- new_path = input("Please enter a new path for the output directory: ")
178
- output_directory = new_path.strip() # Remove leading/trailing spaces
179
-
180
- # Check if the directory exists, if not, create it
181
- if not os.path.exists(output_directory):
182
- os.makedirs(output_directory)
183
- print("INFO: Created output directory: {}".format(output_directory))
184
-
185
- return output_directory
161
+ Read and validate claim data from a file.
186
162
 
187
- def convert_files_for_submission(detailed_patient_data, config):
188
- """
189
- Processes detailed patient data for submission based on their confirmed endpoints,
190
- generating a separate 837P file for each endpoint.
163
+ Args:
164
+ file_path (str): The path to the file containing the claim data.
165
+ config (dict): Configuration settings loaded from a JSON file.
166
+
167
+ Returns:
168
+ tuple: A tuple containing a list of valid parsed data and a list of validation errors.
169
+ """
170
+ valid_claims = [] # List to store valid parsed data
171
+ validation_errors = [] # List to store validation errors
172
+
173
+ # Iterate over data in the file
174
+ for personal_info, insurance_info, service_info, service_info_2, service_info_3 in read_fixed_width_data(file_path):
175
+ # Parse data into a usable format
176
+ parsed_data = parse_fixed_width_data(personal_info, insurance_info, service_info, service_info_2, service_info_3, config.get('MediLink_Config', config))
177
+ # Validate the parsed data
178
+ is_valid, errors = validate_claim_data(parsed_data, config)
179
+ if is_valid:
180
+ valid_claims.append(parsed_data) # Add valid data to the list
181
+ else:
182
+ validation_errors.append(errors) # Add validation errors to the list
183
+ # Log validation failure
184
+ MediLink_ConfigLoader.log("Validation failed for claim data in file: {}. Errors: {}".format(file_path, errors), config, level="ERROR")
185
+
186
+ return valid_claims, validation_errors
187
+
188
+ def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_control_number):
189
+ """
190
+ Formats a list of parsed claim data into 837P segments.
191
191
 
192
192
  Parameters:
193
- - detailed_patient_data: A list containing detailed patient data with endpoint information.
193
+ - parsed_data_list: List of dictionaries containing parsed claim data.
194
194
  - config: Configuration settings loaded from a JSON file.
195
+ - endpoint: The endpoint key representing the specific endpoint.
196
+ - starting_transaction_set_control_number: Starting transaction set control number for 837P segments.
195
197
 
196
198
  Returns:
197
- - A list of paths to the converted files ready for submission.
199
+ - A list of formatted 837P claims and the next transaction set control number.
198
200
  """
201
+ formatted_claims = []
202
+ transaction_set_control_number = starting_transaction_set_control_number
199
203
 
200
- # Initialize a dictionary to hold patient data segregated by confirmed endpoints
201
- data_by_endpoint = {}
202
-
203
- # Populate the dictionary with patient data
204
- for data in detailed_patient_data:
205
- endpoint = data['confirmed_endpoint']
206
- if endpoint not in data_by_endpoint:
207
- data_by_endpoint[endpoint] = []
208
- data_by_endpoint[endpoint].append(data)
209
-
210
- # List to store paths of converted files for each endpoint
211
- converted_files_paths = []
212
-
213
- # Determine the total number of unique endpoints for progress bar
214
- # total_endpoints = len(data_by_endpoint)
204
+ for parsed_data in parsed_data_list:
205
+ formatted_claim = format_single_claim(parsed_data, config, endpoint, transaction_set_control_number)
206
+ formatted_claims.append(formatted_claim)
207
+ transaction_set_control_number += 1 # Increment for each successfully processed claim
215
208
 
216
- # Iterate over each endpoint and process its corresponding patient data
217
- for endpoint, patient_data_list in data_by_endpoint.items():
218
- # tqdm(data_by_endpoint.items(), desc="Creating 837p(s)", total=total_endpoints, ascii=True)
219
- # Each endpoint might have multiple patients' data to be processed into a single 837P file
220
- if patient_data_list:
221
- converted_path = process_claim(config['MediLink_Config'], endpoint, patient_data_list)
222
- if converted_path:
223
- converted_files_paths.append(converted_path)
224
-
225
- return converted_files_paths
209
+ return formatted_claims, transaction_set_control_number
226
210
 
227
- def process_claim(config, endpoint, patient_data_list):
211
+ # Validation Function checks the completeness and correctness of each claim's data
212
+ def validate_claim_data(parsed_data, config, required_fields=[]):
228
213
  """
229
- Processes patient data for a specified endpoint, converting it into the 837P format.
230
- Generates a separate 837P file for each endpoint based on detailed patient data.
214
+ Used by both paths.
215
+
216
+ Validates the completeness and correctness of each claim's data based on configurable requirements.
231
217
 
232
218
  Parameters:
219
+ - parsed_data: Dictionary containing claim data to validate.
233
220
  - config: Configuration settings loaded from a JSON file.
234
- - endpoint_key: The key representing the endpoint for which the data is being processed.
235
- - patient_data_list: A list of dictionaries, each containing detailed patient data.
221
+ - required_fields: Optional list of tuples indicating required fields and their respective regex patterns for validation.
236
222
 
237
223
  Returns:
238
- - Path to the converted file, or None if an error occurs.
239
- """
240
- # Retrieve endpoint-specific configuration
241
- endpoint_config = config['endpoints'].get(endpoint)
242
- if not endpoint_config:
243
- print("Endpoint configuration for {} not found.".format(endpoint))
244
- return None
224
+ - (bool, list): Tuple containing a boolean indicating whether the data is valid and a list of error messages if any.
245
225
 
246
- # Retrieve desired default output file path from config
247
- output_directory = config.get('outputFilePath', '')
226
+ # TODO This required fields thing needs to be redone.
248
227
 
249
- # BUG This is a WinSCP validation because of the mishandling of spaces in paths.
250
- # This shouldn't need to exist.
251
- output_directory = winscp_validate_output_directory(output_directory)
228
+ if required_fields is None:
229
+ required_fields = [
230
+ ('CHART', None),
231
+ ('billing_provider_npi', r'^\d{10}$'),
232
+ ('IPOLICY', None),
233
+ ('CODEA', None),
234
+ ('DATE', r'^\d{8}$'),
235
+ ('AMOUNT', None),
236
+ ('TOS', None),
237
+ ('DIAG', None)
238
+ ]
239
+ """
240
+ errors = []
241
+ MediLink_ConfigLoader.log("Starting claim data validation...")
242
+ if not required_fields:
243
+ # If no required fields are specified, assume validation is true
244
+ return True, []
252
245
 
253
- if not os.path.isdir(output_directory):
254
- print("Output directory does not exist. Please check the configuration.")
255
- return None
256
-
257
- # Initialize document segments with headers
258
- isa_header, gs_header = MediLink_837p_encoder_library.create_interchange_header(config, endpoint)
259
- document_segments = [isa_header, gs_header]
260
-
261
- # Initialize the transaction set control number
262
- transaction_set_control_number = 1
263
-
264
- # Process each patient's data in the list
265
- for patient_data in patient_data_list:
266
- # Assuming validate_claim_data is adapted to handle individual patient data dictionaries
267
- if True: #validate_claim_data(patient_data, config): BUG DISABLED VALIDATION
268
- # The format_single_claim function needs to be adapted to handle individual patient data dictionaries
269
- formatted_claim = format_single_claim(patient_data, config, endpoint, transaction_set_control_number)
270
- document_segments.append(formatted_claim)
271
- transaction_set_control_number += 1
272
-
273
- # Append interchange trailer
274
- ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_trailer(config, endpoint, transaction_set_control_number - 1)
275
- document_segments.extend([ge_trailer, iea_trailer])
246
+ expected_keys = {field[0] for field in required_fields} # Set of expected field keys
247
+ received_keys = set(parsed_data.keys()) # Set of keys present in the parsed data
248
+
249
+ # Check if there is any intersection between expected keys and received keys
250
+ if not expected_keys & received_keys:
251
+ # Log the preview of expected and received keys
252
+ preview_msg = "Validation skipped: No matching fields found between expected and received data."
253
+ error_msg = "{}\nExpected keys: {}\nReceived keys: {}".format(preview_msg, expected_keys, received_keys)
254
+ MediLink_ConfigLoader.log(error_msg, config, level="WARNING")
255
+ print(error_msg) # Optionally print to console for immediate feedback
256
+ return True, [preview_msg] # Return true to say that it's valid data anyway.
257
+
258
+ # Check for missing or empty fields and validate patterns
259
+ for field, pattern in required_fields:
260
+ value = parsed_data.get(field)
261
+ if not value:
262
+ errors.append("Missing or empty field: {}".format(field))
263
+ elif pattern and not re.match(pattern, value):
264
+ errors.append("Invalid format in field {}: {}".format(field, value))
265
+
266
+ # Validate date fields if required and ensure they are in the correct format
267
+ date_field = 'DATE'
268
+ date_value = parsed_data.get(date_field)
269
+ if date_value:
270
+ try:
271
+ datetime.strptime(date_value, "%Y%m%d")
272
+ except ValueError:
273
+ errors.append("Invalid date format: {}".format(date_value))
274
+
275
+ # Log validation errors and return
276
+ if errors:
277
+ for error in errors:
278
+ MediLink_ConfigLoader.log(error, config, level="ERROR")
279
+ return False, errors
276
280
 
277
- # Write the complete 837P document to an output file and return its path
278
- return write_output_file(document_segments, output_directory, endpoint, patient_data_list[0]['file_path'], config)
281
+ return True, []
279
282
 
280
- # Validation Function checks the completeness and correctness of each claim's data
281
- def validate_claim_data(parsed_data, config):
282
- required_fields = ['CHART', 'billing_provider_npi', 'IPOLICY', 'CODEA', 'DATE', 'AMOUNT', 'TOS', 'DIAG']
283
- errors = []
283
+ def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
284
+ """
285
+ Process a single file, create complete 837P document with headers and trailers, and write to output file.
284
286
 
285
- # Check for missing or empty fields
286
- for field in required_fields:
287
- if not parsed_data.get(field):
288
- errors.append("Missing or empty field: {}".format(field))
287
+ Parameters:
288
+ - file_path: Path to the .DAT file to be processed.
289
+ - config: Configuration settings.
290
+ - endpoint: Endpoint key.
291
+ - starting_tscn: Starting Transaction Set Control Number.
292
+ """
293
+ print("Processing: {}".format(file_path))
294
+ MediLink_ConfigLoader.log("Processing: {}".format(file_path))
295
+ formatted_data, transaction_set_control_number = process_file(file_path, config, endpoint, starting_tscn)
296
+ isa_header, gs_header, ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
289
297
 
290
- # Validate NPI format
291
- if not re.match(r'^\d{10}$', parsed_data.get('billing_provider_npi', '')):
292
- errors.append("Invalid NPI format: {}".format(parsed_data.get('billing_provider_npi')))
293
-
294
- # Validate date format
295
- try:
296
- datetime.strptime(parsed_data.get('DATE'), "%Y%m%d")
297
- except ValueError:
298
- errors.append("Invalid date format: {}".format(parsed_data.get('DATE')))
299
-
300
- # Log validation errors
301
- if errors:
302
- for error in errors:
303
- MediLink_837p_encoder_library.log(error, config, level="ERROR")
304
- return False
298
+ # Combine everything into a single document
299
+ complete_document = "{}\n{}\n{}\n{}\n{}".format(
300
+ isa_header,
301
+ gs_header,
302
+ formatted_data,
303
+ ge_trailer,
304
+ iea_trailer
305
+ )
305
306
 
306
- return True
307
+ # Write to output file
308
+ output_file_path = write_output_file([complete_document], config.get('outputFilePath', ''), endpoint, file_path, config)
309
+ print("File processed. Output saved to: {}".format(output_file_path))
307
310
 
308
- if __name__ == "__main__":
311
+ def main():
309
312
  """
310
313
  Converts fixed-width files to 837P format for health claim submissions.
311
314
 
@@ -349,44 +352,126 @@ if __name__ == "__main__":
349
352
 
350
353
  print("Starting the conversion process for {}. Processing {} at '{}'.".format(args.endpoint, 'directory' if args.is_directory else 'file', args.path))
351
354
 
352
- # Load configuration
353
355
  config, _ = MediLink_ConfigLoader.load_configuration()
356
+ config = config.get('MediLink_Config', config)
357
+
358
+ process_files(args.path, config, args.endpoint, args.is_directory)
359
+ print("Conversion complete.")
360
+
361
+ def process_files(path, config, endpoint, is_directory):
362
+ """
363
+ Processes either a single file or all files within a directory.
364
+
365
+ Parameters:
366
+ - path: Path to the input fixed-width file or directory to process.
367
+ - config: Configuration settings loaded from a JSON file.
368
+ - endpoint: The endpoint for which the conversion is intended.
369
+ - is_directory: Boolean flag indicating if the path is a directory.
370
+
371
+ Returns:
372
+ - None
373
+ """
374
+ if is_directory:
375
+ MediLink_ConfigLoader.log("Processing all .DAT files in: {}".format(path))
376
+ for file_name in os.listdir(path):
377
+ if file_name.endswith(".DAT"):
378
+ file_path = os.path.join(path, file_name)
379
+ process_and_write_file(file_path, config, endpoint)
380
+ else:
381
+ MediLink_ConfigLoader.log("Processing the single file: {}".format(path))
382
+ process_and_write_file(path, config, endpoint)
383
+
384
+ if __name__ == "__main__":
385
+ main()
354
386
 
355
- def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
356
- """
357
- Process a single file, create complete 837P document with headers and trailers, and write to output file.
358
-
359
- Parameters:
360
- - file_path: Path to the .DAT file to be processed.
361
- - config: Configuration settings.
362
- - endpoint: Endpoint key.
363
- - starting_tscn: Starting Transaction Set Control Number.
364
- """
365
- print("Processing: {}".format(file_path))
366
- formatted_data, transaction_set_control_number = process_file(file_path, config, endpoint, starting_tscn)
367
- isa_header, gs_header, ge_trailer, iea_trailer = create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
387
+ # The functions below are the ones that are used as non-main library by outside scripts.
388
+ #######################################################################################
389
+
390
+ def convert_files_for_submission(detailed_patient_data, config):
391
+ """
392
+ Processes detailed patient data for submission based on their confirmed endpoints,
393
+ generating a separate 837P file for each endpoint.
394
+
395
+ Parameters:
396
+ - detailed_patient_data: A list containing detailed patient data with endpoint information.
397
+ - config: Configuration settings loaded from a JSON file.
398
+
399
+ Returns:
400
+ - A list of paths to the converted files ready for submission.
401
+ """
402
+
403
+ # Initialize a dictionary to hold patient data segregated by confirmed endpoints
404
+ data_by_endpoint = {}
368
405
 
369
- # Combine everything into a single document
370
- complete_document = "{}\n{}\n{}\n{}\n{}".format(
371
- isa_header,
372
- gs_header,
373
- formatted_data,
374
- ge_trailer,
375
- iea_trailer
376
- )
406
+ # Populate the dictionary with patient data
407
+ for data in detailed_patient_data:
408
+ endpoint = data['confirmed_endpoint']
409
+ if endpoint not in data_by_endpoint:
410
+ data_by_endpoint[endpoint] = []
411
+ data_by_endpoint[endpoint].append(data)
412
+
413
+ # List to store paths of converted files for each endpoint
414
+ converted_files_paths = []
415
+
416
+ # Determine the total number of unique endpoints for progress bar
417
+ # total_endpoints = len(data_by_endpoint)
418
+
419
+ # Iterate over each endpoint and process its corresponding patient data
420
+ for endpoint, patient_data_list in data_by_endpoint.items():
421
+ # tqdm(data_by_endpoint.items(), desc="Creating 837p(s)", total=total_endpoints, ascii=True)
422
+ # Each endpoint might have multiple patients' data to be processed into a single 837P file
423
+ if patient_data_list:
424
+ converted_path = process_claim(config['MediLink_Config'], endpoint, patient_data_list)
425
+ if converted_path:
426
+ converted_files_paths.append(converted_path)
377
427
 
378
- # Write to output file
379
- output_file_path = write_output_file([complete_document], config.get('outputFilePath', ''), endpoint, file_path, config)
380
- print("File processed. Output saved to: {}".format(output_file_path))
428
+ return converted_files_paths
429
+
430
+ def process_claim(config, endpoint, patient_data_list):
431
+ """
432
+ Processes patient data for a specified endpoint, converting it into the 837P format.
433
+ Generates a separate 837P file for each endpoint based on detailed patient data.
434
+
435
+ Parameters:
436
+ - config: Configuration settings loaded from a JSON file.
437
+ - endpoint_key: The key representing the endpoint for which the data is being processed.
438
+ - patient_data_list: A list of dictionaries, each containing detailed patient data.
439
+
440
+ Returns:
441
+ - Path to the converted file, or None if an error occurs.
381
442
 
382
- if args.is_directory:
383
- # Process each .DAT file within the directory
384
- for file_name in os.listdir(args.path):
385
- if file_name.endswith(".DAT"):
386
- file_path = os.path.join(args.path, file_name)
387
- process_and_write_file(file_path, config, args.endpoint)
388
- else:
389
- # Process a single file
390
- process_and_write_file(args.path, config, args.endpoint)
443
+ TODO (LOW) Why are there duplicated interchange flows? Because the arg if we're doing a .dat directory or not.
444
+ Although, that shouldn't be making duplicates of these interchange headers. That's still confusing and could end up making
445
+ duplicate interchange headers because processing .dat in batch might be fast enough to be a problem.
446
+ """
447
+ output_directory = MediLink_837p_encoder_library.get_output_directory(config)
448
+ if not output_directory:
449
+ return None
391
450
 
392
- print("Conversion complete.")
451
+ # Initialize the transaction set control number and document segments
452
+ transaction_set_control_number = 1
453
+ document_segments = []
454
+
455
+ # Process each patient's data in the list
456
+ for patient_data in patient_data_list:
457
+ # Validate each patient's data
458
+ is_valid, validation_errors = validate_claim_data(patient_data, config)
459
+ if is_valid:
460
+ # Format the claim if data is valid
461
+ formatted_claim = format_single_claim(patient_data, config, endpoint, transaction_set_control_number)
462
+ document_segments.append(formatted_claim)
463
+ transaction_set_control_number += 1
464
+ else:
465
+ if MediLink_837p_encoder_library.handle_validation_errors(transaction_set_control_number, validation_errors, config):
466
+ continue # Skip the current patient
467
+
468
+ # Create interchange elements with the final transaction set control number
469
+ isa_header, gs_header, ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
470
+
471
+ # Insert headers at the beginning and append trailers at the end of document segments
472
+ document_segments.insert(0, gs_header)
473
+ document_segments.insert(0, isa_header)
474
+ document_segments.extend([ge_trailer, iea_trailer])
475
+
476
+ # Write the complete 837P document to an output file and return its path
477
+ return write_output_file(document_segments, output_directory, endpoint, patient_data_list[0]['file_path'], config)