ICS 33 Summer 2013
Project #1: Easy Money

Due date and time: Wednesday, July 10, 11:59pm


Introduction

Like many aspects of our lives that we view transparently, when we make many purchases in stores, we often simply hand a piece of plastic to a cashier and — voila! — all of the details are taken care of automatically; the purchase is charged to the card and, if it's a credit card, we'll need to pay for it later. For us, it seems that nothing could be more convenient. Behind the scenes, though, there is a fair amount of technology that plays a role in the transaction:

This project explores a couple of aspects of this problem, by asking you to write a program that runs on behalf of an international credit union that, among other things, issues credit cards. Your program will track information about all of the cards that have been issued, and change that information as transactions are processed. Cards are issued in different currencies (e.g., US Dollars, Japanese Yen), but purchases can be made in currencies other than the one that the card was issued in, with appropriate conversions being done by your program. The information managed by your program will also need to have some level of persistence; when the program is stopped, the current set of card information will need to be saved to a file, and then loaded when the program is started again.

As you work through this project, you'll have the opportunity to learn more about Python classes and, specifically, how to create classes whose objects are more than just holders of data; your objects will be able to do things, as opposed to just storing things. You'll also get a chance to write special methods that overload operators like +, -, <, and so on, and to explore parts of the Python standard library that may be new to you.


The program

Your program manages information about a collection of credit cards. For each card that has been issued, the following information is kept:

At startup, your program will read a currency file called currency.txt, which describes the currencies to be used by the program. The contents of that file are described in the section titled Currencies below. If that file can't be opened, the program should simply print an error message and end.

Also, your program will read the file that contains the saved data from the last execution of the program (if any). This is described in the section titled Persistence below. If this file cannot be opened and read, the program should simply proceed with an empty collection of credit cards.

Once this data has been loaded, it's now time for your program to process commands. Your program should read a sequence of commands from the console and write the output from each command to the console. It should print no prompts or other output (e.g., no user-friendly menus or prompts that say "Enter a command: "), other than the output required in response to each command.

When the EXIT command is entered, the program saves information about all of the credit cards currently issued, so it can be loaded again the next time the program starts. See the section titled Persistence below for more details.


Currencies

While residents of the United States transact most of their business using US Dollars, it's certainly not the case that all financial business across the world is transacted that way. There are almost as many currencies in the world as there are countries, and the relative values of those currencies (as well as the real value of those currencies, in terms of their purchasing power) fluctuate over time. Since your program will be able to handle transactions in any of a number of different currencies, it will be necessary for your program to know those relative values.

When your program starts up, it will expect to read a text file called currency.txt that defines which currencies will be supported and what their relative values are. The format of that file will be as follows:

An example currency file is linked below, with data I derived from the European Central Bank on June 26, 2013 (and converted the baseline currency from Euros to US Dollars). (Note that I made up the number of decimal places for most currencies, so feel free to adjust them if you know the truth is different from my guess; I didn't take the time to research all of them.)

Having read this information at startup, your program will be expected to make the appropriate conversions for all of the currencies specified. Amounts should always be calculated to no more than the maximum number of decimal places for a particular currency (e.g., given my example currency file, you'll never have 1.0562 US Dollars or 75.56 Yen).

In general, you can assume that the file's format will be as described here, but you shouldn't assume that the file will always be precisely the example linked here. There might be more or fewer currencies, and US Dollars will not always be the baseline currency.


Commands

The commands your program should support are listed in the table below. For each command, the Format column specifies what the user would input, and the Output column specifies a list of the possible outputs, but any given instance of a command will only ever generate one line of output.

Command Format Description Output
ISSUE ISSUE currency_code limit Issues a new credit card, denominated in the currency specified. For example, given the example currency file, ISSUE USD 5000 would issue a new credit card denominated in US Dollars with a credit limit of $5,000. The given currency must exist and the credit limit must be positive. Your program must give the card a unique card number, different from all of the others that have ever been issued; you can solve that problem any way you'd like. ISSUED new_card_number
NO_SUCH_CURRENCY
NEGATIVE_LIMIT
CANCEL CANCEL card_number Cancels an existing credit card, given its card number. The card must exist already and it must have a zero balance in order to be canceled (i.e., the card must be paid off before it can be canceled). CANCELED card_number
NO_SUCH_CARD
NONZERO_BALANCE
PURCHASE PURCHASE card_number currency_code amount Makes a purchase against an existing credit card. The card must exist. The purchase can be in any known currency. The amount must be positive. If the card's balance plus the new amount (converted to the card's currency) is not above the card's limit, the balance is increased accordingly and the purchase is authorized; if the new amount would be above the card's limit, the charge is denied. AUTHORIZED card_currency_code new_balance
OVER_LIMIT
NO_SUCH_CARD
NO_SUCH_CURRENCY
NONPOSITIVE_AMOUNT
PAYMENT PAYMENT card_number currency_code amount Makes a payment toward the balance of an existing credit card. The card must exist. The payment can be in any known currency. The amount must be positive. The card's balance is reduced by the amount of the payment (converted to the card's currency); if the amount paid exceeds the card's balance, the balance is only reduced to zero. PAID card_currency_code new_balance
NO_SUCH_CARD
NO_SUCH_CURRENCY
NONPOSITIVE_AMOUNT
EXIT EXIT Exits the program. Note the persistence requirement in the section titled Persistence below. GOODBYE

