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.
- MediBot/MediBot.bat +56 -16
- MediBot/MediBot.py +100 -78
- MediBot/MediBot_Crosswalk_Library.py +496 -194
- MediBot/MediBot_Preprocessor.py +22 -14
- MediBot/MediBot_Preprocessor_lib.py +301 -143
- MediBot/MediBot_UI.py +25 -24
- MediBot/MediBot_dataformat_library.py +17 -25
- MediBot/MediBot_docx_decoder.py +267 -110
- MediBot/update_json.py +26 -1
- MediBot/update_medicafe.py +134 -44
- MediLink/MediLink.py +95 -53
- MediLink/MediLink_837p_encoder.py +83 -66
- MediLink/MediLink_837p_encoder_library.py +159 -102
- MediLink/MediLink_API_Generator.py +1 -7
- MediLink/MediLink_API_v3.py +348 -63
- MediLink/MediLink_APIs.py +1 -2
- MediLink/MediLink_ClaimStatus.py +21 -6
- MediLink/MediLink_ConfigLoader.py +9 -9
- MediLink/MediLink_DataMgmt.py +321 -100
- MediLink/MediLink_Decoder.py +249 -87
- MediLink/MediLink_Deductible.py +62 -56
- MediLink/MediLink_Down.py +115 -121
- MediLink/MediLink_Gmail.py +2 -11
- MediLink/MediLink_Parser.py +63 -36
- MediLink/MediLink_UI.py +36 -23
- MediLink/MediLink_Up.py +188 -115
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/METADATA +1 -1
- medicafe-0.240925.9.dist-info/RECORD +47 -0
- medicafe-0.240716.2.dist-info/RECORD +0 -47
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/LICENSE +0 -0
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/WHEEL +0 -0
- {medicafe-0.240716.2.dist-info → medicafe-0.240925.9.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
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
|
|
92
|
+
- String specifying the path to the successfully created output file, or None if an error occurred.
|
|
100
93
|
"""
|
|
101
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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 =
|
|
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,
|
|
349
|
+
config, crosswalk = MediLink_ConfigLoader.load_configuration()
|
|
356
350
|
config = config.get('MediLink_Config', config)
|
|
357
351
|
|
|
358
|
-
|
|
352
|
+
process_dat_files(args.path, config, args.endpoint, args.is_directory, crosswalk)
|
|
359
353
|
print("Conversion complete.")
|
|
360
354
|
|
|
361
|
-
def
|
|
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
|
|
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
|
-
#
|
|
403
|
+
|
|
404
|
+
# Group patient data by endpoint
|
|
407
405
|
for data in detailed_patient_data:
|
|
408
|
-
endpoint = data
|
|
409
|
-
if endpoint
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
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
|
-
#
|
|
477
|
-
|
|
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
|