afb6d5185d
Feature/lesser table caching refactor hybrid * chore: Remove unused duplicate main.py from shared pipeline * fix: Correct crosswalk paths in aarete_derived.py * chore: Remove unused documentation files from fieldExtraction * docs: Add documentation files to documentation folder * docs: Update README with uv setup, expanded project structure, and branching conventions * docs: Add uv installation steps with Ubuntu/WSL emphasis * Enable prompt caching for all remaining LLM calls - Add _INSTRUCTION() functions for: EXHIBIT_HEADER, EXHIBIT_LINKAGE, EXHIBIT_TITLE_MATCH, DATE_FIX, DERIVED_TERM_DATE, CHECK_PROVIDER_NAME_MATCH, SPECIAL_CASE_ASSIGNMENT - Update all invoke_claude() calls in saas and clover pipelines to use cache=True with corresponding _INSTRUCTION() functions - Add new instructions to get_cacheable_instructions() for cache warming - Update tests for new instruction functions Functions now using caching: - prompt_exhibit_level - prompt_exhibit_lesser (EXHIBIT_LEVEL_LESSER_OF) - prompt_fee_schedule_breakout - prompt_grouper_breakout - prompt_special_case_assignment - prompt_exhibit_linkage - prompt_exhibit_header - prompt_smart_chunked (ONE_TO_ONE templates) - prompt_date_fix - prompt_derived_term_date - prompt_exhibit_title_match - provider_name_match_check 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Reorder * feat: Add bcbs_promise client pipeline with OFFSET_TERM extraction - Add new bcbs_promise client with HSC-based OFFSET_TERM field extraction - Extract full paragraph text of offset/recoupment provisions from contracts - Derive OFFSET_INDICATOR (Y/N) from OFFSET_TERM presence - Fix reorder_columns to preserve extra columns not in COLUMN_ORDER - Update QC/QA output path to outputs/qc_qa/ * fix: Update dev deps and test assertions for QC/QA output path - Add pytest/pytest-mock to dev dependencies for mypy type checking - Update test assertions to expect outputs/qc_qa instead of qa_qc_output * style: Apply black formatting to prompt_templates.py * Merge main, move scripts * Archive some scripts * update py version * remove .py version file * Remove ASCII characters * Restore testbed code * restore tracking * Update testbed metrics * Enable prompt caching for CODE_LAST_CHECK, FILL_BILL_TYPE, DUAL_LOB_CHECK, and GROUPER_BREAKOUT - Add CODE_LAST_CHECK_INSTRUCTION() for service specificity classification - Add FILL_BILL_TYPE_INSTRUCTION() for bill type code determination - Add DUAL_LOB_CHECK_INSTRUCTION() for Medicare/Medicaid classification - Update code_funcs.py to use caching for CODE_LAST_CHECK, FILL_BILL_TYPE, GROUPER_BREAKOUT - Update postprocessing_funcs.py to use caching for DUAL_LOB_CHECK - Add new instructions to get_cacheable_instructions() for cache warming - Add unit tests for new instruction functions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix postprocessing_funcs to remove invalid columns * Merge branch 'main' into feature/lesser-table-caching-refactor-hybrid * Revert prompt caching changes from aed1b73c * update formatting * Update imports Approved-by: Sha Brown Approved-by: Praneel Panchigar
198 lines
7.5 KiB
Python
198 lines
7.5 KiB
Python
import boto3
|
|
import pandas as pd
|
|
from io import StringIO
|
|
import os
|
|
import json
|
|
import requests
|
|
|
|
|
|
"""
|
|
This script is used to process files from a source S3 bucket and trigger the pipeline for processing.
|
|
|
|
The script does the following:
|
|
1. Load files from the source S3 bucket based on the directory prefix
|
|
2. Create a batch in the client bucket
|
|
3. Copy files to the client bucket and add tags
|
|
4. Move the original files to a processed folder in the source bucket
|
|
5. Create a contract list and send it to the pipeline API
|
|
6. Update the CSV with the processed batch details
|
|
7. Trigger the pipeline API
|
|
|
|
The script uses the following variables:
|
|
- source_bucket: The source S3 bucket name
|
|
(Client bucket where all source files are located.
|
|
NOTE: This function will move the files to processed folder in the source bucket, so the source prefix will be empty after the execution)
|
|
|
|
- api_trigger_url: The URL of the pipeline trigger API ( This explicit url can be reaplced by using AWS SDK to get the API URL)
|
|
|
|
- create_batch_url: The URL of the create batch API
|
|
|
|
- csv_file: The name of the CSV file to store processed batch details
|
|
(Usually we add relevant bucket of files to track this, e.g. Facility_files.csv )
|
|
|
|
- processed_csv_s3_key: The S3 key to store the processed CSV file
|
|
|
|
- client_bucket: The client S3 bucket name
|
|
|
|
- directory_prefix_list: A list of directory prefixes to search for files in the source bucket
|
|
|
|
- client_name: The name of the client (for logs)
|
|
|
|
- username: The username of the user triggering the pipeline (for logs)
|
|
|
|
The script can be scheduled to run periodically to process files from the source bucket.
|
|
|
|
Add this code to a lambda function, ensure there is a lambda layer for pandas
|
|
AWS provides a layer for pandas, you can use that or create your own layer (AWSSDKPandas-Python310)
|
|
|
|
Once the function is created, we can create an event trigger for the lambda function to run at a specific time or a a specific interval.
|
|
|
|
Note: The script assumes that the pipeline trigger API and create batch API are already set up and working correctly.
|
|
|
|
"""
|
|
|
|
|
|
|
|
source_bucket = 'centene-texas-files'
|
|
api_trigger_url = 'https://<APISTAGE>.execute-api.us-east-2.amazonaws.com/dev/trigger-pipeline'
|
|
create_batch_url = "https://<APISTAGE>.execute-api.us-east-2.amazonaws.com/dev/create-batch"
|
|
csv_file = 'ancillary_files.csv' #file count need to as prefix
|
|
processed_csv_s3_key = f'processed_batch_ids/{csv_file}'
|
|
client_bucket = 'doczyai-use2-u-cn1-s3-textract-processing-001'
|
|
client_name = 'Centene-texas'
|
|
username = '<Username>'
|
|
|
|
directory_prefix_list = [
|
|
'for-textract/'
|
|
# Can add more prefixes here if needed
|
|
|
|
|
|
# 'TX-Legacy-agreements_2/batch2/for-processing/',
|
|
# 'TX-Legacy-agreements_2/batch2/for-processing/',
|
|
# 'TX-Legacy-agreements_3/batch3/for-processing/',
|
|
# 'TX-Legacy-agreements_4/batch4/for-processing/',
|
|
# 'TX-Legacy-Amendments/TX-Legacy-Amendments/for-processing/'
|
|
]
|
|
|
|
|
|
def lambda_handler(event, context):
|
|
s3_resource = boto3.resource('s3')
|
|
|
|
for directory_prefix in directory_prefix_list:
|
|
bucket_obj = s3_resource.Bucket(source_bucket)
|
|
files = [obj.key for obj in bucket_obj.objects.filter(Prefix=directory_prefix) if obj.key.endswith('.Pdf') or obj.key.endswith('.pdf')]
|
|
|
|
if files:
|
|
print(f"Processing files for directory prefix: {directory_prefix}")
|
|
process_files(source_bucket, directory_prefix, files[:60], create_batch_url, api_trigger_url)
|
|
break
|
|
else:
|
|
print(f"No files found for directory prefix: {directory_prefix}. Moving to the next prefix...")
|
|
|
|
def process_files(source_bucket, directory_prefix, files, create_batch_url, api_trigger_url):
|
|
s3_client = boto3.client('s3')
|
|
|
|
|
|
# Load existing CSV from S3 if it exists
|
|
existing_df = None
|
|
try:
|
|
csv_obj = s3_client.get_object(Bucket=source_bucket, Key=processed_csv_s3_key)
|
|
existing_df = pd.read_csv(csv_obj['Body'])
|
|
print(f"Loaded existing CSV with {len(existing_df)} records")
|
|
except s3_client.exceptions.NoSuchKey:
|
|
print("No existing CSV found. A new one will be created.")
|
|
|
|
batch_id, landing_zone = create_batch(client_bucket, create_batch_url)
|
|
if batch_id == 'failed_cases':
|
|
print("Batch creation failed. Skipping this batch...")
|
|
return
|
|
|
|
contract_list = []
|
|
for s3_key in files:
|
|
|
|
filename = os.path.basename(s3_key).replace('.Pdf', '.pdf')
|
|
new_s3_key = f'contracts-landing-zone/{batch_id}/{filename}'
|
|
|
|
# Copy the file to the new key with .pdf extension
|
|
copy_source = {'Bucket': source_bucket, 'Key': s3_key}
|
|
s3_client.copy_object(CopySource=copy_source, Bucket=client_bucket, Key=new_s3_key)
|
|
|
|
# Add tags to the copied file
|
|
s3_client.put_object_tagging(
|
|
Bucket=client_bucket,
|
|
Key=new_s3_key,
|
|
Tagging={
|
|
'TagSet': [
|
|
{
|
|
'Key': 'BatchId',
|
|
'Value': batch_id
|
|
}
|
|
]
|
|
}
|
|
)
|
|
|
|
# Move the original file to the processed subfolder
|
|
processed_s3_key = f'processed/{os.path.basename(s3_key)}'
|
|
s3_client.copy_object(CopySource=copy_source, Bucket=source_bucket, Key=processed_s3_key)
|
|
s3_client.delete_object(Bucket=source_bucket, Key=s3_key)
|
|
|
|
# Add file details to contract list
|
|
contract_list.append({
|
|
"contract_name": filename,
|
|
"groups": ["A", "C"], # This should be dynamic based on your requirements
|
|
"contract_source_path": new_s3_key
|
|
})
|
|
print(f"Uploaded {len(files)} files to S3 bucket for batch id {batch_id}")
|
|
data = {
|
|
"s3_bucket": client_bucket,
|
|
"batch_id": batch_id,
|
|
"client_name": client_name,
|
|
"username": username,
|
|
"contract_list": contract_list
|
|
}
|
|
|
|
# Convert contract list to DataFrame and append to existing DataFrame
|
|
new_df = pd.DataFrame(contract_list)
|
|
new_df['batch_id'] = batch_id
|
|
if existing_df is not None:
|
|
existing_df = pd.concat([existing_df, new_df])
|
|
else:
|
|
existing_df = new_df
|
|
|
|
# Upload updated CSV back to S3
|
|
csv_buf = StringIO()
|
|
existing_df.to_csv(csv_buf, header=True, index=False)
|
|
csv_buf.seek(0)
|
|
|
|
s3_client.put_object(Bucket=source_bucket, Body=csv_buf.getvalue(), Key=processed_csv_s3_key)
|
|
|
|
print(f"Saved updated batch details to {processed_csv_s3_key} and sending to API...")
|
|
response = requests.post(api_trigger_url, json=data)
|
|
print(response.text)
|
|
print(response.status_code)
|
|
|
|
return {
|
|
"statusCode": 200,
|
|
"body": json.dumps("Processed files successfully")
|
|
}
|
|
|
|
def create_batch(client_bucket, create_batch_url):
|
|
myobj = { "client-bucket-name": client_bucket }
|
|
response = requests.post(create_batch_url, json=myobj)
|
|
if response.status_code >= 200 and response.status_code < 300:
|
|
try:
|
|
response_body = json.loads(json.loads(response.text)['body'])
|
|
new_batch_id = response_body['batch_id']
|
|
landing_zone = response_body['landing_zone']
|
|
except:
|
|
print(myobj)
|
|
print(response.text)
|
|
new_batch_id = 'failed_cases'
|
|
landing_zone = 'contracts_landing_zone'
|
|
else:
|
|
print(response.text)
|
|
new_batch_id = 'failed_cases'
|
|
landing_zone = 'contracts_landing_zone'
|
|
|
|
return new_batch_id, landing_zone
|