Merged in feature/further-reimb-primary-improvements (pull request #546)
Feature/further reimb primary improvements * Refactor identify_reimbursement_exhibits to return both EXPLICIT and IMPLICIT values * Moving from # Debugging lines to `logger` * Enhance "lesser of" and exception language instructions in reimbursement terms * clarify unclear print/logging statements * Replace debugging print statements with logging in get_reimbursement_primary function * Add logging for page processing in run_one_to_n_prompts function Approved-by: Katon Minhas
This commit is contained in:
@@ -2,6 +2,8 @@ import csv
|
||||
import json
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
import src.investment.aarete_derived as aarete_derived
|
||||
import src.investment.dynamic_funcs as dynamic_funcs
|
||||
@@ -57,8 +59,8 @@ def process_file(file_object, all_dataset, run_timestamp):
|
||||
text_dict, top_sheet_dict = preprocess.split_text(contract_text)
|
||||
text_dict = preprocess.clean_tables(text_dict, filename)
|
||||
exhibit_pages, exhibit_chunk_mapping = preprocess.one_to_n_exhibit_chunking(text_dict, filename)
|
||||
# print(f"Exhibit Pages: {exhibit_pages}") # Debugging line
|
||||
# print(f"Exhibit Chunk Mapping: {exhibit_chunk_mapping}") # Debugging line
|
||||
logging.debug(f"Exhibit Pages: {exhibit_pages}")
|
||||
logging.debug(f"Exhibit Chunk Mapping: {exhibit_chunk_mapping}")
|
||||
print(f"{datetime_str()} Preprocessing Complete - {filename}")
|
||||
|
||||
################## ONE TO N ##################
|
||||
@@ -150,11 +152,12 @@ def run_one_to_n_prompts(filename, text_dict, exhibit_pages, exhibit_chunk_mappi
|
||||
reimbursement_exhibits = one_to_n_funcs.identify_reimbursement_exhibits(all_exhibit_headers, filename)
|
||||
|
||||
pages_to_process = preprocess.get_pages_to_process(reimbursement_exhibits, exhibit_chunk_mapping)
|
||||
# print(f"Pages to process: {pages_to_process}") # Debugging line
|
||||
logging.debug(f"Pages to process: {pages_to_process}")
|
||||
|
||||
################## RUN PROMPTS ##################
|
||||
one_to_n_results = []
|
||||
for page in pages_to_process:
|
||||
logging.debug(f"Processing page: {page}")
|
||||
# Use individual page text for subpages, full chunk for regular pages
|
||||
if '.' in page:
|
||||
# Subpage - get the text for this specific page
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import hashlib
|
||||
import re
|
||||
from typing import Callable
|
||||
import logging
|
||||
|
||||
import src.constants.investment_values as investment_values
|
||||
import src.investment.code_funcs as code_funcs
|
||||
@@ -68,10 +69,9 @@ def identify_reimbursement_exhibits(exhibit_headers: dict, filename: str) -> lis
|
||||
Returns:
|
||||
list[str]: List of page numbers (as strings) that likely contain reimbursement terms.
|
||||
"""
|
||||
# print(exhibit_headers) # Debugging line
|
||||
# print(type(exhibit_headers)) # Debugging line
|
||||
prompt = investment_prompts.IDENTIFY_REIMBURSEMENT_EXHIBITS_PROMPT(exhibit_headers, yaml_output=True) # Trying YAML output
|
||||
# print(prompt) # Debugging line
|
||||
logging.debug(f"""Identifying reimbursement exhibits for {filename} with prompt:
|
||||
{prompt}""")
|
||||
llm_answer_raw = llm_utils.invoke_claude(
|
||||
prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 2000
|
||||
)
|
||||
@@ -83,32 +83,33 @@ def identify_reimbursement_exhibits(exhibit_headers: dict, filename: str) -> lis
|
||||
if lines[0].strip().lower() == 'yaml':
|
||||
yaml_content = '\n'.join(lines[1:])
|
||||
|
||||
# print(llm_answer_raw) # Debugging line
|
||||
logging.debug(f"""LLM raw output for {filename}:
|
||||
{llm_answer_raw}""")
|
||||
# print("Extracted YAML content:\n", yaml_content) # Debugging line
|
||||
logging.debug(f"""YAML content for {filename}:
|
||||
{yaml_content}""")
|
||||
try:
|
||||
# Parse the YAML output into a dictionary
|
||||
exhibits_dict = yaml.safe_load(yaml_content)
|
||||
|
||||
# If any are values are EXPLICIT, return ONLY those that are EXPLICIT
|
||||
if any(v == 'EXPLICIT' for v in exhibits_dict.values()):
|
||||
return [str(page) for page, value in exhibits_dict.items() # Convert back to string
|
||||
if value == 'EXPLICIT']
|
||||
else:
|
||||
return [str(page) for page, value in exhibits_dict.items() # Convert back to string
|
||||
if value == 'IMPLICIT']
|
||||
# Return both EXPLICIT and IMPLICIT exhibits (exclude only 'NO')
|
||||
return [str(page) for page, value in exhibits_dict.items()
|
||||
if value in ['EXPLICIT', 'IMPLICIT']]
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error parsing YAML: {e}")
|
||||
print(f"Raw LLM output: {llm_answer_raw}")
|
||||
logging.error(f"Error parsing YAML: {e}")
|
||||
logging.error(f"Raw LLM output: {llm_answer_raw}")
|
||||
return list(exhibit_headers.keys()) # Return all keys of the exhibit headers if parsing fails
|
||||
|
||||
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)
|
||||
# print(prompt) # Debugging line
|
||||
logging.debug(f"""Running reimbursement primary prompt for {filename} with prompt:
|
||||
{prompt}""")
|
||||
llm_answer_raw = llm_utils.invoke_claude(
|
||||
prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=25000
|
||||
)
|
||||
# print(llm_answer_raw) # Debugging line
|
||||
logging.debug(f"""LLM raw output for {filename}:
|
||||
{llm_answer_raw}""")
|
||||
|
||||
# Check for the special "no results" case
|
||||
if "NO_REIMBURSEMENT_TERMS_FOUND" in llm_answer_raw:
|
||||
@@ -117,8 +118,8 @@ def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename
|
||||
try:
|
||||
llm_answer_final = string_utils.universal_json_load(llm_answer_raw)
|
||||
except ValueError as e:
|
||||
print(f"Error parsing LLM response: {e}")
|
||||
print(f"Raw LLM output: {llm_answer_raw}")
|
||||
logging.error(f"Error parsing LLM response: {e}")
|
||||
logging.error(f"Raw LLM output: {llm_answer_raw}")
|
||||
raise
|
||||
return llm_answer_final
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ from src import config, keywords, preprocessing_funcs
|
||||
import src.investment.smart_chunking_funcs as smart_chunking_funcs
|
||||
from src import keywords, preprocessing_funcs
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def clean_text(contract_text):
|
||||
|
||||
@@ -100,17 +102,17 @@ def get_pages_to_process(reimbursement_exhibits: list, exhibit_chunk_mapping: di
|
||||
# Find all pages mapped to this exhibit
|
||||
exhibit_pages_in_chunk = [page for page, mapped_exhibit in exhibit_chunk_mapping.items()
|
||||
if mapped_exhibit == exhibit_page]
|
||||
|
||||
logging.debug(f"Exhibit {exhibit_page} contains pages: {exhibit_pages_in_chunk}")
|
||||
# Check if any pages are subpages (contain '.')
|
||||
has_subpages = any('.' in page for page in exhibit_pages_in_chunk)
|
||||
|
||||
if has_subpages:
|
||||
# Split into individual subpages
|
||||
print(f"Exhibit {exhibit_page} has subpages - processing individually: {exhibit_pages_in_chunk}")
|
||||
logging.debug(f"Exhibit {exhibit_page} has subpages - will process individually: {exhibit_pages_in_chunk}")
|
||||
pages_to_process.extend(exhibit_pages_in_chunk)
|
||||
else:
|
||||
# Keep as one chunk (use the exhibit_page as representative)
|
||||
print(f"Exhibit {exhibit_page} is regular - processing as chunk")
|
||||
logging.debug(f"Exhibit {exhibit_page} is regular - will process as a single chunk")
|
||||
pages_to_process.append(exhibit_page)
|
||||
|
||||
return pages_to_process
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import logging
|
||||
|
||||
from src.prompts import investment_prompts
|
||||
import src.utils.llm_utils as llm_utils
|
||||
@@ -269,12 +270,15 @@ def get_exhibit_pages(text_dict: dict[str, str], filename: str) -> list[str]:
|
||||
for subpage in subpages:
|
||||
page_content = text_dict[subpage]
|
||||
prompt = investment_prompts.EXHIBIT_CHECK(page_content[0:200])
|
||||
# print(f"Prompting LLM for exhibit check on page {subpage}...") # Debugging line
|
||||
# print("Prompt:", prompt) # Debugging line
|
||||
logging.debug(f"Prompting LLM for exhibit check on page {subpage}...")
|
||||
logging.debug(f"""Prompt for exhibit check:
|
||||
{prompt}
|
||||
""")
|
||||
claude_answer_raw = llm_utils.invoke_claude(
|
||||
prompt, config.MODEL_ID_CLAUDE3_HAIKU, filename, max_tokens=150
|
||||
)
|
||||
# print("Claude answer:", claude_answer_raw) # Debugging line
|
||||
logging.debug(f"""Claude answer for exhibit check on page {subpage}:
|
||||
{claude_answer_raw}""")
|
||||
claude_answer_extracted = string_utils.extract_text_from_delimiters(
|
||||
claude_answer_raw, Delimiter.PIPE
|
||||
)
|
||||
|
||||
@@ -388,10 +388,15 @@ If no reimbursement terms are found, return the text "NO_REIMBURSEMENT_TERMS_FOU
|
||||
[C. CRITICAL INSTRUCTIONS]
|
||||
|
||||
LESSER OF LANGUAGE: When a contract contains "lesser of" statements:
|
||||
- Include with EVERY affected rate/service
|
||||
- Include even when separated from the actual rate information
|
||||
- Format consistently as "lesser of X and Y"
|
||||
- When a contract contains a global "lesser of" statement (such as "the lesser of the rates listed below or Provider's billed charges"), this statement applies to EVERY SINGLE RATE in the ENTIRE fee schedule that follows, regardless of format (percentage vs. dollar amount), location in the table (top, middle, or bottom), service type (OB, anesthesia, lab services, etc.), or grouping or formatting within the table
|
||||
- SPECIFIC "LESSER OF": Statements like "Payment shall be based on the lesser of X or Y"
|
||||
- GLOBAL CAPS: Statements like "At no time shall Plan pay more than..." or "shall not exceed Provider's billed charges"
|
||||
- When exception phrases appear (e.g., "with the exception of"), they only apply to the SPECIFIC "lesser of" statement, NOT to global caps
|
||||
- Global caps apply to ALL rates throughout the contract, regardless of exceptions
|
||||
|
||||
EXCEPTION LANGUAGE: When contracts contain exception clauses:
|
||||
- Exception phrases create carve-outs from SPECIFIC "lesser of" statements only
|
||||
- Exception items should combine: [their listed rate] + [any global caps that still apply]
|
||||
- Global caps (like "not to exceed billed charges") remain in effect even for exception items
|
||||
|
||||
CONTEXTUAL COMPLETENESS: The full payment context must be preserved:
|
||||
- "The maximum compensation shall be..." + "lesser of" statements
|
||||
@@ -436,6 +441,20 @@ Answer: "[{{'SERVICE_TERM' : 'Covered Services that are Medicare Covered Service
|
||||
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'
|
||||
Answer: "[{{'SERVICE_TERM' : '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', ...}}]"
|
||||
|
||||
[EXAMPLE 5]
|
||||
Here is an example with a "lesser of" statement followed by exceptions, but NO additional global caps:
|
||||
Text: 'Payment for services shall be based on the lesser of 75% of AHCCCS Fee for Service rates or Provider's charges less any applicable Co-Payments, with the exception of those services listed below.
|
||||
{{'HOPOS': ['A6544', 'L3201'], 'Description': ['COMPRESSION STOCKING', 'ORTHOPEDIC SHOE'], 'Amount': ['$ 25.00', '$ 30.00']}}'
|
||||
|
||||
Answer: "[{{'SERVICE_TERM': 'COMPRESSION STOCKING A6544', 'REIMB_TERM': '$ 25.00', ...}},
|
||||
{{'SERVICE_TERM': 'ORTHOPEDIC SHOE L3201', 'REIMB_TERM': '$ 30.00', ...}}]"
|
||||
|
||||
[EXAMPLE 6]
|
||||
Text: 'Payment shall be based on the lesser of 75% of AHCCCS rates or Provider's charges, with the exception of services below. At no time shall Plan pay more than Provider's billed charges.
|
||||
A6544: $25.00'
|
||||
|
||||
Answer: "[{{'SERVICE_TERM': 'A6544', 'REIMB_TERM': 'lesser of $25.00 and Provider's billed charges', ...}}]"
|
||||
|
||||
[E. ANALYSIS CONTEXT]
|
||||
[Start Context]
|
||||
{context.replace('"', "'")}
|
||||
|
||||
Reference in New Issue
Block a user