Files
Moon-Household-Budget/bills.py
tonym be74ca1eb7 Harden JSON storage (atomic writes + crash-safe loads) + suggestions lightbulb
- storage.py: atomic save_json (temp+fsync+os.replace) and load_json that
  survives missing/corrupt files (moves corrupt aside). Closes #11/#12.
- route all data modules + app.py helpers through storage (closes #31 core);
  fixes mutable DEFAULT_FUNDS/DEFAULT_CARS return (#23).
- gunicorn --workers 1 --threads 4 to remove the write race (#13); strip
  Dockerfile template scaffolding (#40).
- base.html: 💡 suggestions link in the shared nav (shows on every page).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:34:06 -05:00

80 lines
2.1 KiB
Python
Raw Permalink 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 json
import os
from datetime import date, timedelta
import calendar
from storage import load_json, save_json
DATA_DIR = os.environ.get("DATA_DIR", os.path.join(os.path.dirname(__file__), "data"))
BILLS_FILE = os.path.join(DATA_DIR, "bills.json")
def load_bills():
return load_json(BILLS_FILE, [])
def save_bills(bills):
save_json(BILLS_FILE, bills)
def get_week_label(day):
if day <= 7:
return "Week 1 (17)"
elif day <= 14:
return "Week 2 (814)"
elif day <= 21:
return "Week 3 (1521)"
else:
return "Week 4 (22end)"
def get_week_num(day):
if day <= 7:
return 1
elif day <= 14:
return 2
elif day <= 21:
return 3
else:
return 4
def bills_for_month(year, month):
from datetime import datetime
all_bills = load_bills()
_, num_days = calendar.monthrange(year, month)
result = []
for b in all_bills:
if not b.get("active", True):
continue
# Specific-date bills (BNPL payments)
if b.get("due_date_str"):
try:
d = datetime.strptime(b["due_date_str"], "%Y-%m-%d").date()
if d.year == year and d.month == month:
result.append({**b, "due_date": d, "due_day": d.day})
except ValueError:
pass
# Recurring monthly bills
elif b.get("due_day"):
due_day = b["due_day"]
if 1 <= due_day <= num_days:
result.append({**b, "due_date": date(year, month, due_day)})
return sorted(result, key=lambda x: x.get("due_day", 0))
def calendar_grid(year, month):
_, num_days = calendar.monthrange(year, month)
first_weekday = calendar.monthrange(year, month)[0] # 0=Mon
# Convert to Sun-first
first_weekday = (first_weekday + 1) % 7
days = []
for _ in range(first_weekday):
days.append(None)
for d in range(1, num_days + 1):
days.append(d)
while len(days) % 7 != 0:
days.append(None)
return [days[i:i+7] for i in range(0, len(days), 7)]