def expenses_period(expenses=None, start=PeriodStart.this_month_year_ago, end=PeriodEnd.this_month, period_size=PeriodSize.month): """ Calculate the amount of money that when into an expense account over the given period. :param expenses: account walker parameters for accounts to calculate :param start: the start of the time frame for the report :param end: the end of the time frame for the report :param period_size: the size of the buckets for the report :return: dictionary containing: expenses: sorted dictionary containing keys for date and value. """ expenses = expenses or [] accounts = parse_walker_parameters(expenses) start_period = PeriodStart(start) end_period = PeriodEnd(end) period_size = PeriodSize(period_size) bucket = PeriodCollate(start_period.date, end_period.date, decimal_generator, split_summation, frequency=period_size.frequency, interval=period_size.interval) for account in account_walker(**accounts): for split in get_splits(account, start_period.date, end_period.date): bucket.store_value(split) sorted_results = [] for key, value in bucket.container.iteritems(): sorted_results.append((time.mktime(key.timetuple()), value)) data_set = { 'expenses': sorted(sorted_results, key=itemgetter(0)) } return data_set
def account_usage_categories(start=PeriodStart.this_month, end=PeriodEnd.this_month, accounts=None): """ Walk through the accounts defined, and find calculate how money is spent based on the categories that the transaction split belongs to. :param start: date value to be used to filter down the data. :param end: date value to be used to filter down the data. :param accounts: walker parameters for the accounts that should be used to find how spending occurs. :return: dictionary containing - categories - tuple containing category name, and the amount that is spent in that category. """ start_of_trend = PeriodStart(start) end_of_trend = PeriodEnd(end) accounts = parse_walker_parameters(accounts) data_values = CategoryCollate(decimal_generator, split_summation) for account in account_walker(**accounts): for split in get_splits(account, start_of_trend.date, end_of_trend.date, credit=False): transaction = split.transaction for transaction_split in [s for s in transaction.splits if s.account.fullname != account.fullname]: data_values.store_value(transaction_split) return { 'categories': sorted([[k, v] for k, v in data_values.container.iteritems()], key=itemgetter(0)) }
def cash_flow(accounts=None, start=PeriodStart.this_month_year_ago, end=PeriodEnd.this_month, period_size=PeriodSize.month): """ Create a report showing the amount of money that enters and exits an account over a period of time. Each row in the report is based on period size values. :param accounts: account walker properties to define the accounts that are reported on :param start: the PeriodStart definition that tells when the report should start collecting data from :param end: the PeriodEnd definition that tells when the report should stop collecting data :param period_size: the size of the buckets to store the data into. :return: dictionary containing: credits - sorted list of dictionaries containing date and value keys debit - sorted list of dictionaries containing date and value keys net - sorted list of dictionaries containing date and value keys """ accounts = accounts or [] accounts = parse_walker_parameters(accounts) period_start = PeriodStart(start) period_end = PeriodEnd(end) period_size = PeriodSize(period_size) bucket = PeriodCollate(period_start.date, period_end.date, debit_credit_generator, store_credit_debit, frequency=period_size.frequency, interval=period_size.interval) for account in account_walker(**accounts): for split in get_splits(account, period_start.date, period_end.date): bucket.store_value(split) credit_values = [] debit_values = [] difference_value = [] for key, value in bucket.container.iteritems(): store_key = time.mktime(key.timetuple()) credit_values.append((store_key, value['credit'])) debit_values.append((store_key, value['debit'])) difference_value.append((store_key, value['credit'] + value['debit'])) return {'credits': sorted(credit_values, key=itemgetter(0)), 'debits': sorted(debit_values, key=itemgetter(0)), 'net': sorted(difference_value, key=itemgetter(0))}
def expenses_box(expenses=None, start=PeriodStart.this_month_year_ago, end=PeriodEnd.this_month, period_size=PeriodSize.month): """ Calculate the amount of money that when into an expense account over the given period. :param expenses: account walker parameters for accounts to calculate :param start: the start of the time frame for the report :param end: the end of the time frame for the report :param period_size: the size of the buckets for the report :return: dictionary containing: expenses: dictionary containing the following keys: low - lowest amount spent high - highest amount spent q1 - first quartile value q2 - second quartile value q3 - third quartile value """ expenses = expenses or [] accounts = parse_walker_parameters(expenses) start_period = PeriodStart(start) end_period = PeriodEnd(end) period_size = PeriodSize(period_size) bucket = PeriodCollate(start_period.date, end_period.date, decimal_generator, split_summation, frequency=period_size.frequency, interval=period_size.interval) for account in account_walker(**accounts): for split in get_splits(account, start_period.date, end_period.date): bucket.store_value(split) results = [] for key, value in bucket.container.iteritems(): results.append(float(value)) results = sorted(results) return {'low': results[0], 'high': results[-1], 'q1': get_median(get_lower_half(results)), 'q2': get_median(results), 'q3': get_median(get_upper_half(results))}
def expenses_categories(expenses=None, start=PeriodStart.this_month, end=PeriodEnd.this_month): """ Walk through the accounts defined in expenses base and collate the spending in the period into the categories defined in the configuration object. :param expenses: account walker definition of the accounts to grab expenses for. :param start: when the report should start collecting data from :param end: when the report should stop collecting data :return: dictionary containing: categories - list of tuples (category name, value) containing the results sorted by category name """ expenses = expenses or [] accounts = parse_walker_parameters(expenses) start_period = PeriodStart(start) end_period = PeriodEnd(end) bucket = CategoryCollate(decimal_generator, split_summation) for account in account_walker(**accounts): for split in get_splits(account, start_period.date, end_period.date): bucket.store_value(split) return {'categories': sorted([[key, value] for key, value in bucket.container.iteritems()], key=itemgetter(0))}
def income_vs_expense(income_accounts=None, expense_accounts=None, start=PeriodStart.this_month_year_ago, end=PeriodEnd.this_month, period_size=PeriodSize.month): """ Report that collects the expenses from the expense accounts defined, the income from the income accounts defined and collates the data into buckets based on the arguments for start, end, and period_size. :param income_accounts: account walker parameters for the income accounts to walk through :param expense_accounts: account walker parameters for the expense accounts to walk through :param start: how far back to collect data for :param end: how far forward to collect data for :param period_size: the size of the buckets to collect data for. :return: dictionary containing: - expenses: list of dictionaries containing date and value key value pairs for the expenses defined - income: list of dictionaries containing date and value paris for the income defined - net: list of dictionaries containing date and value pairs for the net values defined """ income_accounts = income_accounts or [] expense_accounts = expense_accounts or [] income_accounts = parse_walker_parameters(income_accounts) expense_accounts = parse_walker_parameters(expense_accounts) period_start = PeriodStart(start) period_end = PeriodEnd(end) period_size = PeriodSize(period_size) bucket = PeriodCollate(period_start.date, period_end.date, debit_credit_generator, store_credit_debit, frequency=period_size.frequency, interval=period_size.interval) for account in account_walker(**income_accounts): for split in get_splits(account, period_start.date, period_end.date): bucket.store_value(split) for account in account_walker(**expense_accounts): for split in get_splits(account, period_start.date, period_end.date): bucket.store_value(split) credit_values = [] debit_values = [] difference_value = [] for key, value in bucket.container.iteritems(): time_value = time.mktime(key.timetuple()) # Have to switch the signs so that the graph will make sense. In GNUCash the income accounts are debited # when paid, and the expense accounts are 'credited' when purchased. credit_values.append((time_value, -value['credit'])) debit_values.append((time_value, -value['debit'])) difference_value.append( (time_value, -(value['debit'] + value['credit']))) return { 'expenses': sorted(credit_values, key=itemgetter(0)), 'income': sorted(debit_values, key=itemgetter(0)), 'net': sorted(difference_value, key=itemgetter(0)) }
def investment_trend(investment_accounts=None, start=PeriodStart.this_month_year_ago, end=PeriodEnd.this_month, period_size=PeriodSize.month): """ Report showing how the investment has changed over a period of time. This report provides data based on the period size provided in the arguments. :param investment_accounts: account walker parameters for all of the accounts that this report should provide data on :param start: the start time frame of the report :param end: the end time frame of the report :param period_size: the step size between start and end :return: dictionary containing the following start_value - no clue income - time series data containing the income generated by account (dividends) money_in - time series data containing the purchases into the accounts expense - time series data containing the expenses of the accounts value - time series data containing the current value of the accounts basis - time series data containing the basis value of the accounts (not sure if this is correct) """ # TODO: Figure out what this report is really doing with start_value and basis values. Verify they are calculated # correctly. investment_accounts = investment_accounts or [] investment_accounts = parse_walker_parameters(investment_accounts) period_start = PeriodStart(start) period_end = PeriodEnd(end) period_size = PeriodSize(period_size) investment_value = dict() buckets = PeriodCollate(period_start.date, period_end.date, investment_bucket_generator, store_investment, frequency=period_size.frequency, interval=period_size.interval) start_value = Decimal('0.0') start_value_date = period_start.date - relativedelta(days=1) currency = get_currency() for account in account_walker(**investment_accounts): for split in get_splits(account, period_start.date, period_end.date): buckets.store_value(split) start_value += get_balance_on_date(account, start_value_date, currency) for key in buckets.container.keys(): date_value = key + relativedelta(months=1) - relativedelta(days=1) investment_value[key] = investment_value.get( key, Decimal('0.0')) + get_balance_on_date( account, date_value, currency) results = { 'start_value': start_value, 'income': time_series_dict_to_list(buckets.container, value=lambda x: x['income']), 'money_in': time_series_dict_to_list(buckets.container, value=lambda x: x['money_in']), 'expense': time_series_dict_to_list(buckets.container, value=lambda x: x['expense']), 'value': time_series_dict_to_list(investment_value), 'basis': sorted([[time.mktime(key.timetuple()), Decimal('0.0')] for key in buckets.container.keys()], key=itemgetter(0)) } monthly_start = start_value for index, record in enumerate(results['basis']): record[1] += (monthly_start + results['income'][index][1] + results['money_in'][index][1] + results['expense'][index][1]) monthly_start = record[1] return results
def budget_level(accounts=None, budget_value='0.0', year_to_date=True): """ Calculates how much is spent in the accounts provided. :param accounts: account walker parameters to calculate spending in. :param budget_value: budget value for the month :param year_to_date: should a year to date be calculated as well :return: dictionary containing: balance - the current balance for the month month - the month number daysInMonth - the number of days in the month today - current day in the month budgetValue - the maximum allowed to be spent in this month Optional values returned if year_to_date is true: yearlyBalance - the amount spent in these accounts this year daysInYear - the number of days in the current year currentYearDay - the current year day for this year """ accounts = accounts or [] budget_value = Decimal(budget_value or '0.0') accounts = parse_walker_parameters(accounts) budget_value = Decimal(budget_value) balance = Decimal('0.0') for account in account_walker(**accounts): split_list = get_splits(account, PeriodStart.this_month.date, PeriodEnd.today.date, debit=False) for split in split_list: balance += split.value data_payload = { 'balance': balance, 'month': PeriodEnd.today.date.month, 'daysInMonth': monthrange(PeriodEnd.today.date.year, PeriodEnd.today.date.month)[1], 'today': PeriodEnd.today.date.day, 'budgetValue': budget_value } if year_to_date: yearly_balance = Decimal('0.0') for account in account_walker(**accounts): split_list = get_splits(account, PeriodStart.this_year.date, PeriodEnd.today.date, debit=False) for split in split_list: yearly_balance += split.value today = PeriodStart.today.date data_payload.update({ 'yearlyBalance': yearly_balance, 'daysInYear': date(today.year, 12, 31).timetuple().tm_yday, 'currentYearDay': today.timetuple().tm_yday }) return data_payload
def income_tax(income=None, tax=None, start=PeriodStart.this_year, end=PeriodEnd.this_year, tax_name='federal', tax_status='single', deductions=None, deduction=None): """ Walk through all of the income accounts provided to determine tax owed based on tax tables. Then use deductions, deduction_accounts to reduce income, and then calculate a simple tax owed based on the tax_name and tax_status information. Will only use the account data between the start and end values. :param income: account walker parameters for the income accounts :param tax: account walker parameters for the tax paid accounts :param start: Period start date :param end: Period end date, :param tax_name: name of tax table to use :param tax_status: name of tax sub-table to use :param deductions: list of decimal definitions that should be deducted from the taxable income :param deduction: account walker parameters that should be used to reduce the taxable income as well :return: dictionary containing income - total taxable income made during the period tax_value - total calculated tax based on income value taxes_paid - total taxes paid into the tax accounts """ income_accounts = income or [] tax_accounts = tax or [] deductions = deductions or [] deduction_accounts = deduction or [] income_accounts = parse_walker_parameters(income_accounts) tax_accounts = parse_walker_parameters(tax_accounts) period_start = PeriodStart(start) period_end = PeriodEnd(end) deduction_accounts = parse_walker_parameters(deduction_accounts) total_income = Decimal(0.0) total_taxes = Decimal(0.0) pre_tax_deductions = Decimal(0.0) # Find all of the deduction accounts, and store them in a list so that they can be walked when handling the # income from the income accounts deduction_account_names = set() for account in account_walker(**deduction_accounts): deduction_account_names.add(clean_account_name(account.fullname)) # Calculate all of the income that has been received, and calculate all of the contributions to the deduction # accounts that will reduce tax burden. for account in account_walker(**income_accounts): for split in get_splits(account, period_start.date, period_end.date): value = split.value * -1 # negate the value because income is leaving these accounts total_income += value # Go through the split's parent and find all of the values that are in the deduction accounts as well transaction = split.transaction for t_split in transaction.splits: if clean_account_name( t_split.account.fullname) in deduction_account_names: pre_tax_deductions += t_split.value # Calculate all of the taxes that have been currently paid for account in account_walker(**tax_accounts): for split in get_splits(account, period_start.date, period_end.date): value = split.value total_taxes += value # Remove all of the deductions from the total income value for deduction in deductions: pre_tax_deductions += Decimal(deduction) # Remove all of the contributions from the income accounts that went into pre-tax accounts and any standard # deductions total_income -= pre_tax_deductions tax_value = calculate_tax(tax_name, tax_status, total_income) return { 'income': total_income, 'tax_value': tax_value, 'taxes_paid': total_taxes }