-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
293 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from datetime import datetime | ||
from dateutil.relativedelta import relativedelta | ||
|
||
DATE = 'Date' | ||
ASSETS = 'Assets' | ||
LIABILITIES = 'Liabilities' | ||
CAPITAL = 'Capital' | ||
DUES = 'Dues' | ||
DONATIONS = 'Donations' | ||
FOOD_DONATIONS = 'Food donations' | ||
MEMBERS = 'Members' | ||
DONATING_MEMBERS = 'Donating members' | ||
EXPENSES = 'Expenses' | ||
FOOD_EXPENSES = 'Food expenses' | ||
PROJECTED_CAPITAL = 'Projected capital' | ||
PROJECTED_DUES = 'Projected dues' | ||
PROJECTED_DONATIONS = 'Projected donations' | ||
PROJECTED_MEMBERS = 'Projected members' | ||
PROJECTED_DONATING_MEMBERS = 'Projected donating members' | ||
PROJECTED_FOOD_DONATIONS = 'Projected food donations' | ||
PROJECTED_FOOD_EXPENSES = 'Projected food expenses' | ||
CAPITAL_TARGET = 'Target balance (3 month buffer)' | ||
|
||
MONTH_START_DAY = 4 | ||
|
||
def figure_out_today_and_start_and_end_dates(): | ||
today = datetime.now().replace(second=0, microsecond=0) | ||
if today.day < MONTH_START_DAY: | ||
today -= relativedelta(months=+1) | ||
delta = relativedelta(months=+6) | ||
start = today - delta | ||
end = today + delta | ||
|
||
return [start, today, end] | ||
|
||
def report_days(start_date, end_date): | ||
delta = relativedelta(months=+1) | ||
d = start_date.replace(day=MONTH_START_DAY) | ||
while d < end_date.replace(day=MONTH_START_DAY): | ||
d += delta | ||
yield d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import csv | ||
|
||
import financial_common | ||
|
||
def write_to_csv_the(financial_records): | ||
fieldnames = [ | ||
DATE, | ||
ASSETS, | ||
LIABILITIES, | ||
CAPITAL, | ||
PROJECTED_CAPITAL, | ||
DUES, | ||
PROJECTED_DUES, | ||
DONATIONS, | ||
PROJECTED_DONATIONS, | ||
MEMBERS, | ||
PROJECTED_MEMBERS, | ||
DONATING_MEMBERS, | ||
PROJECTED_DONATING_MEMBERS, | ||
EXPENSES, | ||
FOOD_DONATIONS, | ||
PROJECTED_FOOD_DONATIONS, | ||
FOOD_EXPENSES, | ||
PROJECTED_FOOD_EXPENSES, | ||
CAPITAL_TARGET, | ||
] | ||
|
||
with open('foobar.csv', 'wb') as csvfile: | ||
writer = csv.DictWriter(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL, fieldnames=fieldnames) | ||
|
||
writer.writeheader() | ||
for finances_for_a_month in financial_records: | ||
writer.writerow(finances_for_a_month) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from __future__ import print_function | ||
from datetime import datetime | ||
|
||
import financial_common | ||
|
||
def get_future_projections_based_on( historical_records ): | ||
future_projections = [] | ||
|
||
monthly_amount_projections = get_monthly_amount_projections_based_on( historical_records ) | ||
|
||
start, today, end = figure_out_today_and_start_and_end_dates() | ||
for month in report_days(today, end): | ||
starting_capital = future_projections[-1][PROJECTED_CAPITAL] if len(future_projections) else historical_records[-1][CAPITAL] | ||
|
||
future_projections.append( project_finances_for( month, starting_capital, finances_for_current_month, monthly_amount_projections ) ) | ||
|
||
return future_projections | ||
|
||
def project_finances_for( month, starting_capital, finances_for_current_month, monthly_amount_projections ): | ||
print(month) | ||
|
||
return { | ||
# same for each projected month | ||
DATE: month, | ||
PROJECTED_DUES: finances_for_current_month[DUES], | ||
PROJECTED_DONATIONS: finances_for_current_month[DONATIONS], | ||
PROJECTED_MEMBERS: finances_for_current_month[MEMBERS], | ||
PROJECTED_DONATING_MEMBERS: finances_for_current_month[DONATING_MEMBERS], | ||
PROJECTED_FOOD_DONATIONS: monthly_amount_projections[ 'food income' ], | ||
PROJECTED_FOOD_EXPENSES: monthly_amount_projections[ 'food expenses' ], | ||
|
||
# differs in each month | ||
PROJECTED_CAPITAL: starting_capital + monthly_amount_projections['income'] + monthly_amount_projections['expenses'], | ||
|
||
# two different values - before & after rent increase | ||
CAPITAL_TARGET: (monthly_amount_projections['expenses'] - rent_increase_for( month )) * -3, | ||
} | ||
|
||
def get_monthly_amount_projections_based_on( historical_records ): | ||
current_month_amounts = historical_records[-1] | ||
|
||
monthly_amount_projections['income'] = current_month_amounts[DUES] + current_month_amounts[DONATIONS] | ||
monthly_amount_projections['expenses'] = get_average( EXPENSES, historical_records ) | ||
monthly_amount_projections['food income'] = get_average( FOOD_DONATIONS, historical_records ) | ||
monthly_amount_projections['food expenses'] = get_average( FOOD_EXPENSES, historical_records ) | ||
|
||
print( "Projected income: ", monthly_amount_projections['income'] ) | ||
print( "Projected expenses: ", monthly_amount_projections['expenses'] ) | ||
|
||
return monthly_amount_projections | ||
|
||
def rent_increase_for( month ): | ||
february_2015 = datetime(2015, 02, 01): | ||
|
||
if month >= february_2015 | ||
rent_increase = 100 | ||
else | ||
rent_increase = 0 | ||
|
||
return rent_increase | ||
|
||
def get_average( datum_key, financial_records ): | ||
total = sum( record[datum_key] for record in financial_records ) | ||
|
||
return total / len( financial_records ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import calendar | ||
import gnucashxml | ||
import sys | ||
from datetime import datetime | ||
from __future__ import print_function | ||
|
||
import financial_common | ||
|
||
def get_historical_records(): | ||
historical_records = [] | ||
|
||
book = get_requested_financial_book() | ||
|
||
start, today, end = figure_out_today_and_start_and_end_dates() | ||
for month in report_days(start, today): | ||
monthly_finances = book.finances_for(month, book) | ||
display(monthly_finances) | ||
historical_records.append(monthly_finances) | ||
|
||
def get_requested_financial_book(): | ||
provided_by_user = sys.argv[1] | ||
return Book( filename = gnucashxml.from_filename(provided_by_user) ) | ||
|
||
class Book: | ||
def __init__( self, gnu_cash_book ): | ||
self.book = gnu_cash_book | ||
|
||
def finances_for( self, month, book ): | ||
assets = self.get_assets_on( date = month ) | ||
liabilities = self.get_liability_on( date = month ) | ||
expenses = self.get_expenses_for( month ) | ||
|
||
return { | ||
DATE: month, | ||
ASSETS: assets, | ||
LIABILITIES: liabilities, | ||
CAPITAL: assets + liabilities, | ||
DUES: self.get_dues_for( month ), | ||
DONATIONS: self.get_donations_for( month ), | ||
FOOD_DONATIONS: self.get_food_donations_for( month ), | ||
MEMBERS: self.get_num_paying_members_in( month ), | ||
DONATING_MEMBERS: self.get_num_donating_members_in( month ), | ||
EXPENSES: expenses, | ||
FOOD_EXPENSES: self.get_food_expenses_for( month ), | ||
CAPITAL_TARGET: expenses * -3, | ||
} | ||
|
||
def get_assets_on( self, date ): | ||
assets = self.book.find_account("Current Assets") | ||
|
||
asset_total = 0 | ||
for account in assets.children: | ||
if account.name != "Prepaid Rent": | ||
asset_total += sum(split.value for split in account.splits if split.transaction.date.replace(tzinfo=None) <= date) | ||
|
||
return asset_total | ||
|
||
def get_liability_on( self, date ): | ||
liability = self.get_total( "Active Members", date ) + self.get_total( "Former Members", date ) | ||
|
||
return liability | ||
|
||
def get_total( self, account_name, date ): | ||
account = self.book.find_account( account_name ) | ||
total = sum( split.value for split in account.get_all_splits() if split.transaction.date.replace(tzinfo=None) <= date) | ||
|
||
return total | ||
|
||
def get_expenses_for( self, month_end ): | ||
end_date = month_end | ||
start_date = subtract_month(month_end) | ||
|
||
expense_accounts = self.book.find_account("Expenses") | ||
expenses = 0 | ||
for account in expense_accounts.children: | ||
if account.name != "Anti-social 10-04" and account.name != "Groceries": | ||
for split in account.splits: | ||
if start_date < split.transaction.date.replace(tzinfo=None) <= end_date: | ||
expenses += split.value | ||
|
||
for subaccount in account.children: | ||
for split in subaccount.splits: | ||
if start_date < split.transaction.date.replace(tzinfo=None) <= end_date: | ||
expenses += split.value | ||
|
||
|
||
return expenses * -1 | ||
|
||
def get_dues_for( self, month_end ): | ||
return self.get_monthly_amount_of( "Member Dues", month_end ) | ||
|
||
def get_food_expenses_for_month( self, month_end ): | ||
return self.get_monthly_amount_of( "Groceries", month_end ) | ||
|
||
def get_food_donations_for_month( self, month_end ): | ||
return self.get_monthly_amount_of( "Food and Drink Donations", month_end ) | ||
|
||
def get_donations_for( self, month_end ): | ||
return self.get_monthly_amount_of( "Regular donations", month_end ) | ||
|
||
def get_donating_members( self, month_end ): | ||
return self.get_monthly_number_of( "Regular donations", month_end ) | ||
|
||
def get_paying_members( self, month_end ): | ||
return self.get_monthly_number_of( "Member Dues", book, month_end ) | ||
|
||
def get_monthly_number_of( self, account_name, month_end ): | ||
return sum( len( entry.transaction.splits ) - 1 for entry in self.get_entries_for( account_name, month_end ) ) | ||
|
||
def get_monthly_amount_of( self, account_name, month_end ): | ||
return sum( entry.value for entry in self.get_entries_for( account_name, month_end ) ) * -1 | ||
|
||
def get_entries_for( self, account_name, month_end ): | ||
end_date = month_end | ||
start_date = subtract_month(month_end) | ||
|
||
account = self.book.find_account( account_name ) | ||
|
||
entries = [] | ||
|
||
for split in account.get_all_splits(): | ||
if start_date < split.transaction.date.replace(tzinfo=None) <= end_date: | ||
entries.add( split ) | ||
|
||
return entries | ||
|
||
def subtract_month(date): | ||
month = date.month - 2 | ||
month = month % 12 + 1 | ||
year = date.year - 1/12 | ||
day = min(date.day, calendar.monthrange(year, month)[1]) | ||
return datetime(year, month, day) | ||
|
||
def display( monthly_finances ): | ||
print(monthly_finances[ DATE ]) | ||
print("Total assets: ", monthly_finances[ ASSETS ]) | ||
print("Total liability: ", monthly_finances[ LIABILITIES ]) | ||
print("Available capital: ", monthly_finances[ CAPITAL ]) | ||
print("Dues collected last month: ", monthly_finances[ DUES ]) | ||
print("Dues paying members: ", monthly_finances[ MEMBERS ]) | ||
print("Regular donations collected last month: ", monthly_finances[ DONATIONS ]) | ||
print("Regularly donating members: ", monthly_finances[ DONATING_MEMBERS ]) | ||
print("Food donations: ", monthly_finances[ FOOD_DONATIONS ]) | ||
print("Total expected income: ", (monthly_finances[ DUES ] + monthly_finances[ DONATIONS ] + monthly_finances[ FOOD_DONATIONS ])) | ||
print("Expenses: ", monthly_finances[ EXPENSES ]) | ||
print("Food expenses: ", monthly_finances[ FOOD_EXPENSES ]) | ||
print() |
Oops, something went wrong.