CASE STUDY 01 · PYTHON

Intermediate CSV + Decimal + Reports

Personal Expense Tracker

Capture genuine transactions, preserve them in CSV, produce category summaries and compare monthly spending with a budget without losing money precision.

01 · PROBLEM DEFINITION

Turn transactions into a spending explanation

A useful tracker does more than add numbers. It records when money was spent, assigns a consistent category, preserves a description, survives program restarts and answers questions such as “Which category consumes the most?” and “Am I over my September budget?”

Capture

Validate date, category, description and positive amount.

Persist

Append genuine records to a reusable CSV file.

Explain

Sort, aggregate, calculate shares and check budgets.

System boundary: This local learning project stores no bank credentials and makes no automatic payments. The user enters only expense records.
02 · REQUIREMENTS & INVARIANTS

Protect the data at entry and loading

FieldRuleRepresentation
DateValid ISO date YYYY-MM-DDdatetime.date
CategoryNon-empty; normalized title casestr
DescriptionNon-emptystr
AmountPositive; two decimal placesDecimal

Every in-memory Expense is valid. Invalid user input is never appended, and malformed existing rows are reported with line numbers and skipped rather than crashing the entire report.

Accounting invariant: The grand total equals the sum of category totals, and every valid expense contributes to exactly one category.
03 · PYTHON DATA MODEL

An immutable dataclass represents one transaction

Expense(spent_on: date, category: str,
        description: str, amount: Decimal)

@dataclass(frozen=True) generates readable initialization and comparison while preventing accidental field reassignment. Parsing functions convert external strings into domain types before an object is created.

Why Decimal?

Binary floating point cannot represent many base-ten currency fractions exactly. Decimal("19.90") preserves decimal intent and explicit two-place quantization. Production systems also need a documented rounding rule and currency identifier.

Why separate parsing?

parse_date and parse_amount create one validation path shared by keyboard input and CSV loading. Duplicated validation eventually produces inconsistent behavior.

04 · GENUINE CSV PERSISTENCE

The file is data—not simulated standard input

date,category,description,amount
2026-09-01,Food,Lunch,120.00
2026-09-02,Travel,Bus pass,850.00

The program opens expenses.csv with UTF-8 and newline="", lets Python’s CSV module quote commas safely and writes the header only for a new or empty file. Appending one record is O(1) file growth.

CSV is human-readable and portable, but it has no schema enforcement, transaction support or safe multi-user concurrency. SQLite is a natural next step when editing, deletion, concurrent access or richer queries become important.

Privacy: Descriptions can reveal personal habits. A shared-device version needs access control, encryption decisions, backups and a retention policy.
05 · COLLECTIONS & ANALYTICS

Aggregate with a dictionary, then sort by meaning

  1. Create defaultdict(Decimal) keyed by category.
  2. For each expense, add its amount to exactly one total.
  3. Sum category totals to obtain the grand total.
  4. Calculate category share: amount×100/grand_total.
  5. Sort by descending amount, then category name for deterministic ties.

Monthly budget

spent = sum(amount where date formatted as YYYY-MM equals selected month)
remaining = budget − spent

A negative remaining value is displayed as a positive “over budget” amount. This presentation avoids asking the user to interpret a double negative.

06 · COMPLETE IMPLEMENTATION

Runnable Python program

programs/expense-tracker.py
Open Compiler
Loading source…

For real persistence, run locally in a writable folder. Many online compilers use temporary storage, so downloaded CSV data may disappear after the session.

07 · INTERACTIVE PROGRAM TRACE

Trace one addition and summary

  1. Validate the date.
  2. Normalize text.
  3. Parse money exactly.
  4. Build the domain object.
  5. Prepare persistence.
  6. Save the record.
  7. Read for reporting.
  8. Aggregate by category.
  9. Produce deterministic summary.
Current state

Press Next to begin.

08 · TEST STRATEGY

Test calculations and file behavior

Valid append and reload
Write two records to a temporary CSV, reload it and compare every typed field.
Currency precision
Add 0.10 and 0.20; the Decimal total must be exactly 0.30.
Invalid fields
Reject impossible date, blank text, zero/negative amount and non-numeric amount without changing the file.
Malformed stored row
Insert one damaged line between valid rows; load must report and skip only that row.
Budget boundaries
Test spent below, equal to and above budget; equality must show zero remaining, not over budget.
09 · COMPLEXITY & TRADE-OFFS

Reports scan the file once

OperationTimeMemory
Append expenseO(1)O(1)
Load n rowsO(n)O(n)
Category summaryO(n+c log c)O(c)
List sorted by dateO(n log n)O(n)

Here c is the number of categories. The simple design reloads for every menu action so the file is the source of truth. A larger application would cache data carefully or query a database.

10 · PRACTICE & EXTENSIONS

Check the design

Why is Decimal preferred for stored money?

Why use csv.writer instead of joining fields with commas?

Extensions

  1. Add transaction IDs and safe edit/delete operations.
  2. Generate monthly trend reports and category limits.
  3. Move persistence to SQLite with parameterized queries.
  4. Add import/export with duplicate detection.
  5. Write automated tests using temporary directories.
11 · INTERVIEW PREPARATION

Explain reliability choices

Why use a dataclass?

It expresses the record schema clearly and removes repetitive initialization code while retaining normal Python objects.

How are malformed rows handled?

Each row is parsed independently inside a specific exception boundary, so one bad row is reported without losing valid records.

What is CSV’s main limitation here?

It lacks transactions, indexes and concurrency control, making update/delete and multiple writers fragile.

How would you make appending crash-safe?

For stronger guarantees, write a complete validated replacement to a temporary file, flush as required, then atomically replace the original—or use a transactional database.

12 · KEY TAKEAWAY

Useful automation begins with trustworthy data

Collections and reports matter only after external strings become valid domain values. This tracker combines typed modeling, Decimal arithmetic, genuine persistence, defensive loading and auditable totals.