From 8fda260fe19dd1e4999a2d6efc3b294d94dcdcfa Mon Sep 17 00:00:00 2001 From: CherryKitten Date: Sun, 27 Nov 2022 12:57:33 +0100 Subject: [PATCH] Add budget app --- .../3-budget-app/.replit | 2 + .../3-budget-app/README.md | 3 + .../3-budget-app/budget.py | 94 ++++++++++++++++ .../3-budget-app/main.py | 25 +++++ .../3-budget-app/test_module.py | 105 ++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 8-scientific-computing-python/3-budget-app/.replit create mode 100644 8-scientific-computing-python/3-budget-app/README.md create mode 100644 8-scientific-computing-python/3-budget-app/budget.py create mode 100644 8-scientific-computing-python/3-budget-app/main.py create mode 100644 8-scientific-computing-python/3-budget-app/test_module.py diff --git a/8-scientific-computing-python/3-budget-app/.replit b/8-scientific-computing-python/3-budget-app/.replit new file mode 100644 index 0000000..c88f45f --- /dev/null +++ b/8-scientific-computing-python/3-budget-app/.replit @@ -0,0 +1,2 @@ +language = "python3" +run = "python main.py" \ No newline at end of file diff --git a/8-scientific-computing-python/3-budget-app/README.md b/8-scientific-computing-python/3-budget-app/README.md new file mode 100644 index 0000000..32c513b --- /dev/null +++ b/8-scientific-computing-python/3-budget-app/README.md @@ -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 diff --git a/8-scientific-computing-python/3-budget-app/budget.py b/8-scientific-computing-python/3-budget-app/budget.py new file mode 100644 index 0000000..198e286 --- /dev/null +++ b/8-scientific-computing-python/3-budget-app/budget.py @@ -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 \ No newline at end of file diff --git a/8-scientific-computing-python/3-budget-app/main.py b/8-scientific-computing-python/3-budget-app/main.py new file mode 100644 index 0000000..b2a7b91 --- /dev/null +++ b/8-scientific-computing-python/3-budget-app/main.py @@ -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) \ No newline at end of file diff --git a/8-scientific-computing-python/3-budget-app/test_module.py b/8-scientific-computing-python/3-budget-app/test_module.py new file mode 100644 index 0000000..46929da --- /dev/null +++ b/8-scientific-computing-python/3-budget-app/test_module.py @@ -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()