Browse Source

First actual commit, working budget program

Lily Carpenter 11 years ago
parent
commit
b93c853fdc
4 changed files with 216 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 3 0
      README.md
  3. 171 0
      budget.py
  4. 39 0
      settings.py.example

+ 3 - 0
.gitignore

@@ -1,5 +1,8 @@
1 1
 *.py[cod]
2 2
 
3
+# Custom
4
+settings.py
5
+
3 6
 # C extensions
4 7
 *.so
5 8
 

+ 3 - 0
README.md

@@ -2,3 +2,6 @@ simple_budget_tracker
2 2
 =====================
3 3
 
4 4
 Tracks budget based on paydate and when bills are due, lets you know how much you can spend
5
+
6
+Built with python3
7
+Needs the prettytable python module

+ 171 - 0
budget.py

@@ -0,0 +1,171 @@
1
+#!/usr/bin/python3
2
+import datetime
3
+from decimal import *
4
+from settings import DATA
5
+from prettytable import PrettyTable
6
+
7
+TWOPLACES = Decimal(10) ** -2
8
+
9
+def daterange(start_date, end_date):
10
+    for n in range(int ((end_date - start_date).days)):
11
+        yield start_date + datetime.timedelta(n)
12
+
13
+class PayPeriod:
14
+    def __init__(self, calculate=False):
15
+        self.bills = []
16
+        self.expenses = DATA.get('expenses')
17
+        self.delta = None
18
+        self.beginning = None
19
+        self.end = None
20
+        self.total_bills = None
21
+        self.money_left = None
22
+        self.total_expenses = None
23
+        if calculate:
24
+            self.calculate_budget()
25
+
26
+    def lookup_bills(self):
27
+        if not self.beginning or not self.end:
28
+            self.current_pay_period()
29
+        bill_data = DATA.get('bills')
30
+
31
+        for day in daterange(self.beginning, self.end):
32
+            x = bill_data.get(day.day)
33
+            if x:
34
+                for bill in x:
35
+                    self.bills.append(bill)
36
+
37
+    def pay_period_delta(self):
38
+        period = DATA.get('pay_period')
39
+        if not period:
40
+            raise Error
41
+
42
+        unit = period.get('unit')
43
+        value = period.get('value')
44
+
45
+        if unit == 'week':
46
+            self.delta = datetime.timedelta(weeks=value)
47
+        elif unit == 'day':
48
+            self.delta = datetime.timedelta(days=value)
49
+        elif unit == 'month':
50
+            self.delta = value
51
+        else:
52
+            raise Error
53
+
54
+    def current_pay_period(self):
55
+        self.find_pay_period(datetime.date.today())
56
+
57
+    def next_pay_period(self):
58
+        if self.delta == None:
59
+            self.pay_period_delta()
60
+
61
+        next_period = PayPeriod()
62
+        next_period.find_pay_period(datetime.date.today() + self.delta)
63
+        next_period.calculate_budget()
64
+
65
+        return next_period
66
+
67
+    def find_pay_period(self, date):
68
+        if self.delta == None:
69
+            self.pay_period_delta()
70
+
71
+        pay_date_str = DATA.get('pay_date')
72
+        if not pay_date_str:
73
+            raise Error
74
+
75
+        pay_period = DATA.get('pay_period')
76
+        if not pay_period:
77
+            raise Error
78
+
79
+        pay_date_list = pay_date_str.split('/')
80
+        pay_date = datetime.date(int(pay_date_list[0]), int(pay_date_list[1]), int(pay_date_list[2]))
81
+
82
+        beginning = pay_date
83
+        end = pay_date
84
+
85
+        while date > end:
86
+            beginning = end
87
+            end += self.delta
88
+
89
+        self.beginning = beginning
90
+        self.end = end
91
+
92
+    def calculate_budget(self):
93
+        if not self.bills:
94
+            self.lookup_bills()
95
+
96
+        self.total_bills = Decimal(0)
97
+        for bill in self.bills:
98
+            self.total_bills += bill.get('amt')
99
+
100
+        self.total_expenses = Decimal(0)
101
+        for expense in self.expenses:
102
+            self.total_expenses += expense.get('amt')
103
+
104
+        self.money_left = DATA.get("pay_amount") - self.total_bills - self.total_expenses
105
+
106
+    def print_budget(self):
107
+        if not self.total_bills:
108
+            self.calculate_budget()
109
+
110
+        print("\nBills")
111
+        print("***************************")
112
+        bills_table = PrettyTable(["Description", "Amount"])
113
+        bills_table.align["Amount"] = "r"
114
+        for bill in self.bills:
115
+            bills_table.add_row([bill.get('desc'), bill.get('amt').quantize(TWOPLACES)])
116
+        bills_table.add_row(["----------", "-----"])
117
+        bills_table.add_row(["Total", self.total_bills.quantize(TWOPLACES)])
118
+        print(bills_table)
119
+
120
+        print("\nExpenses")
121
+        print("***************************")
122
+        expenses_table = PrettyTable(["Description", "Amount"])
123
+        expenses_table.align["Amount"] = "r"
124
+        for expense in self.expenses:
125
+            expenses_table.add_row([expense.get('desc'), expense.get('amt').quantize(TWOPLACES)])
126
+        expenses_table.add_row(["----------", "-----"])
127
+        expenses_table.add_row(["Total", self.total_expenses.quantize(TWOPLACES)])
128
+        print(expenses_table)
129
+
130
+        print("\nTotals")
131
+        print("***************************")
132
+        print("Expenses Total: " + str(self.total_expenses.quantize(TWOPLACES)))
133
+        print("Money After Bills + Expenses: " + str(self.money_left.quantize(TWOPLACES)))
134
+        next_period = self.next_pay_period()
135
+        money_left_next = next_period.money_left
136
+        print("Money After Bills + Expenses Next: " + str(money_left_next.quantize(TWOPLACES)))
137
+
138
+        if money_left_next <= 0:
139
+            print("Not enough money left next paycheck!! Expenses being rolled together!")
140
+            self.expenses += next_period.expenses
141
+            self.calculate_budget()
142
+            self.money_left = self.money_left + money_left_next + next_period.total_expenses
143
+            print("Money left after combining: " + str(self.money_left.quantize(TWOPLACES)))
144
+
145
+        print("\nPercent Expenses")
146
+        print("***************************")
147
+
148
+        total_perc_expenses = Decimal(0)
149
+        perc_expenses = PrettyTable(["Description", "Amount"])
150
+        perc_expenses.align["Amount"] = "r"
151
+        for perc_expense in DATA.get("perc_expenses", []):
152
+            amt = perc_expense.get('perc') * self.money_left
153
+            total_perc_expenses += amt
154
+            perc_expenses.add_row([perc_expense.get('desc'), amt.quantize(TWOPLACES)])
155
+        perc_expenses.add_row(["----------", "-----"])
156
+        perc_expenses.add_row(["Total", total_perc_expenses.quantize(TWOPLACES)])
157
+        print(perc_expenses)
158
+
159
+        money_left_perc = self.money_left - total_perc_expenses
160
+
161
+        print("\nAllowances")
162
+        print("***************************")
163
+        allowance_table = PrettyTable(["Description", "Amount"])
164
+        allowance_table.align["Amount"] = "r"
165
+        for allowance in DATA.get("allowances", []):
166
+            amt = allowance.get('perc') * money_left_perc
167
+            allowance_table.add_row([allowance.get('desc'), amt.quantize(TWOPLACES)])
168
+        print(allowance_table)
169
+
170
+pay_period = PayPeriod()
171
+pay_period.print_budget()

