From 7ac8f5a1baa699415ffa3723bb5135387a82f675 Mon Sep 17 00:00:00 2001 From: Katon Minhas Date: Fri, 21 Jun 2024 15:04:33 -0700 Subject: [PATCH] Updated Lesser Of methodology --- src/postprocessingfuncs.py | 1 + src/prompt_funcs.py | 50 ++++++++++++++++++++++- src/prompts.py | 18 +++++---- src/test.py | 82 ++++++++++++++++++++++++++++---------- 4 files changed, 122 insertions(+), 29 deletions(-) diff --git a/src/postprocessingfuncs.py b/src/postprocessingfuncs.py index 344f4da..0f45fe5 100644 --- a/src/postprocessingfuncs.py +++ b/src/postprocessingfuncs.py @@ -452,3 +452,4 @@ def clean_dates(df): df.loc[idx, 'LOB_PRICING_TERMS_TERMINATION_DATE'] = 'N/A' return df + diff --git a/src/prompt_funcs.py b/src/prompt_funcs.py index db21445..674b185 100644 --- a/src/prompt_funcs.py +++ b/src/prompt_funcs.py @@ -1,5 +1,6 @@ import json +import difflib import config import prompts @@ -8,8 +9,44 @@ import dict_operations import utils import postprocessingfuncs +def get_least_similar(dict_list, original_dict, field): + print(f"Running similarity for {field}") + + min_similarity = float('inf') + least_similar_dict = None + for dictionary in dict_list: + # Extract the methodology text + field_text = dictionary[field] + print(f"Field text: {field_text}") + + # Compute similarity using difflib + similarity = difflib.SequenceMatcher(None, field_text, original_dict[field]).ratio() + print(f"Simlarity: {similarity}") + + # If the similarity is less than the current minimum, update the minimum and the corresponding dictionary + if similarity < min_similarity: + min_similarity = similarity + least_similar_dict = dictionary + + return least_similar_dict -def run_bottom_up_lesser(d, text_dict, tokens=8000): +def get_lesser_of_dict(dict_list, original_dict): + + unique_rates = list({d['REIMBURSEMENT_RATE'] for d in dict_list}) + unique_fees = list({d['REIMBURSEMENT_FLAT_FEE'] for d in dict_list}) + + # If the rates are different, return the lesser of dict with a different rate than original + if len(unique_rates) > 1: + least_similar_dict = get_least_similar(dict_list, original_dict, 'REIMBURSEMENT_RATE') + elif len(unique_fees) > 1: + least_similar_dict = get_least_similar(dict_list, original_dict, 'REIMBURSEMENT_FLAT_FEE') + else: + least_similar_dict = get_least_similar(dict_list, original_dict, 'FULL_METHODOLOGY') + + return least_similar_dict + + +def run_bottom_up_lesser(d, text_dict, tokens=4000): """ Processes specific clauses or conditions within the document text, such as 'lesser of' or 'greater of', using a language model to analyze and extract relevant data from the identified text segment. @@ -31,7 +68,16 @@ def run_bottom_up_lesser(d, text_dict, tokens=8000): prompt = prompts.BOTTOM_UP_LESSER(d, text_dict[d['page_num']]) lesser_of_answer = claude_funcs.invoke_claude_3(prompt, max_tokens=tokens) - lesser_of_dict = dict_operations.secondary_string_to_dict(lesser_of_answer) + lesser_of_dict_list = dict_operations.primary_string_to_dict({d['page_num'] : lesser_of_answer}, d['Filename']) + print(lesser_of_dict_list) + # If lesser of is Y + if len(lesser_of_dict_list) > 1: + lesser_of_dict = get_lesser_of_dict(lesser_of_dict_list, d) + else: + d['LESSER_OF_LANGUAGE_IND'] = 'N' + d['GREATER_OF_LANGUAGE_IND'] = 'N' + return ({}, d) + lesser_of_dict['page_num'] = d['page_num'] if lesser_of_dict['LESSER_OF_LANGUAGE_IND'] == 'Y' or lesser_of_dict['GREATER_OF_LANGUAGE_IND'] == 'Y': lesser_of_dict['SERVICE'] = d['SERVICE'] diff --git a/src/prompts.py b/src/prompts.py index bbd5729..33fabf9 100644 --- a/src/prompts.py +++ b/src/prompts.py @@ -78,25 +78,29 @@ def BOTTOM_UP_LESSER(d, page): ### PAGE START ### {page} ### PAGE END The above text contains a number of reimbursement terms. For the rest of this request, focus only on the specific term listed below: -Service: {d['SERVICE']} -Original_Methodology: {d['FULL_METHODOLOGY']} +Original_Dictionary: +- Original_Service: {d['SERVICE']} +- Original_Rate: {d['REIMBURSEMENT_RATE']} +- Original_Flat_Fee: {d['REIMBURSEMENT_FLAT_FEE']} +- Original_Methodology: {d['FULL_METHODOLOGY']} Your job is to identify if there is any 'lesser of' language or 'greater of' language that pertains to the specific service and methodology above. Note that a 'lesser of'/'greater of' statement may not be directly written for a given Service, but that does not necessarily mean that it doesn't apply. -Read and analyze the page, then populate a JSON dictionary with the following fields (descriptions provided): +Read and analyze the page, then populate two JSON dictionaries with the following fields (descriptions provided): 'LESSER_OF_LANGUAGE_IND' : Write 'Y' if there is 'lesser of' language somewhere on the page that applies to the Service and Original_Methodology. Write 'N' if not. 'GREATER_OF_LANGUAGE_IND' : Write 'Y' if there is 'greater of' language somewhere on the page that applies to the Service and Original_Methodology. Write 'N' if not. -'FULL_METHODOLOGY' : If either of the above are 'Y', what is the reimbursement the lesser or greater of? Do not write the full sentence - only include the half of the lesser/greater of statement that is NOT Original_Methodology. For example, if the reimbursement is the lesser of X or Original_Methodology, return only X. +'FULL_METHODOLOGY' : If either of the above are 'Y', what is the reimbursement the lesser or greater of? Do not write the full sentence. 'REIMBURSEMENT_RATE' : If the Lesser/Greater Of Methodology is a percent of something, write just the numeric percent value here. Only include percentages. If not, write N/A. 'REIMBURSEMENT_FLAT_FEE' : If the Lesser/Greater Of Methodology is a dollar value, write just the numeric value here. Only include dollar values. If not, write N/A. -For REIMBURSEMENT_RATE and REIMBURSEMENT_FLAT_FEE, do NOT include the Rate or Flat Fee from Original_Methodology. +If either LESSER_OF_LANGUAGE_IND or GREATER_OF_LANGUAGE_IND is 'Y', then both dictionaries must be populated. One of the dictionaries will have REIMBURSEMENT_RATE, REIMBURSEMENT_FLAT_FEE, and FULL_METHODOLOGY values equal to the Original term. +Fill the first dictionary with the values corresponding to the LEFT half of the Lesser/Greater Of Statement, and the second dictionary with the RIGHT half of the Lesser/Greater Of Statement. -Note that in some cases, the REIMBURSEMENT_RATE will not be explicitly written. It may say something like "Provider's billed charges", which is essentially equivalent to "100% of Provider's billed charges". In these cases, assign REIMBURSEMENT_RATE a value of 100%. +If both LESSER_OF_LANGUAGE_IND and GREATER_OF_LANGUAGE_IND are 'N', return a list with just the original dictionary. -Only return the dictionary, with no other commentary or explanation. Ensure you abide by proper JSON formatting. +Only return the list of dictionaries, with no other commentary or explanation. Ensure you abide by proper JSON formatting. """ def BOTTOM_UP_EXCEPT_ESC(d, page): diff --git a/src/test.py b/src/test.py index 994e0d1..55d4330 100644 --- a/src/test.py +++ b/src/test.py @@ -17,34 +17,76 @@ import prompts import prompt_funcs import claude_funcs import merge_funcs +import dict_operations -# input_dict = utils.read_input() +input_dict = utils.read_input() -# (filename, contract_text) = list(input_dict.items())[0] +(filename, contract_text) = list(input_dict.items())[0] -# contract_text = preprocess.clean_newlines(contract_text) -# contract_text = preprocess.clean_billed_charges(contract_text) -# text_dict = preprocess.split_text(contract_text) -# text_dict = table_funcs.align_and_format_tables(text_dict) -# text_dict = preprocess.highlight_rates(text_dict) - -# for page_num, page_text in text_dict.items(): -# if page_num == '31': -# prompt = prompts.TOP_DOWN_PRIMARY(page_text) -# answer = claude_funcs.invoke_claude_3(prompt, max_tokens=4000) -# answer_dict = json.loads(answer) - -# print(answer_dict) +contract_text = preprocess.clean_newlines(contract_text) +contract_text = preprocess.clean_billed_charges(contract_text) +text_dict = preprocess.split_text(contract_text) +text_dict = table_funcs.align_and_format_tables(text_dict) +text_dict = preprocess.highlight_rates(text_dict) -# bu_results = prompt_funcs.run_bottom_up(filename, text_dict) # Returns list of dictionaries -# print(bu_results) +def BOTTOM_UP_LESSER(d, page): + return f""" +### PAGE START ### {page} ### PAGE END + +The above text contains a number of reimbursement terms. For the rest of this request, focus only on the specific term listed below: +Original_Dictionary: +- Original_Service: {d['SERVICE']} +- Original_Rate: {d['REIMBURSEMENT_RATE']} +- Original_Flat_Fee: {d['REIMBURSEMENT_FLAT_FEE']} +- Original_Methodology: {d['FULL_METHODOLOGY']} + +Your job is to identify if there is any 'lesser of' language or 'greater of' language that pertains to the specific service and methodology above. + +Note that a 'lesser of'/'greater of' statement may not be directly written for a given Service, but that does not necessarily mean that it doesn't apply. + +Read and analyze the page, then populate two JSON dictionaries with the following fields (descriptions provided): +'LESSER_OF_LANGUAGE_IND' : Write 'Y' if there is 'lesser of' language somewhere on the page that applies to the Service and Original_Methodology. Write 'N' if not. +'GREATER_OF_LANGUAGE_IND' : Write 'Y' if there is 'greater of' language somewhere on the page that applies to the Service and Original_Methodology. Write 'N' if not. +'FULL_METHODOLOGY' : If either of the above are 'Y', what is the reimbursement the lesser or greater of? Do not write the full sentence. +'REIMBURSEMENT_RATE' : If the Lesser/Greater Of Methodology is a percent of something, write just the numeric percent value here. Only include percentages. If not, write N/A. +'REIMBURSEMENT_FLAT_FEE' : If the Lesser/Greater Of Methodology is a dollar value, write just the numeric value here. Only include dollar values. If not, write N/A. + +If either LESSER_OF_LANGUAGE_IND or GREATER_OF_LANGUAGE_IND is 'Y', then both dictionaries must be populated. One of the dictionaries will have REIMBURSEMENT_RATE, REIMBURSEMENT_FLAT_FEE, and FULL_METHODOLOGY values equal to the Original term. +Fill the first dictionary with the values corresponding to the LEFT half of the Lesser/Greater Of Statement, and the second dictionary with the RIGHT half of the Lesser/Greater Of Statement. + +If both LESSER_OF_LANGUAGE_IND and GREATER_OF_LANGUAGE_IND are 'N', return a list with just the original dictionary. + +Only return the list of dictionaries, with no other commentary or explanation. Ensure you abide by proper JSON formatting. +""" + + +d = {'SERVICE': 'Inpatient Services', + 'REIMBURSEMENT_FLAT_FEE': 'N/A', + 'REIMBURSEMENT_RATE': '105%', + 'FULL_METHODOLOGY': "The lesser of 105% of the West Virginia Medicaid DRG (based on Hospital's current DHHR payment rate) or 100% of Hospital's allowable billed charges.", + 'page_num': '26', + 'Filename': 'App Regional Healthcare_Hospital Agreement_20160414_Dually Executed WV MCD-C13439125AA.txt'} + +answer = prompt_funcs.run_bottom_up_lesser(d, text_dict) +print(answer) + + +# answer_strings = prompt_funcs.run_bottom_up_primary({'3' : text_dict['3']}, 8000) +# answer_dicts = dict_operations.primary_string_to_dict(answer_strings, filename) +# answer_dicts_filtered = postprocessingfuncs.filter_service_column(answer_dicts) + +# print(answer_dicts_filtered) + +# for d in answer_dicts_filtered: +# print(f"Original dictionary: {d}") +# # Bottom Up Lesser +# if config.RUN_LESSER: +# lesser_object = prompt_funcs.run_bottom_up_lesser(d.copy(), text_dict.copy(), tokens=4000) +# print(f"Lesser dictionary: {lesser_object}") -sample_results = pd.read_csv('output/ASMG MU/combined_results_post_processed.csv') -df = postprocessingfuncs.clean_dates(sample_results) -print(df)