Project 7 — Contacts Manager · Project 8 — BMI Tracker · Project 9 — Budget Tracker · Project 10 — Library Manager
Build an object-oriented contacts manager with name, phone, email, and tags. Support search, sort, and CSV/JSON export. Track BMI over time with a history log, trend analysis, and an ASCII chart of weight progression. Track income and expenses by category. Generate monthly summaries, spending breakdowns, and savings rate reports — all stored in CSV. Manage a book library with borrow/return tracking, member accounts, overdue detection, and full-text search.
What you'll learn
- 1Model a Contact with a dataclass
- 2Implement ContactBook CRUD operations
- 3Full-text search across all fields
- 4Export to CSV and JSON formats
- 5Calculate BMI and classify the result
- 6Store daily measurements in JSON
- 7Plot weight history as an ASCII chart
- 8Compute 7-day and 30-day trends
- 9Model transactions with a dataclass
- 10Categorize income vs. expense transactions
- 11Compute monthly totals, savings rate, and top expenses
- 12Load and save all data to CSV
- 13Design a multi-class system: Book, Member, Library
- 14Implement borrow/return with due dates
- 15Detect overdue books and calculate fines
- 16Full-text search by title, author, or ISBN
Project 7 — Contacts Manager
Build an object-oriented contacts manager with name, phone, email, and tags. Support search, sort, and CSV/JSON export.
Features
- Contact: name, phones (list), emails (list), tags (list), notes
- Commands: add, show, edit, delete, search, list, export csv/json, quit
- Search: case-insensitive full-text across name, email, tags
- Sort: by name (default), by date added
Sample Session
> add
Name: Alice Johnson
Phone: 010-1234-5678
Email: alice@example.com
Tags (comma-sep): work, python
Contact saved.
> search python
[1] Alice Johnson 010-1234-5678 alice@example.com [work, python]
> export csv contacts.csv
Exported 1 contacts to contacts.csvProject 8 — BMI Tracker
Track BMI over time with a history log, trend analysis, and an ASCII chart of weight progression.
BMI Categories
| BMI | Category |
|---|---|
| < 18.5 | Underweight |
| 18.5–24.9 | Normal |
| 25–29.9 | Overweight |
| ≥ 30 | Obese |
Sample Output
Date: 2024-05-01
Weight (kg): 72.5
Height (cm): 175
BMI: 23.67 — Normal
Weight History (last 7 days):
2024-04-25 |████████████████████ 74.0
2024-04-28 |███████████████████ 73.2
2024-05-01 |██████████████████ 72.5
Trend: ↓ 1.5 kg over 6 daysCommon Mistakes (FAQ)
Q. My BMI comes out as ~7700 — what's wrong?
You used height in centimeters. BMI = kg / m², so convert first: 175 cm → 1.75 m, then bmi = weight / (1.75 ** 2). Dividing by 175² instead of 1.75² is the classic bug.
Q. The program crashes when I type the weight.
float(input()) raises ValueError on non-numeric input. Wrap it in try/except ValueError and re-prompt, so a stray letter or empty line doesn't kill the session.
Q. How do I avoid a ZeroDivisionError?
Guard the height before dividing: if height_m <= 0, ask again. A height of 0 (or a blank that you coerced to 0) is the only way to hit it.
Q. Which category does exactly 25.0 fall into?
Decide your boundaries explicitly: < 18.5 Underweight, 18.5–24.9 Normal, 25–29.9 Overweight, ≥ 30 Obese. Use >= for the lower edge of each band so 25.0 is Overweight, and document it — boundary handling is the most common grading miss.
Q. How do I scale the ASCII bar chart?
Scale each bar to the max weight in the window: bar_len = round(w / max_w * WIDTH), then print '█' * bar_len. Don't scale to a fixed number or short series look identical.
Project 9 — Budget Tracker
Track income and expenses by category. Generate monthly summaries, spending breakdowns, and savings rate reports — all stored in CSV.
Transaction Model
@dataclass
class Transaction:
date: str # YYYY-MM-DD
type: str # income | expense
amount: float
category: str # food, transport, salary, ...
note: str = Commands
- add <income|expense> <amount> <category> [note]
- list [--month YYYY-MM]
- summary [--month YYYY-MM]
- categories — show all categories with totals
- export <filename.csv>
Common Mistakes (FAQ)
Q. My totals are off by a cent (e.g. 0.1 + 0.2 = 0.30000000000000004).
Floating-point can't represent money exactly. For display, round(total, 2); for exact arithmetic, store amounts as integer cents (7250 = $72.50) or use decimal.Decimal. Pick one and stay consistent.
Q. Should expenses be negative or positive?
Cleanest: store a positive amount plus a type field ('income'/'expense'), then balance = sum(income) − sum(expense). Mixing signed amounts and a type field double-counts — choose one convention.
Q. How do I filter by month?
With ISO dates (YYYY-MM-DD), a string prefix compare is enough: tx.date[:7] == '2024-05'. No datetime parsing needed for month filtering.
Q. KeyError when totaling a new category.
Use collections.defaultdict(float) (or totals.get(cat, 0)) so a never-seen category starts at 0 instead of raising.
Q. After reloading the CSV, amounts won't add up.
The csv module returns every field as a string — '50' + '20' is '5020'. Convert on load: amount = float(row['amount']). Also write with newline='' on Windows to avoid blank rows.
Project 10 — Library Manager
Manage a book library with borrow/return tracking, member accounts, overdue detection, and full-text search.
Classes
- Book: isbn, title, author, category, copies_total, copies_available
- Member: id, name, email, borrowed_books (list of {isbn, due_date})
- Library: books (dict), members (dict), borrow/return/search methods
Commands
- book add/list/search <query>
- member add/list
- borrow <member_id> <isbn>
- return <member_id> <isbn>
- overdue — list all overdue borrowings with fines
Common Mistakes (FAQ)
Q. Should books/members be a list or a dict?
Use a dict keyed by ISBN / member id — lookup is O(1). A list forces an O(n) scan on every borrow/return/search and makes duplicate-key bugs easy to miss.
Q. A book gets borrowed even when no copies are left.
Check copies_available > 0 before decrementing, and return a clear message otherwise. Decrement on borrow, increment on return — and guard return so copies_available never exceeds copies_total.
Q. How do I compute overdue days correctly?
Parse the stored ISO date with date.fromisoformat(due) and compare to date.today(): overdue_days = (date.today() - due).days, overdue when > 0. Don't string-compare dates for arithmetic.
Q. Adding a member/book with an existing id silently overwrites.
dict assignment replaces without warning. Check `if isbn in self.books:` first and reject (or merge copies) so you don't lose the existing record.
Q. json.dump fails on my Book/Member objects.
Objects (and dataclasses) aren't JSON-serializable directly. Convert to plain dicts on save (dataclasses.asdict, or a to_dict()), and reconstruct the objects when loading.
📝 Exercises
Try them yourself first, then open the solution to compare.
Build the contacts manager
Goal: Implement the contacts manager with all features.
- Contact dataclass with all fields
- CRUD + search
- JSON persistence
- CSV export
Build the BMI tracker
Goal: Implement measurement recording, BMI calculation, and ASCII chart.
- Record weight+height+date
- BMI and category
- JSON history
- ASCII weight chart
Build the budget tracker
Goal: Implement all commands with CSV storage.
- Transaction dataclass
- add/list/summary/categories commands
- CSV persistence
- Monthly filtering
Build the library manager
Goal: Implement Book, Member, and Library classes with all commands.
- 3 classes with clean interfaces
- Borrow/return with due date tracking
- Overdue detection (>14 days = overdue)
- JSON persistence
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗Comments 0
No comments yet. Be the first.
Only members can comment.
Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy