Build a Budget App Project - Build a Budget App Project

Tell us what’s happening:

Need help understanding why I can’t pass the tests and intended output.

Test 1, 3, 7
What is a specific object. I am sure I have created it in the init method.
Why can I pass Test 11 what is the difference.

Test 4
I wrote the exact same code for Test 2 but somehow still fail?? :moai: even though output displays blank.

Test 6
Output returns the exact value its asking for.

Test 9 and 10
Output shows the correct deductions and additions.

Test 16
What is this asking for.

Test 17-24
I can’t pass any of it even though my output is identical to the example.

Your code so far

from itertools import zip_longest
import math

class Category:
    def __init__(self, category):
        self.ledger = []
        self.category = category
        self.initial = False
        self.total = 0
        self.withdrawals = 0
        self.total_deposited = 0

    #! Use dictionaries for structured data
   
    def __str__(self):
        ledger_str = f'{self.category.center(30, "*")}\n'

        for i in range(len(self.ledger)): # list
            #* justify both direction to ensure correct spacing
            amount = f'{float(self.ledger[i]["amount"]):.2f}'.rjust(7)
            #* length is used to determine length of description and padding
            length = 29 - len(amount)
            description = self.ledger[i]['description'][:length].ljust(length)
            
            ledger_str += f'{description} {amount}\n'
        ledger_str += f'Total: {self.total}\n'
        return ledger_str


    def deposit(self, amt, description = '', transfer = False):
        if amt >= 10000:
            self.ledger.append({'amount': '0', 'description': "Over 10k's tough, bruv"})
            return False
        
        if not self.initial and description.lower() == 'deposit':
            self.deposit(amt, "Initial deposit")
            self.initial = True
        elif transfer:
            self.deposit(amt, f"Transfer from {description}")
        else:
            self.ledger.append({'amount': amt, 'description': description})
            self.total += amt
            self.total_deposited += amt
        

    def withdraw(self, amt, description = ''):
        if self.check_funds(amt):
            self.ledger.append({'amount': f'-{amt}', 'description': description})
            self.total -= amt
            self.withdrawals += amt
            return True
        else:
            return False
    
    def get_balance(self):
        print(self.total)

    def transfer(self, amt, category):
        if self.check_funds(amt):
            #* category.category to access target avoids the *
            self.ledger.append({'amount': f'-{amt}', 'description': f'Transfer to {category.category}'})
            category.deposit(amt, self.category, True)
            self.total -= amt
            return True
        else:
            return False


    def check_funds(self, amt):
        if amt >= 10000:
            self.ledger.append({'amount': '0', 'description': "Over 10k's tough, bruv"})
            return False

        if amt > self.total:
            self.ledger.append({'amount': '0', 'description': 'Not Enough Money Bruh'})
            return False
        else:
            return True


def create_spend_chart(categories):
    output_str = ''
    output_str += 'Percentage spent by category\n'

    category_names = [name.category for name in categories]
    withdrawal = [val.withdrawals for val in categories]
    total = [val.total_deposited for val in categories]

    percentages = [math.floor((w / t) * 100 / 10) * 10 if t else 0 for w, t in zip(withdrawal, total)]
    
    # print(total)
    # print(withdrawal)
    # print(percentages)

    #* Starting from 100 to 0 in 10s
    for level in range(100, -1, -10):
        #* :>3 is to right align it (no need for \n because its looping)
        line = f'{level:>3}|'

        for per in percentages:
            #* [20, 100, 0] if matches add 0 
            if per >= level:
                line += ' o '
            else:
                line += '   '

        output_str += f'{line}\n'
    
    #* Each name gets 3 '-' + 1 for spacing
    dashes = '-' * (len(category_names) * 3 + 1)
    output_str += f"    {dashes}\n"
    
    #* Iterates the longest, fills shorter with ''||* operator unpacks the names
    for row in zip_longest(*category_names, fillvalue=" "):
        #* This is to indent the names
        output_str += "     "
        output_str += f"{'  '.join(row)}\n"
    
    output_str = output_str.rstrip()
    print(output_str)

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

Challenge Information:

Build a Budget App Project - Build a Budget App Project

Solved Tests 17,18, 21 and 22 by returning instead of printing. Still don’t understand why I am failing the other tests

