Module 10 · Software Engineering with Python
"Final project: Tallybook's invoicing service"
Plan your final project, a tested, version-controlled invoicing package and API that replaces the old billing module, with every rule tested at its boundaries and every row of the export accounted for.
About 20 minutes
The problem
Tallybook wants to retire billing.py. Your final project is its replacement: a small, tested invoicing package with an API, in a Git repository, and a short report showing that it's correct, how it handles the messy export, and what it changes for customers.
The concept
What the project contains
| Part | Built in |
|---|---|
invoicing.py: totals, VAT, discount, rounding, late fees | lessons 2 to 4 |
test_invoicing.py: boundary and regression tests | lessons 3 and 4 |
importer.py: validation and conversion of the export | lessons 5 and 6 |
| A Git history of small commits on branches | lesson 7 |
A review of billing.py and your own code | lesson 8 |
api.py with tests using the test client | lesson 9 |
Prove it
A test run with every test passing, a reconciliation of the export (every row valid, fixed, dropped as a duplicate, or quarantined, with counts that add up), and the totals the new code gives compared with the old, invoice by invoice.
Example
A reconciliation of the export: every row accounted for, with counts that add up to the total.
import pandas as pd
base = "https://academy.cloudtechanalytics.com/datasets/invoicing/"
raw = pd.read_csv(base + "invoices_raw.csv", dtype=str, keep_default_na=False)
customers = set(pd.read_csv(base + "customers.csv", dtype=str)["customer_id"])
lines = pd.read_csv(base + "invoice_lines.csv")
bad_quantity = set(lines.loc[lines["quantity"] <= 0, "invoice_id"])
def outcome(row, is_duplicate):
if is_duplicate:
return "dropped: duplicate"
if row["customer_id"] not in customers or int(row["discount_pct"]) > 20 or row["invoice_id"] in bad_quantity:
return "quarantined for review"
if row["due_date"] < row["issue_date"] and "/" not in row["issue_date"]:
return "quarantined for review"
if "/" in row["issue_date"] or not row["amount_paid"].replace(".", "").isdigit():
return "fixed automatically"
return "valid"
duplicates = raw.duplicated(keep="first")
raw["outcome"] = [outcome(row, dup) for (_, row), dup in zip(raw.iterrows(), duplicates)]
reconciliation = raw["outcome"].value_counts()
print(reconciliation)
print("Total:", reconciliation.sum(), "of", len(raw), "rows")outcome
valid 1107
fixed automatically 59
quarantined for review 34
dropped: duplicate 5
Name: count, dtype: int64
Total: 1205 of 1205 rowsEvery row has exactly one outcome, and they add up. That table, with your test run and the before-and-after totals, is the evidence the finance team needs to switch.
Walkthrough
- Build the package and tests, and get every test passing.
- Build the importer and reconcile the export.
- Compare old and new totals for every valid invoice, and explain the differences.
- Open the project brief on the course page and plan the write-up.
Practice
Practice
How many rows are quarantined for review?
Task
8 minWrite the summary for the finance team (80 to 180 words): what the new service changes for customers' totals, how it's tested, how the export is handled (with counts), and what they need to do before the switch.
Your work is checked for
- Totals change (kobo, rounding)
- Testing (tests, boundary, passing)
- Export handling with numbers
- An action for the finance team
- Between 80 and 180 words
Check your understanding
Answer every question to check.