Refactored report module

Bring report up-to-date with sqlite3 database and yaml configuration
files.
Updates report command in runnable.py. Moves date arg parsing to utils
module
Report and graph now share an ArgumentParser with shared date options.
This commit is contained in:
Luís Murta 2021-07-05 22:53:03 +01:00
parent f811b5c711
commit 37c97453a9
Signed by: satprog
GPG Key ID: DDF2EFC6179009DC
3 changed files with 83 additions and 109 deletions

View File

@ -1,78 +1,42 @@
from .categories import ( from __future__ import annotations
get_income_categories, from dateutil.rrule import rrule, YEARLY
get_fixed_expenses, from typing import TYPE_CHECKING
get_required_expenses, import datetime as dt
get_health_expenses,
get_discretionary_expenses, import pfbudget.categories as categories
)
from .transactions import load_transactions, by_year_and_category if TYPE_CHECKING:
from pfbudget.database import DBManager
def net(state, start=None, end=None): def net(db: DBManager, start: dt.date = dt.date.min, end: dt.date = dt.date.max):
transactions = load_transactions(state.data_dir) transactions = db.get_daterange(start, end)
if not start: start, end = transactions[0].date, transactions[-1].date
start = transactions[0].date
if not end:
end = transactions[-1].date
income, fixed, required, health, discretionary = [], [], [], [], [] yearly_transactions = tuple(
yearly_transactions_by_categories = by_year_and_category(transactions, start, end) (
for _, transactions_by_category in yearly_transactions_by_categories.items(): year,
income.append( {
sum( group: sum(
float(t.value) transaction.value
for category, transactions in transactions_by_category.items() for transaction in transactions
if transactions and category in get_income_categories() if transaction.category in categories
for t in transactions and year <= transaction.date <= year.replace(month=12, day=31)
) )
for group, categories in categories.groups.items()
},
) )
fixed.append( for year in [
sum( year.date()
-float(t.value) for year in rrule(
for category, transactions in transactions_by_category.items() YEARLY, dtstart=start.replace(day=1), until=end.replace(day=1)
if transactions and category in get_fixed_expenses()
for t in transactions
) )
) ]
required.append( )
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_required_expenses()
for t in transactions
)
)
health.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_health_expenses()
for t in transactions
)
)
discretionary.append(
sum(
-float(t.value)
for category, transactions in transactions_by_category.items()
if transactions and category in get_discretionary_expenses()
for t in transactions
)
)
for i, year in enumerate(yearly_transactions_by_categories.keys()): for year, groups in yearly_transactions:
print(year) print(year.year)
print( print(f"Income: {groups.pop('income'):.2f}")
"Income: {:.2f}, Expenses: {:.2f}, Net: {:.2f}\n" for group, value in groups.items():
"Fixed Expenses: {:.2f}\n" print(f"{group.capitalize()} expenses: {value:.2f}")
"Required Expenses: {:.2f}\n" print()
"Health Expenses: {:.2f}\n"
"Discretionary Expenses: {:.2f}\n".format(
income[i],
fixed[i] + required[i] + health[i] + discretionary[i],
income[i] - (fixed[i] + required[i] + health[i] + discretionary[i]),
fixed[i],
required[i],
health[i],
discretionary[i],
)
)

View File

