SG-2025's picture
Update app.py
33c20cc verified
Raw
History Blame Contribute Delete
20.3 kB
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()