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:
parent
a1f5699b12
commit
59406c35c1
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"
|
||||
],
|
||||
|
||||
@ -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")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user