From c9c287aa8a6576b70f653ebae1f5287d09a51162 Mon Sep 17 00:00:00 2001 From: Jake Date: Sat, 5 Apr 2025 11:49:15 +0100 Subject: [PATCH] starting to implement callbacks --- main.py | 2 +- visuals/components.py | 155 ++++++++++++++++++++----------------- visuals/dash_app.py | 36 +++------ visuals/layout.py | 172 ++++++++++++++++++++++-------------------- 4 files changed, 190 insertions(+), 175 deletions(-) diff --git a/main.py b/main.py index a4b3ed8..ae494d5 100644 --- a/main.py +++ b/main.py @@ -60,7 +60,7 @@ if __name__ == '__main__': # Check if the data was successfully created data_exists = os.path.exists('data/processed') and os.listdir('data/processed') if data_exists: - app.run() # debug=True + app.run(debug=True) else: logging.error('Data pipeline did not produce any data. Dash app will not run.') sys.exit(ec.NO_DATA_PRODUCED) diff --git a/visuals/components.py b/visuals/components.py index d6eed15..a941a6c 100644 --- a/visuals/components.py +++ b/visuals/components.py @@ -4,8 +4,7 @@ import pandas as pd import logging import sys import config.exit_codes as ec - - +import datetime try: accounts = pl.read_parquet('data/warehouse/accounts.parquet') @@ -31,80 +30,98 @@ except Exception as e: logging.error(f'Error joining DataFrames: {e}') sys.exit(ec.BAD_JOIN) -# Create aggregations -spend_per_day = master_transactions.sql(''' - SELECT - date, - year, - month, - day, - ABS(SUM(transaction_amount)) as total - FROM self - WHERE category_name != 'Inflow: Ready to Assign' - GROUP BY date, year, month, day - ORDER BY date DESC - ''' -) +def update_dates(start_date, end_date): + start_year = int(start_date[:4]) + start_month = int(start_date[5:7]) + start_day = int(start_date[8:10]) -spend_per_category = master_transactions.sql(''' - SELECT - category_name, - ABS(SUM(transaction_amount)) as total - FROM self - WHERE category_name != 'Inflow: Ready to Assign' - GROUP BY category_name - ORDER BY total DESC - ''' -) + end_year = int(end_date[:4]) + end_month = int(end_date[5:7]) + end_day = int(end_date[8:10]) -spend_per_payee = master_transactions.sql(''' - SELECT - payee_name, - ABS(SUM(transaction_amount)) as total - FROM self - WHERE payee_name != 'Starting Balance' - AND transaction_amount < 0 - GROUP BY payee_name - ORDER BY total DESC - ''' -) -total_spend = master_transactions.sql(''' - SELECT ABS(SUM(transaction_amount)) AS total - FROM self - WHERE payee_name != 'Starting Balance' - AND transaction_amount < 0 -''').item() + master_data = master_transactions.filter( + (pl.col('year_date') >= start_year ) & (pl.col('year_date') <= end_year) & + (pl.col('month_date') >= start_month ) & (pl.col('month_date') <= end_month) & + (pl.col('day_date') >= start_day ) & (pl.col('day_date') <= end_day) + ) + return master_data + +def update_data(master_data): + # Create aggregations + spend_per_day = master_data.sql(''' + SELECT + date, + year, + month, + day, + ABS(SUM(transaction_amount)) as total + FROM self + WHERE category_name != 'Inflow: Ready to Assign' + GROUP BY date, year, month, day + ORDER BY date DESC + ''' + ) + + spend_per_category = master_data.sql(''' + SELECT + category_name, + ABS(SUM(transaction_amount)) as total + FROM self + WHERE category_name != 'Inflow: Ready to Assign' + GROUP BY category_name + ORDER BY total DESC + ''' + ) + + spend_per_payee = master_data.sql(''' + SELECT + payee_name, + ABS(SUM(transaction_amount)) as total + FROM self + WHERE payee_name != 'Starting Balance' + AND transaction_amount < 0 + GROUP BY payee_name + ORDER BY total DESC + ''' + ) + total_spend = master_data.sql(''' + SELECT ABS(SUM(transaction_amount)) AS total + FROM self + WHERE payee_name != 'Starting Balance' + AND transaction_amount < 0 + ''').item() -# Convert DataFrame to list of dictionaries -spend_per_day_data = spend_per_day.to_dicts() -spend_per_category_data = spend_per_category.to_dicts() -spend_per_payee_data = spend_per_payee.to_dicts() + # Convert DataFrame to list of dictionaries + spend_per_day_data = spend_per_day.to_dicts() + spend_per_category_data = spend_per_category.to_dicts() + spend_per_payee_data = spend_per_payee.to_dicts() -# Convert list of dictionaries to Pandas DataFrame -spend_per_day_df = pd.DataFrame(spend_per_day_data) -spend_per_category_df = pd.DataFrame(spend_per_category_data) -spend_per_payee_df = pd.DataFrame(spend_per_payee_data) + # Convert list of dictionaries to Pandas DataFrame + spend_per_day_df = pd.DataFrame(spend_per_day_data) + spend_per_category_df = pd.DataFrame(spend_per_category_data) + spend_per_payee_df = pd.DataFrame(spend_per_payee_data) -spend_per_day_line = px.line(spend_per_day_df, x="date", y="total") -spend_per_day_line.update_layout( - plot_bgcolor='black', - paper_bgcolor='black', - font_color='white' -) + spend_per_day_line = px.line(spend_per_day_df, x="date", y="total") + spend_per_day_line.update_layout( + plot_bgcolor='black', + paper_bgcolor='black', + font_color='white' + ) -spend_per_category_bar = px.bar(spend_per_category_df, x="category_name", y="total") -spend_per_category_bar.update_layout( - plot_bgcolor='black', - paper_bgcolor='black', - font_color='white' -) + spend_per_category_bar = px.bar(spend_per_category_df, x="category_name", y="total") + spend_per_category_bar.update_layout( + plot_bgcolor='black', + paper_bgcolor='black', + font_color='white' + ) -spend_per_payee_bar = px.bar(spend_per_payee_df, x="payee_name", y="total") -spend_per_payee_bar.update_layout( - plot_bgcolor='black', - paper_bgcolor='black', - font_color='white' -) + spend_per_payee_bar = px.bar(spend_per_payee_df, x="payee_name", y="total") + spend_per_payee_bar.update_layout( + plot_bgcolor='black', + paper_bgcolor='black', + font_color='white' + ) + return spend_per_day_line, spend_per_category_bar, spend_per_payee_bar, total_spend diff --git a/visuals/dash_app.py b/visuals/dash_app.py index 832d931..e08345f 100644 --- a/visuals/dash_app.py +++ b/visuals/dash_app.py @@ -4,35 +4,23 @@ import dash_bootstrap_components as dbc from dash.dependencies import Input, Output import visuals.layout as layout +import visuals.components as charts - +def update_visuals(start_date, end_date): + # Update the data based on the selected date range + master = charts.update_dates(start_date, end_date) + data = charts.update_data(master) + return layout.create_layout(data) # Initialize the app with a dark theme app = dash.Dash(external_stylesheets=[dbc.themes.DARKLY]) # App layout -app.layout = layout.create_layout() +app.layout = update_visuals() -# Update toggle visibility of off-canvas section @app.callback( - Output('off-canvas-section', 'style'), - [Input('toggle-button', 'n_clicks')] -) -def update_off_canvas_section(n_clicks): - if n_clicks is not None: - return {'display': 'block'} - else: - return {'display': 'none'} - -# Update off-canvas section with date range picker -@app.callback( - Output('off-canvas-section', 'children'), - [Input('toggle-button', 'n_clicks')] -) -def update_off_canvas_content(n_clicks): - if n_clicks is not None: - # Include a date range picker library (e.g., Dash Bootstrap components) for the off-canvas section - return [ - dbc.Input(id='date-picker-start', type='date'), - dbc.Input(id='date-picker-end', type='date') - ] \ No newline at end of file + [ + Input('date-picker-range', 'start_date'), + Input('date-picker-range', 'end_date') + ] +) \ No newline at end of file diff --git a/visuals/layout.py b/visuals/layout.py index dcca368..85ed882 100644 --- a/visuals/layout.py +++ b/visuals/layout.py @@ -2,104 +2,114 @@ from dash import html, dcc from datetime import date import dash_bootstrap_components as dbc -import visuals.components as charts -def create_layout(): - main_body = create_main_body() - sidebar = create_sidebar() - - return [html.Div(main_body + sidebar)] +def create_layout(data): + main_body = create_main_body(data) + topbar = create_topbar() -def create_sidebar(): + return [html.Div(topbar + main_body)] + + +def create_topbar(): return [ - html.Div(id='off-canvas-section', style={'display': 'none'}), - dbc.Row( - dbc.Col( - dcc.DatePickerRange( - id='date-picker-range', - start_date=date(2024, 1, 1), - end_date=date(2026, 1, 1) - ), - width=4 - ), - align='center' + dbc.Container( + dbc.Row( + [ + dbc.Col( + dcc.DatePickerRange( + id="date-picker-range", + start_date=date(2024, 1, 1), + end_date=date(2026, 1, 1), + ), + width=4, + ), + dbc.Col( + html.Button("Change Date Range", id="date-range-confirm-button"), + width=2, + ), + ] + ) ) ] -def create_main_body(): - return [html.Button('Toggle Off-Canvas', id='toggle-button'), +def create_main_body(data): + return [ dbc.Container( - [ - dbc.Row( - dbc.Col( - html.Div( - "Data Pipeline For YNAB, Preview Visualisations", - className="text-center text-light", - ), - width=12, - ) - ), - dbc.Row( - [ + [ + dbc.Row( dbc.Col( - dbc.Card( - dbc.CardBody( - [ - html.H4("Spend Per Day", className="card-title"), - dcc.Graph(figure=charts.spend_per_day_line), - ] - ), - className="mb-4", + html.Div( + "Data Pipeline For YNAB, Preview Visualisations", + className="text-center text-light", ), width=12, ) - ] - ), - dbc.Row( - [ - dbc.Col( - dbc.Card( - dbc.CardBody( - [ - html.H4( - "Spend Per Category", className="card-title" - ), - dcc.Graph(figure=charts.spend_per_category_bar), - ] + ), + dbc.Row( + [ + dbc.Col( + dbc.Card( + dbc.CardBody( + [ + html.H4( + "Spend Per Day", className="card-title" + ), + dcc.Graph(figure=data.spend_per_day_line), + ] + ), + className="mb-4", ), - className="mb-4", + width=12, + ) + ] + ), + dbc.Row( + [ + dbc.Col( + dbc.Card( + dbc.CardBody( + [ + html.H4( + "Spend Per Category", className="card-title" + ), + dcc.Graph(figure=data.spend_per_category_bar), + ] + ), + className="mb-4", + ), + width=5, ), - width=5, - ), - dbc.Col( - dbc.Card( - dbc.CardBody( - [ - dcc.Markdown(f""" + dbc.Col( + dbc.Card( + dbc.CardBody( + [ + dcc.Markdown(f""" ## Total Spend: -### £{charts.total_spend:,} +### £{data.total_spend:,} """), - ] + ] + ), + className="mb-4", ), - className="mb-4", + width=2, ), - width=2, - ), - dbc.Col( - dbc.Card( - dbc.CardBody( - [ - html.H4("Spend Per Payee", className="card-title"), - dcc.Graph(figure=charts.spend_per_payee_bar), - ] + dbc.Col( + dbc.Card( + dbc.CardBody( + [ + html.H4( + "Spend Per Payee", className="card-title" + ), + dcc.Graph(figure=data.spend_per_payee_bar), + ] + ), + className="mb-4", ), - className="mb-4", + width=5, ), - width=5, - ), - ] - ), - ], - fluid=True, - ) + ] + ), + ], + fluid=True, + ), ]