Test_Create_Spend_Chart Is not Matching. Please Help

Tell us what’s happening:
I’m on my final test of the budget app and when I get to test_create_spend_chart the code keeps failing. I have looked over every single inch of my code and it is outputting the correct information, I even set up a test function and input the info that the “TEST CODE” is looking for in that test function and it output correctly. The only problem I can find is that when test_create_spend_chart runs, the test module code its self is not giving the instances enough data in order to have the same outcome they are hoping for. However I may be wrong and have just missed something completely. I would appreciate if someone would look over my code and let me know if something is wrong with my code or the test module. Thanks!
Your code so far

class Category:

    def __init__(self, name):
        self.name = name
        self.ledger = list()

    def deposit(self, amount, description=""):
        # print(self.name, "Depositing")
        action = {"amount": amount, "description": description}
        # print("Depositing:", action)
        self.ledger.append(action)

    def withdraw(self, amount, description=""):
        # print(self.name, "Attempting to withdraw")
        if not self.check_funds(float(amount)):
            # print("Withdraw Failed")
            return False
        amountWithdrawn = float(amount * -1)
        action = {"amount": amountWithdrawn, "description": description}
        self.ledger.append(action)
        # print("Withdraw Successful")
        return True

    def get_balance(self):
        total = 0
        for actions in self.ledger:
            amt = float(actions["amount"])
            total += float(amt)
        return float(total)

    def transfer(self, amount, budgetCategory):
        # Check if you got the funds
        if not self.check_funds(amount):
            return False
        self.withdraw(amount, f"Transfer to {budgetCategory.name}")
        budgetCategory.deposit(amount, f"Transfer from {self.name}")

        return True

    def check_funds(self, amount):
        # used by both transfer and withdraw
        return amount <= self.get_balance()

    def __str__(self):
        output = ""
        sps = int(15 - (len(self.name) / 2))
        topLine = ("*" * sps) + self.name + ("*" * sps)
        if len(topLine) > 30:
            topLine = topLine[1:]
        output += topLine + "\n"

        def CreateLine(description, amt):
            lss = 23 - len(description)
            fDesc = description[0:23]
            leftSide = fDesc + (" " * lss)
            strAmt = str("{:.2f}".format(amt))
            rss = 7 - len(strAmt)
            rightSide = (" " * rss) + strAmt
            return leftSide + rightSide

        for item in self.ledger:
            output += CreateLine(item["description"], item["amount"]) + "\n"

        lastLine = "Total: " + str(self.get_balance())
        output += lastLine
        return output


def create_spend_chart(categories):
    # Grabs the total budget from all categories
    def GrabTotalBudget(categs):
        tAmt = 0
        for cat in categs:
            tAmt += cat.get_balance()
        return float(tAmt)

    # Finds what percent of that total budget is going to each category
    def PercentFromTotal(total, amt):
        percent = float(amt / total) * 100
        percent = int(percent)
        percent = str(percent)[0] + "0"
        return int(percent)

    totalAmt = GrabTotalBudget(categories)
    vitalInfo = []

    # pulls out the crucial data I need for the graph
    for cat in categories:
        name = cat.name
        bal = cat.get_balance()
        per = PercentFromTotal(totalAmt, bal)
        circles = (per / 10) + 1
        info = {"name": name, "circles": circles}
        vitalInfo.append(info)

    return FormatGraph(vitalInfo)


# This creates the graph output
def FormatGraph(infoArr):
    output = '''Percentage spent by category\n'''
    names = []
    lineStartCircle = []
    longestLength = 0

    # Grabs the longest line so the function knows when to stop creating lines on the graph
    # Also simplifies the data from infoArr
    for i in range(len(infoArr)):
        names.append(infoArr[i]['name'])
        cStart = 12 - infoArr[i]['circles']
        lineStartCircle.append(cStart)
        if len(names[i]) > longestLength:
            longestLength = len(names[i])

    # This creates one line deals with the top half of the graph
    # and is called repeatedly
    def NextGraphLine(prcent, currLine, lineStarts):
        output = ''
        pls = 3 - (len(str(percent)))
        output += (" " * pls) + str(prcent) + '| '
        for circleStart in lineStarts:
            if circleStart <= currLine:
                output += "o"
            else:
                output += " "
            output += " " * 2
        return output

    # This Creates the bottom line of the graph before it starts with the names
    def BottomGraphLine(lineStarts):
        output = (" " * 4)
        inpsLen = len(lineStarts) * 3
        inpsLen += 1
        output += "-" * inpsLen
        return output
    
    # This Section works similar to "NextGraphLine" but it fills out each line of the 
    # bottom part of the graph
    def NameLines(nameso, currLine):
        output = " " * 5
        for name in nameso:
            curIndex = currLine - 13
            if curIndex < len(name):
                output += str(name)[curIndex]
            else:
                output += " "
            output += " " * 2
        return output
    
    # data needed before drawing the graph
    totalLines = 12 + longestLength
    currentLine = 1
    percent = 100
    firstPart = True
    # runs through all the lines and fills out the output
    while currentLine <= totalLines:
        if firstPart:
            output += str(NextGraphLine(percent, currentLine, lineStartCircle)) + '\n'
            percent -= 10
            if percent < 0:
                output += str(BottomGraphLine(lineStartCircle)) + '\n'
                currentLine += 1
                firstPart = False
        else:
            output += str(NameLines(names, currentLine))
            if currentLine != totalLines:
                output += '\n'
        currentLine += 1
    # finally returns the completed graph
    return output