Could you update your code to the latest version?

from itertools import zip_longest
import math

class Category:
    def __init__(self, category):
        self.ledger = []
        self.category = category
        self.initial = False
        self.total = 0
        self.withdrawals = 0
        self.total_deposited = 0

    #! Use dictionaries for structured data
   
    def __str__(self):
        ledger_str = f'{self.category.center(30, "*")}\n'

        for i in range(len(self.ledger)): # list
            #* justify both direction to ensure correct spacing
            amount = f'{float(self.ledger[i]["amount"]):.2f}'.rjust(7)
            #* length is used to determine length of description and padding
            length = 29 - len(amount)
            description = self.ledger[i]['description'][:length].ljust(length)
            
            ledger_str += f'{description} {amount}\n'
        ledger_str += f'Total: {self.total}\n'
        return ledger_str


    def deposit(self, amt, description = '', transfer = False):
        if amt >= 10000:
            self.ledger.append({'amount': '0', 'description': "Over 10k's tough, bruv"})
            return False
        
        if not self.initial and description.lower() == 'deposit':
            self.deposit(amt, "Initial deposit")
            self.initial = True
        elif transfer:
            self.deposit(amt, f"Transfer from {description}")
        else:
            self.ledger.append({'amount': amt, 'description': description})
            self.total += amt
            self.total_deposited += amt
        

    def withdraw(self, amt, description = ''):
        if self.check_funds(amt):
            self.ledger.append({'amount': f'-{amt}', 'description': description})
            self.total -= amt
            self.withdrawals += amt
            return True
        else:
            return False
    
    def get_balance(self):
        print(self.total)

    def transfer(self, amt, category):
        if self.check_funds(amt):
            #* category.category to access target avoids the *
            self.ledger.append({'amount': f'-{amt}', 'description': f'Transfer to {category.category}'})
            category.deposit(amt, self.category, True)
            self.total -= amt
            return True
        else:
            return False


    def check_funds(self, amt):
        if amt >= 10000:
            self.ledger.append({'amount': '0', 'description': "Over 10k's tough, bruv"})
            return False

        if amt > self.total:
            self.ledger.append({'amount': '0', 'description': 'Not Enough Money Bruh'})
            return False
        else:
            return True


def create_spend_chart(categories):
    output_str = ''
    output_str += 'Percentage spent by category\n'

    category_names = [name.category for name in categories]
    withdrawal = [val.withdrawals for val in categories]
    total = [val.total_deposited for val in categories]

    percentages = [math.floor((w / t) * 100 / 10) * 10 if t else 0 for w, t in zip(withdrawal, total)]
    
    # print(total)
    # print(withdrawal)
    # print(percentages)

    #* Starting from 100 to 0 in 10s
    for level in range(100, -1, -10):
        #* :>3 is to right align it (no need for \n because its looping)
        line = f'{level:>3}|'

        for per in percentages:
            #* [20, 100, 0] if matches add 0 
            if per >= level:
                line += ' o '
            else:
                line += '   '

        output_str += f'{line}\n'
    
    #* Each name gets 3 '-' + 1 for spacing
    dashes = '-' * (len(category_names) * 3 + 1)
    output_str += f"    {dashes}\n"
    
    #* Iterates the longest, fills shorter with ''||* operator unpacks the names
    for row in zip_longest(*category_names, fillvalue=" "):
        #* This is to indent the names
        output_str += "     "
        output_str += f"{'  '.join(row)}\n"
    
    return output_str.rstrip()

Hi @relzarick

Why is there a minimum amount for deposits?

Happy coding

Thanks for the reply, I’m not sure what you mean by minimum amount for deposits but that IF block is to check if amount inputted would exceed seven characters.

If user inputs an amount above or equal to ten thousand, the total characters displayed at the end will exceed seven.

  • A list of the items in the ledger. Each line should show the description and amount. The first 23 characters of the description should be displayed, then the amount. The amount should be right aligned, contain two decimal places, and display a maximum of 7 characters.

Have you looked at the browser’s console? There will be displayed some details in it regarding failing tests.

I would be pretty disappointed if I deposited $10,000 and my bank credited my account 0.

display a maximum of 7 characters.

