palexis3 commited on
Commit
e506f4d
·
1 Parent(s): 7c83de0

Add logging to show encoding/decoding issue

Browse files
app/api/routers/file_upload.py CHANGED
@@ -5,6 +5,7 @@ from app.schema.index import FileUploadCreate
5
  import asyncio
6
  import os
7
  import csv
 
8
 
9
  from app.engine.postgresdb import get_db_session
10
  from sqlalchemy.ext.asyncio import AsyncSession
@@ -38,19 +39,19 @@ async def create_file(input_file: UploadFile, db: AsyncSession = Depends(get_db_
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
 
5
  import asyncio
6
  import os
7
  import csv
8
+ import codecs
9
 
10
  from app.engine.postgresdb import get_db_session
11
  from sqlalchemy.ext.asyncio import AsyncSession
 
39
  # result["output"] = read_csv
40
 
41
  # Write items of .csv filte to directory
42
+ with open(output_csv_file_path, 'w', encoding="utf-8") as csv_file:
43
  result["processed_file"] = read_csv
44
+ [csv_file.write(" ".join(row)+'\n') for row in read_csv.decode("utf-8").strip().splitlines()]
45
  csv_file.close()
46
 
47
+ with open(output_csv_file_path, 'r', encoding="utf-8") as csv_file:
48
+ result["output"] = csv_file.read()
49
+ csv_file.close()
50
 
51
+ print(f"create_file result: {result}")
52
 
53
  # With the newly created file and it's path, process and save it for embedding
54
+ processed_file = await process_file(output_csv_file_path)
55
  result["processed_file"] = processed_file
56
  result = await asyncio.gather(processed_file)
57
  result["result"] = result
app/categorization/categorizer.py CHANGED
@@ -77,7 +77,7 @@ async def llm_list_categorizer(tx_list: pd.DataFrame) -> pd.DataFrame:
77
  chain = LLMChain(llm=llm, prompt=prompt)
78
 
79
  # Iterate over the DataFrame in batches of TX_PER_LLM_RUN transactions
80
- tasks = [llm_sublist_categorizer(tx_list.attrs['file_name'], chain=chain, tx_descriptions="\n".join(chunk['name/description']).strip())
81
  for chunk in np.array_split(tx_list, tx_list.shape[0] // TX_PER_LLM_RUN + 1)]
82
 
83
  # Gather results and extract (valid) outputs
@@ -92,7 +92,7 @@ async def llm_list_categorizer(tx_list: pd.DataFrame) -> pd.DataFrame:
92
  output for valid_result in valid_results for output in valid_result]
93
 
94
  # Return a DataFrame with the valid outputs
95
- return pd.DataFrame(valid_outputs, columns=['name/description', 'category'])
96
 
97
 
98
  @retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6))
 
77
  chain = LLMChain(llm=llm, prompt=prompt)
78
 
79
  # Iterate over the DataFrame in batches of TX_PER_LLM_RUN transactions
80
+ tasks = [llm_sublist_categorizer(tx_list.attrs['file_name'], chain=chain, tx_descriptions="\n".join(chunk['name_description']).strip())
81
  for chunk in np.array_split(tx_list, tx_list.shape[0] // TX_PER_LLM_RUN + 1)]
82
 
83
  # Gather results and extract (valid) outputs
 
92
  output for valid_result in valid_results for output in valid_result]
93
 
94
  # Return a DataFrame with the valid outputs
95
+ return pd.DataFrame(valid_outputs, columns=['name_description', 'category'])
96
 
97
 
98
  @retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(6))
app/categorization/categorizer_list.py CHANGED
@@ -1,11 +1,10 @@
1
- # Standard library imports
2
  import os
3
  from datetime import datetime
4
 
5
- # Third-party library imports
 
6
  import pandas as pd
7
 
8
- # Local application/library specific imports
9
  from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE
10
  from app.categorization.categorizer import llm_list_categorizer, fuzzy_match_list_categorizer
11
 
@@ -25,46 +24,60 @@ async def categorize_list(tx_list: pd.DataFrame) -> pd.DataFrame:
25
  Returns:
26
  pd.DataFrame: The original DataFrame with an additional column for the category.
27
  """
 
28
 
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
- )
43
-
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)
58
-
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
-
67
- # Fill remaining NaN values in 'category' with 'Other'
68
- tx_list['category'] = tx_list['category'].fillna('Other')
69
 
70
- return tx_list
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from datetime import datetime
3
 
4
+ from fastapi import HTTPException
5
+
6
  import pandas as pd
7
 
 
8
  from app.categorization.config import CATEGORY_REFERENCE_OUTPUT_FILE
9
  from app.categorization.categorizer import llm_list_categorizer, fuzzy_match_list_categorizer
10
 
 
24
  Returns:
25
  pd.DataFrame: The original DataFrame with an additional column for the category.
26
  """
27
+ result = {"tx_list": tx_list, "description_category_pairs": "", "uncategorized_descriptions": "", "categorized_descriptions": "", "error": ""}
28
 
29
+ try:
30
+ if not os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
31
+ os.makedirs(CATEGORY_REFERENCE_OUTPUT_FILE)
32
+
33
+ if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
34
+ # Read description-category pairs from the reference file
35
+ description_category_pairs = pd.read_csv(
36
+ CATEGORY_REFERENCE_OUTPUT_FILE, header=None, names=['name_description', 'category']
37
+ )
38
+ result['description_category_pairs'] = description_category_pairs
39
+
40
+ # Extract only descriptions for faster matching
41
+ descriptions = description_category_pairs['name_description'].values
42
+
43
+ # Use fuzzy matching to find similar descriptions and assign the category
44
+ tx_list['category'] = tx_list['name_description'].apply(
45
+ fuzzy_match_list_categorizer,
46
+ args=(descriptions, description_category_pairs),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  )
 
 
 
48
 
49
+ # Filter out uncategorized transactions, deduplicate, and sort by description
50
+ uncategorized_descriptions = (
51
+ tx_list[tx_list['category'].isnull()]
52
+ .drop_duplicates(subset=['name_description'])
53
+ .sort_values(by=['name_description'])
54
+ )
55
+ result['uncategorized_descriptions'] = uncategorized_descriptions
56
+
57
+ # Ask the language model to categorize the remaining descriptions
58
+ if not uncategorized_descriptions.empty:
59
+ categorized_descriptions = await llm_list_categorizer(
60
+ uncategorized_descriptions[['name_description', 'category']]
61
+ )
62
+
63
+ categorized_descriptions.dropna(inplace=True)
64
+ result['categorized_descriptions'] = categorized_descriptions
65
+
66
+ # Update the category for uncategorized transactions based on the language model results
67
+ if not categorized_descriptions.empty:
68
+ tx_list['category'] = tx_list['category'].fillna(
69
+ tx_list['name_description'].map(
70
+ categorized_descriptions.set_index('name_description')['category']
71
+ )
72
+ )
73
+
74
+ # Fill remaining NaN values in 'category' with 'Other'
75
+ tx_list['category'] = tx_list['category'].fillna('Other')
76
+
77
+ return tx_list
78
+
79
+ except Exception as e:
80
+ # Return an error indicator and exception info
81
+ print(f"ERROR categorizer_list: {e} errorType: {type(e)}")
82
+ result["error"] = str(e)
83
+ raise HTTPException(status_code = 500, detail=f"categorize_list result: {result}")
app/categorization/file_processing.py CHANGED
@@ -34,8 +34,9 @@ async def process_file(file_path: str) -> Dict[str, Union[str, pd.DataFrame]]:
34
  file_name = os.path.basename(file_path)
35
  result = {"file_name": file_name, "output": pd.DataFrame(), "error": ""}
36
  try:
37
- # Read file into standardized tx format: source, date, type, category, description, amount
38
  tx_list = standardize_csv_file(file_path)
 
39
 
40
  # Categorize transactions
41
  result["output"] = await categorize_list(tx_list)
@@ -44,7 +45,7 @@ async def process_file(file_path: str) -> Dict[str, Union[str, pd.DataFrame]]:
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
 
@@ -63,40 +64,43 @@ def standardize_csv_file(file_path: str) -> pd.DataFrame:
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)
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:
@@ -110,51 +114,58 @@ async def save_results(db: AsyncSession, results: List) -> None:
110
  Returns:
