import streamlit as st import pandas as pd import matplotlib.pyplot as plt from datetime import date, datetime import numpy as np import os import json # Local storage file for user credentials CREDENTIALS_FILE = "user_credentials.json" # Initialize credentials file if it doesn't exist def initialize_credentials_file(): if not os.path.exists(CREDENTIALS_FILE): with open(CREDENTIALS_FILE, "w") as f: json.dump({}, f) # Save user credentials def save_user_credentials(username, password): with open(CREDENTIALS_FILE, "r") as f: credentials = json.load(f) credentials[username] = password with open(CREDENTIALS_FILE, "w") as f: json.dump(credentials, f) # Validate user credentials def validate_credentials(username, password): with open(CREDENTIALS_FILE, "r") as f: credentials = json.load(f) return credentials.get(username) == password # Check if username exists def username_exists(username): with open(CREDENTIALS_FILE, "r") as f: credentials = json.load(f) return username in credentials # Initialize credentials file initialize_credentials_file() # Session state for user management if "is_authenticated" not in st.session_state: st.session_state["is_authenticated"] = False if "current_user" not in st.session_state: st.session_state["current_user"] = None # Initialize session state if "income_data" not in st.session_state: st.session_state["income_data"] = pd.DataFrame(columns=["Date", "Category", "Amount"]) if "expense_data" not in st.session_state: st.session_state["expense_data"] = pd.DataFrame(columns=["Date", "Category", "Amount"]) if "custom_income_sources" not in st.session_state: st.session_state["custom_income_sources"] = [] if "custom_expense_categories" not in st.session_state: st.session_state["custom_expense_categories"] = [] if "saving_goals" not in st.session_state: st.session_state["saving_goals"] = pd.DataFrame(columns=["Goal", "Target Amount", "Current Savings", "Deadline", "Allocated Savings"]) # Helper functions def format_date(date_obj): return date_obj.strftime("%d-%m-%y") def update_transaction_log(): st.session_state["transaction_log"] = pd.concat([ st.session_state["income_data"].assign(Type="Income"), st.session_state["expense_data"].assign(Type="Expense") #st.session_state["saving_transaction_log"].assign(Type="Saving") ]).reset_index(drop=True) st.session_state["transaction_log"].index += 1 if "transaction_log" not in st.session_state: update_transaction_log() # Login and Sign-Up Pages def login_page(): st.title("Login") username = st.text_input("Username") password = st.text_input("Password", type="password") if st.button("Login"): if validate_credentials(username, password): st.session_state["is_authenticated"] = True st.session_state["current_user"] = username st.success("Login successful!") st.rerun() else: st.error("Invalid username or password") def signup_page(): st.title("Sign Up") username = st.text_input("Choose a Username") password = st.text_input("Choose a Password", type="password") if st.button("Sign Up"): if username_exists(username): st.error("Username already exists. Please choose a different username.") else: save_user_credentials(username, password) st.success("Sign up successful! You can now log in.") # Main app after login def main_app(): # Sidebar for navigation st.sidebar.title("Navigation") tabs = st.sidebar.radio("", ["Home", "Record Income", "Record Expense", "Transaction Log", "Savings", "Budget Recommendations", "Logout"]) # Home Tab if tabs == "Home": # Total income, expense, and net savings total_income = st.session_state['income_data']['Amount'].sum() total_expense = st.session_state['expense_data']['Amount'].sum() allocated_savings = st.session_state["saving_goals"]["Allocated Savings"].sum() net_savings = total_income - total_expense - allocated_savings # Cash in hand (remaining income after subtracting expenses and allocated savings) cash_in_hand = total_income - total_expense - allocated_savings # Show metrics col1, col2, col3 = st.columns(3) col1.metric("Total Income", f"{total_income:,}") col2.metric("Total Expense", f"{total_expense:,}") col3.metric("Net Savings", f"{net_savings:,}") # Display Cash in Hand (remaining after expenses and savings) col4, col5 = st.columns(2) col4.metric("Cash in Hand", f"{cash_in_hand:,}") # Find the nearest goal if not st.session_state["saving_goals"].empty: closest_goal = st.session_state["saving_goals"].iloc[0] # Default to first goal closest_goal_amount = closest_goal["Target Amount"] - closest_goal["Current Savings"] closest_goal_name = closest_goal["Goal"] closest_goal_deadline = closest_goal["Deadline"] # Loop through goals and find the closest one based on the deadline for _, goal in st.session_state["saving_goals"].iterrows(): remaining_amount = goal["Target Amount"] - goal["Current Savings"] if goal["Deadline"] < closest_goal_deadline and remaining_amount > 0: closest_goal = goal closest_goal_name = goal["Goal"] closest_goal_deadline = goal["Deadline"] closest_goal_amount = remaining_amount # Display label for the nearest goal st.write(f"Amount needed to reach goal '{closest_goal_name}': {closest_goal_amount:,} (Deadline: {closest_goal_deadline})") if total_income > 0 and total_expense > 0: # Pie chart for income breakdown labels = ['Expense', 'Savings', 'Unallocated Income'] sizes = [total_expense, allocated_savings, cash_in_hand] sizes = [0 if np.isnan(value) else value for value in sizes] colors = ['#ff9999','#66b3ff','#99ff99'] fig, ax = plt.subplots() ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors) ax.axis('equal') # Equal aspect ratio ensures that pie chart is drawn as a circle. st.pyplot(fig) # Progress bar for each goal for idx, goal in st.session_state["saving_goals"].iterrows(): goal_name = goal["Goal"] target_amount = goal["Target Amount"] current_savings = goal["Current Savings"] progress = current_savings / target_amount * 100 # Progress as percentage st.subheader(f"Progress for Goal: {goal_name}") st.progress(progress) st.write(f"Target Amount: {target_amount}, Current Savings: {current_savings}") # Record Income Tab elif tabs == "Record Income": st.header("Record Income") with st.form("income_form"): income_date = st.date_input("Date", value=date.today()) income_source = st.selectbox("Source", ["Salary", "Freelance", "Investments"] + st.session_state["custom_income_sources"]) income_amount = st.number_input("Amount", min_value=0, step=1) submit_income = st.form_submit_button("Add Income") if submit_income: new_entry = {"Date": income_date, "Category": "Income from "+income_source, "Amount": income_amount} st.session_state["income_data"] = pd.concat( [st.session_state["income_data"], pd.DataFrame([new_entry])], ignore_index=True ) update_transaction_log() st.success("Income added successfully!") if not st.session_state["income_data"].empty: fig, ax = plt.subplots() st.session_state['income_data']['Date'] = pd.to_datetime(st.session_state['income_data']['Date']) sorted_data = st.session_state["income_data"].sort_values("Date") ax.bar(sorted_data["Date"], sorted_data["Amount"], label='Income', width=0.4) ax.plot(sorted_data["Date"], sorted_data["Amount"].cumsum(), label='Cumulative Income', marker='o', color='green') ax.set_title("Income Over Time") ax.set_xlabel("Date") ax.set_ylabel("Amount") plt.xticks(rotation=45) ax.legend() st.pyplot(fig) # Record Expense Tab elif tabs == "Record Expense": st.header("Record Expense") with st.form("expense_form"): expense_date = st.date_input("Date", value=date.today()) expense_category = st.selectbox("Category", ["Food", "Travel", "Utilities"] + st.session_state["custom_expense_categories"]) expense_amount = st.number_input("Amount", min_value=0, step=1) submit_expense = st.form_submit_button("Add Expense") if submit_expense: new_entry = {"Date": expense_date, "Category": "Expense for " + expense_category, "Amount": expense_amount} st.session_state["expense_data"] = pd.concat( [st.session_state["expense_data"], pd.DataFrame([new_entry])], ignore_index=True ) update_transaction_log() st.success("Expense added successfully!") if not st.session_state["expense_data"].empty: fig, ax = plt.subplots() st.session_state['expense_data']['Date'] = pd.to_datetime(st.session_state['expense_data']['Date']) sorted_data = st.session_state["expense_data"].sort_values("Date") ax.bar(sorted_data["Date"], sorted_data["Amount"], label='Expense', width=0.4) ax.plot(sorted_data["Date"], sorted_data["Amount"].cumsum(), label='Cumulative Expense', marker='o', color='red') ax.set_title("Expenses Over Time") ax.set_xlabel("Date") ax.set_ylabel("Amount") plt.xticks(rotation=45) ax.legend() st.pyplot(fig) # Transaction Log Tab elif tabs == "Transaction Log": st.header("Transaction Log") # Check if income, expense, or saving transactions exist if "income_data" in st.session_state and "expense_data" in st.session_state: # Concatenate income, expense, and saving transactions transaction_log = pd.concat([ st.session_state["income_data"].assign(Type="Income"), st.session_state["expense_data"].assign(Type="Expense") ]).sort_values(by="Date") if "saving_transaction_log" in st.session_state: transaction_log = pd.concat([ st.session_state["income_data"].assign(Type="Income"), st.session_state["expense_data"].assign(Type="Expense"), st.session_state["saving_transaction_log"].assign(Type="Saving") # Add saving transactions ]).sort_values(by="Date") # Ensure 'Date' column is of type datetime64 transaction_log["Date"] = pd.to_datetime(transaction_log["Date"]) # Display the combined transaction log st.dataframe(transaction_log) else: st.warning("No transactions recorded yet.") # Set Saving Goals Tab elif tabs == "Savings": st.header("Savings Goals") # Fetch net savings from Home tab total_income = st.session_state['income_data']['Amount'].sum() total_expense = st.session_state['expense_data']['Amount'].sum() net_savings = 0 max_savings = total_income - total_expense - net_savings # Initialize session state for buttons if not already done if "show_set_goal_form" not in st.session_state: st.session_state["show_set_goal_form"] = False if "show_allocate_savings_form" not in st.session_state: st.session_state["show_allocate_savings_form"] = False # Buttons to toggle between setting goal and allocating savings set_goal_button = st.button("Set Saving Goal") allocate_savings_button = st.button("Allocate Saving to Goal") # Toggle the display state for each form if set_goal_button: st.session_state["show_set_goal_form"] = True st.session_state["show_allocate_savings_form"] = False # Hide other form if allocate_savings_button: st.session_state["show_allocate_savings_form"] = True st.session_state["show_set_goal_form"] = False # Hide other form # Set Saving Goal Form if st.session_state["show_set_goal_form"]: st.subheader("Set Saving Goal") goal_name = st.text_input("Goal Name") target_amount = st.number_input("Target Amount", min_value=0, step=100) goal_deadline = st.date_input("Deadline", value=date.today()) # Button to create a new goal if st.button("Create Goal"): if goal_name and target_amount > 0: new_goal = {"Goal": goal_name, "Target Amount": target_amount, "Current Savings": net_savings, "Deadline": goal_deadline, "Allocated Savings": 0} st.session_state["saving_goals"] = pd.concat( [st.session_state["saving_goals"], pd.DataFrame([new_goal])], ignore_index=True ) st.success("Goal set successfully!") else: st.error("Please provide valid goal name and target amount.") # Allocate Savings Form if st.session_state["show_allocate_savings_form"]: st.subheader("Allocate Savings to Goals") if not st.session_state["saving_goals"].empty: # Add a dropdown to select a goal goal_name_selected = st.selectbox("Select Saving Goal", st.session_state["saving_goals"]["Goal"].values) if goal_name_selected: # Fetch the selected goal details selected_goal = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] == goal_name_selected].iloc[0] # Show the current progress and target amount of the selected goal st.write(f"**{goal_name_selected}**") st.write(f"Target Amount: {selected_goal['Target Amount']}, Current Savings: {selected_goal['Current Savings']}, Deadline: {selected_goal['Deadline']}") # Input field to allocate savings allocated_savings = st.number_input(f"Enter Amount to Allocate for {goal_name_selected}", min_value=0, max_value=max_savings, step=100, key=f"allocate_{goal_name_selected}") # Button to save the allocation for a goal save_allocation_button = st.button(f"Save Allocation for {goal_name_selected}") if save_allocation_button and allocated_savings > 0: # Find the index of the selected goal and update the savings idx = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] == goal_name_selected].index[0] # Update the saving goal st.session_state["saving_goals"].at[idx, "Allocated Savings"] += allocated_savings max_savings -= allocated_savings # Adjust max savings st.session_state["saving_goals"].at[idx, "Current Savings"] += allocated_savings # Update current savings # Ensure the transaction log exists in session_state if "saving_transaction_log" not in st.session_state: st.session_state["saving_transaction_log"] = pd.DataFrame(columns=["Date", "Amount", "Category"]) # Append the transaction entry for the saving allocation transaction_entry = { "Date": pd.to_datetime("today"), # Ensuring Date is a datetime object "Amount": allocated_savings, "Category": "Savings for " + goal_name_selected } # Append the new transaction entry to the transaction log st.session_state["saving_transaction_log"] = st.session_state["saving_transaction_log"].append(transaction_entry, ignore_index=True) # Success message st.success(f"Allocated {allocated_savings} to goal '{goal_name_selected}'") elif save_allocation_button: st.error("Please allocate a valid amount greater than 0.") else: st.write("No saving goals set yet.") # Display current goals and their progress if not st.session_state["saving_goals"].empty: st.subheader("Current Goals and Allocated Savings") for idx, goal in st.session_state["saving_goals"].iterrows(): progress = (goal["Current Savings"] / goal["Target Amount"]) if goal["Target Amount"] > 0 else 0 st.write(f"**{goal['Goal']}**") st.write(f"Target Amount: {goal['Target Amount']}, Current Savings: {goal['Current Savings']}, Deadline: {goal['Deadline']}") st.progress(progress) # progress as a fraction (0.0 to 1.0) st.write(f"Progress: {progress * 100:.2f}%") # Display as a percentage # Option to delete a goal delete_goal_idx = st.selectbox("Select Goal to Delete", options=[""] + list(st.session_state["saving_goals"]["Goal"])) if delete_goal_idx: st.session_state["saving_goals"] = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] != delete_goal_idx] st.success(f"Goal '{delete_goal_idx}' deleted successfully.") else: st.write("No savings goals set yet.") # Budget Recommendations Tab elif tabs == "Budget Recommendations": st.header("Budget Recommendations") if st.session_state["income_data"].empty or st.session_state["expense_data"].empty: st.warning("Please record your income and expenses before getting budget recommendations.") else: total_income = st.session_state["income_data"]["Amount"].sum() total_expense = st.session_state["expense_data"]["Amount"].sum() # Display budget allocation advice budget_allocation = { "Housing": total_income * 0.30, "Transportation": total_income * 0.15, "Food": total_income * 0.10, "Savings": total_income * 0.20, "Entertainment": total_income * 0.05, "Health": total_income * 0.10, "Miscellaneous": total_income * 0.10 } st.write("Budget Recommendations based on your income:") for category, amount in budget_allocation.items(): st.write(f"{category}: {amount:,.2f}") # Logout Tab elif tabs == "Logout": st.session_state["is_authenticated"] = False st.session_state["current_user"] = None st.success("Logged out successfully!") st.rerun() # Streamlit App Logic if not st.session_state["is_authenticated"]: page = st.selectbox("Choose a page", ["Login", "Sign Up"]) if page == "Login": login_page() elif page == "Sign Up": signup_page() else: main_app()