Add budget app
This commit is contained in:
parent
a794f63cd8
commit
8fda260fe1
5 changed files with 229 additions and 0 deletions
2
8-scientific-computing-python/3-budget-app/.replit
Normal file
2
8-scientific-computing-python/3-budget-app/.replit
Normal file
|
@ -0,0 +1,2 @@
|
|||
language = "python3"
|
||||
run = "python main.py"
|
3
8-scientific-computing-python/3-budget-app/README.md
Normal file
3
8-scientific-computing-python/3-budget-app/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Budget App
|
||||
|
||||
This is the boilerplate for the Budget App project. Instructions for building your project can be found at https://www.freecodecamp.org/learn/scientific-computing-with-python/scientific-computing-with-python-projects/budget-app
|
94
8-scientific-computing-python/3-budget-app/budget.py
Normal file
94
8-scientific-computing-python/3-budget-app/budget.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import math
|
||||
|
||||
|
||||
class Category():
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.ledger = []
|
||||
|
||||
def __str__(self):
|
||||
title = self.name.center(30, "*") + '\n'
|
||||
items = ""
|
||||
for item in self.ledger:
|
||||
desc = item["description"]
|
||||
amount = "{:0.2f}".format(float(item["amount"]))
|
||||
if len(desc) > 23: desc = desc[0:23]
|
||||
if len(amount) > 7: amount = amount[0:7]
|
||||
items = items + desc + (30 - len(desc) - len(amount)) * " " + amount + '\n'
|
||||
|
||||
return title + items + "Total: " + str(float(self.get_balance()))
|
||||
|
||||
def deposit(self, amount, desc=""):
|
||||
self.ledger.append({ "amount" : amount, "description": desc })
|
||||
|
||||
def withdraw(self, amount, desc=""):
|
||||
if self.check_funds(amount):
|
||||
amount = amount * - 1
|
||||
self.ledger.append({ "amount" : amount, "description": desc })
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_balance(self):
|
||||
balance = 0
|
||||
for entry in self.ledger:
|
||||
balance = balance + entry["amount"]
|
||||
return balance
|
||||
|
||||
def transfer(self, amount, category):
|
||||
if self.check_funds(amount):
|
||||
self.withdraw(amount, "Transfer to " + category.name)
|
||||
category.deposit(amount, "Transfer from " + self.name)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_funds(self, amount):
|
||||
if amount <= self.get_balance():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def create_spend_chart(categories):
|
||||
totals = {}
|
||||
total = 0
|
||||
chart = "Percentage spent by category\n"
|
||||
for category in categories:
|
||||
totals[category.name] = 0
|
||||
for entry in category.ledger:
|
||||
if entry["amount"] < 0:
|
||||
totals[category.name] = totals[category.name] + entry["amount"]
|
||||
|
||||
for k, v in totals.items():
|
||||
totals[k] = v * - 1
|
||||
total = total - v
|
||||
|
||||
for k, v in totals.items():
|
||||
percentage = math.trunc((v / total * 100))
|
||||
totals[k] = percentage
|
||||
|
||||
|
||||
for i in range(100, -10, -10):
|
||||
chart = chart + str(i).rjust(3, " ") + "|"
|
||||
for k, v in totals.items():
|
||||
if v >= i:
|
||||
chart = chart + " o "
|
||||
else:
|
||||
chart = chart + " "
|
||||
chart = chart + " \n"
|
||||
|
||||
chart = chart + 4 * " " + (4 + 2 * len(totals)) * "-"
|
||||
i = 0
|
||||
while i < max(len(k) for k, v in totals.items()):
|
||||
chart = chart + "\n" + 4 * " "
|
||||
for key in totals:
|
||||
if len(key) <= i:
|
||||
chart = chart + " "
|
||||
else:
|
||||
chart = chart + " " + key[i] + " "
|
||||
chart = chart + " "
|
||||
i = i + 1
|
||||
|
||||
return chart
|
25
8-scientific-computing-python/3-budget-app/main.py
Normal file
25
8-scientific-computing-python/3-budget-app/main.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# This entrypoint file to be used in development. Start by reading README.md
|
||||
import budget
|
||||
from budget import create_spend_chart
|
||||
from unittest import main
|
||||
|
||||
food = budget.Category("Food")
|
||||
food.deposit(1000, "initial deposit")
|
||||
food.withdraw(10.15, "groceries")
|
||||
food.withdraw(15.89, "restaurant and more food for dessert")
|
||||
print(food.get_balance())
|
||||
clothing = budget.Category("Clothing")
|
||||
food.transfer(50, clothing)
|
||||
clothing.withdraw(25.55)
|
||||
clothing.withdraw(100)
|
||||
auto = budget.Category("Auto")
|
||||
auto.deposit(1000, "initial deposit")
|
||||
auto.withdraw(15)
|
||||
|
||||
print(food)
|
||||
print(clothing)
|
||||
|
||||
print(create_spend_chart([food, clothing, auto]))
|
||||
|
||||
# Run unit tests automatically
|
||||
main(module='test_module', exit=False)
|
105
8-scientific-computing-python/3-budget-app/test_module.py
Normal file
105
8-scientific-computing-python/3-budget-app/test_module.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
import unittest
|
||||
import budget
|
||||
from budget import create_spend_chart
|
||||
|
||||
|
||||
class UnitTests(unittest.TestCase):
|
||||
maxDiff = None
|
||||
def setUp(self):
|
||||
self.food = budget.Category("Food")
|
||||
self.entertainment = budget.Category("Entertainment")
|
||||
self.business = budget.Category("Business")
|
||||
|
||||
def test_deposit(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
actual = self.food.ledger[0]
|
||||
expected = {"amount": 900, "description": "deposit"}
|
||||
self.assertEqual(actual, expected, 'Expected `deposit` method to create a specific object in the ledger instance variable.')
|
||||
|
||||
def test_deposit_no_description(self):
|
||||
self.food.deposit(45.56)
|
||||
actual = self.food.ledger[0]
|
||||
expected = {"amount": 45.56, "description": ""}
|
||||
self.assertEqual(actual, expected, 'Expected calling `deposit` method with no description to create a blank description.')
|
||||
|
||||
def test_withdraw(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
|
||||
actual = self.food.ledger[1]
|
||||
expected = {"amount": -45.67, "description": "milk, cereal, eggs, bacon, bread"}
|
||||
self.assertEqual(actual, expected, 'Expected `withdraw` method to create a specific object in the ledger instance variable.')
|
||||
|
||||
def test_withdraw_no_description(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
good_withdraw = self.food.withdraw(45.67)
|
||||
actual = self.food.ledger[1]
|
||||
expected = {"amount": -45.67, "description": ""}
|
||||
self.assertEqual(actual, expected, 'Expected `withdraw` method with no description to create a blank description.')
|
||||
self.assertEqual(good_withdraw, True, 'Expected `withdraw` method to return `True`.')
|
||||
|
||||
def test_get_balance(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
|
||||
actual = self.food.get_balance()
|
||||
expected = 854.33
|
||||
self.assertEqual(actual, expected, 'Expected balance to be 854.33')
|
||||
|
||||
def test_transfer(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
|
||||
transfer_amount = 20
|
||||
food_balance_before = self.food.get_balance()
|
||||
entertainment_balance_before = self.entertainment.get_balance()
|
||||
good_transfer = self.food.transfer(transfer_amount, self.entertainment)
|
||||
food_balance_after = self.food.get_balance()
|
||||
entertainment_balance_after = self.entertainment.get_balance()
|
||||
actual = self.food.ledger[2]
|
||||
expected = {"amount": -transfer_amount, "description": "Transfer to Entertainment"}
|
||||
self.assertEqual(actual, expected, 'Expected `transfer` method to create a specific ledger item in food object.')
|
||||
self.assertEqual(good_transfer, True, 'Expected `transfer` method to return `True`.')
|
||||
self.assertEqual(food_balance_before - food_balance_after, transfer_amount, 'Expected `transfer` method to reduce balance in food object.')
|
||||
self.assertEqual(entertainment_balance_after - entertainment_balance_before, transfer_amount, 'Expected `transfer` method to increase balance in entertainment object.')
|
||||
actual = self.entertainment.ledger[0]
|
||||
expected = {"amount": transfer_amount, "description": "Transfer from Food"}
|
||||
self.assertEqual(actual, expected, 'Expected `transfer` method to create a specific ledger item in entertainment object.')
|
||||
|
||||
def test_check_funds(self):
|
||||
self.food.deposit(10, "deposit")
|
||||
actual = self.food.check_funds(20)
|
||||
expected = False
|
||||
self.assertEqual(actual, expected, 'Expected `check_funds` method to be False')
|
||||
actual = self.food.check_funds(10)
|
||||
expected = True
|
||||
self.assertEqual(actual, expected, 'Expected `check_funds` method to be True')
|
||||
|
||||
def test_withdraw_no_funds(self):
|
||||
self.food.deposit(100, "deposit")
|
||||
good_withdraw = self.food.withdraw(100.10)
|
||||
self.assertEqual(good_withdraw, False, 'Expected `withdraw` method to return `False`.')
|
||||
|
||||
def test_transfer_no_funds(self):
|
||||
self.food.deposit(100, "deposit")
|
||||
good_transfer = self.food.transfer(200, self.entertainment)
|
||||
self.assertEqual(good_transfer, False, 'Expected `transfer` method to return `False`.')
|
||||
|
||||
def test_to_string(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
|
||||
self.food.transfer(20, self.entertainment)
|
||||
actual = str(self.food)
|
||||
expected = f"*************Food*************\ndeposit 900.00\nmilk, cereal, eggs, bac -45.67\nTransfer to Entertainme -20.00\nTotal: 834.33"
|
||||
self.assertEqual(actual, expected, 'Expected different string representation of object.')
|
||||
|
||||
def test_create_spend_chart(self):
|
||||
self.food.deposit(900, "deposit")
|
||||
self.entertainment.deposit(900, "deposit")
|
||||
self.business.deposit(900, "deposit")
|
||||
self.food.withdraw(105.55)
|
||||
self.entertainment.withdraw(33.40)
|
||||
self.business.withdraw(10.99)
|
||||
actual = create_spend_chart([self.business, self.food, self.entertainment])
|
||||
expected = "Percentage spent by category\n100| \n 90| \n 80| \n 70| o \n 60| o \n 50| o \n 40| o \n 30| o \n 20| o o \n 10| o o \n 0| o o o \n ----------\n B F E \n u o n \n s o t \n i d e \n n r \n e t \n s a \n s i \n n \n m \n e \n n \n t "
|
||||
self.assertEqual(actual, expected, 'Expected different chart representation. Check that all spacing is exact.')
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Reference in a new issue