+ 39 - 0
settings.py.example

@@ -0,0 +1,39 @@
1
+#!/usr/bin/python3
2
+
3
+from decimal import *
4
+
5
+# Based off my actual config
6
+DATA = \
7
+{
8
+    'bills':
9
+    {
10
+        1:  [{'desc': 'Electricity', 'amt': Decimal(0)}, {'desc': 'Water', 'amt': Decimal(0)}],
11
+        3:  [{'desc': 'Internet', 'amt': Decimal(0)}],
12
+        4:  [{'desc': 'Rent', 'amt': Decimal(0)}],
13
+        11: [{'desc': 'Renters Insurance', 'amt': Decimal(0)}, {'desc': 'Auto Insurance', 'amt': Decimal(0)}, {'desc': 'Trash', 'amt': Decimal(0)}],
14
+        13: [{'desc': 'Car payment', 'amt': Decimal(0)}],
15
+    },
16
+    'expenses':
17
+    [
18
+        {'desc': 'Groceries', 'amt': Decimal(0)},
19
+        {'desc': 'Diapers', 'amt': Decimal(0)},
20
+        {'desc': 'Toiletries', 'amt': Decimal(0)},
21
+        {'desc': 'Gas', 'amt': Decimal(0)},
22
+    ],
23
+    'perc_expenses':
24
+    [
25
+        {'desc': 'Savings', 'perc': Decimal(.15)},
26
+        {'desc': 'Investment', 'perc': Decimal(.10)},
27
+    ],
28
+    'allowances':
29
+    [
30
+        {'desc': 'Dad', 'perc': Decimal(.285)},
31
+        {'desc': 'Mom', 'perc': Decimal(.285)},
32
+        {'desc': 'Kid', 'perc': Decimal(.2)},
33
+        {'desc': 'Eating out', 'perc': Decimal(.115)},
34
+        {'desc': 'Misc', 'perc': Decimal(.115)},
35
+    ],
36
+    'pay_date': '2013/09/25',
37
+    'pay_period': {'unit': 'week', 'value': 2},
38
+    'pay_amount': Decimal(0),
39
+}