111
  None
112
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- # Concatenate all (valid) results into a single DataFrame
115
- # Print errors to console
116
- ok_files = []
117
- ko_files = []
118
- error_messages = []
119
-
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"]
127
- result_df.columns = col_list
128
- tx_list = pd.concat([tx_list, result_df], ignore_index=True)
129
- else:
130
- ko_files.append(result["file_name"])
131
- error_messages.append(f"{result['file_name']}: {result['error']}")
132
-
133
- # Save to database
134
- # FIXME: get user_id from session
135
- txn_list_to_save = [TransactionCreate(**row.to_dict(), user_id=1) for _, row in tx_list.iterrows()]
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)
146
- new_ref_data = pd.concat([old_ref_data, new_ref_data], ignore_index=True)
147
-
148
- # Drop duplicates, sort, and write to create new Master File
149
- new_ref_data.drop_duplicates(subset=["name_description"]).sort_values(by=["name_description"]).to_csv(
150
- CATEGORY_REFERENCE_OUTPUT_FILE, mode="w", index=False, header=True
151
- )
152
-
153
- # Summarize results
154
- print(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n")
155
- logging.debug(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n")
156
- if len(ko_files):
157
- print(f"Errors in the following files:")
158
- for message in error_messages:
159
- print(f" {message}")
160
- print("\n")
 
34
  file_name = os.path.basename(file_path)
35
  result = {"file_name": file_name, "output": pd.DataFrame(), "error": ""}
36
  try:
37
+ # Read file into standardized tx format: transaction_date, name_description, type, amount, category, source
38
  tx_list = standardize_csv_file(file_path)
39
+ print(f"process_file tx_list columns: \n {tx_list.columns.tolist()}")
40
 
41
  # Categorize transactions
42
  result["output"] = await categorize_list(tx_list)
 
45
  except Exception as e:
46
  # Return an error indicator and exception info
47
  logging.debug(logging.ERROR, f"| File: {file_name} | Unexpected Error: {e}")
48
+ print(f"ERROR processing file {file_name}: {e} errorType: {type(e)}")
49
  result["error"] = str(e)
50
  raise HTTPException(status_code = 500, detail=f"process_file result: {result}")
51
 
 
64
  """
65
  result = {"csv_file": "", "file_path": file_path, "tx_list_columns": {}, "transaction_date": "", "tx_list_new_columns": "", "error": ""}
66
  try:
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
+ tx_list = pd.read_csv(file_path, encoding="utf-8")
73
+ print(f"standardize_csv_file tx_list: {tx_list} \n")
74
+ tx_list.loc[:, "category"] = ""
75
+ result["csv_file"] = tx_list
76
+
77
+ # result["tx_list_columns"] = tx_list
78
+ tx_list.attrs["file_name"] = file_path
79
+ tx_list.columns = tx_list.columns.str.lower().str.strip()
80
 
81
+ # # Standardize dates to YYYY/MM/DD format
82
+ # result["transaction_date"] = pd.to_datetime(tx_list["transaction_date"])
83
+ # print(f"transaction_date: {tx_list['transaction_date']}")
84
+ # tx_list["transaction_date"] = pd.to_datetime(tx_list["transaction_date"]).dt.strftime("%Y/%m/%d")
85
+
86
+ # Add source and reindex to desired tx format; category column is new and therefore empty
87
+ tx_list.loc[:, "source"] = os.path.basename(file_path)
88
+ # tx_list = tx_list.reindex(columns=["transaction_date", "name_description", "type", "amount", "category"])
89
+ # result["tx_list_new_columns"] = tx_list.columns
90
+
91
+ # print(f"standardize_csv_file result: {result}")
92
 
93
+ # with open(file_path, 'r') as csv_file:
94
+ # return tx_list
95
 
96
  except Exception as e:
97
  # Return an error indicator and exception info
98
  # print(f"standardize_csv_file exception: {e}")
99
  logging.debug("standardize_csv_file Error: {e}")
100
  result["error"] = str(e)
101
+ raise HTTPException(status_code = 401, detail=f"standardize_csv_file exception: {result}")
102
 
103
+ return tx_list
104
 
105
 
106
  async def save_results(db: AsyncSession, results: List) -> None:
 
114
  Returns:
115
  None
116
  """
117
+ try:
118
+ # Concatenate all (valid) results into a single DataFrame
119
+ # Print errors to console
120
+ ok_files = []
121
+ ko_files = []
122
+ error_messages = []
123
+
124
+ col_list = ["transaction_date", "name_description", "type", "amount", "category"]
125
+ tx_list = pd.DataFrame(columns=col_list)
126
+ for result in results:
127
+ print(f"save_results result: {result}")
128
+ if not result["error"]:
129
+ ok_files.append(result["file_name"])
130
+ result_df = result["output"]
131
+ result_df.columns = col_list
132
+ tx_list = pd.concat([tx_list, result_df], ignore_index=True)
133
+ else:
134
+ ko_files.append(result["file_name"])
135
+ error_messages.append(f"{result['file_name']}: {result['error']}")
136
+
137
+ # Save to database
138
+ # FIXME: get user_id from session
139
+ txn_list_to_save = [TransactionCreate(**row.to_dict(), user_id=1) for _, row in tx_list.iterrows()]
140
+ await Transaction.bulk_create(db, txn_list_to_save)
141
+
142
+ new_ref_data = tx_list[["name_description", "category"]]
143
+
144
+ if not os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
145
+ os.makedirs(CATEGORY_REFERENCE_OUTPUT_FILE)
146
+
147
+ if os.path.exists(CATEGORY_REFERENCE_OUTPUT_FILE):
148
+ # If it exists, add master file to interim results
149
+ old_ref_data = pd.read_csv(CATEGORY_REFERENCE_OUTPUT_FILE, names=["name_description", "category"], header=0)
150
+ new_ref_data = pd.concat([old_ref_data, new_ref_data], ignore_index=True)
151
+
152
+ # Drop duplicates, sort, and write to create new Master File
153
+ new_ref_data.drop_duplicates(subset=["name_description"]).sort_values(by=["name_description"]).to_csv(
154
+ CATEGORY_REFERENCE_OUTPUT_FILE, mode="w", index=False, header=True
155
+ )
156
+
157
+ # Summarize results
158
+ print(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n")
159
+ logging.debug(f"\nProcessed {len(results)} files: {len(ok_files)} successful, {len(ko_files)} with errors\n")
160
+ if len(ko_files):
161
+ print(f"Errors in the following files:")
162
+ for message in error_messages:
163
+ print(f" {message}")
164
+ print("\n")
165
 
166
+ except Exception as e:
167
+ # Return an error indicator and exception info
168
+ # print(f"standardize_csv_file exception: {e}")
169
+ logging.debug("save_results Error: {e}")
170
+ result["error"] = str(e)
171
+ raise HTTPException(status_code = 401, detail=f"save_results exception: {result}")