You can deposit as much as you want, but when it’s printed it will display just this many characters. A pretty interested solution though!

Thanks for the reply, but the console just displays what tests I failed it does not detail why.

open the browser console with F12 for more details

it’s mentioned in hint text 24 and at the end of the instructions

here an example of what you could find

AssertionError: {'amount': 900, 'description': 'Initial deposit'}
             != {'amount': 900, 'description': 'deposit'}

(first is the actual, the second is the expected)
what could be that make the description Initial deposit instead of deposit?

Thanks for the reply, how should I approach your suggestion?
Lets say I input a value of $12,3456. The output would then be 123456.00, it would exceed the 7 character limit. If I would to display it I would have to omit some parts of the output resulting in 1234.00. This would then be incorrect.

the app does not expect to receive that big of numbers, but if it does, it should work correctly for everything but the visualization, as that is what has the 7 characters limit.

You can show 123456.00 as 123456, and that doesn’t exceed the character limit, even if it doesn’t have decimals. Dividing by 100 is the wrong approach

Thanks for the reply, my description results in ‘Initial deposit’ because I appended it it in deposit method for the first ‘deposit’ the category receives.

def deposit(self, amt, description = '', transfer = False): 
        if not self.initial and description.lower() == 'deposit':
            self.deposit(amt, "Initial deposit")
            self.initial = True

So the correct solution is to handle it all in the str method?
What about my transfer and withdraw methods?

self.ledger.append({'amount': f'-{amt}', 'description': description})
self.ledger.append({'amount': f'-{amt}', 'description': f'Transfer to {category.category}'})

What do you mean by dividing by 100 is the wrong approach?

amount = f'{float(self.ledger[i]["amount"]):.2f}'.rjust(7)

This is how I handle the amount, what should I do to improve it?

I would truncate the smaller units. The bigger units are more important.

Think of the internal numbers and what gets displayed as two totally separate things.

you said that if you have a number like 123456.00, that is dividing by 100 and you need to use less digits you would write 1234.00

check the assertion errors, what do they say?

Thanks for your reply, I still don’t get what you mean by dividing by 100.

For my transfer and withdraw methods, I was getting an error because its was a str instead of an int. Thank you.

I am failing Test 1 because when the test adds a deposit, my code checks if its the first instance of it and appends initial to it like what is shown in the example. This feels like an oversight

If you look at the example:

Here is an example usage:

food = Category('Food')
food.deposit(1000, 'deposit')
food.withdraw(10.15, 'groceries')

And here is an example of the output:

*************Food*************
initial deposit        1000.00
groceries               -10.15

I thought the first deposit must say “initial deposit”. This seems mis-leading though looks like the test does not work this way. I’m sure this has confused me in the past as well.

Not sure if it’s worth updating at this point… ?

Test 16
Printing a Category instance should give a different string representation of the object.
What does this mean?

It means your output is wrong. Please show your output.

You can also check devtools console, there should be an error with more information. You NEED to check this to complete this problem.

Thanks for the reply, but what is it I’m supposed to look for?

food = Category('Food')
clothing = Category('Clothing')
auto = Category('auto')

auto.deposit(1000, 'deposit') 

food.deposit(2000, 'deposit')
food.withdraw(45.67, 'milk, cereal, eggs, bacon, bread')
food.withdraw(1)

food.transfer(1000, auto)

This is my output

*************Food*************
deposit                2000.00
milk, cereal, eggs, ba  -45.67
                         -1.00
Transfer to auto      -1000.00
Total: 953.33

*************auto*************
deposit                1000.00
Transfer from Food     1000.00
Total: 2000

This is my code

def __str__(self):
        ledger_str = f'{self.category.center(30, "*")}\n'

        for i in range(len(self.ledger)): # list
            #* justify both direction to ensure correct spacing
            amount = f'{float(self.ledger[i]["amount"]):.2f}'.rjust(7)
            #* length is used to determine length of description and padding
            length = 29 - len(amount)
            description = self.ledger[i]['description'][:length].ljust(length)
            
            ledger_str += f'{description} {amount}\n'
        ledger_str += f'Total: {round(self.total, 2)}\n'
        return ledger_str

Removing the \n at the end of the code does not help me pass