Spaces:
Running
Running
Edgar Garcia
commited on
transferring files from spendtracker
Browse files- LLM_openai.py +31 -0
- app.py +102 -0
- dataframe_processing.py +42 -0
- utils.py +70 -0
- vision_api_call.py +61 -0
LLM_openai.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
import os
|
3 |
+
my_key = os.environ.get('MY_OPENAI_KEY')
|
4 |
+
client = OpenAI(
|
5 |
+
api_key= my_key
|
6 |
+
)
|
7 |
+
|
8 |
+
def expense_classifier(expense):
|
9 |
+
chat_completion = client.chat.completions.create(
|
10 |
+
messages=[
|
11 |
+
{ "role": "system",
|
12 |
+
"content": [
|
13 |
+
{
|
14 |
+
"type": "text",
|
15 |
+
"text": """You are a helpful personal finance assistant.
|
16 |
+
The user will input some expense concept and you will classify it in a broader category from the following:
|
17 |
+
[alcohol, food, restaurant, clothing, entertainment, transport, sports, wellbeing, personal_development,others]
|
18 |
+
Provide only the category as an answer
|
19 |
+
"""
|
20 |
+
}
|
21 |
+
]
|
22 |
+
},
|
23 |
+
{
|
24 |
+
"role": "user",
|
25 |
+
"content": expense,
|
26 |
+
}
|
27 |
+
],
|
28 |
+
model="gpt-4o-mini",
|
29 |
+
temperature=0
|
30 |
+
)
|
31 |
+
return chat_completion.choices[0].message.content
|
app.py
ADDED
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import os
|
3 |
+
import gspread
|
4 |
+
import gradio as gr
|
5 |
+
import datetime
|
6 |
+
import pandas as pd
|
7 |
+
import matplotlib
|
8 |
+
matplotlib.use('Agg')
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
from LLM_openai import client, expense_classifier
|
11 |
+
from utils import create_plot, create_barplot
|
12 |
+
from dataframe_processing import dataframe_process
|
13 |
+
from vision_api_call import process_image
|
14 |
+
|
15 |
+
#connect to the service account
|
16 |
+
gc = gspread.service_account(filename="spend_tracker_creds.json")
|
17 |
+
#connect to your sheet (between "" = the name of your G Sheet, keep it short)
|
18 |
+
spreadsheet = gc.open("spend_tracker").sheet1
|
19 |
+
|
20 |
+
def update_spend_from_image(img):
|
21 |
+
##This processes the image
|
22 |
+
extracted_dictionay_from_image=process_image(img)
|
23 |
+
total_from_receipt=extracted_dictionay_from_image['total']
|
24 |
+
concept_from_receipt=extracted_dictionay_from_image['purchase_summary']
|
25 |
+
shop_type=extracted_dictionay_from_image['store_type']
|
26 |
+
shop_name=extracted_dictionay_from_image['store']
|
27 |
+
receipt_items=str(extracted_dictionay_from_image['items'])
|
28 |
+
|
29 |
+
concept_and_shop=concept_from_receipt+" from "+shop_type+f" ({shop_name})"
|
30 |
+
|
31 |
+
##The function update_spend in the line below only takes a string and float
|
32 |
+
category, day_month, todays_amount, current_week_amount , fig, fig2, fig3=update_spend(concept_and_shop, total_from_receipt, receipt_items)
|
33 |
+
|
34 |
+
return category, day_month, todays_amount, current_week_amount , fig, fig2, fig3
|
35 |
+
|
36 |
+
|
37 |
+
|
38 |
+
def update_spend(concept, amount, items_from_receipt=None):
|
39 |
+
category=expense_classifier(concept)
|
40 |
+
today = datetime.date.today()
|
41 |
+
|
42 |
+
# Append a new row
|
43 |
+
spreadsheet.append_row([str(today), concept,float(amount),category, items_from_receipt ])
|
44 |
+
|
45 |
+
day_month, todays_amount, current_week_amount , fig, fig2, fig3=dataframe_process(spreadsheet)
|
46 |
+
return category, day_month, todays_amount, current_week_amount , fig, fig2, fig3
|
47 |
+
|
48 |
+
def show_plots():
|
49 |
+
category='N/A'
|
50 |
+
today = 'N/A'
|
51 |
+
|
52 |
+
# Append a new row
|
53 |
+
# spreadsheet.append_row([str(today), concept,float(amount),category, items_from_receipt ])
|
54 |
+
|
55 |
+
day_month, todays_amount, current_week_amount , fig, fig2, fig3=dataframe_process(spreadsheet)
|
56 |
+
return category, day_month, todays_amount, current_week_amount , fig, fig2, fig3
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
|
61 |
+
with gr.Blocks() as demo:
|
62 |
+
gr.Markdown("Expense tracker")
|
63 |
+
|
64 |
+
with gr.Tab("Manual input"):
|
65 |
+
with gr.Row():
|
66 |
+
concept = gr.Textbox(label="concept")
|
67 |
+
with gr.Row():
|
68 |
+
amount= gr.Textbox(label="amount")
|
69 |
+
btn_manual = gr.Button("Submit expense")
|
70 |
+
|
71 |
+
with gr.Tab("Upload receipt"):
|
72 |
+
with gr.Row():
|
73 |
+
input_image = gr.Image( type="pil")
|
74 |
+
btn_image = gr.Button("Submit expense")
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
with gr.Row():
|
81 |
+
btn_show = gr.Button("Show plots")
|
82 |
+
|
83 |
+
with gr.Row():
|
84 |
+
expense_class= gr.Textbox(label='expense class')
|
85 |
+
ui_date=gr.Textbox(label='date')
|
86 |
+
expenses_today= gr.Textbox(label='expenses today')
|
87 |
+
expenses_this_week= gr.Textbox(label='expenses this week')
|
88 |
+
with gr.Row():
|
89 |
+
daily_plot=gr.Plot(label=None)
|
90 |
+
week_plot=gr.Plot()
|
91 |
+
category_plot=gr.Plot()
|
92 |
+
|
93 |
+
|
94 |
+
btn_manual.click(fn=update_spend, inputs=[concept, amount], outputs=[expense_class,ui_date, expenses_today,expenses_this_week,
|
95 |
+
daily_plot,week_plot,category_plot])
|
96 |
+
btn_image.click(fn=update_spend_from_image, inputs=[input_image], outputs=[expense_class,ui_date, expenses_today,expenses_this_week,
|
97 |
+
daily_plot,week_plot,category_plot])
|
98 |
+
btn_show.click(fn=show_plots, inputs=[], outputs=[expense_class,ui_date, expenses_today,expenses_this_week,
|
99 |
+
daily_plot,week_plot,category_plot])
|
100 |
+
|
101 |
+
|
102 |
+
demo.launch()
|
dataframe_processing.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import re
|
3 |
+
from utils import create_plot, create_barplot
|
4 |
+
|
5 |
+
def dataframe_process(sheet):
|
6 |
+
# Create dataframe
|
7 |
+
df = pd.DataFrame(sheet.get_all_records())
|
8 |
+
df['date'] = pd.to_datetime(df['date'])
|
9 |
+
df['week'] = df['date'].dt.to_period('W')
|
10 |
+
|
11 |
+
#Per day
|
12 |
+
daily_total=df[['concept','amount','category','date']].groupby('date').sum().reset_index()
|
13 |
+
daily_total['month_day'] = daily_total['date'].dt.strftime('%m-%d')
|
14 |
+
|
15 |
+
|
16 |
+
|
17 |
+
fig = create_plot(daily_total['month_day'],daily_total['amount'])
|
18 |
+
|
19 |
+
daily_total=daily_total.sort_values(by='date', ascending=False)
|
20 |
+
daily_total=daily_total[['date', 'amount']]
|
21 |
+
todays_amount=daily_total.iloc[0]['amount']
|
22 |
+
todays_date=daily_total.iloc[0]['date']
|
23 |
+
day_month = todays_date.strftime('%m-%d-%Y')
|
24 |
+
|
25 |
+
#Per week
|
26 |
+
# Group by 'week' and sum the values
|
27 |
+
weekly_df = df[['concept','amount','category','week']].groupby('week').sum().reset_index()
|
28 |
+
weekly_df=weekly_df.sort_values(by='week', ascending=True)
|
29 |
+
# weekly_df.index=weekly_df.index.strftime('%m-%d')
|
30 |
+
current_week_amount="{:.2f}".format(weekly_df.iloc[-1]['amount'])
|
31 |
+
weekly_df['week'] = weekly_df['week'].astype(str)
|
32 |
+
weekly_df['week']=weekly_df['week'].apply(lambda x: re.sub(r'\d{4}-', '', x))
|
33 |
+
|
34 |
+
fig2 = create_barplot(weekly_df['week'],weekly_df['amount'],"week","amount","expense per week")
|
35 |
+
|
36 |
+
|
37 |
+
#Per category
|
38 |
+
expenses_per_category=df[['concept','amount','category']].groupby('category').sum().reset_index()
|
39 |
+
expenses_per_category=expenses_per_category.sort_values(by='amount', ascending=False)
|
40 |
+
fig3 = create_barplot(expenses_per_category['category'],expenses_per_category['amount'],"category","amount","expense per category")
|
41 |
+
|
42 |
+
return day_month, todays_amount, current_week_amount , fig, fig2, fig3
|
utils.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import matplotlib.pyplot as plt
|
2 |
+
from LLM_openai import client, expense_classifier
|
3 |
+
import datetime
|
4 |
+
import base64
|
5 |
+
from io import BytesIO
|
6 |
+
import re
|
7 |
+
import json
|
8 |
+
import matplotlib.dates as mdates
|
9 |
+
|
10 |
+
def create_plot(x, y):
|
11 |
+
fig, ax = plt.subplots()
|
12 |
+
ax.plot(x, y, marker='o')
|
13 |
+
for i in range(len(x)):
|
14 |
+
ax.text(x[i], y[i], f'{y[i]:.2f}', fontsize=10, ha='left', va='bottom')
|
15 |
+
ax.set_xlabel('Money')
|
16 |
+
ax.set_ylabel('Expenses')
|
17 |
+
ax.set_title('Daily expenses')
|
18 |
+
ax.set_xticks(x[::5]) # Show every 5th day
|
19 |
+
ax.set_xticklabels(x[::5], rotation=45, ha='right')
|
20 |
+
plt.xticks(rotation=30)
|
21 |
+
return fig
|
22 |
+
|
23 |
+
|
24 |
+
def create_barplot(x, y, xlabel,ylabel, title):
|
25 |
+
fig, ax = plt.subplots()
|
26 |
+
ax.bar(x, y)
|
27 |
+
for i in range(len(x)):
|
28 |
+
ax.text(x[i], y[i], f'{y[i]:.2f}', fontsize=10, ha='left', va='bottom')
|
29 |
+
ax.set_xlabel(xlabel)
|
30 |
+
ax.set_ylabel(ylabel)
|
31 |
+
ax.set_title(title)
|
32 |
+
plt.xticks(rotation=30)
|
33 |
+
return fig
|
34 |
+
|
35 |
+
|
36 |
+
####
|
37 |
+
##OCR functions
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
def pil_to_base64(pil_img):
|
42 |
+
img_buffer = BytesIO()
|
43 |
+
pil_img.save(img_buffer, format='JPEG')
|
44 |
+
byte_data = img_buffer.getvalue()
|
45 |
+
base64_str = base64.b64encode(byte_data).decode("utf-8")
|
46 |
+
return base64_str
|
47 |
+
|
48 |
+
def js_to_prefere_the_back_camera_of_mobilephones():
|
49 |
+
custom_html = """
|
50 |
+
<script>
|
51 |
+
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
52 |
+
|
53 |
+
navigator.mediaDevices.getUserMedia = (constraints) => {
|
54 |
+
if (!constraints.video.facingMode) {
|
55 |
+
constraints.video.facingMode = {ideal: "environment"};
|
56 |
+
}
|
57 |
+
return originalGetUserMedia(constraints);
|
58 |
+
};
|
59 |
+
</script>
|
60 |
+
"""
|
61 |
+
return custom_html
|
62 |
+
|
63 |
+
def result_cleaner(text):
|
64 |
+
pattern = r'\{[^}]*\}'
|
65 |
+
match = re.search(pattern, text)
|
66 |
+
match_string=match[0]
|
67 |
+
json_dict=json.loads(match_string)
|
68 |
+
|
69 |
+
|
70 |
+
return json_dict
|
vision_api_call.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
import os
|
3 |
+
import base64
|
4 |
+
from io import BytesIO
|
5 |
+
from PIL import Image
|
6 |
+
from utils import pil_to_base64, result_cleaner
|
7 |
+
|
8 |
+
|
9 |
+
my_key = os.environ.get('MY_OPENAI_KEY')
|
10 |
+
client = OpenAI(
|
11 |
+
api_key= my_key
|
12 |
+
)
|
13 |
+
|
14 |
+
prompt="""
|
15 |
+
|
16 |
+
You'll be analyzing purchase receipts you will extract the following information:
|
17 |
+
-date: date of the purchase in the format YYYY-MM-DD
|
18 |
+
-Store: Name of the store where items or services were purchased
|
19 |
+
-Store_type: Type of store (supermarket, restaurant, bookstore, etc)
|
20 |
+
-Purchase_summary: in maximum 5 words summarize the purchase (cleaning products, breakfast, books, food items for home, online services, clothes ,etc)
|
21 |
+
-items: create a list of all the items in the receipt
|
22 |
+
-total: total amount spent
|
23 |
+
|
24 |
+
Provide your answer in a dictionary like the following
|
25 |
+
{{date: "xxxx-xx-xx"
|
26 |
+
store: "example store",
|
27 |
+
store_type: "supermarket",
|
28 |
+
purchase_summary: "food items for home",
|
29 |
+
items: "xxxx, xxxx, xxx"
|
30 |
+
total: xxxx }}
|
31 |
+
"""
|
32 |
+
|
33 |
+
|
34 |
+
def analyse_image(processed_image):
|
35 |
+
#
|
36 |
+
response = client.chat.completions.create(
|
37 |
+
model="gpt-4o-mini",
|
38 |
+
messages=[
|
39 |
+
{
|
40 |
+
"role": "user",
|
41 |
+
"content": [
|
42 |
+
{
|
43 |
+
"type": "text",
|
44 |
+
"text": prompt,
|
45 |
+
},
|
46 |
+
{
|
47 |
+
"type": "image_url",
|
48 |
+
"image_url": {"url": f"data:image/jpeg;base64,{processed_image}"},
|
49 |
+
},
|
50 |
+
],
|
51 |
+
}
|
52 |
+
],
|
53 |
+
)
|
54 |
+
return response.choices[0].message.content
|
55 |
+
|
56 |
+
def process_image(img):
|
57 |
+
image_base64=pil_to_base64(img)
|
58 |
+
analysis_result=analyse_image(image_base64)
|
59 |
+
clean_analysis_result=result_cleaner(analysis_result)
|
60 |
+
|
61 |
+
return clean_analysis_result
|