Files
doczyai-pipelines/fieldExtraction/src/bottom_up_funcs.py
T
Michael McGuinness d3aa4f936b rename
2024-09-30 11:12:18 +01:00

215 lines
9.4 KiB
Python

import dict_operations
import postprocessing_funcs
import prompts
import config
import claude_funcs
import utils
import difflib
def run_bottom_up(filename, text_dict):
"""
Processes the text of a document using a two-tiered Bottom Up approach to extract key financial and operational information.
This function first runs BOTTOM_UP_PRIMARY and BOTTOM_UP_SECONDARY prompts, then processes these initial results to further
refine and structure them into a dictionary form.
Parameters:
filename (str): The name of the file being processed, used to tag output data.
text_dict (dict): A dictionary of text keyed by page numbers that contains the content to be analyzed.
Returns:
list of dict: A list of dictionaries with each dictionary containing refined and structured information
from both primary and secondary Bottom Up analyses, all tagged with the filename.
"""
# Bottom Up Primary - SERVICE, REIMBURSEMENT_FLAT_FEE, REIMBURSEMENT_RATE, METHODOLOGY
answer_strings = run_bottom_up_primary(text_dict, 8000)
answer_dicts = dict_operations.primary_string_to_dict(answer_strings, filename)
answer_dicts_filtered = postprocessing_funcs.filter_service_column(answer_dicts)
# Bottom Up Secondary
results_dicts = run_bottom_up_secondary(answer_dicts_filtered, text_dict, 8000)
for d in results_dicts:
d['Filename'] = filename
return results_dicts # List of dictionaries
def run_bottom_up_primary(text_dict, tokens):
"""
Executes the primary Bottom Up processing for pages that contain reimbursement terms.
This function scans through a dictionary of page texts, identifies pages containing reimbursement terms,
and processes those pages using Claude to extract relevant information.
Parameters:
text_dict (dict): A dictionary containing text content of documents keyed by page numbers.
tokens (int): The maximum number of tokens to use in the language model invocation for generating the response.
Returns:
dict: A dictionary where each key is a page number and the value is the response from the language model.
"""
#chunk_dict = preprocess.chunk_text(text_dict)
answer_dict = {}
#for page_number in chunk_dict.keys():
#if '%' in chunk_dict[page_number] or '$' in chunk_dict[page_number]:
#prompt = prompts.BOTTOM_UP_PRIMARY(chunk_dict[page_number], config.CLIENT_NAME)
for page_number in text_dict.keys():
if utils.contains_reimbursement(text_dict, page_number):
# Run Primary
prompt = prompts.BOTTOM_UP_PRIMARY(text_dict[page_number], config.CLIENT_NAME)
answer = claude_funcs.invoke_claude_3(prompt, max_tokens=tokens)
answer_dict[page_number] = answer
return answer_dict
def run_bottom_up_secondary(answer_dicts, text_dict, tokens):
"""
Executes the secondary Bottom Up processing phase on the results obtained from the primary Bottom Up analysis.
This function enhances the primary results with additional analyses based on configured conditions. Invokes
Claude 3 with tailored prompts to generate structured information that complements the initial results.
Each piece of data processed possibly undergoes several rounds of checks and transformations, ensuring detailed and
comprehensive output.
Parameters:
answer_dicts (list of dict): Initial processed data from the primary Bottom Up analysis.
text_dict (dict): Dictionary containing the original document text keyed by page numbers.
tokens (int): Token limit for language model invocations.
Returns:
list of dict: A list of dictionaries containing enriched and finalized structured data from both the primary and
secondary analyses.
"""
# New rows for lesser of
temp_dicts = []
for d in answer_dicts:
# Bottom Up Lesser
if config.RUN_LESSER:
lesser_object = run_bottom_up_lesser(d.copy(), text_dict.copy(), tokens)
temp_dicts.append(lesser_object[1]) # Add original object from d
if lesser_object[0]: temp_dicts.append(lesser_object[0]) # If lesser, add lesser object
else:
temp_dicts.append(d)
# Add additional fields to each row
final_dicts = []
for d in temp_dicts:
if d is not None:
page_num = d['page_num']
# Bottom Up Methodology
if config.RUN_METHODOLOGY:
prompt = prompts.BOTTOM_UP_METHODOLOGY(d)
methodology_answer = claude_funcs.invoke_claude_3(prompt, max_tokens=100)
d['REIMBURSEMENT_METHODOLOGY'] = methodology_answer
# # Bottom Up FS
if config.RUN_FS:
prompt = prompts.BOTTOM_UP_FS(d)
fs_answer = claude_funcs.invoke_claude_3(prompt, model_id=config.MODEL_ID_CLAUDE3_HAIKU, max_tokens=tokens)
fs_dict = dict_operations.secondary_string_to_dict(fs_answer)
d.update(fs_dict)
# # Bottom Up Exception/Escalator
if config.RUN_EXCEPTION:
prompt = prompts.BOTTOM_UP_EXCEPT_ESC(d, text_dict[page_num])
exc_answer = claude_funcs.invoke_claude_3(prompt, model_id=config.MODEL_ID_CLAUDE3_HAIKU, max_tokens=tokens)
exc_dict = dict_operations.secondary_string_to_dict(exc_answer)
d.update(exc_dict)
# # Bottom Up Codes
if config.RUN_CODES:
prompt = prompts.BOTTOM_UP_CODES(d, text_dict[page_num])
codes_answer = claude_funcs.invoke_claude_3(prompt, max_tokens=tokens)
codes_dict = dict_operations.secondary_string_to_dict(codes_answer)
d.update(codes_dict)
final_dicts.append(d)
return final_dicts
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.
This function takes a dictionary representing initial results and a text dictionary, uses the page number from the initial
results to locate the relevant text, and generates a prompt for language model analysis. Based on the response, it
determines whether 'lesser of' or 'greater of' language is indicated, updates the results, and returns a new dictionary
for the updated data or retains the original if no such language is present.
Parameters:
d (dict): A dictionary containing initial processing results from previous analyses, including a 'page_num' key.
text_dict (dict): A dictionary of text keyed by page numbers that contains the content to be analyzed.
tokens (int): Token limit for language model invocations, default set to 8000.
Returns:
tuple: A tuple containing two dictionaries, the first for any identified 'lesser of' or 'greater of' data and the second
containing the original or updated data depending on the presence of such language.
"""
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_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']
return (lesser_of_dict, d)
else:
d['LESSER_OF_LANGUAGE_IND'] = 'N'
d['GREATER_OF_LANGUAGE_IND'] = 'N'
return ({}, d)
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, str(field_text), str(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 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