diff --git a/main.py b/main.py
new file mode 100644
index 0000000..861b5d0
--- /dev/null
+++ b/main.py
@@ -0,0 +1,301 @@
+# SplitBuddy — tiny Flask app to track IOUs between two roommates
+# ---------------------------------------------------------------
+# Features
+# - Add entries with + / - amount, reason, and method (cash/transfer/other)
+# - Auto-summary: who owes whom and how much
+# - List with delete (with confirm)
+# - CSV export
+# - Single-file app; uses SQLite (splitbuddy.db)
+
+from __future__ import annotations
+import os, sqlite3, csv, io, datetime as dt
+from typing import Optional
+from flask import Flask, g, request, redirect, url_for, render_template_string, jsonify, send_file
+
+app = Flask(__name__)
+
+# ---------------------------- Config ---------------------------- #
+DB_PATH = os.environ.get("SPLITBUDDY_DB", "splitbuddy.db")
+CURRENCY = "₪" # change if you want
+PERSON_A = os.environ.get("SPLITBUDDY_ME", "Me")
+PERSON_B = os.environ.get("SPLITBUDDY_ROOMIE", "Idan")
+
+# Convention: stored amount is signed
+# > 0 => PERSON_A owes PERSON_B ("I owe Idan")
+# < 0 => PERSON_B owes PERSON_A ("Idan owes me")
+
+# ------------------------- DB helpers --------------------------- #
+
+def get_db() -> sqlite3.Connection:
+ if "db" not in g:
+ g.db = sqlite3.connect(DB_PATH)
+ g.db.row_factory = sqlite3.Row
+ return g.db
+
+@app.teardown_appcontext
+def close_db(_=None):
+ db = g.pop("db", None)
+ if db is not None:
+ db.close()
+
+def init_db():
+ db = get_db()
+ db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS entries (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ created_at TEXT NOT NULL,
+ amount REAL NOT NULL, -- signed
+ method TEXT NOT NULL, -- cash | transfer | other
+ note TEXT -- reason / memo
+ )
+ """
+ )
+ db.commit()
+
+# --------------------------- Templates -------------------------- #
+BASE = r"""
+
+
+
+
+
+ SplitBuddy
+
+
+
+
+
+
+
+
+ SplitBuddy
+
+
+ {% if summary.total > 0 %}
+ {{ A }} owes {{ B }} {{ currency }}{{ '%.2f'|format(summary.total) }}
+ {% elif summary.total < 0 %}
+ {{ B }} owes {{ A }} {{ currency }}{{ '%.2f'|format(-summary.total) }}
+ {% else %}
+ All settled ✨
+ {% endif %}
+
+
Balance: {{ currency }}{{ '%.2f'|format(summary.total) }}
+
Export CSV
+
+
+
+
+
+
+
+ Stats
+ Total entries: {{ summary.count }}
+ Latest: {{ summary.latest or '—' }}
+
+ Cash: {{ currency }}{{ '%.2f'|format(summary.by_method.cash) }}
+ Transfer: {{ currency }}{{ '%.2f'|format(summary.by_method.transfer) }}
+ Other: {{ currency }}{{ '%.2f'|format(summary.by_method.other) }}
+
+
+
+
+
+ Ledger
+
+
+
+ | Time |
+ Reason |
+ Method |
+ Amount |
+ |
+
+
+
+ {% for e in entries %}
+
+ | {{ e.created_at }} |
+ {{ e.note or '—' }} |
+ {{ e.method }} |
+ {{ currency }}{{ '%.2f'|format(e.amount) }} |
+
+
+ |
+
+ {% endfor %}
+
+
+
+
+
+
+"""
+
+# --------------------------- Utilities -------------------------- #
+
+def _now_local_iso_min() -> str:
+ # default to local time (no tz), minute precision for nicer input value
+ now = dt.datetime.now().replace(second=0, microsecond=0)
+ return now.isoformat(timespec="minutes")
+
+class ByMethod:
+ def __init__(self, cash=0.0, transfer=0.0, other=0.0):
+ self.cash = cash
+ self.transfer = transfer
+ self.other = other
+
+class Summary:
+ def __init__(self, total: float, count: int, latest: Optional[str], by_method: ByMethod):
+ self.total = total
+ self.count = count
+ self.latest = latest
+ self.by_method = by_method
+
+# ----------------------------- Routes --------------------------- #
+@app.before_request
+def _ensure_db():
+ init_db()
+
+@app.get("/")
+def index():
+ db = get_db()
+ rows = db.execute("SELECT id, created_at, amount, method, note FROM entries ORDER BY datetime(created_at) DESC, id DESC").fetchall()
+ entries = [dict(r) for r in rows]
+
+ total = sum(e["amount"] for e in entries) if entries else 0.0
+ latest = entries[0]["created_at"] if entries else None
+
+ bm = ByMethod(
+ cash=sum(e["amount"] for e in entries if e["method"] == "cash"),
+ transfer=sum(e["amount"] for e in entries if e["method"] == "transfer"),
+ other=sum(e["amount"] for e in entries if e["method"] == "other"),
+ )
+ summary = Summary(total=total, count=len(entries), latest=latest, by_method=bm)
+
+ return render_template_string(
+ BASE,
+ entries=entries,
+ summary=summary,
+ A=PERSON_A, B=PERSON_B,
+ currency=CURRENCY,
+ now_local=_now_local_iso_min(),
+ )
+
+@app.post("/add")
+def add():
+ amount_raw = request.form.get("amount", type=float)
+ sign = request.form.get("sign", "+")
+ method = request.form.get("method", "cash").strip().lower()
+ note = request.form.get("note", "").strip()
+ created_at = request.form.get("created_at") or _now_local_iso_min()
+
+ if amount_raw is None:
+ return redirect(url_for("index"))
+ if sign not in (+1, -1, "+", "-"):
+ sign = "+"
+
+ signed = amount_raw * (1 if sign in (+1, "+") else -1)
+
+ db = get_db()
+ db.execute(
+ "INSERT INTO entries (created_at, amount, method, note) VALUES (?, ?, ?, ?)",
+ (created_at, signed, method, note)
+ )
+ db.commit()
+ return redirect(url_for("index"))
+
+@app.post("/delete/")
+def delete(entry_id: int):
+ db = get_db()
+ db.execute("DELETE FROM entries WHERE id = ?", (entry_id,))
+ db.commit()
+ return redirect(url_for("index"))
+
+@app.get("/export.csv")
+def export_csv():
+ db = get_db()
+ rows = db.execute("SELECT id, created_at, amount, method, note FROM entries ORDER BY datetime(created_at) DESC, id DESC").fetchall()
+ buff = io.StringIO()
+ writer = csv.writer(buff)
+ writer.writerow(["id", "created_at", "amount", "method", "note"]) # header
+ for r in rows:
+ writer.writerow([r["id"], r["created_at"], r["amount"], r["method"], r["note"]])
+ buff.seek(0)
+ return send_file(io.BytesIO(buff.read().encode("utf-8")), mimetype="text/csv", as_attachment=True, download_name="splitbuddy_export.csv")
+
+# Compatibility alias for templates
+export_csv = export_csv
+
+# --------------------------- Entrypoint -------------------------- #
+if __name__ == "__main__":
+ os.makedirs(os.path.dirname(DB_PATH) or ".", exist_ok=True)
+ with app.app_context():
+ init_db()
+ app.run(debug=True)
\ No newline at end of file