From 2230d8d0f8cefbebee7d770751a9d4c075293dc0 Mon Sep 17 00:00:00 2001 From: Katon Minhas Date: Wed, 29 Jan 2025 22:20:54 +0000 Subject: [PATCH] Merged in field/reimbursement-level-primary-breakout (pull request #368) Field/reimbursement level primary breakout * Update reimbursement-primary prompt * Merge branch 'main' into field/reimbursement-level-primary-breakout * Add methodology breakout - initial prompts * Merge branch 'main' into field/reimbursement-level-primary-breakout * Bugfix * Add print statements * Merged main into field/reimbursement-level-primary-breakout Approved-by: Alex Galarce --- .../src/constants/investment_values.py | 14 ++++ .../src/investment/file_processing.py | 4 + .../src/investment/one_to_n_funcs.py | 37 +++++++-- .../src/prompts/investment_prompts.json | 77 ++++++++++--------- .../src/prompts/investment_prompts.py | 52 +++++++++++-- 5 files changed, 133 insertions(+), 51 deletions(-) diff --git a/fieldExtraction/src/constants/investment_values.py b/fieldExtraction/src/constants/investment_values.py index 3c25ddd..a02d8b6 100644 --- a/fieldExtraction/src/constants/investment_values.py +++ b/fieldExtraction/src/constants/investment_values.py @@ -51,3 +51,17 @@ VALID_CONTRACT_CLAIM_TYPE = [ "Ancillary" ] +VALID_REIMBURSEMENT_METHOD = [ + "Billed Charges", + "Fee Schedule", + "Flat Rate", + "Case Rate", + "Per Diem Rate", + "Cost Plus", + "AWP", + "Pass Through" + "Outlier", + "Reductions", + "DOFR" +] + diff --git a/fieldExtraction/src/investment/file_processing.py b/fieldExtraction/src/investment/file_processing.py index 88ac305..1129513 100644 --- a/fieldExtraction/src/investment/file_processing.py +++ b/fieldExtraction/src/investment/file_processing.py @@ -31,6 +31,7 @@ def process_file(file_object, run_timestamp): ################## ONE TO ONE ################## one_to_one_results = run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict) # Return df one_to_one_results['CONTRACT_FILE_NAME'] = filename + print(f"One to One Complete - {filename}") ################## ONE TO N ################## if string_utils.contains_reimbursement(contract_text): @@ -39,12 +40,15 @@ def process_file(file_object, run_timestamp): contract_results = pd.merge(one_to_one_results, one_to_n_results, how='right', on='CONTRACT_FILE_NAME') else: contract_results = one_to_n_results + print(f"One to N Complete - {filename}") ################## AARETE-DERIVED ################## final_results = aarete_derived.get_aarete_derived(contract_results) + print(f"AArete-Derived Complete - {filename}") ################## POSTPROCESS ################## final_df = postprocess.postprocess(final_results) + print(f"Postprocessing Complete - {filename}") ################## WRITE INDIVIDUAL ################## if config.WRITE_TO_S3: diff --git a/fieldExtraction/src/investment/one_to_n_funcs.py b/fieldExtraction/src/investment/one_to_n_funcs.py index 7a4044d..d9e17bd 100644 --- a/fieldExtraction/src/investment/one_to_n_funcs.py +++ b/fieldExtraction/src/investment/one_to_n_funcs.py @@ -136,6 +136,33 @@ def dynamic_and_exhibit_level(exhibit_text, # reimbursement_value_dict contains the found values for the dynamic fields that got moved to reimbursement-level return exhibit_level_answer_dict +def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename): + field_prompts = reimbursement_level_fields.get_prompt_dict() + prompt = investment_prompts.REIMBURSEMENT_LEVEL_PRIMARY(exhibit_text, field_prompts) + llm_answer_raw = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename) + llm_answer_final = string_utils.universal_json_load(llm_answer_raw) + return llm_answer_final + +def get_methodology_breakout(reimbursement_primary_answers, filename): + already_seen = {} + + methodology_breakout_questions = FieldSet(file_path=config.FIELD_JSON_PATH, field_type="methodology_breakout").get_prompt_dict() + methodology_breakout_answers = [] + for answer_dict in reimbursement_primary_answers: + if "CONTRACT_REIMBURSEMENT_METHOD" in answer_dict.keys(): + contract_reimbursement_method = answer_dict["CONTRACT_REIMBURSEMENT_METHOD"] + if contract_reimbursement_method not in already_seen.keys(): + prompt = investment_prompts.REIMBURSEMENT_METHODOLOGY_BREAKOUT(answer_dict["CONTRACT_REIMBURSEMENT_METHOD"], methodology_breakout_questions) + llm_answer_raw = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename) + llm_answer_final = string_utils.universal_json_load(llm_answer_raw) + already_seen[contract_reimbursement_method] = llm_answer_final + else: + llm_answer_final = already_seen[contract_reimbursement_method] + for breakout_dict in llm_answer_final: + breakout_answer = {**answer_dict, **breakout_dict} + methodology_breakout_answers.append(breakout_answer) + return methodology_breakout_answers + def reimbursement_level(exhibit_text, filename, reimbursement_level_fields): """ @@ -150,11 +177,11 @@ def reimbursement_level(exhibit_text, filename, reimbursement_level_fields): Returns: dict: The parsed LLM response as a list of dictionaries """ - field_prompts = reimbursement_level_fields.get_prompt_dict() - prompt = investment_prompts.REIMBURSEMENT_LEVEL_PRIMARY(exhibit_text, field_prompts) - llm_answer_raw = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename) - llm_answer_final = string_utils.universal_json_load(llm_answer_raw) - return llm_answer_final + + reimbursement_primary_answers = get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename) # Returns list of dicts + methodology_breakout_answers = get_methodology_breakout(reimbursement_primary_answers, filename) + + return methodology_breakout_answers diff --git a/fieldExtraction/src/prompts/investment_prompts.json b/fieldExtraction/src/prompts/investment_prompts.json index b9b9ec2..5a0757f 100644 --- a/fieldExtraction/src/prompts/investment_prompts.json +++ b/fieldExtraction/src/prompts/investment_prompts.json @@ -3,13 +3,50 @@ "field_name" :"CONTRACT_SERVICE_CD_OR_DESC", "relationship" : "one_to_n", "field_type" : "reimbursement_level", - "prompt" : "What is the Service or Service code that is being reimbursed. Use exact text from the contract." + "prompt" : "What is the Service that is being reimbursed? Include the full Service, including any distinguishing context. Include any codes that are part of the service, including revenue codes and procedure codes. Use only exact text from the document. Do not leave this field N/A." }, { "field_name" : "CONTRACT_REIMBURSEMENT_METHOD", "relationship" : "one_to_n", "field_type" : "reimbursement_level", - "prompt" : "What is the method of reimbursement? Return the entire sentence or sentences describing the reimbursement methodology, including any lesser of language and rates." + "prompt" : "Write the full sentence or paragraph in the text describing the reimbursement methodology, including any lesser of language, rates, or other important information." + }, + { + "field_name": "CONTRACT_LESSER_OF_IND", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "If the methodology text indicates that the reimbursement shall be the lesser of two or more values, write 'Y' for this field. Otherwise, write 'N'." + }, + { + "field_name": "CONTRACT_GREATER_OF_IND", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "If the methodology text indicates that the reimbursement shall be the greater of two or more values, write 'Y' for this field. Otherwise, write 'N'." + }, + { + "field_name": "AARETE_DERIVED_REIMBURSEMENT_METHOD", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "What is the method of reimbursement? Choose only from the following: {valid_values}.", + "valid_values": "VALID_REIMBURSEMENT_METHOD" + }, + { + "field_name": "CONTRACT_REIMBURSEMENT_FEE_RATE", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "If the reimbursement rate is a dollar amount, what is that dollar amount?" + }, + { + "field_name": "CONTRACT_REIMBURSEMENT_PERCENT_RATE", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "If the reimbursement rate is a percentage of something, what is that percentage? Note that 100% may be implied." + }, + { + "field_name": "CONTRACT_FEE_SCHEDULE_DESC", + "relationship": "one_to_n", + "field_type": "methodology_breakout", + "prompt": "If the reimbursement is paid as a percentage of a Fee Schedule, write the full name of that Fee Schedule here. Only use exact text from the methodology." }, { "field_name" : "CONTRACT_LINE_OF_BUSINESS", @@ -266,30 +303,6 @@ "field_type": "TBD", "prompt": "TBD" }, - { - "field_name": "AARETE_DERIVED_REIMBURSEMENT_METHOD", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, - { - "field_name": "CONTRACT_REIMBURSEMENT_FEE_RATE", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, - { - "field_name": "CONTRACT_REIMBURSEMENT_PERCENT_RATE", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, - { - "field_name": "CONTRACT_FEE_SCHEDULE_DESC", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, { "field_name": "AARETE_DERIVED_FEE_SCHEDULE", "relationship": "one_to_n", @@ -302,18 +315,6 @@ "field_type": "TBD", "prompt": "TBD" }, - { - "field_name": "CONTRACT_LESSER_OF_IND", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, - { - "field_name": "CONTRACT_GREATER_OF_IND", - "relationship": "one_to_n", - "field_type": "TBD", - "prompt": "TBD" - }, { "field_name": "CONTRACT_CARVEOUT_IND", "relationship": "one_to_n", diff --git a/fieldExtraction/src/prompts/investment_prompts.py b/fieldExtraction/src/prompts/investment_prompts.py index 7b74055..22375f0 100644 --- a/fieldExtraction/src/prompts/investment_prompts.py +++ b/fieldExtraction/src/prompts/investment_prompts.py @@ -1,4 +1,4 @@ -from src.constants.investment_values import VALID_LOBS, VALID_NETWORKS, VALID_PROGRAMS, VALID_CONTRACT_CLAIM_TYPE +from src.constants.investment_values import VALID_LOBS, VALID_NETWORKS, VALID_PROGRAMS, VALID_CONTRACT_CLAIM_TYPE, VALID_REIMBURSEMENT_METHOD import src.constants.valid as valid ##################################################################################### @@ -9,11 +9,13 @@ import importlib class Field: def __init__(self, field_dict): + # Base Attributes self.field_name = field_dict['field_name'] self.relationship = field_dict['relationship'] self.field_type = field_dict['field_type'] self.prompt = field_dict['prompt'] self.valid_values = field_dict['valid_values'] if 'valid_values' in field_dict else None + # AArete-Derived Attributes self.base_field = field_dict['base_field'] if 'base_field' in field_dict else None # Smart Chunking Attributes self.keywords = field_dict['keywords'] if 'keywords' in field_dict else None @@ -107,7 +109,7 @@ class FieldSet: self.load_from_file(file_path, relationship, field_type) def load_from_file(self, file_path, relationship=None, field_type=None): - """Loads prompts from an external JSON file and resolves dynamic placeholders.""" + """Loads Fields from an external JSON file""" with open(file_path, 'r') as file: data = json.load(file) self.fields = [ @@ -226,19 +228,53 @@ Explain each answer before putting the final answer in the JSON dictionary forma def REIMBURSEMENT_LEVEL_PRIMARY(context, fields): - return f"""Analyze the following text and extract the desired information in json format. The text is a section of a contract. Your job is to retrieve information associated with the contracted reimbursement for different services. + return f"""Your job is to extract attributes from a section of a contract. +The attributes are related to the reimbursement of different services and specialties. Ensure every compensation term (with a % or $ value) is captured. -For each of the below fields, return a dictionary for every field group you see. Field groups are fields associated with each other. +Some values are found in sections explicitly identified as examples - these must be omitted from output. Other values might be related to liability - these must also be excluded. -Here are the fields that +Return at least one json object for each combination of attributes seen. It is possible that a page will not have any attributes. When this is the case, return an empty list. -Here are the fields that must be returned, with a description of how to find each correct field value: +Here are the attributes to be included in each dictionary, and instructions on how to correctly answer: {fields} -Return a json list of dictionaries, where each dictionary represents a set of fields that go with each other. +Ensure any relevant detail is included, including rates in tables if applicable. Include any 'Lesser of' statement that applies to the reimbursement as part of the CONTRACT_REIMBURSEMENT_METHOD. The 'Lesser of' statement might not be found in immediate proximity to the reimbursement term and may instead be found in a paragraph above. If the methodology is presented in a table, concatenate any relevant lesser of statement that applies to the table with the portion of the methodology found in the table. + +Here are some examples of language with additional context to be included in the CONTRACT_SERVICE_CD_OR_DESC: +Text: 'Covered Services that are Medicare Covered Services and are not Medicaid Covered Services', +Text: 'Covered Services rendered to Covered Person who are eligible for Medicare and enrolled in a MMP Plan that may include coverage for both Medicare and Medicaid Covered Services' + +Here is an example of text with a Lesser of statement and a table that should be included as a CONTRACT_REIMBURSEMENT_METHOD answer: +Text: 'Professional Covered Services is the lesser of: (i) Allowable Charges; or (ii) the "Contracted Rate" percentage found in Table 1. Table 1 - OB and Anesthesia Services : 100% of the Payor's Oregon Health Plan DMAP fee schedule. Radiology Services : 110% of Medicare Fee Schedule' +Answer: "[{{'CONTRACT_SERVICE_CD_OR_DESC' : 'OB and Anesthesia Services', 'CONTRACT_REIMBURSEMENT_METHOD' : 'lesser of (i) Allowable Charges; or (ii) 100% of the Payor's Oregon Health Plan DMAP fee schedule', ...}}, +{{'CONTRACT_SERVICE_CD_OR_DESC' : 'Radiology Services', 'CONTRACT_REIMBURSEMENT_METHOD' : 'lesser of (i) Allowable Charges; or (ii) 110% of Medicare Fee Schedule', ...}}]" + +If there is no clear CONTRACT_SERVICE_CD_OR_DESC, then fill in 'Covered Services' for the CONTRACT_SERVICE_CD_OR_DESC value. + +Ensure that you do not forget to extract reimbursement items. MAKE SURE THAT YOU DON'T MISS ANY REIMBURSEMENT ITEMS AND RETURN ALL NECESSARY AND RELEVANT JSON OBJECTS. Here is the text to analyze: -### PAGE START ### {context} ### PAGE END +Start Context: +{context} +End Context + +Return the list of answers below. Ensure you abide by proper JSON formatting. +""" + +def REIMBURSEMENT_METHODOLOGY_BREAKOUT(methodology, questions): + return f"""Analyze a given reimbursement methodology from a Payer-Provider contract: + +Populate a list of JSON dictionaries with the following fields: + +{questions} + +In some cases, there will only be one reimbursement method listed. In these cases, the list should only contain a single JSON dictionary with all fields present. +In other cases, there will be multiple reimbursement methods listed. This is particularly common in the presence of 'lesser of' language. In these cases, the list should contain a list of JSON dictionaries with all fields present for each individual methodology seen. + +Here is the text to analyze and respond to: +{methodology} + +Write your JSON list below: """ def ONE_TO_ONE_SINGLE_FIELD_TEMPLATE(context, question):