diff --git a/frontend/classes/api.py b/frontend/classes/api.py index ca93837..39d95d1 100644 --- a/frontend/classes/api.py +++ b/frontend/classes/api.py @@ -64,5 +64,43 @@ def refresh_token_auth(self, email: str, tokens: dict) -> dict: except Exception as e: raise Exception(f"There was an issue getting a refresh token") + def basiq_api_transaction_data(self) -> dict: + try: - \ No newline at end of file + url = self.base_url+"/basiq/transactions" + + payload = {} + headers = { + "accepts": "application/json", + "Authorization": "eyJraWQiOiJlcXhraW1yN21mRkwySmpjMmx1ZCtuT0JlNk5DVU00aHNMaEtta1p6MjVRPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlN2NlM2ZhYy0zOGNjLTRiNjAtOTc4MC1jMDBiMTZjYmI3OTYiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtc291dGhlYXN0LTIuYW1hem9uYXdzLmNvbVwvYXAtc291dGhlYXN0LTJfNzNlY0FOdWRNIiwiY29nbml0bzp1c2VybmFtZSI6ImU3Y2UzZmFjLTM4Y2MtNGI2MC05NzgwLWMwMGIxNmNiYjc5NiIsIm9yaWdpbl9qdGkiOiI2ODFmNjBmNy01YjIzLTQzYzItYjA0OC03OWFkNmQyYTgyNzUiLCJhdWQiOiI1N2kzOW9xcG1wdGIxZmxxZmoxdXUzaWJwYSIsImV2ZW50X2lkIjoiYTJlNTc0OTAtMDQzZS00ZGI4LTk0MzktNGI0ZGNlZGJlMWI1IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2ODQzNTkzNTYsIm5hbWUiOiJkYW5ubm5vcnIiLCJleHAiOjE2ODQzNjI5NTYsImlhdCI6MTY4NDM1OTM1NiwianRpIjoiNGEwM2JlNmItNTM5NC00MWRlLWFiMGEtMTQwOWNhMzk0MzZlIiwiZW1haWwiOiJkYW5AZW1haWwuY29tbSJ9.ZVlGgqM_Bxz1IdF65eN097rh0KBUeVxkw2zm806uwqpAeiGoxGXjJYVLx3kIwcSk50dhqNCu5_MZqt9lIfHNobFGs7Xjz-g2GVEPDttMELqllmCGier9m8QjhAmE3VJ3TwXbR1-oN1IxPkJe4slxKiFKN4OMqdZpAYETsfX0IJc2WQFrC4A00LYmgTLD4Uk0lxToGStsqK3UOp4ZZhmzRUfbKYgOcfNIGvpuUqWmHFjqtxgSG2g5s7Wa1QCloLmIadL3GEOqw8ulTS_BoPNYusy2VW63VSJX2VjO3DPYY41ViBBnaE7Ih7TuuTNiE4mVetBN8XZJ5bLdc4UuMvtFhw" + } + + response = requests.request("GET", url, headers=headers, data=payload) + + if response.status_code > 299: + raise Exception("HTTP {response.statuscode} Bad Status Code") + #print(response.json()) + return json.loads(response.text) + + except Exception as e: + raise Exception("Error getting dashboard data failed") + + def get_accounts_for_user(self) -> dict: + basiq_id="11103cba-4a08-4397-84a5-22ac125ed2f6" + try: + url = self.base_url + "/basiq/balance" + + payload = {} + headers = { + "accepts": "application/json", + "Authorization": "eyJraWQiOiJlcXhraW1yN21mRkwySmpjMmx1ZCtuT0JlNk5DVU00aHNMaEtta1p6MjVRPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlN2NlM2ZhYy0zOGNjLTRiNjAtOTc4MC1jMDBiMTZjYmI3OTYiLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuYXAtc291dGhlYXN0LTIuYW1hem9uYXdzLmNvbVwvYXAtc291dGhlYXN0LTJfNzNlY0FOdWRNIiwiY29nbml0bzp1c2VybmFtZSI6ImU3Y2UzZmFjLTM4Y2MtNGI2MC05NzgwLWMwMGIxNmNiYjc5NiIsIm9yaWdpbl9qdGkiOiI2ODFmNjBmNy01YjIzLTQzYzItYjA0OC03OWFkNmQyYTgyNzUiLCJhdWQiOiI1N2kzOW9xcG1wdGIxZmxxZmoxdXUzaWJwYSIsImV2ZW50X2lkIjoiYTJlNTc0OTAtMDQzZS00ZGI4LTk0MzktNGI0ZGNlZGJlMWI1IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2ODQzNTkzNTYsIm5hbWUiOiJkYW5ubm5vcnIiLCJleHAiOjE2ODQzNjI5NTYsImlhdCI6MTY4NDM1OTM1NiwianRpIjoiNGEwM2JlNmItNTM5NC00MWRlLWFiMGEtMTQwOWNhMzk0MzZlIiwiZW1haWwiOiJkYW5AZW1haWwuY29tbSJ9.ZVlGgqM_Bxz1IdF65eN097rh0KBUeVxkw2zm806uwqpAeiGoxGXjJYVLx3kIwcSk50dhqNCu5_MZqt9lIfHNobFGs7Xjz-g2GVEPDttMELqllmCGier9m8QjhAmE3VJ3TwXbR1-oN1IxPkJe4slxKiFKN4OMqdZpAYETsfX0IJc2WQFrC4A00LYmgTLD4Uk0lxToGStsqK3UOp4ZZhmzRUfbKYgOcfNIGvpuUqWmHFjqtxgSG2g5s7Wa1QCloLmIadL3GEOqw8ulTS_BoPNYusy2VW63VSJX2VjO3DPYY41ViBBnaE7Ih7TuuTNiE4mVetBN8XZJ5bLdc4UuMvtFhw" + } + + response = requests.request("GET", url, headers=headers) + if response.status_code > 299: + raise Exception("HTTP {response.statuscode} Bad Status Code") + #print(response.json()) + return json.loads(response.text) + + except Exception as e: + raise Exception("Error getting account details failed") diff --git a/frontend/environment/.env b/frontend/environment/.env index 9bde55c..786e2d7 100644 --- a/frontend/environment/.env +++ b/frontend/environment/.env @@ -2,4 +2,4 @@ HOST=0.0.0.0 PORT=8080 DEBUG=True DEV_TOOLS_PROPS_CHECK=False -BASE_API_URL="https://tz2pv43ci1.execute-api.ap-southeast-2.amazonaws.com/prod" \ No newline at end of file +BASE_API_URL="https://9u48mrkt22.execute-api.ap-southeast-2.amazonaws.com/prod" \ No newline at end of file diff --git a/frontend/environment/.env.development b/frontend/environment/.env.development index a744d4a..1dfe87c 100644 --- a/frontend/environment/.env.development +++ b/frontend/environment/.env.development @@ -2,4 +2,4 @@ HOST=127.0.0.1 PORT=8080 DEBUG=True DEV_TOOLS_PROPS_CHECK=True -BASE_API_URL="https://tz2pv43ci1.execute-api.ap-southeast-2.amazonaws.com/prod" +BASE_API_URL="https://pu0s16t327.execute-api.ap-southeast-2.amazonaws.com/prod" diff --git a/frontend/pages/breakdown/breakdown.py b/frontend/pages/breakdown/breakdown.py index 5e7bae8..bdba1a2 100644 --- a/frontend/pages/breakdown/breakdown.py +++ b/frontend/pages/breakdown/breakdown.py @@ -7,15 +7,13 @@ from app import app from pages.breakdown import breakdown_callbacks - -df = pd.read_csv('pages/breakdown/dummies.csv') +dfIncoming, dfOutgoing = breakdown_callbacks.get_incoming_outgoing_transactions() layout = dbc.Container([ dbc.Row([ dbc.Col(html.H1("YOUR FINANCIAL DASHBOARD", className='text-center text-primary mb-4',id="title"), - width=12) - + width=12) ]), dbc.Row([ @@ -25,7 +23,7 @@ dbc.Col([ dcc.Dropdown(id = 'yearly_spending_dd', multi = False, placeholder = 'Select a year', - options = [{'label':x, 'value':x} for x in sorted(df.iloc[:,3].unique())]), + options = [{'label':x, 'value':x} for x in sorted(dfOutgoing.iloc[:,0].unique())]), dcc.Graph(id = 'yearly_spending_graph', figure={}) ], width = {'size':6}), @@ -35,7 +33,6 @@ ]), - dbc.Row([ dbc.Col(html.H2("Yearly Saving", @@ -43,7 +40,7 @@ dbc.Col([ dcc.Dropdown(id = 'yearly_saving_dd', multi = False, placeholder = 'Select a year', - options = [{'label':x, 'value':x} for x in sorted(df.iloc[:,3].unique())]), + options = [{'label':x, 'value':x} for x in sorted(dfIncoming.iloc[:,0].unique())]), dcc.Graph(id = 'yearly_saving_graph', figure={}) ], width = {'size':6}), @@ -53,4 +50,3 @@ ]) ], fluid = False) - diff --git a/frontend/pages/breakdown/breakdown_callbacks.py b/frontend/pages/breakdown/breakdown_callbacks.py index 23affbc..8ba2789 100644 --- a/frontend/pages/breakdown/breakdown_callbacks.py +++ b/frontend/pages/breakdown/breakdown_callbacks.py @@ -3,28 +3,115 @@ from app import app import pandas as pd import plotly.express as px - -df = pd.read_csv('pages/breakdown/dummies.csv') +import json @app.callback( Output('yearly_spending_graph', 'figure'), Input('yearly_spending_dd', 'value') ) def update_graph(year): - dff = df[df.iloc[:,3]==year] - figln = px.line(dff, x='Month', y='Spending') + dfIncoming, dfOutgoing = get_incoming_outgoing_transactions() + dff = dfOutgoing[dfOutgoing.iloc[:,0]==year] + figln = px.line(dff, x='month', y='amount') return figln - @app.callback( Output('yearly_saving_graph', 'figure'), Input('yearly_saving_dd', 'value') ) def update_graph(year): - dff = df[df.iloc[:,3]==year] - figln = px.line(dff, x='Month', y='Saving') + dfIncoming, dfOutgoing = get_incoming_outgoing_transactions() + dff = dfIncoming[dfIncoming.iloc[:,0]==year] + figln = px.line(dff, x='month', y='amount') return figln -# def callback(n): -# api = API() -# return f"{n}" \ No newline at end of file +def get_transaction_data(): + api = API() + transaction_result = api.basiq_api_transaction_data() + data = transaction_result["data"] + + accounts = get_accounts() + + df = pd.json_normalize(data) + accounts_df = pd.json_normalize(accounts) + + processedData = df.filter(["id", "status", "amount", "direction", "account", "class", "postDate", "subClass.title"], axis=1) + monthsForProcessing = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"] + + #Renamed 'subClass.title' as 'category'. This is not the category I was looking to use but it could do the job + processedData.rename(columns = {'subClass.title':'category'}, inplace = True) + + #Processing the date value that is returned. It will be split from a string in to 3 arrays listed below + year = [] + month = [] + day = [] + + #Account name included to give something human readable to the account id number + accountName = [] + #Here I am transforming the data in the postDate and account columns. + for column in df[["postDate", "account"]]: + for val in df[column]: + if column == "postDate": + + #postDate has the initial string value split, giving us a date formatted to yyy-mm-dd at the 0th index. This is then + #split at the '-' into separate years, months and dates and added in to the appropriate array above + + initialSplit = val.split('T') + yearMonthDay = initialSplit[0].split("-") + year.append(yearMonthDay[0]) + month.append(monthsForProcessing[int(yearMonthDay[1]) - 1]) + day.append(yearMonthDay[2]) + if column == "account": + + #If the account id matches the account id in the 'Accounts' dataframe (created 2 cells above) then the account name + #is added to the accountName array, to be added in to the dataframe + for index, accountNumber in enumerate(accounts_df["id"]): + if val == accountNumber: + accountName.append(accounts_df["name"][index]) + break + + #All of the new values are added to the existing dataframe + processedData["year"] = year + processedData["month"] = month + processedData["day"] = day + # processedData["accountName"] = accountName + processedData = processedData.drop('postDate', axis=1) + processedData["category"].fillna("Unknown", inplace = True) + + return processedData + +def get_accounts(): + api = API() + account_details = api.get_accounts_for_user() + data = account_details["data"] + + return data + +def get_incoming_outgoing_transactions(): + processedData = get_transaction_data() + + outgoingTransactions = processedData[processedData.amount.astype('float64') < 0] + + #transforming values to absolute value to prevent the issue mentioned above + for index, row in outgoingTransactions.iterrows(): + row["amount"] = abs(float(row["amount"])) + + incomingTransactions = processedData[processedData.amount.astype('float64') > 0] + + for index, row in incomingTransactions.iterrows(): + row["amount"] = abs(float(row["amount"])) + + monthlySpending = outgoingTransactions.groupby(['year', 'month']).sum().reset_index().filter(['year', 'month', 'amount'], axis=1) + monthlySaving = incomingTransactions.groupby(['year', 'month']).sum().reset_index().filter(['year', 'month', 'amount'], axis=1) + + ordered_months = ["Jan", "Feb", "Mar", "Apr", "May", "June", + "July", "Aug", "Sep", "Oct", "Nov", "Dec"] + + #Both incoming models are sorted by month + monthlySaving['to_sort']=monthlySaving['month'].apply(lambda x:ordered_months.index(x)) + monthlySaving = monthlySaving.sort_values('to_sort') + + monthlySpending['to_sort']=monthlySpending['month'].apply(lambda x:ordered_months.index(x)) + monthlySpending = monthlySpending.sort_values('to_sort') + + return monthlySaving, monthlySpending diff --git a/frontend/pages/breakdown/dummies.csv b/frontend/pages/breakdown/dummies.csv deleted file mode 100644 index 8aae1fa..0000000 --- a/frontend/pages/breakdown/dummies.csv +++ /dev/null @@ -1,37 +0,0 @@ -Saving,Spending,Month,Year -227,4075,Jan,2020 -368,4209,Feb,2020 -808,4496,Mar,2020 -826,4183,Apr,2020 -421,4155,May,2020 -340,4033,Jun,2020 -752,4138,July,2020 -235,4016,Aug,2020 -564,4425,Sep,2020 -782,4104,Oct,2020 -846,4045,Nov,2020 -149,4368,Dec,2020 -453,4467,Jan,2021 -180,4070,Feb,2021 -953,4002,Mar,2021 -929,4307,Apr,2021 -571,4206,May,2021 -860,4032,Jun,2021 -999,4439,July,2021 -503,4162,Aug,2021 -465,4046,Sep,2021 -225,4119,Oct,2021 -506,4237,Nov,2021 -625,4048,Dec,2021 -841,4352,Jan,2022 -524,4328,Feb,2022 -487,4475,Mar,2022 -256,4194,Apr,2022 -493,4113,May,2022 -974,4361,Jun,2022 -601,4458,July,2022 -115,4419,Aug,2022 -849,4230,Sep,2022 -669,4191,Oct,2022 -813,4273,Nov,2022 -233,4323,Dec,2022 diff --git a/frontend/pages/dashboard/dashboard.py b/frontend/pages/dashboard/dashboard.py index 4b5ae57..6763e3e 100644 --- a/frontend/pages/dashboard/dashboard.py +++ b/frontend/pages/dashboard/dashboard.py @@ -4,9 +4,20 @@ from pages.dashboard import dashboard_callbacks import pandas as pd import dash_table +import json +df_outgoing_transactions = dashboard_callbacks.get_outgoing_transactions() +df_absolute_spending_transaction_values = dashboard_callbacks.list_outgoing_transactions() -df = pd.read_csv('pages/dashboard/dummies.csv') +accounts = dashboard_callbacks.get_accounts() + +accounts_df = pd.json_normalize(accounts) + +columns = [{"name": i, "id": i} for i in df_absolute_spending_transaction_values.columns] +columns[2]["name"] = "Amount" +columns[6]["name"] = "Category" +columns[7]["name"] = "Year" +columns[8]["name"] = "Month" layout = dbc.Container([ @@ -16,24 +27,24 @@ html.H3("YEARLY SPENDING CATEGORIES", className='text-center text-primary mb-4'), - dbc.Row([ dbc.Col(html.P('Chart shows categories of spending over the selected year.', className='text-right text-primary, mb-4'), width = {'size': 3,'offset' : 0}), dbc.Col([ dcc.Dropdown(id = 'piechart_dd', multi = False, placeholder = 'Select a year', - options = [{'label':x, 'value':x} for x in sorted(df.iloc[:,3].unique())]), + options = [{'label':x, 'value':x} for x in sorted(df_absolute_spending_transaction_values.iloc[:,7].unique())]), dcc.Graph(id = 'piechart', figure={}) ]) ] ), - + dbc.Row( dash_table.DataTable( - data = df.to_dict('records'), - columns = [{"name": i, "id": i} for i in df.columns], + data = df_outgoing_transactions.to_dict('records'), + columns = columns, + hidden_columns = ["index", "transactionNumber", "id", "status", "direction", "account", "class", "day"], style_cell = {'textAlign':'left'}, style_header={ 'backgroundColor': '#4f8fe3', diff --git a/frontend/pages/dashboard/dashboard_callbacks.py b/frontend/pages/dashboard/dashboard_callbacks.py index 38c1056..6f1c349 100644 --- a/frontend/pages/dashboard/dashboard_callbacks.py +++ b/frontend/pages/dashboard/dashboard_callbacks.py @@ -4,8 +4,7 @@ import pandas as pd import plotly import plotly.express as px - -df = pd.read_csv('pages/dashboard/dummies.csv') +import json @app.callback( @@ -13,12 +12,97 @@ Input('piechart_dd', 'value') ) def update_graph(year): - dff = df[df.iloc[:,3]==year] + selectedYear = year + + df = list_outgoing_transactions() + + dff = df.loc[df["year"]==selectedYear] fig_pie = px.pie( data_frame = dff, - names = "Categories", - values = 'Amount', + names = "category", + values = 'amount', hole = .3, - labels = 'Categories' + labels = 'category' ) - return fig_pie \ No newline at end of file + return fig_pie + +def get_transaction_data(): + api = API() + transaction_result = api.basiq_api_transaction_data() + data = transaction_result["data"] + + accounts = get_accounts() + + df = pd.json_normalize(data) + accounts_df = pd.json_normalize(accounts) + + processedData = df.filter(["id", "status", "amount", "direction", "account", "class", "postDate", "subClass.title"], axis=1) + monthsForProcessing = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"] + + #Renamed 'subClass.title' as 'category'. This is not the category I was looking to use but it could do the job + processedData.rename(columns = {'subClass.title':'category'}, inplace = True) + + #Processing the date value that is returned. It will be split from a string in to 3 arrays listed below + year = [] + month = [] + day = [] + + #Account name included to give something human readable to the account id number + accountName = [] + + #Here I am transforming the data in the postDate and account columns. + for column in df[["postDate", "account"]]: + for val in df[column]: + if column == "postDate": + + #postDate has the initial string value split, giving us a date formatted to yyy-mm-dd at the 0th index. This is then + #split at the '-' into separate years, months and dates and added in to the appropriate array above + + initialSplit = val.split('T') + yearMonthDay = initialSplit[0].split("-") + year.append(yearMonthDay[0]) + month.append(monthsForProcessing[int(yearMonthDay[1]) - 1]) + day.append(yearMonthDay[2]) + if column == "account": + + #If the account id matches the account id in the 'Accounts' dataframe (created 2 cells above) then the account name + #is added to the accountName array, to be added in to the dataframe + for index, accountNumber in enumerate(accounts_df["id"]): + if val == accountNumber: + accountName.append(accounts_df["name"][index]) + break + + #All of the new values are added to the existing dataframe + processedData["year"] = year + processedData["month"] = month + processedData["day"] = day + # processedData["accountName"] = accountName + processedData = processedData.drop('postDate', axis=1) + processedData["category"].fillna("Unknown", inplace = True) + + return processedData + +def get_accounts(): + api = API() + account_details = api.get_accounts_for_user() + data = account_details["data"] + + return data + +def get_outgoing_transactions(): + initial_transactions = get_transaction_data() + + outgoing_transactions = initial_transactions[initial_transactions.amount.astype('float64') < 0] + + return outgoing_transactions + + +def list_outgoing_transactions(): + outgoing_transactions = get_outgoing_transactions() + + #transforming values to absolute value to prevent the issue mentioned above + for index, row in outgoing_transactions.iterrows(): + row["amount"] = abs(float(row["amount"])) + + return outgoing_transactions + diff --git a/frontend/pages/dashboard/dummies.csv b/frontend/pages/dashboard/dummies.csv deleted file mode 100644 index f4c1e91..0000000 --- a/frontend/pages/dashboard/dummies.csv +++ /dev/null @@ -1,253 +0,0 @@ -Amount,Categories,Month,Year -1795,Rent,Jan,2020 -1113,Food,Jan,2020 -296,Entertainment,Jan,2020 -157,Clothes,Jan,2020 -206,Energy,Jan,2020 -182,Phone,Jan,2020 -223,Others,Jan,2020 -1621,Rent,Feb,2020 -1189,Food,Feb,2020 -221,Entertainment,Feb,2020 -153,Clothes,Feb,2020 -242,Energy,Feb,2020 -200,Phone,Feb,2020 -231,Others,Feb,2020 -1512,Rent,March,2020 -1098,Food,March,2020 -266,Entertainment,March,2020 -180,Clothes,March,2020 -200,Energy,March,2020 -190,Phone,March,2020 -269,Others,March,2020 -1637,Rent,Apr,2020 -1126,Food,Apr,2020 -262,Entertainment,Apr,2020 -170,Clothes,Apr,2020 -205,Energy,Apr,2020 -159,Phone,Apr,2020 -228,Others,Apr,2020 -1549,Rent,May,2020 -1118,Food,May,2020 -290,Entertainment,May,2020 -184,Clothes,May,2020 -239,Energy,May,2020 -160,Phone,May,2020 -220,Others,May,2020 -1974,Rent,Jun,2020 -1032,Food,Jun,2020 -227,Entertainment,Jun,2020 -178,Clothes,Jun,2020 -227,Energy,Jun,2020 -161,Phone,Jun,2020 -268,Others,Jun,2020 -1849,Rent,Jul,2020 -1019,Food,Jul,2020 -267,Entertainment,Jul,2020 -172,Clothes,Jul,2020 -210,Energy,Jul,2020 -198,Phone,Jul,2020 -239,Others,Jul,2020 -1774,Rent,Aug,2020 -1200,Food,Aug,2020 -249,Entertainment,Aug,2020 -154,Clothes,Aug,2020 -204,Energy,Aug,2020 -192,Phone,Aug,2020 -291,Others,Aug,2020 -1576,Rent,Sep,2020 -1002,Food,Sep,2020 -231,Entertainment,Sep,2020 -175,Clothes,Sep,2020 -202,Energy,Sep,2020 -173,Phone,Sep,2020 -294,Others,Sep,2020 -1932,Rent,Oct,2020 -1109,Food,Oct,2020 -274,Entertainment,Oct,2020 -187,Clothes,Oct,2020 -245,Energy,Oct,2020 -190,Phone,Oct,2020 -255,Others,Oct,2020 -1760,Rent,Nov,2020 -1170,Food,Nov,2020 -243,Entertainment,Nov,2020 -171,Clothes,Nov,2020 -205,Energy,Nov,2020 -173,Phone,Nov,2020 -229,Others,Nov,2020 -1794,Rent,Dec,2020 -1137,Food,Dec,2020 -278,Entertainment,Dec,2020 -172,Clothes,Dec,2020 -225,Energy,Dec,2020 -182,Phone,Dec,2020 -272,Others,Dec,2020 -1689,Rent,Jan,2021 -1169,Food,Jan,2021 -230,Entertainment,Jan,2021 -167,Clothes,Jan,2021 -230,Energy,Jan,2021 -183,Phone,Jan,2021 -236,Others,Jan,2021 -1852,Rent,Feb,2021 -1173,Food,Feb,2021 -261,Entertainment,Feb,2021 -182,Clothes,Feb,2021 -206,Energy,Feb,2021 -172,Phone,Feb,2021 -252,Others,Feb,2021 -1903,Rent,March,2021 -1194,Food,March,2021 -268,Entertainment,March,2021 -191,Clothes,March,2021 -233,Energy,March,2021 -181,Phone,March,2021 -221,Others,March,2021 -1596,Rent,Apr,2021 -1031,Food,Apr,2021 -211,Entertainment,Apr,2021 -151,Clothes,Apr,2021 -248,Energy,Apr,2021 -151,Phone,Apr,2021 -212,Others,Apr,2021 -1652,Rent,May,2021 -1184,Food,May,2021 -203,Entertainment,May,2021 -171,Clothes,May,2021 -226,Energy,May,2021 -173,Phone,May,2021 -217,Others,May,2021 -1845,Rent,Jun,2021 -1161,Food,Jun,2021 -288,Entertainment,Jun,2021 -190,Clothes,Jun,2021 -250,Energy,Jun,2021 -183,Phone,Jun,2021 -287,Others,Jun,2021 -1951,Rent,Jul,2021 -1025,Food,Jul,2021 -277,Entertainment,Jul,2021 -156,Clothes,Jul,2021 -244,Energy,Jul,2021 -194,Phone,Jul,2021 -207,Others,Jul,2021 -1924,Rent,Aug,2021 -1173,Food,Aug,2021 -269,Entertainment,Aug,2021 -199,Clothes,Aug,2021 -221,Energy,Aug,2021 -165,Phone,Aug,2021 -269,Others,Aug,2021 -1729,Rent,Sep,2021 -1031,Food,Sep,2021 -240,Entertainment,Sep,2021 -193,Clothes,Sep,2021 -205,Energy,Sep,2021 -198,Phone,Sep,2021 -268,Others,Sep,2021 -1790,Rent,Oct,2021 -1156,Food,Oct,2021 -243,Entertainment,Oct,2021 -169,Clothes,Oct,2021 -227,Energy,Oct,2021 -171,Phone,Oct,2021 -259,Others,Oct,2021 -1669,Rent,Nov,2021 -1072,Food,Nov,2021 -232,Entertainment,Nov,2021 -152,Clothes,Nov,2021 -203,Energy,Nov,2021 -165,Phone,Nov,2021 -250,Others,Nov,2021 -1765,Rent,Dec,2021 -1191,Food,Dec,2021 -282,Entertainment,Dec,2021 -166,Clothes,Dec,2021 -247,Energy,Dec,2021 -176,Phone,Dec,2021 -260,Others,Dec,2021 -1611,Rent,Jan,2022 -1099,Food,Jan,2022 -216,Entertainment,Jan,2022 -197,Clothes,Jan,2022 -235,Energy,Jan,2022 -156,Phone,Jan,2022 -213,Others,Jan,2022 -1712,Rent,Feb,2022 -1143,Food,Feb,2022 -258,Entertainment,Feb,2022 -177,Clothes,Feb,2022 -208,Energy,Feb,2022 -174,Phone,Feb,2022 -291,Others,Feb,2022 -1549,Rent,March,2022 -1104,Food,March,2022 -239,Entertainment,March,2022 -166,Clothes,March,2022 -210,Energy,March,2022 -152,Phone,March,2022 -288,Others,March,2022 -1771,Rent,Apr,2022 -1029,Food,Apr,2022 -223,Entertainment,Apr,2022 -166,Clothes,Apr,2022 -236,Energy,Apr,2022 -153,Phone,Apr,2022 -282,Others,Apr,2022 -1943,Rent,May,2022 -1034,Food,May,2022 -295,Entertainment,May,2022 -191,Clothes,May,2022 -247,Energy,May,2022 -168,Phone,May,2022 -222,Others,May,2022 -1559,Rent,Jun,2022 -1001,Food,Jun,2022 -244,Entertainment,Jun,2022 -200,Clothes,Jun,2022 -234,Energy,Jun,2022 -180,Phone,Jun,2022 -293,Others,Jun,2022 -1745,Rent,Jul,2022 -1176,Food,Jul,2022 -246,Entertainment,Jul,2022 -185,Clothes,Jul,2022 -250,Energy,Jul,2022 -193,Phone,Jul,2022 -236,Others,Jul,2022 -1857,Rent,Aug,2022 -1065,Food,Aug,2022 -213,Entertainment,Aug,2022 -164,Clothes,Aug,2022 -230,Energy,Aug,2022 -184,Phone,Aug,2022 -253,Others,Aug,2022 -1519,Rent,Sep,2022 -1069,Food,Sep,2022 -206,Entertainment,Sep,2022 -154,Clothes,Sep,2022 -248,Energy,Sep,2022 -177,Phone,Sep,2022 -205,Others,Sep,2022 -1507,Rent,Oct,2022 -1161,Food,Oct,2022 -285,Entertainment,Oct,2022 -191,Clothes,Oct,2022 -207,Energy,Oct,2022 -167,Phone,Oct,2022 -262,Others,Oct,2022 -1587,Rent,Nov,2022 -1109,Food,Nov,2022 -264,Entertainment,Nov,2022 -182,Clothes,Nov,2022 -235,Energy,Nov,2022 -197,Phone,Nov,2022 -232,Others,Nov,2022 -1547,Rent,Dec,2022 -1034,Food,Dec,2022 -214,Entertainment,Dec,2022 -176,Clothes,Dec,2022 -239,Energy,Dec,2022 -199,Phone,Dec,2022 -291,Others,Dec,2022 \ No newline at end of file