Introduction

In this assignment, the fictitious Amazing Banking Corporation (ABC) has asked you to write software for their bank account management system. You will use Python to automate tracking financial records of their clients.

Goals of this Assignment

  • Write function bodies using dictionaries and file reading.
  • Write code to mutate lists and dictionaries.
  • Use top down design to break a problem down into subtasks and implement helper functions to complete those tasks.
  • Write tests to check whether a function is correct.

Files to Download

Starter code:

banking_functions.py

This file contains some of the headers for the functions you will need to write for this assignment, and a few completed function docstrings. You will start development here, using the Function Design Recipe process to implement each required function. The functions in this file can call each other, and several of them will also be called by the main program (client_program.py). You can, and should, write some helper functions in this file that are called by your required functions.

test_open_savings_account.py and test_get_financial_range_to_clients.py

We are providing the beginning of two unit test files, test_open_savings_account.py and test_get_financial_range_to_clients.py. These files will contain your unit tests for the open_savings_account and get_financial_range_to_clients functions, respectively, that are described below.

Data: client_data_1.txt and client_data_2.txt

The client_data_*.txt files contain a small amount data for (fictitious) clients. . These files are sample data, and you should not modify these files. You may want to create your own data files to supplement these file for testing. See the next section for instructions on how to interpret this data.

Main Program: client_program.py

The file contains a program that loads some data and then calls some functions that you will implement in banking_functions.py. You do not need to modify this file. After you have implemented all of your banking_functions.py functions, you can run this program to interactively test your code using different profiles.

Checker: a3_checker.py

We have provided a checker program that you should use to check your code. See below for more information about a3_checker.py.

Background

This assignment will depend on some financial terminology and formulas which are summarized here for your reference. The definitions are given solely in the context of the assignment, and are not meant to be wholly representative of the usage of the terminology.

  • Balance: The amount of money present in the user's account. We will consider only non-negative account balances (i.e. zero or greater) for all chequing and savings accounts.
  • Interest: The rate of return on a bank balance. In this assignment, we will refer to this as a percentage value.
    • Compound period: The interval at which the interest rate is calculated on the bank balances. We will consider all interest rates to be stated and compounded per annum.
  • Present Value (PV): The value of money at the current time.
  • Future Value (FV): The future value that the money will hold at a later time, based on the present value and compound interest.

The (simplified) formula to find FV, given on an interest rate of i % compounded annually for n years is given as: FV = PV * ((1 + i/100) ** n)

Constants

You may refer back to A1 for a detailed description of the purpose and usage of constants. We have provided the following constants in the starter code for you to use in your solutions. Read on to see how they should be used in your code. Note that you may not need to use all the constants provided.

Constants provided in the starter code

To represent data indices in the client_to_accounts Dict To represent transactions (withdrawal or deposit) To represent pre-set interest rates
Constant Value Constant Value Constant Value
BALANCES 0 WITHDRAW_CODE -1 LOAN_INTEREST_RATE 2.2
INTEREST_RATES 1 DEPOSIT_CODE 1

Data Format

The information for clients in the bank's database is stored in a file. Your program will need to read the data from the file and process it.

The file contains 0 or more clients. Each client's information has the following format:

  • 1 line containing the client's name.
  • 1 line containing the client's social insurance number (SIN).
  • 3 lines for each bank account the client has (the user can have 0 or more bank accounts).
    • 1 line for the bank account type (and number, if the account is a savings account). If the client has one or more accounts open, the first bank account in the file is always a chequing account, and any subsequent accounts (if any) are savings accounts.
    • 1 line for the bank account balance (always 0 or greater).
    • 1 line for the interest rate (per annum, as a percentage value) for the bank account.

The functions that you write later in this assignment will allow clients to open loan accounts with a negative balance, but these loan accounts will never be present in the files you will read in to your program.

Your Tasks

You are required to:

  • write 12 required functions
  • write unittests for two of the required functions (open_savings_account and get_financial_range_to_clients)

Data Structures

