Merged in feature/rework-reimb-tin-npi (pull request #502)

Feature/rework reimb tin npi

* use LLM for reimb_[tin/npi/name]

* Merge remote-tracking branch 'origin/main' into feature/rework-reimb-tin-npi

* Merge remote-tracking branch 'origin/main' into feature/rework-reimb-tin-npi

* deduplicate tin/name/npi before adding to prompt

* Pull out prompt and llm_response in methodology_breakout_single_row for easier debugging

* Refactor reimbursement_tin_npi to handle multiple providers and update valid TIN, NPI, and NAME fields accordingly

* Reorder REIMB_PROV_NAME field in investment_prompts.json

* Update REIMB_PROV_NAME prompt to specify valid values for provider names

* Remove valid_values constraint from prov_name to raise accuracy

* Remove debugging print statements from reimbursement_tin_npi and related functions

* Remove debugging print statements from run_provider_info_fields function

* Merge remote-tracking branch 'origin/main' into feature/rework-reimb-tin-npi

* Remove debugging print statements from get_reimbursement_primary function

* Merge remote-tracking branch 'origin/main' into feature/rework-reimb-tin-npi

* Refactor reimbursement_tin_npi to always add providers (when we have any) to reimbursement_level_fields


Approved-by: Katon Minhas
This commit is contained in:
Alex Galarce
2025-05-02 20:13:02 +00:00
parent b0775ef23b
commit 4191a5616e
5 changed files with 48 additions and 39 deletions
@@ -139,7 +139,7 @@ def run_one_to_n_prompts(filename, text_dict, exhibit_pages, exhibit_chunk_mappi
exhibit_header = one_to_n_funcs.get_exhibit_header(exhibit_chunk, filename)
################## GET REIMBURSEMENT TIN/NPI ##################
tin_npi_answers, reimbursement_level_fields = reimbursement_tin_npi(exhibit_chunk, reimbursement_level_fields)
tin_npi_answers, reimbursement_level_fields = reimbursement_tin_npi(exhibit_chunk, reimbursement_level_fields, filename)
################## GET EXHIBIT-LEVEL ANSWERS ##################
exhibit_level_answers = one_to_n_funcs.get_exhibit_level_answers(exhibit_chunk, filename)
@@ -254,10 +254,11 @@ def methodology_breakout_single_row(
contract_reimbursement_method = answer_dict.get("REIMB_TERM")
# Methodology Breakout: AARETE_DERIVED_REIMB_METHOD, REIMB_FEE_RATE, REIMB_PCT_RATE, LESSER_OF_IND, GREATER_OF_IND, AARETE_DERIVED_PROV_TYPE
if contract_reimbursement_method:
llm_response = llm_utils.invoke_claude(
methodology_prompt_template(
prompt = methodology_prompt_template(
service, contract_reimbursement_method, methodology_breakout_questions
),
)
llm_response = llm_utils.invoke_claude(
prompt,
config.MODEL_ID_CLAUDE35_SONNET,
filename,
)
@@ -461,6 +462,7 @@ def reimbursement_level(
Returns:
dict: The parsed LLM response as a list of dictionaries with unique identifiers
"""
print("Inside reimbursement_level function, reimbusement_level_fields:", reimbursement_level_fields.get_prompt_dict())
reimbursement_primary_answers = get_reimbursement_primary(
reimbursement_level_fields, exhibit_text, filename
) # Returns list of dicts
@@ -52,15 +52,11 @@ def run_provider_info_fields(contract_text: str,
page_provider_info = get_provider_info(page_text, filename)
combined_provider_info.extend(page_provider_info)
# print(f"Combined provider info for {filename}: {combined_provider_info}") # Debugging line
# Clean and standardize results
cleaned_provider_info = clean_provider_info(combined_provider_info)
# print(f"Cleaned provider info for {filename}: {cleaned_provider_info}") # Debugging line
# Deduplicate providers based on TIN and NPI
deduplicated_provider_info = deduplicate_providers(cleaned_provider_info)
# print(f"Deduplicated provider info for {filename} [{len(deduplicated_provider_info)} entries]: {deduplicated_provider_info}") # Debugging line
# if payer_name:
# # Filter out providers that are too similar to the payer name
@@ -83,7 +79,6 @@ def run_provider_info_fields(contract_text: str,
elif len(deduplicated_provider_info) == 1:
# If only one provider is found, mark it as the group provider
deduplicated_provider_info[0]["IS_GROUP"] = True
# print(f"After group assignment for {filename}: {deduplicated_provider_info}") # Debugging line
return deduplicated_provider_info
+34 -22
View File
@@ -161,39 +161,55 @@ def merge_provider_info(one_to_one_results: dict, provider_info: list[dict]) ->
one_to_one_results["PROV_OTHER_NAME_FULL"] = "|".join(other_names) if other_names else ""
return one_to_one_results
def reimbursement_tin_npi(exhibit_chunk: str, reimbursement_level_fields: FieldSet) -> tuple[dict, FieldSet]:
def reimbursement_tin_npi(exhibit_chunk: str, reimbursement_level_fields: FieldSet, filename: str) -> tuple[dict, FieldSet]:
"""
Extracts Reimbursement-Level TIN and NPI values from the given exhibit chunk using regex patterns. For multiple TIN/NPIs in Exhibit, assigns the field to be run as part of Reimbursement-Level Primary
Extracts Reimbursement-Level TIN and NPI values from the given exhibit chunk using an LLM.
When multiple providers are found, they're added as valid values for LLM selection
of these fields during reimbursement primary processing.
Args:
exhibit_chunk (str): The text chunk containing reimbursement details.
reimbursement_level_fields (FieldSet): The FieldSet object to store extracted field values.
filename (str): The name of the file being processed.
Returns:
tuple[dict, FieldSet]: A dictionary containing extracted TIN and NPI values, and an
updated FieldSet with additional fields if multiple values are found.
Fields Added: REIMB_PROV_TIN,
Fields Added: REIMB_PROV_NAME,
REIMB_PROV_TIN,
REIMB_PROV_NPI
"""
answer_dict = {}
# Extract all providers from the exhibit chunk
providers = get_provider_info(exhibit_chunk, filename)
# Clean, standardize, and deduplicate extracted providers
filtered_providers = deduplicate_providers(clean_provider_info(providers))
fields = [("REIMB_PROV_TIN", r"\b\d{2}-\d{7}\b|\b\d{9}\b"),
("REIMB_PROV_NPI", r"\b\d{10}\b")]
for field_tuple in fields:
field_name, field_pattern = field_tuple[0], field_tuple[1]
field_matches, page_list = regex_utils.find_regex_matches(field_pattern, {'1' : exhibit_chunk})
if not field_matches:
answer_dict[field_name] = "N/A"
elif len(field_matches) == 1:
answer_dict['REIMB_PROV_TIN'] = field_matches[0]
else: # Multiple tins
reimbursement_field = Field.load_from_file(config.FIELD_JSON_PATH, field_name)
reimbursement_field.update_valid_values(field_matches)
reimbursement_level_fields.add_field(reimbursement_field)
# Always add providers to reimbursement_level fields for proper term attribution
if len(filtered_providers) > 0:
tin_field = Field.load_from_file(config.FIELD_JSON_PATH, "REIMB_PROV_TIN")
tin_list = list(set([provider.get("TIN", "N/A") for provider in filtered_providers])) # Remove duplicates
tin_field.update_valid_values(tin_list)
reimbursement_level_fields.add_field(tin_field)
npi_field = Field.load_from_file(config.FIELD_JSON_PATH, "REIMB_PROV_NPI")
npi_list = list(set([provider.get("NPI", "N/A") for provider in filtered_providers])) # Remove duplicates
npi_field.update_valid_values(npi_list)
reimbursement_level_fields.add_field(npi_field)
name_field = Field.load_from_file(config.FIELD_JSON_PATH, "REIMB_PROV_NAME")
name_list = list(set([provider.get("NAME", "N/A") for provider in filtered_providers])) # Remove duplicates
name_field.update_valid_values(name_list)
reimbursement_level_fields.add_field(name_field)
# If no providers are found, set default values
else:
answer_dict['REIMB_PROV_TIN'] = "N/A"
answer_dict['REIMB_PROV_NPI'] = "N/A"
answer_dict['REIMB_PROV_NAME'] = "N/A"
return answer_dict, reimbursement_level_fields
@@ -233,11 +249,8 @@ def identify_group_provider(text_dict: dict, providers: list[dict], filename: st
# Construct prompt
prompt = investment_prompts.GROUP_TIN_NPI_TEMPLATE(key_sections=key_sections, provider_list=provider_list)
# print(f"Prompt for group provider identification: {prompt}") # Debugging line
# Get response and parse
response = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename)
# print(f"Response from LLM for group provider identification: {response}") # Debugging line
try:
group_info = json.loads(string_utils.extract_text_from_delimiters(response, Delimiter.PIPE))
@@ -380,7 +393,6 @@ def get_provider_info(chunk: str, filename: str) -> list:
prompt = investment_prompts.TIN_NPI_TEMPLATE(chunk, provider_fields.get_prompt_dict())
# Get response from LLM
claude_answer_raw = llm_utils.invoke_claude(prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=8192) # Sometimes rosters can have 50+ providers
print(f"Raw response from LLM: {claude_answer_raw}")
# Defensive programming for any json parsing errors
try:
@@ -62,12 +62,6 @@
"prompt": "Identify the termination date for reimbursement rates:\n- Look for dates associated with:\n • 'Termination' or 'termination date'\n • 'Expires' or 'expiration date'\n • 'Through' or 'until'\n- Dates may appear as:\n • Full dates (12/1/17, December 1, 2017)\n • Month and year (December 2017)\n • Year only (2017)",
"format": "date"
},
{
"field_name": "REIMB_PROV_NAME",
"relationship": "one_to_n",
"field_type": "dynamic",
"prompt": "Identify any Provider Names present that apply to the text. Do NOT return the name of a payer or insurance plan, only the names of specific providers (doctors, hospitals, clinics, etc) listed. Do not return the word 'Provider'."
},
{
"field_name": "NOT_TO_EXCEED_IND",
"relationship": "one_to_n",
@@ -236,13 +230,19 @@
"field_name": "REIMB_PROV_TIN",
"relationship": "one_to_n",
"field_type": "TBD",
"prompt": "What TIN (Tax ID Number) does the reimbursement term apply to? Choose only from the following: {valid_values}."
"prompt": "What TIN (Tax ID Number) does the reimbursement term apply to? When the term applies to multiple providers simultaneously, list ALL applicable TINs separated by commas. Look for language patterns indicating multiple providers (phrases like 'following providers', 'these facilities', 'listed providers', etc.). Choose only from the following: {valid_values}."
},
{
"field_name": "REIMB_PROV_NPI",
"relationship": "one_to_n",
"field_type": "TBD",
"prompt": "What NPI (National Provider Identifier) does the reimbursement term apply to? Choose only from the following: {valid_values}."
"prompt": "What NPI (National Provider Identifier) does the reimbursement term apply to? When the term applies to multiple providers simultaneously, list ALL applicable NPIs separated by commas. Look for language patterns indicating multiple providers (phrases like 'following providers', 'these facilities', 'listed providers', etc.). Choose only from the following: {valid_values}."
},
{
"field_name": "REIMB_PROV_NAME",
"relationship": "one_to_n",
"field_type": "dynamic",
"prompt": "Identify any Provider Names that this reimbursement term applies to. When the term applies to multiple providers simultaneously, list ALL applicable names separated by commas. Look for language patterns indicating multiple providers. Extract only the official names of healthcare providers (doctors, hospitals, clinics, etc.) - not payers or insurance plans. If unable to determine applicable provider names, respond with 'UNKNOWN'."
},
{
"field_name": "AARETE_DERIVED_EFFECTIVE_DT",