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

@@ -1,13 +1,12 @@
1
- import re
1
+ # MediLink_837p_encoder.py
2
+ import re, argparse, os
2
3
  from datetime import datetime
3
- import argparse
4
- import os
5
4
  import MediLink_ConfigLoader
6
5
  from MediLink_DataMgmt import parse_fixed_width_data, read_fixed_width_data
7
6
  import MediLink_837p_encoder_library
8
7
  #from tqdm import tqdm
9
8
 
10
- def format_single_claim(patient_data, config, endpoint, transaction_set_control_number):
9
+ def format_single_claim(patient_data, config, endpoint, transaction_set_control_number, crosswalk, client):
11
10
  """
12
11
  Formats a single claim into 837P segments based on the provided patient data and endpoint.
13
12
 
@@ -21,7 +20,7 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
21
20
  - String representation of the formatted 837P claim.
22
21
  """
23
22
  # 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)
23
+ patient_data = MediLink_837p_encoder_library.payer_id_to_payer_name(patient_data, config, endpoint, crosswalk, client)
25
24
 
26
25
  segments = []
27
26
 
@@ -63,7 +62,7 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
63
62
  segments.extend(MediLink_837p_encoder_library.create_nm1_rendering_provider_segment(config))
64
63
 
65
64
  # Claim information 2300, 2310C Service Facility and 2400 loop segments
66
- segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config))
65
+ segments.extend(MediLink_837p_encoder_library.create_clm_and_related_segments(patient_data, config, crosswalk))
67
66
 
68
67
  # Placeholder for the SE segment to be updated with actual segment count later
69
68
  segments.append("SE**{transaction_set_control_number:04d}~")