A "client to accounts" dictionary (Dict[Tuple[str, int], List[List[float]]) is our data structure for representing the mapping of clients to their bank accounts.

  • key: a tuple of the client's name (str) and SIN (int) in the format:
(FirstName LastName, SIN)
  • value: a two-element list of lists; the sublists with index BALANCES and INTEREST_RATES contain the bank balances and their respective interest rates, respectively. The default value for BALANCES is 0, and the default value for INTEREST_RATES is 1. A loan account, if it exists for the client, will appear at the end of the lists (i.e. indexes the sublists with -1). The number of loan accounts for any client will never exceed one (i.e. it is impossible for a client to have more than one loan account). Chequings and savings accounts always have a balance 0 or greater. Loan accounts always have a balance less than 0.
  • example: this dictionary represents the "client to accounts" data when it is read from client_data_1.txt:
{('Karla Hurst', 770898021): [[768.0, 2070.0], [0.92, 1.5]],

('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],

('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}
  • Karla Hurst has a SIN of 770 898 021, and bank balances of $768.00 and $2070.00 in the Chequing Account and Savings Account 1, respectively. The Chequing Account has an interest rate of 0.92% per annum, and Savings Account 1 has an interest rate of 1.5% per annum.

A "client to total balance" dictionary (Dict[Tuple[str, int], float]) is our data structure for representing the mapping of clients to the total balance across all bank accounts they have open.

  • key: a tuple of the client's name (str) and SIN (int) in the format:
(FirstName LastName, SIN)
  • value: the total balance the client has across all accounts(float)
  • example: this dictionary represents the "client to total balance" dictionary created from the data in client_data_1.txt
{('Karla Hurst', 770898021): 2838.0,
('Pamela Dickson', 971875372): 175705921.0,
('Roland Lozano', 853887123): 7829.0}

Required Testing (unittest)

Write (and submit) unittest test files for functions open_savings_account and get_financial_range_to_clients. These tests should be implemented in the appropriately named starter files.

We will evaluate the completeness of your test files by running them against flawed implementations we have written to see how many errors you catch. Avoid redundant tests. The goal is to catch all of our errors without extra, unnecessary tests.

Your unittest testfiles should stand alone: they should require no additional files (like a clients file). Instead, you should define appropriate test values in the file, such as a dictionary that might be generated from reading a clients file, to use for your tests. You may assume that the folder that contains your unittest test files also contains a banking_functions.py file.

Recall that floating-point numbers are approximations of real numbers.

Required Functions

This section contains a table with detailed descriptions of the 12 functions that you must complete. You'll need to add a second example to the docstrings for each function in the starter code.

We provided 3 helper function in the starter code that you may use. You should follow the approach we've been using on large problems recently and write additional helper functions to break these high-level tasks down. Each helper function must have a clear purpose. Each helper function must have a complete docstring produced by following the Function Design Recipe. You should test your helper functions to make sure they work.

Functions to write for A3

load_financial_data: (TextIO) -> Dict[Tuple[str, int], List[List[float]]]

The parameter refers to a file that is open for reading. The data in the file is in the Data Format described above. This function must build a "client to accounts" dictionary with the data from the open file, and return it.

Notes:

  • You do not need to finish this function before working on the rest of the assignment. If you get stuck on this function, move on to the other functions and come back to it.
  • If a person does not have any accounts, then they must not appear in the "client to accounts" dictionary.

get_num_clients: (Dict[Tuple[str, int], List[List[float]]]) -> int

The parameter represents a "client to accounts" dictionary. This function must return the total number of clients present in the "client to accounts" dictionary.

validate_identity: (Dict[Tuple[str, int], List[List[float]]], str, int) -> bool

The first parameter represents a "client to accounts" dictionary, and the second and third parameters represent a name and SIN, respectively. This function must return True if the name and SIN provided represent a valid client that is present in the "client to accounts" dictionary, and False otherwise.

get_num_accounts: (Dict[Tuple[str, int], List[List[float]]], Tuple[str, int]) -> int

The first parameter represents a "client to accounts" dictionary, and the second parameter represents a valid client in the dictionary. This function must return the total number of accounts the client has open, not including any loan accounts.

For example, given the following argument for the "client to accounts" dictionary (based on client_data_1.txt):

{('Karla Hurst', 770898021): [[768.0, 2070.0], [0.92, 1.5]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

With the argument passed for the client as:

('Karla Hurst', 770898021)

The function should return:

2

get_account_balance: (Dict[Tuple[str, int], List[List[float]]], Tuple[str, int], int) -> float

The first parameter represents a "client to accounts" dictionary, the second parameter represents a valid client, and the third parameter represents the account number. The function should return the balance of the account number given. The account number is 0 for the chequing account, and 1, 2, ... for the subsequent savings accounts. If the given account number is -1, this function may assume a loan account exists and should return the loan balance.

For example, given the following argument for the "client to accounts" dictionary (based on client_data_1.txt):

{('Karla Hurst', 770898021): [[768.0, 2070.0], [0.92, 1.5]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

When the argument for the client is:

('Karla Hurst', 770898021)

And the argument for the account number is:

1

The function should return:

2070.0

open_savings_account: (Dict[Tuple[str, int], List[List[float]]], Tuple[str, int], float, float) -> None

The first parameter represents a "client to accounts" dictionary, the second parameter represents a valid client, and the third and fourth parameters represent the balance and interest rate to open the new account with, respectively. This function should update the "client to accounts" dictionary, where the new account is added:

  • at the end of the lists if the client does not have a loan account open.
  • immediately before the loan account if the client has a loan account open.

For example, given the following "client to accounts" dictionary:

{('Karla Hurst', 770898021): [[768.0, 2070.0, -1000], [0.92, 1.5, 2.2]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

The result of opening a new savings account with a balance of $20.00 and interest rate of 1.2 for Karla Hurst is the following updated "client to accounts" dictionary:

{('Karla Hurst', 770898021): [[768.0, 2070.0, 20.0, -1000], [0.92, 1.5, 1.2, 2.2]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

get_client_to_total_balance: (Dict[Tuple[str, int], List[List[float]]]) -> Dict[Tuple[str, int], float]

The parameter represents a "client to accounts" dictionary. This function should return a dictionary with the following format:

  • key: a tuple of the client's name (str) and SIN (int) in the format:
(FirstName LastName, SIN)
  • value: a float representing the total value across all account balances including the loan account, if it exists..
  • example: this dictionary represents the "client to accounts" data from client_data_1.txt:
{('Karla Hurst', 770898021): [[768.0, 2070.0], [0.92, 1.5]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

The function should return:

{('Karla Hurst', 770898021): 2838.0,
('Pamela Dickson', 971875372): 175705921.0,
('Roland Lozano', 853887123): 7829.0}

update_balance: (Dict[Tuple[str, int], List[List[float]]], Tuple[str, int], int, float, int) -> None

The first parameter represents a "client to accounts" dictionary, the second parameter represents a valid client, the third parameter represents an account number (chequing, savings, or loan), the fourth parameter represents an amount to change the balance by, and the fifth parameter represents a transaction code which is either WITHDRAW_CODE or DEPOSIT_CODE indicated in the "constants" section.

The account number is between 0 and the highest number saving account open, OR -1 for a loan account. You may need to use the constants WITHDRAW_CODE and DEPOSIT_CODE in this function.

This function should update the "client to accounts" dictionary, where the client's account balance, indicated by the account number, is modified by withdrawing the amount indicated if the transaction code is WITHDRAW_CODE, or depositing the amount indicated if the transaction code is DEPOSIT_CODE. If the transaction is a withdrawal, the function does not need to check if the account has sufficient funds to withdraw the specified amount.

get_loan_status: (Dict[Tuple[str, int], List[List[float]]], Dict[Tuple[str, int], float], Tuple[str, int], float) -> bool

The first parameter represents a "client to accounts" dictionary, the second parameter represents a "client to total balance" dictionary, the third parameter represents a valid client, and the fourth parameter represents a positive loan amount the client is seeking approval for.

This function should return True if the loan is approved and False otherwise. If the loan is approved, the function should modify the balance of the client's chequing account to be increased by the loan amount. Additionally, a new account should be opened at the end of the list with a negative balance of the loan amount, with a loan interest rate of LOAN_INTEREST_RATE.

The "average total balance" is calculated by taking the average of the "total balance" across all clients (as given by the value entries of the "client to total balance" dictionary). You will need to call the provided helper function, get_sd and pass in a list of all of the values from the "client to total balance" dictionary. The return value of get_sd will be referred to as sigma. In statistics, this is the standard deviation of a set of values.

A loan application is rejected if either of the following conditions are met:

  • a pre-existing loan account exists with an outstanding negative balance
  • the client does not have a single account with a balance greater than 0

Otherwise, the loan application is evaluated using a point scoring system, indicated below:

  • has a total balance greater than the average total balance: +1 point
  • has a total balance greater than or equal to the loan amount: +1 point
  • for each bank account:
    • the balance is less than average total balance - sigma (i.e. less than one standard deviation below the mean): -2 points
    • the balance is more than average total balance + sigma (i.e. greater than one standard deviation above the mean): +2 points
    • the balance is greater than the loan amount and there is no existing loan account: +5 points
  • half or more of the bank balances are above +1 sigma of the average total balance: +5 points

If the client accumulates 5 or more points, the loan is approved.

get_financial_range_to_clients: (Dict[Tuple[str, int], float], List[Tuple[float, float]])-> Dict[Tuple[float, float], List[Tuple[str, int]]]

The first parameter represents a "client to total balance" dictionary and the second parameter represents a list of financial ranges. The financial ranges are given within the list as tuples, as ordered pairs of float values. This function should return a dictionary with the following format:

  • key: a two-element tuple of the financial range limits (float) in the format:
(LowerLimit, UpperLimit)

Hint: you may wish to use the dict.pop() method for this function.

For example, if the "client to total balance" argument passed is:

{('Karla Hurst', 770898021): 2838.0,
('Pamela Dickson', 971875372): 175705921.0,
('Roland Lozano', 853887123): 7829.0}

and the financial ranges parameter is passed as:

[(100, 5000), (0, 500), (6000, 100000)]

The function should return:

{(100, 5000): [('Karla Hurst', 770898021)],
(6000, 100000): [('Roland Lozano', 853887123)]}

get_fv_from_accounts: (List[float], List[float], int) -> float

The first parameter represents a list of account balances, the second parameter represents a list of interest rates, and the third parameter represents a time in years. This function should return the sum of the future values of all the account balances given their respective interest rates. To do this, the function should: calculate the FV for each account using the PV, i, and n given, then sum the FVs calculated across all the accounts. Do not round the result. Note that the account balances here are the PV.

Hint: you can use one of the given helper functions here.

For example, if the account balances argument is passed as:

[768.0, 2070.0]

With the following interest rates over a time period (n) of 2 years:

[0.92, 1.5]

The function should return:

2914.761953519999

The individual FVs for each bank account balance are calculated using the formula given in the Background section, then summed to get the result:

768.0 * (1 + 0.92/100) ** 2 + 2070.0 * (1 + 1.5/100)**2

time_to_client_goal: (Dict[Tuple[str, int], List[List[float]]], Tuple[str, int], float)->int

The first parameter represents a "client to accounts" dictionary, the second parameter represents a valid client, and the third parameter represents a dollar amount financial "goal" the client wishes to reach. This function should determine the smallest number of integer years it would take for the client's total balance to reach or exceed the goal, given the PV as the current bank balances in the client's accounts and their respective rates of return. You may assume the client does not make any deposits or withdrawals in this calculation. You may also wish to use another function you have written in this assignment to complete this function. You may assume that the given financial goal is a non-negative number.

You may also assume that the client's accounts can reach the given financial goal in a finite amount of time.

For example, if the "client to accounts" dictionary argument is passed as the data from "client_data_1.txt":

{('Karla Hurst', 770898021): [[768.0, 2070.0], [0.92, 1.5]],
('Pamela Dickson', 971875372): [[36358866.0, 5395448.0, 23045442.0, 14316660.0, 45068981.0, 4438330.0, 16260321.0, 7491204.0, 23330669.0], [2.3, 2.35, 2.25, 2.35, 2.05, 2.1, 2.45, 2.4, 2.0]],
('Roland Lozano', 853887123): [[1585.0, 1170.0, 1401.0, 3673.0], [0.63, 0.05, 0.34, 0.92]]}

and the client argument is passed as:

('Karla Hurst', 770898021)

and the financial goal parameter is:

100000.0

Then the function should return:

255

A3 Checker

We are providing a checker module (a3_checker.py) that tests two things:

  • whether your code follows the Python Style Guidelines, and
  • whether your functions are named correctly, have the correct number of parameters, and return the correct types.

To run the checker, open a3_checker.py and run it. Note: the checker file should be in the same directory as your banking_functions.py, as provided in the starter code zip file. Be sure to scroll up to the top and read all messages.

If the checker passes for both style and types:

  • Your code follows the style guidelines.
  • Your function names, number of parameters, and return types match the assignment specification. This does not mean that your code works correctly in all situations. We will run a different set of tests on your code once you hand it in, so be sure to thoroughly test your code yourself before submitting.

If the checker fails, carefully read the message provided:

  • It may have failed because your code did not follow the style guidelines. Review the error description(s) and fix the code style.
  • It may have failed because:
    • you are missing one or more function,
    • one or more of your functions is misnamed,
    • one or more of your functions has the incorrect number or type of parameters, or
    • one of more of your function return types does not match the assignment specification.

Read the error message to identify the problematic function, review the function specification in the handout, and fix your code.

Testing your Code

It is strongly recommended that you test each function as soon as you write it. As usual, follow the Function Design Recipe (we've provided the function name and types for you) to implement your code. Once you've implemented a function, run it against the examples in your docstrings and the unit tests you've defined.

Additional requirements

  • Do not call print, input, or open, except within the if __name__ == '__main__' block.
  • Do not use any break or continue statements.
  • Do not modify or add to the import statements provided in the starter code.
  • Do not add any code outside of a function definition.
  • Do not mutate objects unless specified.

Starter Codes

a3_checker.py

import sys
sys.path.insert(0, 'pyta')

import builtins
import banking_functions as abc
import io
import python_ta

def _mock_disallow(func_name: str):
"""Raise an Exception saying that use of function func_name is not
allowed.

"""

def mocker(*args):
raise Exception(
"The use of function {} is not allowed.".format(func_name))

return mocker

test_module = sys.modules['banking_functions']
setattr(test_module, "input", _mock_disallow("input"))
setattr(test_module, "print", _mock_disallow("print"))

print('==================== Start: checking coding style ===================')

python_ta.check_all('banking_functions.py', config='pyta/a3_pyta.txt')

print('=================== End: checking coding style ===================n')


# Get the initial value of the constants
constants_before = [abc.BALANCES, abc.INTEREST_RATES,
abc.WITHDRAW_CODE, abc.DEPOSIT_CODE, abc.LOAN_INTEREST_RATE]

print('============ Start: checking parameter and return types ============')
# Type check abc.load_financial_data

game_file = open("client_data_1.txt", 'r')
result = abc.load_financial_data(game_file)
game_file.close()

print('Checking load_financial_data...')
assert isinstance(result, dict),
'''abc.load_financial_data should return a dict, but returned {0}
.'''.format(type(result))

for key, value in result.items():
assert isinstance(key, tuple),
'''abc.load_financial_data should return a dict where the keys are
tuples, but key {0} is of type {1}.
'''.format(key, type(key))
assert isinstance(key[0], str),
'''abc.load_financial_data should return a dict where its keys are tuples
with type str at index 0, but {0} is of type {1}.
'''.format(key[0], type(key[0]))
assert isinstance(key[1], int),
'''bc.load_financial_data should return a dict where its keys are tuples
with type int at index 1, but {0} is of type {1}.
'''.format(key[1], type(key[1]))
assert isinstance(value, list),
'''abc.load_financial_data should return a dict where its values are of
of type list, but {0} is of type {1}.
'''.format(value, type(value))
assert isinstance(value[0], list),
'''abc.load_financial_data should return a dict where its values are lists
with type list at index 0
of type list, but {0} is of type {1}.
'''.format(value, type(value[0]))
assert isinstance(value[1], list),
'''abc.load_financial_data should return a dict where its values are lists
with type list at index 1
of type list, but {0} is of type {1}.
'''.format(value, type(value[1]))
assert isinstance(value[0][0], float),
'''abc.load_financial_data should return a dict where its values are list
of list of floats, but {0} is of type {1}.
'''.format(value, type(value[0][0]))
assert isinstance(value[1][0], float),
'''abc.load_financial_data should return a dict where its values are list
of list of floats, but {0} is of type {1}.
'''.format(value, type(value[1][0]))

print(' check complete')

print('Checking get_num_clients...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.get_num_clients(client_to_accounts)
assert isinstance(result, int),
'''abc.get_num_clients should return an int, but returned {0}
.'''.format(type(result))
print(' check complete')


print('Checking validate_identity...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.validate_identity(client_to_accounts, 'Bob Bob', 543210999)
assert isinstance(result, bool),
'''abc.validate_identity should return a bool, but returned {0}
.'''.format(type(result))
print(' check complete')

print('Checking get_num_accounts...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.get_num_accounts(client_to_accounts, ('Bob Bob', 777777777))
assert isinstance(result, int),
'''abc.get_num_accounts should return an int, but returned {0}
.'''.format(type(result))
print(' check complete')

print('Checking get_account_balance...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.get_account_balance(client_to_accounts, ('Bob Bob', 777777777), 0)
assert isinstance(result, float),
'''abc.get_num_accounts should return a float, but returned {0}
.'''.format(type(result))
print(' check complete')


print('Checking open_savings_account...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.open_savings_account(client_to_accounts, ('Bob Bob', 777777777), 1.0, 1.0)
assert result == None,
'''abc.get_num_accounts should return None, but returned {0}
.'''.format(type(result))
print(' check complete')

## --------- get client to total balance
print('Checking get_client_to_total_balance...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.get_client_to_total_balance(client_to_accounts)
assert isinstance(result, dict),
'''abc.get_num_accounts should return dict, but returned {0}
.'''.format(type(result))

# check the keys and values
for key, value in result.items():
assert isinstance(key, tuple),
'''abc.load_financial_data should return a dict where the keys are
tuples, but key {0} is of type {1}.
'''.format(key, type(key))
assert isinstance(key[0], str),
'''abc.load_financial_data should return a dict where its keys are tuples
with type str at index 0, but {0} is of type {1}.
'''.format(key[0], type(key[0]))
assert isinstance(key[1], int),
'''bc.load_financial_data should return a dict where its keys are tuples
with type int at index 1, but {0} is of type {1}.
'''.format(key[1], type(key[1]))
assert isinstance(value, float),
'''abc.load_financial_data should return a dict where its values are of
of type float, but {0} is of type {1}.
'''.format(value, type(value))

print(' check complete')
## --------- end check get client to total balance

print('Checking update_balance...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.update_balance(client_to_accounts, ('Bob Bob', 777777777), 0, 1.0, abc.DEPOSIT_CODE)

assert result == None,
'''abc.update_balance should return None, but returned {0}
.'''.format(type(result))
print(' check complete')

print('Checking get_loan_status...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
client_to_total_balance = {('Bob Bob', 777777777): 1.0, ('Carly Dafford', 555555555): 2.0}
result = abc.get_loan_status(client_to_accounts, client_to_total_balance, ('Bob Bob', 777777777), 10.0)

assert isinstance(result, bool),
'''abc.get_loan_status should return a bool, but returned {0}
.'''.format(type(result))
print(' check complete')

# --- get_financial_range_to_clients
print('Checking get_financial_range_to_clients...')
client_to_total_balance = {('Bob Bob', 777777777):1.0, ('Carly Dafford', 555555555):1.0}
financial_ranges = [(0.0, 1.0), (1.5, 2.0)]
result = abc.get_financial_range_to_clients(client_to_total_balance, financial_ranges)

# check the keys and values
for key, value in result.items():
assert isinstance(key, tuple),
'''abc.load_financial_data should return a dict where the keys are
tuples, but key {0} is of type {1}.
'''.format(key, type(key))
assert isinstance(key[0], float),
'''abc.load_financial_data should return a dict where its keys are tuples
with type float at index 0, but {0} is of type {1}.
'''.format(key[0], type(key[0]))
assert isinstance(key[1], float),
'''abc.load_financial_data should return a dict where its keys are tuples
with type float at index 1, but {0} is of type {1}.
'''.format(key[1], type(key[1]))
assert isinstance(value, list),
'''abc.load_financial_data should return a dict where its values are of
of type list, but {0} is of type {1}.
'''.format(value, type(value))
assert isinstance(value[0], tuple),
'''abc.load_financial_data should return a dict where its values are lists
of tuples, but {0} is of type {1}.
'''.format(value[0], type(value[0]))
assert isinstance(value[0][0], str),
'''abc.load_financial_data should return a dict where its values are lists
of tuples with type str at index 0, but {0} is of type {1}.
'''.format(value[0][0], type(value[0][0]))
assert isinstance(value[0][1], int),
'''abc.load_financial_data should return a dict where its values are lists
of tuples with type int at index 1, but {0} is of type {1}.
'''.format(value[0][1], type(value[0][1]))

print(' check complete')
# --- end check get_financial_range_to_clients

print('Checking get_fv_from_accounts...')
result = abc.get_fv_from_accounts([1.0, 1.0], [1.0, 1.0], 1)
assert isinstance(result, float),
'''abc.get_fv_from_accounts should return float, but returned {0}
.'''.format(type(result))
print(' check complete')

print('Checking time_to_client_goal...')
client_to_accounts = {('Bob Bob', 777777777):[[1.0], [1.0]], ('Carly Dafford', 555555555):[[2.0], [1.5]]}
result = abc.time_to_client_goal(client_to_accounts, ('Bob Bob', 777777777), 10)
assert isinstance(result, int),
'''abc.time_to_client_goal should return int, but returned {0}
.'''.format(type(result))
print(' check complete')

print('============= End: checking parameter and return types =============n')

print('========== Start: checking whether constants are unchanged ==========')

# Get the final values of the constants
constants_after = [abc.BALANCES, abc.INTEREST_RATES,
abc.WITHDRAW_CODE, abc.DEPOSIT_CODE, abc.LOAN_INTEREST_RATE]

print('Checking constants...')
# Check whether the constants are unchanged.
assert constants_before == constants_after,
'''Your function(s) modified the value of one or more constants.
Edit your code so that the value of the constants are not
changed by your functions.'''

print(' check complete')

print('=========== End: checking whether constants are unchanged ===========')

banking_functions.py

"""Amazing Banking Corporation functions"""
from typing import List, Tuple, Dict, TextIO

# Constants
# client_to_accounts value indexing
BALANCES = 0
INTEREST_RATES = 1

# transaction codes
WITHDRAW_CODE = -1
DEPOSIT_CODE = 1

LOAN_INTEREST_RATE = 2.2 #percent

## ------------ HELPER FUNCTIONS GIVEN BELOW -----------
# do not modify

def display_client_accounts(client_to_accounts: Dict, client: Tuple)-> None:
''' Display the indicated client's account balances in a human-friendly
format, using the client_to_account dictionary.

The first account is a chequing account, followed by subsequent savings
account(s). A loan account, if present, is signified as the last account
if it has a negative balance.
'''

i = 0
for account in client_to_accounts[client][BALANCES]:
if i == 0:
# the first account is always a chequing account
print("Chequing Account")
elif account > 0:
print("Savings Account {}".format(i))
else:
print("Loan Account")
print("$ {:.2f}".format(account))
i += 1

def get_fv(present_value: float, r: float, n: int)->float:
''' Return the future value calculated using the given present value (pv)
growing with rate of return (interest rate) r, compounded annually,
for a total of n years.

r is given as a percentage value.

'''
return present_value * (1 + r/100)**n


def get_sd(x: List[float]) -> float:
'''
Return the standard deviation of the values in the list x.
'''
n = len(x)
x_bar = (sum(x))/n

sd = 0
for x_i in x:
sd += (x_i - x_bar) ** 2

return (sd/n) ** 0.5


### ----------- END OF PROVIDED HELPER FUNCTIONS --------------

# Implement the required functions below
def get_num_clients(client_to_accounts: Dict[Tuple[str, int],
List[List[float]]])->int:
''' Returns the number of clients present in the client_to_accounts dictionary.

>>> get_num_clients({('Bob', 555555555): [[100.0], [1.0]],
>>> ('Sally', 123123123): [[250.0], [2.0]]})
2
'''
# complete this function

if __name__ == "__main__":
import doctest
# uncomment the following line to run your docstring examples
#doctest.testmod()

client_data_1.txt

Karla Hurst
770 898 021
Chequing Account
Balance: 768
Interest rate per annum: 0.92
Savings Account 1
Balance: 2070
Interest rate per annum: 1.5

Pamela Dickson
971 875 372
Chequing Account
Balance: 36358866
Interest rate per annum: 2.3
Savings Account 1
Balance: 5395448
Interest rate per annum: 2.35
Savings Account 2
Balance: 23045442
Interest rate per annum: 2.25
Savings Account 3
Balance: 14316660
Interest rate per annum: 2.35
Savings Account 4
Balance: 45068981
Interest rate per annum: 2.05
Savings Account 5
Balance: 4438330
Interest rate per annum: 2.1
Savings Account 6
Balance: 16260321
Interest rate per annum: 2.45
Savings Account 7
Balance: 7491204
Interest rate per annum: 2.4
Savings Account 8
Balance: 23330669
Interest rate per annum: 2.0

Roland Lozano
853 887 123
Chequing Account
Balance: 1585
Interest rate per annum: 0.63
Savings Account 1
Balance: 1170
Interest rate per annum: 0.05
Savings Account 2
Balance: 1401
Interest rate per annum: 0.34
Savings Account 3
Balance: 3673
Interest rate per annum: 0.92

client_data_2.txt

Karla Hurst
770 898 021
Chequing Account
Balance: 768
Interest rate per annum: 0.92
Savings Account 1
Balance: 2070
Interest rate per annum: 1.5

Maurice Daisy
770 898 021
Chequing Account
Balance: 768
Interest rate per annum: 0.92
Savings Account 1
Balance: 2070
Interest rate per annum: 1.5

Pamela Dickson
971 875 372
Chequing Account
Balance: 36358866
Interest rate per annum: 2.3
Savings Account 1
Balance: 5395448
Interest rate per annum: 2.35
Savings Account 2
Balance: 23045442
Interest rate per annum: 2.25
Savings Account 3
Balance: 14316660
Interest rate per annum: 2.35
Savings Account 4
Balance: 45068981
Interest rate per annum: 2.05
Savings Account 5
Balance: 4438330
Interest rate per annum: 2.1
Savings Account 6
Balance: 16260321
Interest rate per annum: 2.45
Savings Account 7
Balance: 7491204
Interest rate per annum: 2.4
Savings Account 8
Balance: 23330669
Interest rate per annum: 2.0

Roland Lozano
853 887 123
Chequing Account
Balance: 1585
Interest rate per annum: 0.63
Savings Account 1
Balance: 1170
Interest rate per annum: 0.05
Savings Account 2
Balance: 1401
Interest rate per annum: 0.34
Savings Account 3
Balance: 3673
Interest rate per annum: 0.92

Louise Revilla
853 887 123
Chequing Account
Balance: 1585
Interest rate per annum: 0.63
Savings Account 1
Balance: 1170
Interest rate per annum: 0.05
Savings Account 2
Balance: 1401
Interest rate per annum: 0.34
Savings Account 3
Balance: 3673
Interest rate per annum: 0.92

Alvin Beacom
521 494 658
Chequing Account
Balance: 913
Interest rate per annum: 1.5
Savings Account 1
Balance: 733
Interest rate per annum: 0.63

Heather Callahan
623 827 565
Chequing Account
Balance: 34302106
Interest rate per annum: 2.05
Savings Account 1
Balance: 20328170
Interest rate per annum: 2.3
Savings Account 2
Balance: 39731637
Interest rate per annum: 2.15
Savings Account 3
Balance: 18462373
Interest rate per annum: 2.3
Savings Account 4
Balance: 36642460
Interest rate per annum: 2.2
Savings Account 5
Balance: 39373170
Interest rate per annum: 2.4
Savings Account 6
Balance: 28580891
Interest rate per annum: 2.1
Savings Account 7
Balance: 37368572
Interest rate per annum: 2.15
Savings Account 8
Balance: 33702574
Interest rate per annum: 2.2
Savings Account 9
Balance: 21639888
Interest rate per annum: 2.45

Robert Garza
133 295 618
Chequing Account
Balance: 44426
Interest rate per annum: 1.7
Savings Account 1
Balance: 24374
Interest rate per annum: 1.7

Monica Girard
521 494 658
Chequing Account
Balance: 913
Interest rate per annum: 1.5
Savings Account 1
Balance: 733
Interest rate per annum: 0.63

Thomas Strohm
454 554 353
Chequing Account
Balance: 3639
Interest rate per annum: 2.08
Savings Account 1
Balance: 3432
Interest rate per annum: 2.66
Savings Account 2
Balance: 4059
Interest rate per annum: 0.92

client_program.py

import banking_functions as abc
if __name__ == "__main__":
data_fname = input("Enter the name of the client file to use: ")

valid_name = 0
while not valid_name:
try:
clients_file = open(data_fname)
valid_name = 1
except:
print("ERROR! Invalid filename.")
data_fname = input("Enter the name of the client file to use: ")
valid_name = 0


client_to_accounts = abc.load_financial_data(clients_file)

while True:

#Validate user
print("------ Welcome to ABC's automated banking service. ------")
client_name = input("Please enter your name as : ")
client_sin = input('''For secondary authentication,
please enter your SIN as ### ### ###: ''')
client_sin = client_sin.strip()
client_sin = int(''.join(client_sin.split()));

is_valid_identity = abc.validate_identity(client_to_accounts,
client_name, client_sin)

if not is_valid_identity:
print("Your credentials do not match any profiles on record. Goodbye.")

else:
print('''Your credentials have been succesfully validated.
Please choose from the following banking options:''')
options = ['Make a transaction', 'Check total balance',
'Check account balances', 'Check savings goal',
'Sign out']

client_option = None

while client_option != 5:
print("nn")
client = (client_name, client_sin)

for i in range(len(options)):
print("({}) ".format(i+1) + options[i])

print('''**Indicate the number of the
option you would like to select**''')

client_option = int(input(">>>>>>>>>>> "))

if client_option == 1:
print("Indicate an account to perform a transaction:")
abc.display_client_accounts(client_to_accounts, client)
account_number = int(input('''**Enter 0 for Chequing,
or the Savings Account number**
n>>>>>>>>>>> '''))

if account_number >= abc.get_num_accounts(client_to_accounts, client):
print("Invalid account number. Transaction cancelled.")
else:
account_balance = abc.get_account_balance(client_to_accounts,
client, account_number)
account_type = ['chequing', 'savings'][account_number % 2]
print('''Your selected {} account has an
available balance of {}'''.format(account_type,
account_balance))

transaction_code = int(input('''**Enter 1 to deposit,
-1 to withdraw** n>>>>>>>>>>> '''))

if not (transaction_code == 1 or transaction_code == -1):
print("Invalid transaction code. Transaction cancelled.")

else:
transaction_amount = float(input('''**Enter the amount you would
like to {}**n>>>>>>>>>>>
'''.format(['',
'deposit',
'withdraw'][transaction_code])))

if transaction_code == abc.WITHDRAW_CODE and transaction_amount > account_balance:
print("Insufficient funds. Transaction cancelled.")
else:
abc.update_balance(client_to_accounts, client, account_number, transaction_amount, transaction_code)
new_account_balance = abc.get_account_balance(client_to_accounts, client, account_number)
print("Your {} account now has a balance of {}.".format(account_type, new_account_balance))

elif client_option == 2:
client_to_total_balance = abc.get_client_to_total_balance(client_to_accounts)
print("Your total balance across all accounts is {}".format(client_to_total_balance[client]))

elif client_option == 3:
print("Your account balances are:")
abc.display_client_accounts(client_to_accounts, client)
elif client_option == 4:
savings_goal = float(input("**Enter a desired savings amount**n>>>>>>>>>>> "))
savings_period = abc.time_to_client_goal(client_to_accounts, client, savings_goal)
client_fv = abc.get_fv_from_accounts(client_to_accounts[client][abc.BALANCES],
client_to_accounts[client][abc.INTEREST_RATES], savings_period)
print("You will reach your savings goal in {} year(s), with an amount of {:.2f}".format(savings_period, client_fv))
elif client_option == 5:
print("Thank you for choosing ABC. Goodbye.")
else:
print("Invalid option")

test_get_financial_range_to_clients.py

"""A3. Test cases for function banking_functions.get_financial_range_to_clients.
"""

import unittest
import banking_functions


class TestGetFinancialRangeToClients(unittest.TestCase):
"""Test cases for function
banking_functions.get_financial_range_to_clients.
"""

def test_00_empty(self):
param1 = {}
param2 = [()]
actual = banking_functions.get_financial_range_to_clients(param1, param2)
expected = {}
msg = "Expected {}, but returned {}".format(expected, actual)
self.assertEqual(actual, expected, msg)

def test_01_one_person_within_one_range(self):
param1 = {('Bob Bob', 786543210):[10.0]}
param2 = [(0, 100000)]
actual = banking_functions.get_financial_range_to_clients(param1, param2)
expected = {(0, 100000): ('Bob Bob', 786543210)}
msg = "Expected {}, but returned {}".format(expected, actual)
self.assertEqual(actual, expected, msg)


if __name__ == '__main__':
unittest.main(exit=False)

test_open_savings_account.py

"""A3. Test cases for function banking_functions.open_savings_account.
"""

import unittest
import banking_functions


class TesOpenSavingsAccount(unittest.TestCase):
"""Test cases for function
banking_functions.get_client_to_total_balance.
"""

def test_00_one_person_one_account(self):
param = {('Bob Bob', 786543210):[[1.0], [1.5]]}
banking_functions.open_savings_account(param, ('Bob Bob', 786543210), 2.0, 1.1)
expected = {('Bob Bob', 786543210): [[1.0, 2.0], [1.5, 1.1]]}
msg = "Expected {}, but got {}".format(expected, param)
self.assertEqual(param, expected, msg)


if __name__ == '__main__':
unittest.main(exit=False)
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.