diff --git a/categories.yaml b/categories.yaml index 07020ae..98b12ec 100644 --- a/categories.yaml +++ b/categories.yaml @@ -1,29 +1,38 @@ Groups: + income-fixed: + color: limegreen + linestyle: dashed + income-extra: + color: limegreen + linestyle: dashed income: color: limegreen + linestyle: solid fixed: color: tab:blue required: color: tab:orange health: color: tab:red + travel: + color: tab:cyan discretionary: color: tab:brown Income1: - group: income + group: income-fixed regex: - company A Income2: - group: income + group: income-fixed regex: - transfer banks: - BankA Income3: - group: income + group: income-extra regex: - company B @@ -76,13 +85,14 @@ Medical: Miscellaneous: Travel: - group: discretionary + group: travel regex: - ryanair - easyjet - airbnb not_in_groups: - - income + - income-fixed + - income-extra - fixed date_fmt: "%Y-%m-%d" vacations: diff --git a/pfbudget/categories.py b/pfbudget/categories.py index ecd9405..ce7f319 100644 --- a/pfbudget/categories.py +++ b/pfbudget/categories.py @@ -30,7 +30,9 @@ Options = namedtuple( cfg = yaml.safe_load(open("categories.yaml")) try: categories = { - k: Options(**v) if v and k != "Groups" else Options() for k, v in cfg.items() + str(k): Options(**v) if v else Options() + for k, v in cfg.items() + if k and k != "Groups" } except TypeError: logging.exception("Invalid option in categories.yaml") @@ -129,7 +131,7 @@ def vacations(db: DBManager) -> None: db.update_categories(transactions) except KeyError as e: - print(e) + logging.exception(e) def nulls(db: DBManager) -> None: diff --git a/pfbudget/graph.py b/pfbudget/graph.py index 8f315f5..3eb954d 100644 --- a/pfbudget/graph.py +++ b/pfbudget/graph.py @@ -47,25 +47,45 @@ def monthly( plt.figure(tight_layout=True) plt.plot( list(rrule(MONTHLY, dtstart=start.replace(day=1), until=end.replace(day=1))), - [groups["income"] for _, groups in monthly_transactions], + [ + sum( + value + for group, value in groups.items() + if group == "income-fixed" or group == "income-extra" + ) + for _, groups in monthly_transactions + ], color=groups["income"]["color"], + linestyle=groups["income"]["linestyle"], + ) + plt.plot( + list(rrule(MONTHLY, dtstart=start.replace(day=1), until=end.replace(day=1))), + [groups["income-fixed"] for _, groups in monthly_transactions], + color=groups["income-fixed"]["color"], + linestyle=groups["income-fixed"]["linestyle"], ) plt.stackplot( list(rrule(MONTHLY, dtstart=start.replace(day=1), until=end.replace(day=1))), [ [-groups[group] for _, groups in monthly_transactions] for group in pfbudget.categories.groups - if group != "income" and group != "investment" + if group != "income-fixed" + and group != "income-extra" + and group != "investment" ], labels=[ group for group in pfbudget.categories.groups - if group != "income" and group != "investment" + if group != "income-fixed" + and group != "income-extra" + and group != "investment" ], colors=[ groups.get(group, {"color": "gray"})["color"] for group in pfbudget.categories.groups - if group != "income" and group != "investment" + if group != "income-fixed" + and group != "income-extra" + and group != "investment" ], ) plt.legend(loc="upper left") @@ -111,25 +131,42 @@ def discrete( sum( value for category, value in categories.items() - if category in pfbudget.categories.groups["income"] + if category in pfbudget.categories.groups["income-fixed"] + or category in pfbudget.categories.groups["income-extra"] ) for _, categories in monthly_transactions ], color=groups["income"]["color"], + linestyle=groups["income"]["linestyle"], + ) + plt.plot( + list(rrule(MONTHLY, dtstart=start.replace(day=1), until=end.replace(day=1))), + [ + sum( + value + for category, value in categories.items() + if category in pfbudget.categories.groups["income-fixed"] + ) + for _, categories in monthly_transactions + ], + color=groups["income-fixed"]["color"], + linestyle=groups["income-fixed"]["linestyle"], ) plt.stackplot( list(rrule(MONTHLY, dtstart=start.replace(day=1), until=end.replace(day=1))), [ [-categories[category] for _, categories in monthly_transactions] for category in pfbudget.categories.categories - if category not in pfbudget.categories.groups["income"] + if category not in pfbudget.categories.groups["income-fixed"] + and category not in pfbudget.categories.groups["income-extra"] and category not in pfbudget.categories.groups["investment"] and category != "Null" ], labels=[ category for category in pfbudget.categories.categories - if category not in pfbudget.categories.groups["income"] + if category not in pfbudget.categories.groups["income-fixed"] + and category not in pfbudget.categories.groups["income-extra"] and category not in pfbudget.categories.groups["investment"] and category != "Null" ], diff --git a/pfbudget/report.py b/pfbudget/report.py index 594cda6..fbecd35 100644 --- a/pfbudget/report.py +++ b/pfbudget/report.py @@ -3,7 +3,7 @@ from dateutil.rrule import rrule, YEARLY from typing import TYPE_CHECKING import datetime as dt -import pfbudget.categories as categories +import pfbudget.categories if TYPE_CHECKING: from pfbudget.database import DBManager @@ -23,7 +23,7 @@ def net(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max) if transaction.category in categories and year <= transaction.date <= year.replace(month=12, day=31) ) - for group, categories in categories.groups.items() + for group, categories in pfbudget.categories.groups.items() }, ) for year in [ @@ -35,8 +35,29 @@ def net(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max) ) for year, groups in yearly_transactions: - print(year.year) - print(f"Income: {groups.pop('income'):.2f}€") + print(f"\n{year.year}\n") + + income = groups.pop("income-fixed") + groups.pop("income-extra") + print(f"Income: {income:.2f} €\n") + + investments = -groups.pop("investment") + + expenses = 0 for group, value in groups.items(): - print(f"{group.capitalize()} expenses: {value:.2f}€") - print() + expenses -= value + if income != 0: + print( + f"{group.capitalize()} expenses: {-value:.2f} € ({-value/income*100:.1f}%)" + ) + else: + print(f"{group.capitalize()} expenses: {-value:.2f} €") + + print(f"\nNet total: {income-expenses:.2f} €") + if income != 0: + print( + f"Total expenses are {expenses:.2f} ({expenses/income*100:.1f}% of income)\n" + ) + else: + print(f"Total expenses are {expenses:.2f}. No income this year!\n") + + print(f"Invested: {investments:.2f}€\n")