@ -1,12 +1,12 @@
from pathlib import Path from pathlib import Path
import argparse import argparse
import datetime as dt
from .categories import categorize_data from .categories import categorize_data
from .database import DBManager from .database import DBManager
from .graph import discrete, monthly
from .parsers import parse_data from .parsers import parse_data
from . import report import pfbudget.graph
import pfbudget.report
import pfbudget.utils
DEFAULT_DB = "data.db" DEFAULT_DB = "data.db"
@ -67,10 +67,18 @@ def argparser() -> argparse.ArgumentParser:
func=lambda args: categorize_data(DBManager(args.database)) func=lambda args: categorize_data(DBManager(args.database))
) )
period = argparse.ArgumentParser(add_help=False).add_mutually_exclusive_group()
period.add_argument(
"--interval", type=str, nargs=2, help="graph interval", metavar=("START", "END")
)
period.add_argument("--start", type=str, nargs=1, help="graph start date")
period.add_argument("--end", type=str, nargs=1, help="graph end date")
period.add_argument("--year", type=str, nargs=1, help="graph year")
""" """
Graph Graph
""" """
p_graph = subparsers.add_parser("graph", parents=[help]) p_graph = subparsers.add_parser("graph", parents=[help, period])
p_graph.add_argument( p_graph.add_argument(
"option", "option",
type=str, type=str,
@ -79,20 +87,17 @@ def argparser() -> argparse.ArgumentParser:
default="monthly", default="monthly",
help="graph option help", help="graph option help",
) )
p_graph_interval = p_graph.add_mutually_exclusive_group()
p_graph_interval.add_argument(
"--interval", type=str, nargs=2, help="graph interval", metavar=("START", "END")
)
p_graph_interval.add_argument("--start", type=str, nargs=1, help="graph start date")
p_graph_interval.add_argument("--end", type=str, nargs=1, help="graph end date")
p_graph_interval.add_argument("--year", type=str, nargs=1, help="graph year")
p_graph.set_defaults(func=graph) p_graph.set_defaults(func=graph)
p_report = subparsers.add_parser("report", help="report help") """
Report
"""
p_report = subparsers.add_parser("report", parents=[help, period])
p_report.set_defaults(func=report)
p_status = subparsers.add_parser("status", help="status help") p_status = subparsers.add_parser("status", help="status help")
p_status.set_defaults(func=status) p_status.set_defaults(func=status)
p_report.set_defaults(func=f_report)
return parser return parser
@ -133,39 +138,22 @@ def graph(args):
state (PFState): Internal state of the program state (PFState): Internal state of the program
args (dict): argparse variables args (dict): argparse variables
""" """
start, end = dt.date.min, dt.date.max start, end = pfbudget.utils.parse_args_period(args)
if args.start or args.interval:
start = dt.datetime.strptime(args.start[0], "%Y/%m/%d").date()
if args.end or args.interval:
end = dt.datetime.strptime(args.end[0], "%Y/%m/%d").date()
if args.interval:
start = dt.datetime.strptime(args.interval[0], "%Y/%m/%d").date()
end = dt.datetime.strptime(args.interval[1], "%Y/%m/%d").date()
if args.year:
start = dt.datetime.strptime(args.year[0], "%Y").date()
end = dt.datetime.strptime(
str(int(args.year[0]) + 1), "%Y"
).date() - dt.timedelta(days=1)
if args.option == "monthly": if args.option == "monthly":
monthly(DBManager(args.database), start, end) pfbudget.graph.monthly(DBManager(args.database), start, end)
elif args.option == "discrete": elif args.option == "discrete":
discrete(DBManager(args.database), start, end) pfbudget.graph.discrete(DBManager(args.database), start, end)
def f_report(state, args): def report(args):
"""Report """Prints a detailed report of the transactions over a period of time.
Prints a detailed report of the transactions over a period of time.
Args: Args:
state (PFState): Internal state of the program state (PFState): Internal state of the program
args (dict): argparse variables args (dict): argparse variables
""" """
report.net(state) start, end = pfbudget.utils.parse_args_period(args)
pfbudget.report.net(DBManager(args.database), start, end)
def run(): def run():

View File

@ -1,3 +1,4 @@
from datetime import date, datetime, timedelta
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
@ -52,3 +53,24 @@ def find_credit_institution(fn, banks, creditcards):
raise CreditCardNotAvailableError raise CreditCardNotAvailableError
return bank, cc return bank, cc
def parse_args_period(args):
start, end = date.min, date.max
if args.start or args.interval:
start = datetime.strptime(args.start[0], "%Y/%m/%d").date()
if args.end or args.interval:
end = datetime.strptime(args.end[0], "%Y/%m/%d").date()
if args.interval:
start = datetime.strptime(args.interval[0], "%Y/%m/%d").date()
end = datetime.strptime(args.interval[1], "%Y/%m/%d").date()
if args.year:
start = datetime.strptime(args.year[0], "%Y").date()
end = datetime.strptime(str(int(args.year[0]) + 1), "%Y").date() - timedelta(
days=1
)
return start, end