palexis3 commited on
Commit
cd5e987
·
1 Parent(s): bd3733d

Fixed code in file_processing file although date formatting needs tweaking

Browse files
app/.DS_Store CHANGED
Binary files a/app/.DS_Store and b/app/.DS_Store differ
 
app/api/routers/file_upload.py CHANGED
@@ -1,5 +1,5 @@
1
  from typing import Annotated
2
- from fastapi import APIRouter, UploadFile, Depends
3
  from app.categorization.file_processing import process_file, save_results
4
  from app.schema.index import FileUploadCreate
5
  import asyncio
@@ -9,6 +9,7 @@ import csv
9
  from app.engine.postgresdb import get_db_session
10
  from sqlalchemy.ext.asyncio import AsyncSession
11
 
 
12
  file_upload_router = r = APIRouter(prefix="/api/v1/file_upload", tags=["file_upload"])
13
 
14
  @r.post(
@@ -21,23 +22,45 @@ file_upload_router = r = APIRouter(prefix="/api/v1/file_upload", tags=["file_upl
21
  )
22
  async def create_file(input_file: UploadFile, db: AsyncSession = Depends(get_db_session)):
23
  try:
 
 
24
  # Create directory to store all uploaded .csv files
25
  file_upload_directory_path = "data/tx_data/input"
 
26
  if not os.path.exists(file_upload_directory_path):
27
  os.makedirs(file_upload_directory_path)
28
 
29
- # Write items of .csv filte to directory
30
- with open(os.path.join(file_upload_directory_path, input_file.filename)) as output_file:
31
- [output_file.write(" ".join(row)+'\n') for row in csv.reader(input_file)]
32
- output_file.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # With the newly created file and it's path, process and save it for embedding
35
- processed_file = process_file(os.path.realpath(input_file.filename))
36
- result = await asyncio.gather(processed_file)
37
- await save_results(db, result)
 
 
 
 
 
 
38
 
39
- except Exception:
40
- return {"message": "There was an error uploading this file. Ensure you have a .csv file with the following columns:"
41
- "\n transaction_date, type, category, name_description, amount"}
42
 
43
  return {"message": f"Successfully uploaded {input_file.filename}"}
 
1
  from typing import Annotated
2
+ from fastapi import APIRouter, UploadFile, Depends, HTTPException
3
  from app.categorization.file_processing import process_file, save_results
4
  from app.schema.index import FileUploadCreate
5
  import asyncio
 
9
  from app.engine.postgresdb import get_db_session
10
  from sqlalchemy.ext.asyncio import AsyncSession
11
 
12
+
13
  file_upload_router = r = APIRouter(prefix="/api/v1/file_upload", tags=["file_upload"])
14
 
15
  @r.post(
 
22
  )
23
  async def create_file(input_file: UploadFile, db: AsyncSession = Depends(get_db_session)):
24
  try:
25
+ result = {"file_name": input_file.filename, "output": "", "result": "", "processed_file": "", "error": ""}
26
+
27
  # Create directory to store all uploaded .csv files
28
  file_upload_directory_path = "data/tx_data/input"
29
+ output_csv_file_path = os.path.join(file_upload_directory_path, input_file.filename)
30
  if not os.path.exists(file_upload_directory_path):
31
  os.makedirs(file_upload_directory_path)
32
 
33
+ input_csv_file = open(output_csv_file_path, "a")
34
+
35
+ try:
36
+ if input_file.filename.endswith(".csv"):
37
+ read_csv = await input_file.read()
38
+ # result["output"] = read_csv
39
+
40
+ # Write items of .csv filte to directory
41
+ with open(output_csv_file_path, 'w') as csv_file:
42
+ result["processed_file"] = read_csv
43
+ [csv_file.write(" ".join(row)+'\n') for row in read_csv.decode("utf-8").splitlines()]
44
+ csv_file.close()
45
+
46
+ # with open(output_csv_file_path, 'r') as csv_file:
47
+ # result["output"] = csv_file.read()
48
+ # csv_file.close()
49
+
50
+ # print(f"create_file result: {result}")
51
 
52
+ # With the newly created file and it's path, process and save it for embedding
53
+ processed_file = process_file(output_csv_file_path)
54
+ result["processed_file"] = processed_file
55
+ result = await asyncio.gather(processed_file)
56
+ result["result"] = result
57
+ await save_results(db, result)
58
+
59
+ except Exception as e:
60
+ result["error"] = str(e)
61
+ raise HTTPException(status_code = 500, detail=f"create_file inner exception: {result} \n")
62
 
63
+ except Exception as e:
64
+ raise HTTPException(status_code = 500, detail=f"create_file outer exception: {result} \n")
 
65
 
66
  return {"message": f"Successfully uploaded {input_file.filename}"}
app/categorization/categorizer_list.py CHANGED
@@ -29,14 +29,14 @@ async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame:
29
  if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
30
  # Read description-category pairs from the reference file
31
  description_category_pairs = pd.read_csv(
32
- CATEGORY_REFERENCE_OUTPUT_FILE, header=None, names=['name/description', 'category']
33
  )
34
 
35
  # Extract only descriptions for faster matching
36
- descriptions = description_category_pairs['name/description'].values
37
 
38
  # Use fuzzy matching to find similar descriptions and assign the category
39
- tx_list['category'] = tx_list['name/description'].apply(
40
  fuzzy_match_list_categorizer,
41
  args=(descriptions, description_category_pairs),
42
  )
@@ -44,14 +44,14 @@ async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame:
44
  # Filter out uncategorized transactions, deduplicate, and sort by description
45
  uncategorized_descriptions = (
46
  tx_list[tx_list['category'].isnull()]
47
- .drop_duplicates(subset=['name/description'])
48
- .sort_values(by=['name/description'])
49
  )
50
 
51
  # Ask the language model to categorize the remaining descriptions
52
  if not uncategorized_descriptions.empty:
53
  categorized_descriptions = await llm_list_categorizer(
54
- uncategorized_descriptions[['name/description', 'category']]
55
  )
56
 
57
  categorized_descriptions.dropna(inplace=True)
@@ -59,8 +59,8 @@ async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame:
59
  # Update the category for uncategorized transactions based on the language model results
60
  if not categorized_descriptions.empty:
61
  tx_list['category'] = tx_list['category'].fillna(
62
- tx_list['name/description'].map(
63
- categorized_descriptions.set_index('name/description')['category']
64
  )
65
  )
66
 
 
29
  if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
30
  # Read description-category pairs from the reference file
31
  description_category_pairs = pd.read_csv(
32
+ CATEGORY_REFERENCE_OUTPUT_FILE, header=None, names=['name_description', 'category']
33
  )
34
 
35
  # Extract only descriptions for faster matching
36
+ descriptions = description_category_pairs['name_description'].values
37
 
38
  # Use fuzzy matching to find similar descriptions and assign the category
39
+ tx_list['category'] = tx_list['name_description'].apply(
40
  fuzzy_match_list_categorizer,
41
  args=(descriptions, description_category_pairs),
42
  )
 
44
  # Filter out uncategorized transactions, deduplicate, and sort by description
45
  uncategorized_descriptions = (
46
  tx_list[tx_list['category'].isnull()]
47
+ .drop_duplicates(subset=['name_description'])
48
+ .sort_values(by=['name_description'])
49
  )
50
 
51
  # Ask the language model to categorize the remaining descriptions
52
  if not uncategorized_descriptions.empty:
53
  categorized_descriptions = await llm_list_categorizer(
54
+ uncategorized_descriptions[['name_description', 'category']]
55
  )
56
 
57
  categorized_descriptions.dropna(inplace=True)
 
59
  # Update the category for uncategorized transactions based on the language model results
60
  if not categorized_descriptions.empty:
61
  tx_list['category'] = tx_list['category'].fillna(
62
+ tx_list['name_description'].map(
63
+ categorized_descriptions.set_index('name_description')['category']
64
  )
65
  )
66
 
app/categorization/file_processing.py CHANGED
@@ -8,6 +8,8 @@ from datetime import datetime
8
 
9
  import pandas as pd
10
  from dateparser import parse
 
 
11
 
12
  from app.categorization.categorizer_list import categorize_list
13
  from app.categorization.config import RESULT_OUTPUT_FILE, CATEGORY_REFERENCE_OUTPUT_FILE
@@ -42,8 +44,9 @@ async def process_file(file_path: str) -> Dict[str, Union[str, pd.DataFrame]]:
42
  except Exception as e:
43
  # Return an error indicator and exception info
44
  logging.debug(logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}")
45
- print(f"ERROR processing file {file_name}: {e}")
46
  result["error"] = str(e)
 
47
 
48
  return result
49
 
@@ -58,23 +61,42 @@ def standardize_csv_file(file_path: str) -> pd.DataFrame:
58
  Returns:
59
  pd.DataFrame: Prepared transaction data.
60
  """
 
61
  try:
62
- tx_list = pd.read_csv(file_path, index_col=False)
63
- tx_list.attrs["file_name"] = file_path
64
- tx_list.columns = tx_list.columns.str.lower().str.strip()
65
-
66
- # Standardize dates to YYYY/MM/DD format
67
- tx_list["date"] = pd.to_datetime(tx_list["date"]).dt.strftime("%Y/%m/%d")
68
-
69
- # Add source and reindex to desired tx format; category column is new and therefore empty
70
- tx_list.loc[:, "source"] = os.path.basename(file_path)
71
- tx_list = tx_list.reindex(columns=["transaction_date", "type", "category", "name_description", "amount"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  except Exception as e:
74
  # Return an error indicator and exception info
 
75
  logging.debug("standardize_csv_file Error: {e}")
 
 
76
 
77
- return tx_list
78
 
79
 
80
  async def save_results(db: AsyncSession, results: List) -> None:
@@ -98,6 +120,7 @@ async def save_results(db: AsyncSession, results: List) -> None:
98
  col_list = ["transaction_date", "type", "category", "name_description", "amount"]
99
  tx_list = pd.DataFrame(columns=col_list)
100
  for result in results:
 
101
  if not result["error"]:
102
  ok_files.append(result["file_name"])
103
  result_df = result["output"]
@@ -113,6 +136,10 @@ async def save_results(db: AsyncSession, results: List) -> None:
113
  await Transaction.bulk_create(db, txn_list_to_save)
114
 
115
  new_ref_data = tx_list[["name_description", "category"]]
 
 
 
 
116
  if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
117
  # If it exists, add master file to interim results
118
  old_ref_data = pd.read_csv(CATEGORY_REFERENCE_OUTPUT_FILE, names=["name_description", "category"], header=0)
 
8
 
9
  import pandas as pd
10
  from dateparser import parse
11
+ from fastapi import HTTPException
12
+ import csv
13
 
14
  from app.categorization.categorizer_list import categorize_list
15
  from app.categorization.config import RESULT_OUTPUT_FILE, CATEGORY_REFERENCE_OUTPUT_FILE
 
44
  except Exception as e:
45
  # Return an error indicator and exception info
46
  logging.debug(logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}")
47
+ # print(f"ERROR processing file {file_name}: {e}")
48
  result["error"] = str(e)
49
+ raise HTTPException(status_code = 500, detail=f"process_file result: {result}")
50
 
51
  return result
52
 
 
61
  Returns:
62
  pd.DataFrame: Prepared transaction data.
63
  """
64
+ result = {"csv_file": "", "file_path": file_path, "tx_list_columns": {}, "transaction_date": "", "tx_list_new_columns": "", "error": ""}
65
  try:
66
+ with open(file_path, 'r') as csv_file:
67
+ # result["csv_file"] = csv_file.read()
68
+ # reader = csv.reader(csv_file)
69
+ # data = list(reader)
70
+ # tx_list = pd.DataFrame(data, columns=data[0])
71
+
72
+ # result["csv_file"] = csv_file.read()
73
+ tx_list = pd.read_csv(file_path, parse_dates=['transaction_date'])
74
+ # tx_list.dropna(inplace=True)
75
+
76
+ # result["tx_list_columns"] = tx_list
77
+ tx_list.attrs["file_name"] = file_path
78
+ tx_list.columns = tx_list.columns.str.lower().str.strip()
79
+
80
+ # Standardize dates to YYYY/MM/DD format
81
+ result["transaction_date"] = pd.to_datetime(tx_list["transaction_date"])
82
+ print(f"transaction_date: {tx_list['transaction_date']}")
83
+ tx_list["transaction_date"] = pd.to_datetime(tx_list["transaction_date"]).dt.strftime("%Y/%m/%d")
84
+
85
+ # Add source and reindex to desired tx format; category column is new and therefore empty
86
+ tx_list.loc[:, "source"] = os.path.basename(file_path)
87
+ tx_list = tx_list.reindex(columns=["transaction_date", "type", "category", "name_description", "amount"])
88
+ # result["tx_list_new_columns"] = tx_list
89
+
90
+ return tx_list
91
 
92
  except Exception as e:
93
  # Return an error indicator and exception info
94
+ # print(f"standardize_csv_file exception: {e}")
95
  logging.debug("standardize_csv_file Error: {e}")
96
+ result["error"] = str(e)
97
+ raise HTTPException(status_code = 401, detail=f"standardize_csv_file result: {result}")
98
 
99
+ # return tx_list
100
 
101
 
102
  async def save_results(db: AsyncSession, results: List) -> None:
 
120
  col_list = ["transaction_date", "type", "category", "name_description", "amount"]
121
  tx_list = pd.DataFrame(columns=col_list)
122
  for result in results:
123
+ print(f"save_results result: {result}")
124
  if not result["error"]:
125
  ok_files.append(result["file_name"])
126
  result_df = result["output"]
 
136
  await Transaction.bulk_create(db, txn_list_to_save)
137
 
138
  new_ref_data = tx_list[["name_description", "category"]]
139
+
140
+ if not os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
141
+ os.makedirs(CATEGORY_REFERENCE_OUTPUT_FILE)
142
+
143
  if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
144
  # If it exists, add master file to interim results
145
  old_ref_data = pd.read_csv(CATEGORY_REFERENCE_OUTPUT_FILE, names=["name_description", "category"], header=0)
tests/test_file_upload.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import List
3
+ from pathlib import Path
4
+ from fastapi import Depends
5
+ from fastapi.testclient import TestClient
6
+ import pytest
7
+
8
+ from app.model.transaction import Transaction
9
+ from app.schema.index import TransactionType, TransactionCreate
10
+
11
+ from sqlalchemy.ext.asyncio import AsyncSession
12
+ from app.engine.postgresdb import get_db_session
13
+
14
+ @pytest.mark.asyncio
15
+ async def test_file_upload(client: TestClient, get_db_session_fixture: AsyncSession) -> None:
16
+ _test_upload_file = Path('/Users/patrickalexis/Documents/codepath-ai-course/codepath-group-project/backend/app/transactions_rag/transactions_2024.csv', 'new-index.json')
17
+ _files = {'input_file': _test_upload_file.open('rb')}
18
+
19
+ response = client.post(("/api/v1/file_upload/"),files=_files)
20
+ assert response.status_code == 201