Initial commit: moon-household-budget

This commit is contained in:
Bonna
2026-06-06 19:12:43 -05:00
commit a3b91f6d6b
57 changed files with 11944 additions and 0 deletions

58
sinking.py Normal file
View File

@@ -0,0 +1,58 @@
import copy
import json
import os
from datetime import date
from storage import load_json, save_json
DATA_DIR = os.environ.get("DATA_DIR", os.path.join(os.path.dirname(__file__), "data"))
SINKING_FILE = os.path.join(DATA_DIR, "sinking_funds.json")
FUND_COLORS = {
"Travel": "#C5BBA0",
"Vet & Pets": "#A0B8C5",
"Holidays & Gifts": "#D4B8C5",
"Renovations": "#C5A8A0",
"Studio Build": "#B4A0C5",
"Emergency Fund": "#A8C5A0",
}
DEFAULT_FUNDS = [
{"name": "Travel", "type": "ongoing", "monthly": 0, "balance": 0, "notes": ""},
{"name": "Vet & Pets", "type": "ongoing", "monthly": 0, "balance": 0, "notes": ""},
{"name": "Holidays & Gifts", "type": "ongoing", "monthly": 0, "balance": 0, "notes": ""},
{"name": "Renovations", "type": "ongoing", "monthly": 0, "balance": 0, "notes": "Subcategories coming later"},
{"name": "Studio Build", "type": "ongoing", "monthly": 0, "balance": 0, "notes": "Goal amount TBD"},
{"name": "Emergency Fund", "type": "ongoing", "monthly": 0, "balance": 0, "notes": "Target: 36 months expenses"},
]
def load_funds():
funds = load_json(SINKING_FILE, None)
if funds is None:
# First run — seed with a copy of the defaults (never hand out the
# module-level list, which callers mutate in place).
funds = copy.deepcopy(DEFAULT_FUNDS)
save_funds(funds)
return funds
def save_funds(funds):
save_json(SINKING_FILE, funds)
def months_until(target_date_str):
try:
target = date.fromisoformat(target_date_str)
today = date.today()
months = (target.year - today.year) * 12 + (target.month - today.month)
return max(months, 1)
except Exception:
return None
def monthly_needed(balance, goal, target_date_str):
m = months_until(target_date_str)
if not m:
return None
remaining = max(goal - balance, 0)
return round(remaining / m, 2)