@@ -77,33 +76,27 @@ def format_single_claim(patient_data, config, endpoint, transaction_set_control_
77
76
 
78
77
  return formatted_837p
79
78
 
80
- def write_output_file(document_segments, output_directory, endpoint_key, input_file_path, config):
79
+ def write_output_file(document_segments, output_directory, endpoint_key, input_file_path, config, suffix=""):
81
80
  """
82
81
  Writes formatted 837P document segments to an output file with a dynamically generated name.
83
-
84
- Development Roadmap:
85
- - Ensure input `document_segments` is a non-empty list to avoid creating empty files.
86
- - Verify `output_directory` exists and is writable before proceeding. Create the directory if it does not exist.
87
- - Consider parameterizing the file naming convention or providing options for customization to accommodate different organizational needs.
88
- - Implement error handling to gracefully manage file writing failures, potentially returning a status or error message alongside the file path.
89
- - Incorporate logging directly within the function, accepting an optional `config` or `logger` parameter to facilitate tracking of the file writing process and outcomes.
90
- - Update the return value to include both the path to the output file and any relevant status information (e.g., success flag, error message) to enhance downstream error handling and user feedback.
91
-
82
+
92
83
  Parameters:
93
84
  - document_segments: List of strings, where each string is a segment of the 837P document to be written.
94
85
  - output_directory: String specifying the directory where the output file will be saved.
95
86
  - endpoint_key: String specifying the endpoint for which the claim is processed, used in naming the output file.
96
87
  - input_file_path: String specifying the path to the input file being processed, used in naming the output file.
97
-
88
+ - config: Configuration settings for logging and other purposes.
89
+ - suffix: Optional string to differentiate filenames, useful for single-patient processing.
90
+
98
91
  Returns:
99
- - String specifying the path to the successfully created output file. Consider returning a tuple (path, status) for enhanced error handling.
92
+ - String specifying the path to the successfully created output file, or None if an error occurred.
100
93
  """
101
- # Check if document segments are empty
94
+ # Ensure the document segments are not empty
102
95
  if not document_segments:
103
96
  MediLink_ConfigLoader.log("Error: Empty document segments provided. No output file created.", config, level="ERROR")
104
97
  return None
105
98
 
106
- # Check if the output directory exists and is writable
99
+ # Verify the output directory exists and is writable, create if necessary
107
100
  if not os.path.exists(output_directory):
108
101
  try:
109
102
  os.makedirs(output_directory)
@@ -114,13 +107,13 @@ def write_output_file(document_segments, output_directory, endpoint_key, input_f
114
107
  MediLink_ConfigLoader.log("Error: Output directory is not writable.", config, level="ERROR")
115
108
  return None
116
109
 
117
- # Generate new output file path
110
+ # Generate the new output file path
118
111
  base_name = os.path.splitext(os.path.basename(input_file_path))[0]
119
112
  timestamp = datetime.now().strftime("%m%d%H%M")
120
- new_output_file_name = "{}_{}_{}.txt".format(base_name, endpoint_key.lower(), timestamp)
113
+ new_output_file_name = "{}_{}_{}{}.txt".format(base_name, endpoint_key.lower(), timestamp, suffix)
121
114
  new_output_file_path = os.path.join(output_directory, new_output_file_name)
122
115
 
123
- # Write formatted 837P document to the output file
116
+ # Write the document to the output file
124
117
  try:
125
118
  with open(new_output_file_path, 'w') as output_file:
126
119
  output_file.write('\n'.join(document_segments))
@@ -130,7 +123,7 @@ def write_output_file(document_segments, output_directory, endpoint_key, input_f
130
123
  MediLink_ConfigLoader.log("Error: Failed to write output file. {}".format(e), config, level="ERROR")
131
124
  return None
132
125
 
133
- def process_file(file_path, config, endpoint_key, transaction_set_control_number):
126
+ def process_single_file(file_path, config, endpoint_key, transaction_set_control_number, crosswalk): # BUG Duplicate function name??
134
127
  """
135
128
  Process the claim data from a file into the 837P format.
136
129
 
@@ -151,7 +144,7 @@ def process_file(file_path, config, endpoint_key, transaction_set_control_number
151
144
  return None, transaction_set_control_number # Halt processing if the user chooses
152
145
 
153
146
  # Process each valid claim
154
- formatted_claims, transaction_set_control_number = format_claims(valid_claims, config, endpoint_key, transaction_set_control_number)
147
+ formatted_claims, transaction_set_control_number = format_claims(valid_claims, config, endpoint_key, transaction_set_control_number, crosswalk)
155
148
 
156
149
  formatted_claims_str = '\n'.join(formatted_claims) # Join formatted claims into a single string
157
150
  return formatted_claims_str, transaction_set_control_number
@@ -185,7 +178,7 @@ def read_and_validate_claims(file_path, config):
185
178
 
186
179
  return valid_claims, validation_errors
187
180
 
188
- def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_control_number):
181
+ def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_control_number, crosswalk):
189
182
  """
190
183
  Formats a list of parsed claim data into 837P segments.
191
184
 
@@ -202,7 +195,7 @@ def format_claims(parsed_data_list, config, endpoint, starting_transaction_set_c
202
195
  transaction_set_control_number = starting_transaction_set_control_number
203
196
 
204
197
  for parsed_data in parsed_data_list:
205
- formatted_claim = format_single_claim(parsed_data, config, endpoint, transaction_set_control_number)
198
+ formatted_claim = format_single_claim(parsed_data, config, endpoint, transaction_set_control_number, crosswalk)
206
199
  formatted_claims.append(formatted_claim)
207
200
  transaction_set_control_number += 1 # Increment for each successfully processed claim
208
201
 
@@ -280,7 +273,7 @@ def validate_claim_data(parsed_data, config, required_fields=[]):
280
273
 
281
274
  return True, []
282
275
 
283
- def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
276
+ def process_and_write_file(file_path, config, endpoint, crosswalk, starting_tscn=1):
284
277
  """
285
278
  Process a single file, create complete 837P document with headers and trailers, and write to output file.
286
279
 
@@ -292,7 +285,7 @@ def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
292
285
  """
293
286
  print("Processing: {}".format(file_path))
294
287
  MediLink_ConfigLoader.log("Processing: {}".format(file_path))
295
- formatted_data, transaction_set_control_number = process_file(file_path, config, endpoint, starting_tscn)
288
+ formatted_data, transaction_set_control_number = process_single_file(file_path, config, endpoint, starting_tscn, crosswalk)
296
289
  isa_header, gs_header, ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
297
290
 
298
291
  # Combine everything into a single document
@@ -309,6 +302,7 @@ def process_and_write_file(file_path, config, endpoint, starting_tscn=1):
309
302
  print("File processed. Output saved to: {}".format(output_file_path))
310
303
 
311
304
  def main():
305
+ # BUG (MAJOR) THIS NEEDS THE API CLIENT TO BE PASSED INTO THE FUNCTION FOR SINGLE FILE PROCESSING.
312
306
  """
313
307
  Converts fixed-width files to 837P format for health claim submissions.
314
308
 
@@ -335,7 +329,7 @@ def main():
335
329
  parser.add_argument(
336
330
  "-e", "--endpoint",
337
331
  required=True,
338
- choices=["AVAILITY", "OPTUMEDI", "PNT_DATA"],
332
+ choices=["AVAILITY", "OPTUMEDI", "PNT_DATA", "UHCAPI", "CLAIMSHUTTLE"], # This should read from the config?
339
333
  help="Specify the endpoint for which the conversion is intended."
340
334
  )
341
335
  parser.add_argument(
@@ -352,13 +346,13 @@ def main():
352
346
 
353
347
  print("Starting the conversion process for {}. Processing {} at '{}'.".format(args.endpoint, 'directory' if args.is_directory else 'file', args.path))
354
348
 
355
- config, _ = MediLink_ConfigLoader.load_configuration()
349
+ config, crosswalk = MediLink_ConfigLoader.load_configuration()
356
350
  config = config.get('MediLink_Config', config)
357
351
 
358
- process_files(args.path, config, args.endpoint, args.is_directory)
352
+ process_dat_files(args.path, config, args.endpoint, args.is_directory, crosswalk)
359
353
  print("Conversion complete.")
360
354
 
361
- def process_files(path, config, endpoint, is_directory):
355
+ def process_dat_files(path, config, endpoint, is_directory, crosswalk):
362
356
  """
363
357
  Processes either a single file or all files within a directory.
364
358
 
@@ -376,10 +370,10 @@ def process_files(path, config, endpoint, is_directory):
376
370
  for file_name in os.listdir(path):
377
371
  if file_name.endswith(".DAT"):
378
372
  file_path = os.path.join(path, file_name)
379
- process_and_write_file(file_path, config, endpoint)
373
+ process_and_write_file(file_path, config, endpoint, crosswalk)
380
374
  else:
381
375
  MediLink_ConfigLoader.log("Processing the single file: {}".format(path))
382
- process_and_write_file(path, config, endpoint)
376
+ process_and_write_file(path, config, endpoint, crosswalk)
383
377
 
384
378
  if __name__ == "__main__":
385
379
  main()
@@ -387,10 +381,10 @@ if __name__ == "__main__":
387
381
  # The functions below are the ones that are used as non-main library by outside scripts.
388
382
  #######################################################################################
389
383
 
390
- def convert_files_for_submission(detailed_patient_data, config):
391
- """
384
+ def convert_files_for_submission(detailed_patient_data, config, crosswalk, client):
385
+ """
392
386
  Processes detailed patient data for submission based on their confirmed endpoints,
393
- generating a separate 837P file for each endpoint.
387
+ generating separate 837P files for each endpoint according to the configured submission type.
394
388
 
395
389
  Parameters:
396
390
  - detailed_patient_data: A list containing detailed patient data with endpoint information.
@@ -398,73 +392,93 @@ def convert_files_for_submission(detailed_patient_data, config):
398
392
 
399
393
  Returns:
400
394
  - A list of paths to the converted files ready for submission.
395
+
396
+ Note:
397
+ - This function currently supports batch and single-patient submissions based on the configuration.
398
+ - Future implementation may include progress tracking using tools like `tqdm`.
401
399
  """
402
400
 
403
401
  # Initialize a dictionary to hold patient data segregated by confirmed endpoints
404
402
  data_by_endpoint = {}
405
-
406
- # Populate the dictionary with patient data
403
+
404
+ # Group patient data by endpoint
407
405
  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)
406
+ endpoint = data.get('confirmed_endpoint')
407
+ if endpoint:
408
+ if endpoint not in data_by_endpoint:
409
+ data_by_endpoint[endpoint] = []
410
+ data_by_endpoint[endpoint].append(data)
412
411
 
413
412
  # List to store paths of converted files for each endpoint
414
413
  converted_files_paths = []
415
414
 
416
- # Determine the total number of unique endpoints for progress bar
417
- # total_endpoints = len(data_by_endpoint)
418
-
419
415
  # Iterate over each endpoint and process its corresponding patient data
420
416
  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)
417
+ # Retrieve submission type from config; default to "batch" if not specified
418
+ submission_type = config.get('MediLink_Config', {}).get('endpoints', {}).get(endpoint, {}).get('submission_type', 'batch')
419
+
420
+ if submission_type == 'single':
421
+ # Process each patient's data individually for single-patient submissions
422
+ for patient_data in patient_data_list:
423
+ # Generate a unique suffix for each patient, e.g., using a truncated chart number
424
+ chart_number = patient_data.get('CHART', 'UNKNOWN')#[:5] truncation might cause collisions.
425
+ suffix = "_{}".format(chart_number)
426
+ # Process and convert each patient's data to a separate file
427
+ converted_path = process_claim(config, endpoint, [patient_data], crosswalk, client, suffix)
428
+ if converted_path:
429
+ converted_files_paths.append(converted_path)
430
+ else:
431
+ # Process all patient data together for batch submissions
432
+ converted_path = process_claim(config, endpoint, patient_data_list, crosswalk, client)
425
433
  if converted_path:
426
434
  converted_files_paths.append(converted_path)
427
-
435
+
428
436
  return converted_files_paths
429
437
 
430
- def process_claim(config, endpoint, patient_data_list):
438
+ def process_claim(config, endpoint, patient_data_list, crosswalk, client, suffix=""):
431
439
  """
432
440
  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.
441
+ Can handle both batch and single-patient submissions.
434
442
 
435
443
  Parameters:
436
444
  - config: Configuration settings loaded from a JSON file.
437
- - endpoint_key: The key representing the endpoint for which the data is being processed.
445
+ - endpoint: The key representing the endpoint for which the data is being processed.
438
446
  - patient_data_list: A list of dictionaries, each containing detailed patient data.
447
+ - suffix: An optional suffix to differentiate filenames for single-patient processing.
439
448
 
440
449
  Returns:
441
450
  - Path to the converted file, or None if an error occurs.
442
-
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
451
  """
452
+ # Ensure we're accessing the correct configuration key
453
+ config = config.get('MediLink_Config', config)
454
+
455
+ # Retrieve the output directory from the configuration
447
456
  output_directory = MediLink_837p_encoder_library.get_output_directory(config)
448
457
  if not output_directory:
449
458
  return None
450
459
 
451
- # Initialize the transaction set control number and document segments
452
460
  transaction_set_control_number = 1
453
461
  document_segments = []
454
462
 
455
- # Process each patient's data in the list
456
463
  for patient_data in patient_data_list:
457
- # Validate each patient's data
464
+ # Validate each patient's data before processing
458
465
  is_valid, validation_errors = validate_claim_data(patient_data, config)
459
466
  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)
467
+ # Format the claim into 837P segments
468
+ formatted_claim = format_single_claim(patient_data, config, endpoint, transaction_set_control_number, crosswalk, client)
462
469
  document_segments.append(formatted_claim)
463
470
  transaction_set_control_number += 1
464
471
  else:
472
+ # Log any validation errors encountered
473
+ MediLink_ConfigLoader.log("Validation errors for patient data: {}".format(validation_errors), config, level="ERROR")
465
474
  if MediLink_837p_encoder_library.handle_validation_errors(transaction_set_control_number, validation_errors, config):
466
475
  continue # Skip the current patient
467
476
 
477
+ if not document_segments:
478
+ # If no valid segments were created, log the issue and return None
479
+ MediLink_ConfigLoader.log("No valid document segments created.", config, level="ERROR")
480
+ return None
481
+
468
482
  # Create interchange elements with the final transaction set control number
469
483
  isa_header, gs_header, ge_trailer, iea_trailer = MediLink_837p_encoder_library.create_interchange_elements(config, endpoint, transaction_set_control_number - 1)
470
484
 
@@ -473,5 +487,8 @@ def process_claim(config, endpoint, patient_data_list):
473
487
  document_segments.insert(0, isa_header)
474
488
  document_segments.extend([ge_trailer, iea_trailer])
475
489
 
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)
490
+ # Use the first patient's file path as a reference for output file naming
491
+ input_file_path = patient_data_list[0].get('file_path', 'UNKNOWN')
492
+ # Write the complete 837P document to an output file
493
+ converted_file_path = write_output_file(document_segments, output_directory, endpoint, input_file_path, config, suffix)
494
+ return converted_file_path