And This is the bit of the test_module that I seem to be having trouble with

    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.')

Your browser information:

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

Challenge: Budget App

Link to the challenge:

The test is fine. Here’s the chart from the test, followed by yours for comparision.

            "Percentage spent by category\n"
            "100|          \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  "
  100|          
   90|          
   80|          
   70|          
   60|          
   50|          
   40|          
   30| o  o  o  
   20| o  o  o  
   10| o  o  o  
    0| o  o  o  
      ----------
       B  F  E  
       u  o  n  
       s  o  t  
       i  d  e  
       n     r  
       e     t  
       s     a  
       s     i  
             n  
             m  
             e  
             n  
             t  

So, there is a problem with your percent calculation. You need the percent spent by category of the total spent (I think I said that right), rounded down to the nearest 10 percent.

I think this is rounding up.

Thank you for your help. The section you were mentioning was not rounding, it was getting the number of circles I would need based on the percent that was input. However when I started to look at the percent I found where my issue was. When searching through the data, I simply took the budget of each category and compared it to the total budget and they all ended up at around 30% . What I needed to do was to look at each Categories spending, Total it up, and Total the Spending amount as well. Then get the percent from that.
The Section of code I fixed is this:

def create_spend_chart(categories):
    # Grabs the total budget from all categories
    def GrabTotalBudget():
        totalBudget = 0
        totalSpent = 0
        categoryBudgetInfo = {}

        for currentCategory in categories:
            cName = currentCategory.name
            categorySpending = 0

            for action in currentCategory.ledger:

                actionAmount = action["amount"]

                if actionAmount > 0:  # find the Positives
                    totalBudget += actionAmount
                else:  # find the Negatives
                    categorySpending += actionAmount
                    totalSpent += actionAmount

            categoryBudgetInfo[cName] = categorySpending

        categoryBudgetInfo["Total Budget"] = totalBudget
        categoryBudgetInfo["Total Spending"] = totalSpent

        return categoryBudgetInfo

    # Finds what percent of that total budget is going to each category
    # and rounds to the nearest 10th
    def PercentFromTotal(total, amt):
        # Gets the percent
        percent = float(amt / total) * 100
        # makes that percent into an integer with no decimals
        percent = int(percent)
        # only takes the 10's place number then adds a 0 the the end
        # if there is no 10's place it rounds down to 0
        if percent < 10:
            percent = "0"
        else:
            percent = str(percent)[0] + "0"
        # returns the integer of that concatenated string
        return int(percent)

    # Parse the data to retrieve vital info
    def ParsedBudgetData():
        # GrabTotalBudget = {'Business': -10.99, 'Food': -105.55, 'Entertainment': -33.4, 'Total Budget': 2700, 'Total Spending': -149.94}
        totalBudget = GrabTotalBudget()
        # Grab the total amount then remove it from the dictionary
        # I don't use totalAmount here but It would be too troublesome to remove it
        totalAmount = totalBudget["Total Budget"]
        totalBudget.pop("Total Budget")

        # Total Spending calculated with all
        totalSpent = totalBudget["Total Spending"]
        totalBudget.pop("Total Spending")

        # Sets up the array and appends the vital information from the given information
        vitalInformation = []
        for key, value in totalBudget.items():
            categoryName = key
            categorySpending = value
            percentOfTotal = PercentFromTotal(totalSpent, categorySpending)
            numOfCircles = (percentOfTotal / 10) + 1
            information = {"name": categoryName, "circles": numOfCircles}
            vitalInformation.append(information)

        return vitalInformation

    vitalInfo = ParsedBudgetData()
    return FormatGraph(vitalInfo)

I cleaned up the code and fixed the GrabTotalBudget function as well as added a function to sort through the data from GrabTotalBudget and parse the vital information to create the graph.

Thank you for your suggestion :slight_smile: it helped :slight_smile:

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.