2024-10-28 23:39:36 +00:00
|
|
|
import os
|
2025-01-06 15:39:29 +00:00
|
|
|
from io import StringIO
|
|
|
|
|
|
2024-10-28 23:39:36 +00:00
|
|
|
import pandas as pd
|
2024-12-06 17:27:42 +00:00
|
|
|
from botocore.exceptions import ClientError
|
2024-12-09 21:40:53 +00:00
|
|
|
from pyxlsb import open_workbook
|
2024-10-28 23:39:36 +00:00
|
|
|
|
2025-01-08 21:59:47 +00:00
|
|
|
import src.tracking.tracking_utils as tracking_utils
|
|
|
|
|
from src import config
|
2025-02-21 14:44:36 +00:00
|
|
|
from src.investment.code_funcs import crosswalk_levels, get_mappings
|
2025-03-04 16:52:14 +00:00
|
|
|
import src.constants.investment_values as investment_values
|
2025-03-04 22:08:16 +00:00
|
|
|
from src.utils.string_utils import datetime_str
|
2024-12-05 22:46:41 +00:00
|
|
|
|
|
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def read_local(file_path): # io_utils.py
|
2024-10-28 23:39:36 +00:00
|
|
|
# Check if the file is a text file
|
|
|
|
|
if os.path.isfile(file_path) and file_path.endswith(".txt"):
|
|
|
|
|
try:
|
|
|
|
|
# First attempt to open the file with UTF-8 encoding
|
|
|
|
|
with open(file_path, "r", encoding="utf-8") as file:
|
|
|
|
|
file_contents = file.read()
|
|
|
|
|
return file_contents
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
# If UTF-8 fails, try reading the file with ANSI encoding
|
|
|
|
|
try:
|
|
|
|
|
with open(file_path, "r", encoding="cp1252") as file:
|
|
|
|
|
file_contents = file.read()
|
|
|
|
|
return file_contents
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
# If ANSI also fails, log an error message or handle it accordingly
|
|
|
|
|
print(f"Failed to decode {file_path} with UTF-8 and cp1252 encodings.")
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-06 17:27:42 +00:00
|
|
|
if os.path.isfile(file_path) and file_path.endswith(".csv"):
|
|
|
|
|
try:
|
|
|
|
|
df = pd.read_csv(file_path)
|
|
|
|
|
return df
|
|
|
|
|
except Exception:
|
|
|
|
|
print('Failed to read .csv file.')
|
|
|
|
|
|
|
|
|
|
if os.path.isfile(file_path) and file_path.endswith(".xlsx"):
|
|
|
|
|
try:
|
|
|
|
|
df = pd.read_excel(file_path)
|
|
|
|
|
return df
|
|
|
|
|
except Exception:
|
|
|
|
|
print('Failed to read .xlsx file.')
|
2024-10-28 23:39:36 +00:00
|
|
|
|
|
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def list_s3_files(prefix=config.S3_PREFIX, bucket=config.S3_BUCKET): # io_utils.py
|
2024-10-28 23:39:36 +00:00
|
|
|
"""List all files in an S3 folder."""
|
|
|
|
|
response = config.S3_CLIENT.list_objects_v2(Bucket=bucket, Prefix=prefix)
|
|
|
|
|
return [item["Key"] for item in response.get("Contents", [])]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_s3():
|
|
|
|
|
s3_client = config.S3_CLIENT
|
|
|
|
|
bucket = config.S3_BUCKET
|
|
|
|
|
prefix = config.S3_PREFIX
|
|
|
|
|
|
|
|
|
|
file_list = []
|
|
|
|
|
continuation_token = None
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
|
|
|
|
|
if continuation_token:
|
|
|
|
|
response = s3_client.list_objects_v2(
|
|
|
|
|
Bucket=bucket, Prefix=prefix, ContinuationToken=continuation_token
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
response = s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix)
|
|
|
|
|
|
|
|
|
|
file_list += [
|
|
|
|
|
obj["Key"]
|
|
|
|
|
for obj in response.get("Contents", [])
|
|
|
|
|
if not obj["Key"].endswith("/")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if response.get("IsTruncated"):
|
|
|
|
|
continuation_token = response.get("NextContinuationToken")
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
contract_list = sorted(file_list)
|
|
|
|
|
files = {}
|
|
|
|
|
|
|
|
|
|
for contract in contract_list:
|
|
|
|
|
print(f"Reading {contract}")
|
|
|
|
|
try:
|
|
|
|
|
data = s3_client.get_object(Bucket=bucket, Key=contract)
|
2024-12-06 17:27:42 +00:00
|
|
|
if contract.endswith('.txt'):
|
|
|
|
|
contents = data["Body"].read()
|
|
|
|
|
context = contents.decode("utf-8")
|
|
|
|
|
path, filename = os.path.split(contract)
|
|
|
|
|
files[filename] = context
|
2024-10-28 23:39:36 +00:00
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
return files
|
|
|
|
|
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def read_s3_csv(csv_path): # io_utils.py
|
2024-12-06 17:27:42 +00:00
|
|
|
s3_client = config.S3_CLIENT
|
|
|
|
|
s3_parts = csv_path[5:].split("/", 1)
|
|
|
|
|
bucket = s3_parts[0]
|
|
|
|
|
prefix = s3_parts[1] if len(s3_parts) > 1 else ""
|
|
|
|
|
try:
|
|
|
|
|
for obj in s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix)["Contents"]:
|
|
|
|
|
key = obj["Key"]
|
|
|
|
|
if key.endswith(".csv"):
|
|
|
|
|
response = s3_client.get_object(Bucket=bucket, Key=key)
|
|
|
|
|
df = pd.read_csv(StringIO(response["Body"].read().decode("utf-8")))
|
|
|
|
|
return df
|
|
|
|
|
except ClientError as e:
|
|
|
|
|
print(f"Error listing S3 objects: {e}")
|
2024-10-28 23:39:36 +00:00
|
|
|
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def read_input(local_path=config.LOCAL_PATH): # io_utils.py
|
2024-10-28 23:39:36 +00:00
|
|
|
if config.READ_MODE == "local":
|
|
|
|
|
files = {}
|
|
|
|
|
for file in os.listdir(local_path):
|
|
|
|
|
full_path = os.path.join(local_path, file)
|
|
|
|
|
file_text = read_local(full_path)
|
|
|
|
|
files[file] = file_text
|
|
|
|
|
return files
|
|
|
|
|
elif config.READ_MODE == "s3":
|
|
|
|
|
return read_s3()
|
2025-01-06 15:39:29 +00:00
|
|
|
|
|
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def read_input_csv(): # io_utils.py
|
2024-12-06 17:27:42 +00:00
|
|
|
if config.DF_READ_MODE == "local":
|
|
|
|
|
ac_df = read_local(config.AC_DF) if config.AC_DF else None
|
|
|
|
|
b_df = read_local(config.B_DF) if config.B_DF else None
|
|
|
|
|
elif config.DF_READ_MODE == "s3":
|
|
|
|
|
ac_df = read_s3_csv(config.AC_DF) if config.AC_DF else None
|
|
|
|
|
b_df = read_s3_csv(config.B_DF) if config.B_DF else None
|
|
|
|
|
|
|
|
|
|
return ac_df, b_df
|
2024-10-28 23:39:36 +00:00
|
|
|
|
|
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def filter_already_processed(input_dict, input_dict_b): # io_utils.py
|
2024-12-17 16:34:59 +00:00
|
|
|
"""Filter out already processed files based on either local files or S3 master tracking"""
|
|
|
|
|
if config.WRITE_TO_S3:
|
|
|
|
|
# Get processed files from S3 master tracking for this specific batch
|
2025-01-09 17:59:28 +00:00
|
|
|
already_processed_ac, already_processed_b = tracking_utils.get_processed_files(config.BATCH_ID)
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
# Keep files that still need either AC or B processing
|
|
|
|
|
input_dict_ac = {
|
2025-01-06 15:39:29 +00:00
|
|
|
k: v for k, v in input_dict.items()
|
2024-12-17 16:34:59 +00:00
|
|
|
if k not in already_processed_ac
|
|
|
|
|
}
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
input_dict_b = {
|
2025-01-06 15:39:29 +00:00
|
|
|
k: v for k, v in input_dict_b.items()
|
2024-12-17 16:34:59 +00:00
|
|
|
if k not in already_processed_b
|
|
|
|
|
}
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
print(f"Filtering based on S3 master tracking for batch {config.BATCH_ID}:")
|
|
|
|
|
print(f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining")
|
|
|
|
|
print(f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining")
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
else:
|
|
|
|
|
# Original local directory checking logic
|
|
|
|
|
already_processed_ac, already_processed_b = [], []
|
|
|
|
|
for folder_name in os.listdir(config.OUTPUT_DIRECTORY):
|
|
|
|
|
folder_path = os.path.join(config.OUTPUT_DIRECTORY, folder_name)
|
|
|
|
|
if os.path.isdir(folder_path):
|
|
|
|
|
if config.AC_RESULTS_NAME in os.listdir(folder_path):
|
|
|
|
|
already_processed_ac.append(folder_name + ".txt")
|
|
|
|
|
if config.B_RESULTS_NAME in os.listdir(folder_path):
|
|
|
|
|
already_processed_b.append(folder_name + ".txt")
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
input_dict_ac = {
|
|
|
|
|
k: v for k, v in input_dict.items()
|
|
|
|
|
if k not in already_processed_ac
|
|
|
|
|
}
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
input_dict_b = {
|
|
|
|
|
k: v for k, v in input_dict_b.items()
|
|
|
|
|
if k not in already_processed_b
|
|
|
|
|
}
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-17 16:34:59 +00:00
|
|
|
print(f"Filtering based on local output directory:")
|
|
|
|
|
print(f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining")
|
|
|
|
|
print(f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining")
|
|
|
|
|
|
2024-11-04 22:54:31 +00:00
|
|
|
return input_dict_ac, input_dict_b
|
2024-10-28 23:39:36 +00:00
|
|
|
|
|
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def remove_txt_extension(filename): # io_utils.py
|
2024-12-06 17:27:42 +00:00
|
|
|
if isinstance(filename, str) and filename.lower().endswith(".txt"):
|
|
|
|
|
return filename[:-4]
|
2024-12-09 21:40:53 +00:00
|
|
|
return filename
|
|
|
|
|
|
2025-01-06 15:39:29 +00:00
|
|
|
|
2024-12-30 21:41:21 +00:00
|
|
|
def read_xlsb(path): # io_utils.py
|
2024-12-09 21:40:53 +00:00
|
|
|
with open_workbook(path) as wb:
|
|
|
|
|
with wb.get_sheet(1) as sheet:
|
|
|
|
|
rows = []
|
|
|
|
|
for row in sheet.rows():
|
|
|
|
|
rows.append([item.v for item in row]) # Extracting cell values
|
|
|
|
|
df = pd.DataFrame(rows[1:], columns=rows[0]) # Converting to DataFrame, assuming first row is header
|
2025-01-27 19:39:23 +00:00
|
|
|
return df
|
|
|
|
|
|
|
|
|
|
def write_local(df, filename, run_timestamp, output_type):
|
|
|
|
|
"""
|
|
|
|
|
Saves a DataFrame as a CSV file in a local directory.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df (pd.DataFrame): The DataFrame to save.
|
|
|
|
|
run_timestamp (str): The timestamp used to organize output files.
|
|
|
|
|
|
|
|
|
|
The function creates a directory based on the configured output path
|
|
|
|
|
and the provided timestamp, then saves the DataFrame as a CSV file
|
|
|
|
|
named "<BATCH_ID>-RESULTS.csv" within it.
|
|
|
|
|
"""
|
|
|
|
|
if output_type == "final":
|
|
|
|
|
output_dir = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, run_timestamp)
|
|
|
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
df.to_csv(os.path.join(output_dir, f'{config.BATCH_ID}-RESULTS.csv'))
|
|
|
|
|
elif output_type == "individual":
|
|
|
|
|
base_filename = os.path.splitext(filename)[0].strip()
|
|
|
|
|
os.makedirs(config.OUTPUT_DIRECTORY, exist_ok=True)
|
2025-01-31 19:45:33 +00:00
|
|
|
df.to_csv(os.path.join(config.OUTPUT_DIRECTORY, f'{base_filename}_-RESULTS.csv'), index=False)
|
2025-01-27 19:39:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def write_s3(df, filename, run_timestamp, output_type):
|
|
|
|
|
"""
|
|
|
|
|
Uploads a DataFrame to an S3 bucket as a CSV file.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
df (pd.DataFrame): The DataFrame to be written to S3.
|
|
|
|
|
run_timestamp (str): A timestamp representing the current run.
|
|
|
|
|
|
|
|
|
|
The function generates an S3 key based on the configured BATCH_ID and
|
|
|
|
|
uploads the DataFrame as a CSV file to the configured S3 bucket.
|
|
|
|
|
The CSV file is saved in the "consolidated" folder with the naming
|
|
|
|
|
convention "<BATCH_ID>-RESULTS.csv".
|
|
|
|
|
|
|
|
|
|
Configurations used:
|
|
|
|
|
- config.BATCH_ID: Identifier for the batch process.
|
|
|
|
|
- config.S3_CLIENT: The AWS S3 client for performing operations.
|
|
|
|
|
- config.S3_OUTPUT_BUCKET: The target S3 bucket for storing the file.
|
|
|
|
|
|
|
|
|
|
Prints:
|
|
|
|
|
Confirmation message with the S3 key of the uploaded file.
|
|
|
|
|
"""
|
|
|
|
|
if output_type == "final":
|
|
|
|
|
output_path = f"{config.BATCH_ID}/{run_timestamp}/{config.BATCH_ID}-RESULTS.csv"
|
|
|
|
|
elif output_type == "individual":
|
|
|
|
|
output_path = f"{config.BATCH_ID}/{run_timestamp}/individual/{filename}-RESULTS.csv"
|
2025-03-24 18:56:50 +00:00
|
|
|
elif output_type == "error":
|
|
|
|
|
output_path = f"{config.BATCH_ID}/{run_timestamp}/{config.BATCH_ID}-ERRORS.csv"
|
|
|
|
|
else:
|
|
|
|
|
# Fallback path for unknown output types
|
|
|
|
|
output_path = f"{config.BATCH_ID}/{run_timestamp}/{config.BATCH_ID}-unknown-{output_type}.csv"
|
2025-01-27 19:39:23 +00:00
|
|
|
|
|
|
|
|
csv_buffer = df.to_csv(index=False).encode()
|
2025-02-14 22:43:01 +00:00
|
|
|
try:
|
|
|
|
|
config.S3_CLIENT.put_object(
|
|
|
|
|
Bucket=config.S3_OUTPUT_BUCKET,
|
|
|
|
|
Key=output_path,
|
|
|
|
|
Body=csv_buffer
|
|
|
|
|
)
|
|
|
|
|
except ClientError as e:
|
|
|
|
|
print(f"Error uploading to S3: {e}")
|
|
|
|
|
|
|
|
|
|
if output_type == "final":
|
|
|
|
|
print(f"Saved batch output file: {output_path}")
|
|
|
|
|
elif output_type == "individual":
|
2025-02-21 14:44:36 +00:00
|
|
|
print(f"Saved individual output file: {output_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_all_dataset():
|
|
|
|
|
"""_summary_: loads all csv files required for processing contracts including crosswalk files
|
|
|
|
|
and mappings (embeddings, past results may be added later)
|
|
|
|
|
"""
|
|
|
|
|
all_dataset = {}
|
2025-03-04 16:52:14 +00:00
|
|
|
all_dataset['proc_codes_crosswalk'] = crosswalk_levels("proc_cd", investment_values.proc_levels)
|
|
|
|
|
all_dataset['diag_codes_crosswalk'] = crosswalk_levels("diag_cd", investment_values.diag_levels)
|
2025-02-21 14:44:36 +00:00
|
|
|
all_dataset['codes_mappings'] = get_mappings()
|
|
|
|
|
|
2025-03-04 16:52:14 +00:00
|
|
|
return all_dataset
|
|
|
|
|
|
|
|
|
|
def load_embeddings():
|
2025-03-04 22:08:16 +00:00
|
|
|
"""loads all embedding files required for processing indirect codes (e.g. proc codes)
|
2025-03-04 16:52:14 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# list all embeddings present in s3
|
|
|
|
|
s3_client = config.S3_CLIENT
|
|
|
|
|
response = s3_client.list_objects_v2(Bucket="doczy-investment", Prefix="embeddings")
|
|
|
|
|
file_list = [
|
|
|
|
|
obj["Key"]
|
|
|
|
|
for obj in response.get("Contents", [])
|
|
|
|
|
if not obj["Key"].endswith("/")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# list embeddings already present locally
|
|
|
|
|
if not os.path.exists("embeddings"):
|
|
|
|
|
os.makedirs("embeddings")
|
|
|
|
|
local_files = []
|
|
|
|
|
for root, dirs, files in os.walk("embeddings"):
|
|
|
|
|
for name in files:
|
|
|
|
|
local_files.append(os.path.join(root, name))
|
|
|
|
|
|
|
|
|
|
# download embeddings not present locally
|
|
|
|
|
for file in file_list:
|
2025-03-04 22:08:16 +00:00
|
|
|
local_path = file
|
|
|
|
|
|
|
|
|
|
# create directory structure for this file
|
|
|
|
|
directory = os.path.dirname(local_path)
|
|
|
|
|
if not os.path.exists(directory):
|
|
|
|
|
os.makedirs(directory)
|
|
|
|
|
|
|
|
|
|
# check if file already exists
|
|
|
|
|
if not os.path.exists(local_path):
|
|
|
|
|
try:
|
|
|
|
|
print(f"{datetime_str()} Downloading {file} to {local_path}...")
|
|
|
|
|
s3_client.download_file("doczy-investment", file, local_path)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"{datetime_str()} Error downloading {file}: {e}")
|