Spaces:
Sleeping
Sleeping
Download finbot/features/budgets.py from spacedout-bits/Oracle: direct link, hf CLI and curl.
- Browser
- Download file 3.12 kB
-
https://huggingface.co/spaces/spacedout-bits/Oracle/resolve/main/finbot/features/budgets.py
- Command line
-
hf download hf://spaces/spacedout-bits/Oracle/finbot/features/budgets.py
-
curl -L -o budgets.py https://huggingface.co/spaces/spacedout-bits/Oracle/resolve/main/finbot/features/budgets.py
3.12 kB
| """Monthly budgets per category.""" | |
| from __future__ import annotations | |
| from typing import Sequence | |
| from .. import render | |
| from ..analytics import budget_status, month_bounds | |
| from ..categories import CATEGORIES, coerce | |
| from ..categories import label as category_label | |
| from ..models import Budget | |
| from ..money import parse_amount | |
| from .base import BotContext, CommandSpec, Reply, SimpleFeature | |
| class BudgetsFeature(SimpleFeature): | |
| name = "budgets" | |
| description = "🎯 Budgets" | |
| priority = 20 | |
| def commands(self) -> Sequence[CommandSpec]: | |
| return ( | |
| CommandSpec("budget", "Set a monthly budget", "<category> <amount>"), | |
| CommandSpec("budgets", "How you're tracking against budgets"), | |
| CommandSpec("unbudget", "Remove a budget", "<category>"), | |
| CommandSpec("categories", "List the category names"), | |
| ) | |
| def cmd_budget(self, ctx: BotContext) -> Reply: | |
| args = ctx.args.strip() | |
| if not args: | |
| return Reply( | |
| "Set a monthly cap, e.g. <code>/budget food_dining 8000</code>.\n" | |
| "/categories lists the names." | |
| ) | |
| parts = args.split() | |
| category = coerce(parts[0]) | |
| if category == "other" and parts[0].lower() not in ("other",): | |
| return Reply( | |
| f"I don't recognise the category <code>{render.escape(parts[0])}</code>.\n" | |
| "/categories lists what I understand." | |
| ) | |
| amount = parse_amount(" ".join(parts[1:]), ctx.settings.default_currency) | |
| if amount is None or amount.minor <= 0: | |
| return Reply("I need an amount, e.g. <code>/budget groceries 12000</code>.") | |
| budget = Budget( | |
| user_id=ctx.user_id, | |
| category=category, | |
| amount_minor=amount.minor, | |
| currency=amount.currency or ctx.settings.default_currency, | |
| ) | |
| ctx.store.set_budget(budget) | |
| return Reply( | |
| f"🎯 Budget set: {render.escape(category_label(category))} " | |
| f"{render.money(budget.amount_minor, budget.currency)}/month." | |
| ) | |
| def cmd_budgets(self, ctx: BotContext) -> Reply: | |
| budgets = ctx.store.get_budgets(ctx.user_id) | |
| start, _ = month_bounds(ctx.today) | |
| expenses = ctx.store.list_expenses(ctx.user_id, start, ctx.today) | |
| return Reply(render.budget_list(budget_status(budgets, expenses))) | |
| def cmd_unbudget(self, ctx: BotContext) -> Reply: | |
| category = coerce(ctx.args.strip()) | |
| if ctx.store.delete_budget(ctx.user_id, category): | |
| return Reply(f"Removed the {render.escape(category_label(category))} budget.") | |
| return Reply("No budget set for that category.") | |
| def cmd_categories(self, ctx: BotContext) -> Reply: | |
| lines = ["<b>Categories</b>"] | |
| lines.extend( | |
| f"<code>{name}</code> — {render.escape(category_label(name))}" | |
| for name in CATEGORIES | |
| ) | |
| lines.append("") | |
| lines.append("Tag an entry to override: <code>250 lunch #transport</code>") | |
| return Reply("\n".join(lines)) | |