zeeshan4801 commited on
Commit
f4fd8a7
·
verified ·
1 Parent(s): c06864f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +153 -0
app.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ from datetime import date
4
+ import io
5
+
6
+ import pandas as pd
7
+ import streamlit as st
8
+ import altair as alt
9
+
10
+ # ---------- Constants ----------
11
+ DATA_DIR = Path("data")
12
+ DATA_FILE = DATA_DIR / "expenses.csv"
13
+ CATEGORIES = ["Food", "Transport", "Bills", "Shopping", "Entertainment", "Other"]
14
+
15
+ # ---------- Helpers ----------
16
+ def ensure_data_file():
17
+ """Create data directory and CSV file with headers if missing."""
18
+ DATA_DIR.mkdir(exist_ok=True)
19
+ if not DATA_FILE.exists():
20
+ df = pd.DataFrame(columns=["Date", "Category", "Description", "Amount"])
21
+ df.to_csv(DATA_FILE, index=False)
22
+
23
+ def load_expenses() -> pd.DataFrame:
24
+ ensure_data_file()
25
+ try:
26
+ df = pd.read_csv(DATA_FILE, parse_dates=["Date"]) if DATA_FILE.exists() else pd.DataFrame(columns=["Date", "Category", "Description", "Amount"])
27
+ if "Date" in df.columns:
28
+ df["Date"] = pd.to_datetime(df["Date"]).dt.date
29
+ return df
30
+ except Exception as e:
31
+ st.error(f"Could not load expenses: {e}")
32
+ return pd.DataFrame(columns=["Date", "Category", "Description", "Amount"])
33
+
34
+ def save_expenses(df: pd.DataFrame):
35
+ ensure_data_file()
36
+ df.to_csv(DATA_FILE, index=False)
37
+
38
+ def add_expense(row: dict):
39
+ df = load_expenses()
40
+ df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
41
+ save_expenses(df)
42
+
43
+ def clear_expenses():
44
+ ensure_data_file()
45
+ df = pd.DataFrame(columns=["Date", "Category", "Description", "Amount"])
46
+ save_expenses(df)
47
+
48
+ def get_summary(df: pd.DataFrame):
49
+ if df.empty:
50
+ return {"total": 0.0, "count": 0}
51
+ total = df["Amount"].sum()
52
+ count = len(df)
53
+ return {"total": total, "count": count}
54
+
55
+ def csv_download_button(df: pd.DataFrame, filename: str = "expenses.csv"):
56
+ buffer = io.StringIO()
57
+ df.to_csv(buffer, index=False)
58
+ buffer.seek(0)
59
+ st.download_button("Download CSV", buffer, file_name=filename, mime="text/csv")
60
+
61
+ # ---------- App UI ----------
62
+ st.set_page_config(page_title="Expense Tracker", page_icon="💸", layout="centered")
63
+ st.title("💸 Simple Expense Tracker")
64
+ st.write("Add and track your expenses. Data persists to `data/expenses.csv` on the Space.")
65
+
66
+ # Load data
67
+ df = load_expenses()
68
+
69
+ # Form to add expense
70
+ with st.form("expense_form", clear_on_submit=True):
71
+ col1, col2 = st.columns(2)
72
+ with col1:
73
+ expense_date = st.date_input("Date", value=date.today())
74
+ category = st.selectbox("Category", options=CATEGORIES)
75
+ with col2:
76
+ amount = st.number_input("Amount", min_value=0.0, format="%.2f")
77
+ description = st.text_input("Description")
78
+
79
+ submitted = st.form_submit_button("Add expense")
80
+
81
+ if submitted:
82
+ if amount <= 0:
83
+ st.warning("Amount must be greater than 0.")
84
+ else:
85
+ row = {
86
+ "Date": expense_date.isoformat(),
87
+ "Category": category,
88
+ "Description": description,
89
+ "Amount": float(amount),
90
+ }
91
+ add_expense(row)
92
+ st.success("Expense added!")
93
+ df = load_expenses()
94
+
95
+ # Sidebar actions
96
+ st.sidebar.header("Actions")
97
+ if st.sidebar.button("Refresh"):
98
+ df = load_expenses()
99
+
100
+ if st.sidebar.button("Clear all expenses"):
101
+ if st.sidebar.checkbox("Confirm clear all (check to confirm)"):
102
+ clear_expenses()
103
+ st.sidebar.success("All expenses cleared")
104
+ df = load_expenses()
105
+
106
+ # Display table and summary
107
+ st.subheader("Expenses")
108
+ if df.empty:
109
+ st.info("No expenses yet — add one using the form above.")
110
+ else:
111
+ df["Amount"] = pd.to_numeric(df["Amount"], errors="coerce").fillna(0.0)
112
+ if "Date" in df.columns:
113
+ try:
114
+ df["Date"] = pd.to_datetime(df["Date"]).dt.date
115
+ except Exception:
116
+ pass
117
+
118
+ st.dataframe(df.sort_values(by="Date", ascending=False))
119
+
120
+ summary = get_summary(df)
121
+ st.markdown(f"**Total:** {summary['total']:.2f} — **Count:** {summary['count']}")
122
+
123
+ # Chart: total by category
124
+ st.subheader("Spending by category")
125
+ cat_df = df.groupby("Category", as_index=False)["Amount"].sum()
126
+ if not cat_df.empty:
127
+ chart = alt.Chart(cat_df).mark_bar().encode(
128
+ x=alt.X("Category:N", sort="-y"),
129
+ y=alt.Y("Amount:Q"),
130
+ tooltip=[alt.Tooltip("Category"), alt.Tooltip("Amount:Q", format=".2f")],
131
+ )
132
+ st.altair_chart(chart, use_container_width=True)
133
+
134
+ # Monthly cumulative line
135
+ st.subheader("Monthly spending (cumulative)")
136
+ df_line = df.copy()
137
+ df_line["Date"] = pd.to_datetime(df_line["Date"])
138
+ df_line["YearMonth"] = df_line["Date"].dt.to_period("M").dt.to_timestamp()
139
+ monthly = df_line.groupby("YearMonth")["Amount"].sum().reset_index()
140
+ if not monthly.empty:
141
+ line = alt.Chart(monthly).mark_line(point=True).encode(
142
+ x="YearMonth:T",
143
+ y="Amount:Q",
144
+ tooltip=[alt.Tooltip("YearMonth:T", title="Month"), alt.Tooltip("Amount:Q", format=".2f")],
145
+ )
146
+ st.altair_chart(line, use_container_width=True)
147
+
148
+ # Download CSV
149
+ st.subheader("Export")
150
+ if not df.empty:
151
+ csv_download_button(df)
152
+
153
+ st.caption("Data is stored locally in the app's `data/expenses.csv`. On Hugging Face Spaces this file persists across runs unless you explicitly remove it.")