(If you've ever had a credit card, you may notice that there is no notion of interest here; you only have to pay back exactly what you spend. While this lacks some realism, it's one detail that I've left out to keep things simpler.)

An example of the program's execution

The following is an example of the program's execution, as it should be. Boldfaced, italicized text indicates input, while normal text indicates output. This example assumes the use of the example currency file linked above. The card numbers issued by my program might be different from the card numbers issued by yours, and that's fine; the only requirement is that they're unique.

ISSUE USD 5000
ISSUED 11111
ISSUE JPY 150000
ISSUED 11112
ISSUE XYZ 25
NO_SUCH_CURRENCY
PURCHASE 11111 USD 1000
AUTHORIZED USD 1000.00
PURCHASE 11112 JPY 10000
AUTHORIZED JPY 10000
PURCHASE 11111 JPY 10000
AUTHORIZED USD 1102.29
PAYMENT 11112 JPY 7000
PAID JPY 3000
PAYMENT 11111 USD 1102.29
PAID USD 0.00
CANCEL 11111
CANCELED 11111
PURCHASE 11111 USD 50
NO_SUCH_CARD
EXIT
GOODBYE

Notice, again, that there are no prompts or other output, other than the output that is required as a response to each command. This may seem strange, but there's a good reason for it, which is described a bit later in the write-up.

The example does not represent a complete test of all the necessary functionality, but it's a good starting point to understand how the program is supposed to work.

Fair assumptions

It's fair to assume that your program will only receive valid input. We will not test your program with non-existent commands, nor with existing commands in the wrong format. This is not to say, of course, that error handling is unimportant in real programs, but it adds a level of complexity to this program that doesn't make it any more interesting. (You're free to implement error checking if you'd like, but it's not something that we'll offer extra credit for.) In the event that your program receives input that doesn't follow the specifications above, it's fine for your program to ignore it, print an error message, misbehave, or even crash; we won't be testing your program in these circumstances.


Persistence

When the EXIT command is entered and the program is getting ready to shut down, save information about all of the credit cards into a file. You can call the file anything you'd like, and you can write the file in any format you'd like; it's up to you. The requirement is that the program must be able to automatically find this file and reload the information the next time it starts up, so that any credit cards that were known previously will be reset to their previous state.

You have a fair amount of latitude here about how to implement this feature, but there are two rules that apply:

You may want to implement this feature near the end of your development, since it will likely be affected by a lot of the other work you're doing, but, in general, the order in which you implement features is up to you.


Wait... what kind of crazy user interface is this?

Unlike programs you may have written in the past, this program has no graphical user interface, or even an attempt at a "user-friendly" console interface. There are no prompts asking for particular input; we just assume that the program's user knows precisely what to do. It's a fair question to wonder why there isn't any kind of user interface. Not all programs require the user-friendly interfaces that are important in application software like Microsoft Word or iTunes, for the simple reason that humans aren't the primary users of all software. For example, consider what happens when you used your web browser to load this page:

The details of how the web works are not the point of this assignment, but this example serves to suggest that not all software needs a clean, "user-friendly" interface. A web server is intended to simply run quietly for months at a time with no human intervention required. It may write some information into a log once in a while, especially when something goes wrong, but otherwise it does nothing but listen for requests (which are generated and formatted by browsers, in response to user activity) and respond to them appropriately.

Now consider again the requirements of the program you're being asked to write for this project. It waits for requests to be sent in via the console — though they could almost as easily be sent across the Internet, if we preferred — in a predefined format, then responds using another predefined format. Our program, in essence, can be thought of in the same way as a web server; it's the engine on top of which lots of other interesting software could be built. When an attempt in being made to charge to a card, that could be coming from a web form filled out by a user. When a card's balance is queried, that could be a result of a user logging into the card issuer's web site and asking for it, or dialing into a phone-based system instead.

While we won't be building these other interesting parts, suffice it to say that there's a place for software with no meaningful user interface; it can serve as the foundation upon which other software can be built. You can think of your program as that foundation.


Raising the level of abstraction using classes

One of the important skills you'll need to develop as you work on gradually larger programs is the ability to find the important abstractions that arise in the problem you're trying to solve, then to find good ways to implement those abstractions. In Python, implementing an abstraction is most often done using a class, providing you the ability to create objects that hide the details of their implementation.

One of the important abstractions in this project is the ability to represent an amount of money. You might be tempted to simply use a float object to store it, because you might think of an amount of money as being a number with a fractional part (e.g., 50.75 for fifty dollars and seventy-five cents). However, there are at least two problems with such a solution:

To solve these problems, you're required to create a class whose objects represent an amount of money in some currency (e.g., an object could represent 50 US Dollars or 125 Yen). In addition to being possible to ask these objects for the amount (e.g., 50) or the currency (e.g., US Dollars), it should also be possible to combine amounts of money using operators like + or -, compare them using operators like < or ==, display them in the Python interpreter in a reasonable way (by writing a __repr__ method), and so on. The amount itself should be stored using Decimal values instead of float values. Represent failures using exceptions whenever appropriate, and define your own kinds of exceptions that are specific to the problem at hand. Objects of your money class should be immutable (like int objects in Python).

In general, your class should automate as many of the details of how money works as possible; this will raise the level of abstraction of the rest of your program, which will be able to rely on your class to handle money correctly. The benefit of object-oriented programming isn't just in being able to cluster related data (the amount and the currency) together into a single object; it's giving that object the ability to do things relevant to the problem domain.

I would also suggest implementing a function or a class that can perform conversions between currencies, using the information read from the currency file. Given an amount, a source currency, and a target currency, it will be able to tell you the appropriate amount in the target currency.

The standard "decimal" module

The Python standard library includes a module called decimal, which provides (among other things) a class called Decimal. A Decimal object represents a decimal amount that is not implemented as a binary approximation; 0.01 is really 0.01, 0.1 is really 0.1, and so on. Have a look through the Python documentation for the decimal library and experiment in the interpreter so you can understand how it works before you start to use it. There are a lot more features in this library than you'll need, and one of the skills you're looking to hone here — which you built some experience with in ICS 32 — is the ability to find what you need in a library and leave behind what you don't, so I'll leave it as an exercise for you to determine how the decimal module can assist you. Don't let the details overwhelm you; try a little bit of it at a time until you get a feel for what's there and what might be useful.


Limitations

Third-party libraries — i.e., anything not included in a standard Python 3.3.2 installation — are strictly off-limits in this project. Other than the standard Python library, all of the code should be written solely by you.


Deliverables

Put your names and student ID in a comment at the top of each of your .py files, then submit all of the files to Checkmate. Take a moment to be sure you've submitted all of your files and be sure you submit the right version; we will only be able to accept the files you submit before the deadline, so forgetting to submit one (or submitting the wrong version) can have a significant impact on the score you receive for this project.

Follow this link for a discussion of how to submit your project via Checkmate.

Can I submit after the deadline?

Yes, it is possible, subject to the late work policy for this course, which is described in the section titled Late work at this link.