Scientific Computing with Python Projects - Budget App

Tell us what’s happening:
When I run my project in replit, I’m getting errors on the create_spend_chart test. I know it has to do with spacing and I tried fiddling everywhere I added spacing or justified text, but I cannot seem to decipher what exactly is wrong with my formatting. Even after looking at tutorials and the unittest documentation I’m still no closer to understanding what the differences are. I don’t know what all the pluses, minuses, carrots, and question marks mean. If anyone can look at my code and tell me what looks like the issue, I’d appreciate it. However what I’d really like is a resource that tells me how to read the unittest output or color code the differences so the information is easier to parse.

Error Message

FAIL: test_create_spend_chart (test_module.UnitTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/boilerplate-budget-app-2/test_module.py", line 102, in test_create_spend_chart
    self.assertEqual(actual, expected, 'Expected different chart representation. Check that all spacing is exact.')
AssertionError: 'Perc[34 chars]     \n 90|         \n 80|         \n 70|    o[303 chars]   t' != 'Perc[34 chars]      \n 90|          \n 80|          \n 70|  [340 chars] t  '
  Percentage spent by category
- 100|         
+ 100|          
?              +
-  90|         
+  90|          
?              +
-  80|         
+  80|          
?              +
-  70|    o    
+  70|    o     
?              +
-  60|    o    
+  60|    o     
?              +
-  50|    o    
+  50|    o     
?              +
-  40|    o    
+  40|    o     
?              +
-  30|    o    
+  30|    o     
?              +
-  20|    o  o 
+  20|    o  o  
?              +
-  10|    o  o 
+  10|    o  o  
?              +
-   0| o  o  o 
+   0| o  o  o  
?              +
      ----------
-      B  F  E
+      B  F  E  
?             ++
-      u  o  n
+      u  o  n  
?             ++
-      s  o  t
+      s  o  t  
?             ++
-      i  d  e
+      i  d  e  
?             ++
-      n     r
+      n     r  
?             ++
-      e     t
+      e     t  
?             ++
-      s     a
+      s     a  
?             ++
-      s     i
+      s     i  
?             ++
-            n
+            n  
?             ++
-            m
+            m  
?             ++
-            e
+            e  
?             ++
-            n
+            n  
?             ++
-            t+            t  ?             ++
 : Expected different chart representation. Check that all spacing is exact.

----------------------------------------------------------------------
Ran 11 tests in 0.023s

FAILED (failures=1)

Your code so far

import re


class Category:
    def __init__(self, category):
        self.category = category
        self.ledger = []
        self.balance = 0.00

    def deposit(self, amount, description=""):
        """Amount is an float amount.
           Description is a string.
           writes a transcation to the ledger"""
        self.balance += amount
        transaction = {"amount": amount, "description": description}
        self.update_ledger(transaction)

    def check_funds(self, amount):
        """Function to check if an amount for a transfer or withdrawal is valid.
           Returns True if valid, False otherwise."""
        if self.balance < amount:
            return False
        return True

    def update_ledger(self, transaction):
        """updates ledger list with transaction statment.
           transaction statement is a dictionary in the form of {"amount": amount, "description": description}
           """
        self.ledger.append(transaction)

    def withdraw(self, amount, description=""):
        """Amount is an float amount.
           Description is a string.
           writes a transcation to the ledger.
           The transaction will be placed as a negative in the ledger.

           returns True if the transaction succeeds
                   False if withdraw amount is greater than balance."""
        if not self.check_funds(amount):
            return False
        else:
            self.balance -= amount
            transaction = {"amount": -amount, "description": description}
            self.update_ledger(transaction)
            return True

    def get_balance(self):
        return self.balance

    def transfer(self, amount, budget_category):
        """amount is a floating point number.
           budget_category is an object that gets an amount transferred to it.
           Takes amount from current object and transfers it to the new object"""
        if not self.check_funds(amount):
            return False
        # make withdral from this budget object
        withdraw_message = f"Transfer to {budget_category.category}"
        self.withdraw(amount, withdraw_message)

        # make a deposit to the budget_category object
        deposit_message = f"Transfer from {self.category}"
        budget_category.deposit(amount, deposit_message)
        return True

    def __str__(self):
        """fomatting object ledger to be printed when object is passed through the print function"""
        page_lines = []
        header = self.category.center(30, "*")
        page_lines.append(header)

        for line in self.ledger:
            description = line["description"][:23]
            amount = "{:.2f}".format(line["amount"])

            # move amount to be right justified relative to the header
            amount = amount.rjust(30 - len(description))
            page_line = description + amount
            page_lines.append(page_line)
        final_balance = "{:.2f}".format(self.balance)
        page_total = f"Total: {final_balance}"
        page_lines.append(page_total)
        formatted_page = "\n".join(page_lines)
        return formatted_page


def create_spend_chart(category_list):
    """category_list is a list of objects
       creates a histogram based on the pecentage of the budget spent on that category rounded down

       Returns a formatted histogram string"""
    graph_title = "Percentage spent by category"
    # create empty chart
    chart = []
    for i in range(100, -1, -10):
        y_label = str(i) + "|"
        if i != 100:
            y_label = y_label.rjust(len(chart[0]))
        chart.append(y_label)
    # find percentages
    overall_withdraws = 0
    category_withdraws = defaultdict(int)
    # total perecntage spent is total category withdraws divided by the overall withdraws rounded down
    for budget_item in category_list:
        for transaction in budget_item.ledger:
            if transaction["amount"] < 0:
                category_withdraws[budget_item.category] += - \
                    transaction["amount"]
                overall_withdraws += -transaction["amount"]
    # nested loop calulates percentages and fills chart accordingly
    for budget_item in category_list:
        percentage_spent_per_category = int(
            (category_withdraws[budget_item.category] / overall_withdraws) * 100)
        for i, chart_line in enumerate(chart):
            chart_percent = re.findall("[0-9]+", chart_line)
            chart_percent = int(chart_percent[0])
            if percentage_spent_per_category >= chart_percent:
                chart[i] += " o "
            else:
                chart[i] += " " * len(" o ")
    # print(*chart, sep="\n")
    # use regex to find the longest bar length horizontal & use that number plus two
    #  to create the underline that creates the x- axis
    longest_bar = 0
    longest_row = 0
    for row in chart:
        
        bar = re.findall("\so.*", row)
        if len(row) > longest_row:
            longest_row = len(row)
        if bar != []:
            bar_length = len(bar[0])
            if bar_length > longest_bar:
                longest_bar = bar_length
    under_line = "-" * (longest_bar + 1)

    # loop for x-axis labels
    x_axis = ""
    longest_string_length = 0
    category_names = []
    for word in category_list:
        category_names.append(word.category)
        if len(word.category) > longest_string_length:
            longest_string_length = len(word.category)

    i = 0
    while i < longest_string_length:
        letter_part = " " * ((longest_row - longest_bar) + 1) 
        for j in range(len(category_names)):
            # put each letter for the category_names in a row with spacing. Make sure last letter has no spacing.
            # handles unequal word lengths
            try:
                letter_part += category_names[j][i]
            except IndexError:
                letter_part += " "
            if j != len(category_list) - 1:
                letter_part += "  "
            else:
                if i != (longest_string_length -1):
                    letter_part += "\n"
                x_axis += letter_part
        
        i += 1
    formatted_chart = "\n".join(chart)
    
    under_line = (" " * (longest_row - longest_bar)) + under_line 
    histogram_string = f"{graph_title}\n{formatted_chart}\n{under_line}\n{x_axis}"
    return histogram_string

Your browser information:

User Agent is: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/118.0

Challenge: Scientific Computing with Python Projects - Budget App

Link to the challenge:

minus is your incorrect line
plus is the expected correct line
? shows what is different, so here you need an extra space at the end of your line

Here you need an extra 2 spaces.

This line is also useful:

‘Perc[34 chars] \n 90| \n 80| \n 70| o[303 chars] t’ != 'Perc[34 chars] \n 90| \n 80| \n 70| [340 chars] t ’

'Perc[34 chars]     \n 90|         \n 80|         \n 70|    o[303 chars]  
'Perc[34 chars]      \n 90|          \n 80|          \n 70|  [340 chars] t  

You can see how the fist line (your line) needs an extra space before the \n 90 to match the lower line (expected output)

Thank you so much. I’ll remember this for the future. I just solved it now. Thank you for your help

1 Like