Files
Moon-Household-Budget/sinking.py
2026-06-06 19:12:43 -05:00

59 lines
2.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)