Splits income into fixed and extra

This will provide differentiation between income that's regular and
stable (long-term contract) and that which is not, such as gigs,
presents, etc.
It presents the information separated in both graphs and reports.
This commit is contained in:
Luís Murta 2021-12-01 18:36:45 +00:00
parent a1f5699b12
commit 59406c35c1
Signed by: satprog
GPG Key ID: DDF2EFC6179009DC
4 changed files with 90 additions and 20 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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"
],

View File

@ -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")