Eleven modules after that print("Hello, World"), you have every piece on the table: a papyrus package with models, errors and tests, two ways of serving it over the web, and a data lab that answers business questions. What you have not done yet is build a whole system on your own, from a requirements sheet to a deliverable. That leap — from following lessons to deciding, implementing and verifying without a safety net — is exactly what separates someone who "took a Python course" from someone who "can build with Python". Module 12 is your self-taught final exam: Papyrus Online, the full version of Ana's bookshop. This first lesson defines the contract: what gets built, how we measure that it is right, and what deliberately stays out.
Contents
- Why an integrative project (and why now)
- What Papyrus Online is: the complete system
- Functional requirements (FR1-FR7)
- Non-functional requirements (NFR1-NFR4)
- Measurable acceptance criteria
- Scope: what stays out (and why)
- Two tracks: Flask API or Django site
- Map of requirements → course modules
- How to work through this module
Why an integrative project
Until now, every lesson handed you the problem and the way to solve it. In real work, nobody does that: you receive requirements (often vague ones), and the decisions — which structure, which errors, what to test — are yours. An integrative project trains three muscles no isolated lesson can train:
- Deciding under ambiguity: choosing between valid alternatives and owning the consequences (track A or B, dict or list, JSON or CSV).
- Integration: pieces that work on their own fail together in brand-new ways — relative paths that break, floats that don't add up, circular imports. Debugging between layers is a different skill from debugging inside a function.
- Closure: deciding when something is "done" and shipping it. Personal projects tend to die at 80 %; this module gives you explicit acceptance criteria so that 100 % exists and is reachable.
The lessons in this module will not give you all the code. They give you structure, skeletons, contracts and checkpoints; you implement. Where a piece integrates too many things at once (like the sales service), there will be a complete, commented solution. For the rest, reference solutions: if yours is different but passes the criteria, it is every bit as valid as ours.
What Papyrus Online is
Papyrus Online is the system Ana would actually use: it manages the catalog and the members, records sales applying pricing rules and coupons, persists everything to disk, exposes it through a web interface, and produces a monthly report with numbers and a chart.
flowchart LR
subgraph Interface
API["Flask API (track A)<br/>or Django site (track B)"]
end
subgraph Core["papyrus package (domain)"]
M["models: Book, Member"]
S["services: sales, coupons"]
E["errors: PapyrusError hierarchy"]
end
subgraph Persistence
J["catalog.json / members.json"]
C["sales.csv"]
end
subgraph Analysis["Analysis"]
I["monthly report<br/>pandas + Matplotlib"]
end
API --> Core
Core --> Persistence
Persistence --> Analysis
Core -. "logging → data/papyrus.log" .-> L[(papyrus.log)]
Notice the direction of the arrows: the interface uses the core, never the other way around. It is the rule M10 called "the web translates, the package solves", and in 12-02 we will turn it into architecture.
Functional requirements
Numbered so we can refer to them throughout the module (in 12-04 each one gets its own test):
- FR1 — Catalog management (CRUD): create, read, update and delete books. Every book has a title, an author, a price and a stock. No two books may share a title (the title is the key, as it has been all course long).
- FR2 — Members and discounts: member registration with a unique code (format
NAME-NNN, likeLUIS-001). An identified member gets a 5 % discount (MEMBER_DISCOUNT = 0.05) on the VAT-inclusive price. A nonexistent code must be rejected withInvalidMemberError. - FR3 — Sales with coupons and stock control: selling N units of a title checks the stock (if there isn't enough,
InsufficientStockErrorand nothing is modified), applies the member rate where it applies and a coupon where it applies (PAPYRUS10→ 10 %,MEMBER5→ 5 %; unknown coupon →InvalidCouponError), decrements the stock and records the sale. - FR4 — Persistence: catalog and members in JSON; sales in CSV with columns
date,title,units,amount. The system starts by loading the files and loses no data if a write dies halfway (atomic save, like M8'sCatalogTransaction). - FR5 — Interface (your choice): track A, a REST API with Flask (
GET/POST/PUT/DELETE /api/books,POST /api/sales, 404/409/400 errors as JSON); track B, a Django site (listing, detail, sale form with aModelForm, admin enabled). - FR6 — Monthly data report: from the sales CSV, a script produces: total units and amount for the month, the top 3 titles, a weekday comparison, and a chart saved as a PNG. Run against
sales_2026.csvit must reproduce the canonical numbers from M11 (520 units Jan-Jun, Saturday as the best day with 130). - FR7 — Logging: every sale, business error and persistence operation is recorded in
data/papyrus.logat the appropriate level (INFOfor operations,WARNINGfor business rejections,ERRORfor technical failures).
Non-functional requirements
- NFR1 — Tests: the business logic (FR1-FR4) covered with pytest. Minimum bar: the 12-04 suite (about 25 tests) all green. The acceptance criteria below are the specification for those tests.
- NFR2 — Type hints: every public function of the
papyruspackage annotated (parameters and return), as you learned in 08-01. - NFR3 — Custom errors: no business error is communicated with a generic
ValueErroror withprint; always thePapyrusErrorhierarchy from M7. The interface translates them into HTTP status codes or user messages. - NFR4 — README: a
README.mdsaying what the project is, how to install it (venv +requirements.txt), how to use it and how to run the tests. Full template in 12-05.
Measurable acceptance criteria
"It works" is not a criterion; this is. Every row can be verified with a concrete action:
| Requirement | Acceptance criterion (verifiable) |
|---|---|
| FR1 | Adding "Faust" twice produces an error (409 in the API / a message in the form); after deleting a book, looking it up returns BookNotFoundError (404). |
| FR2 | Selling The Odyssey to LUIS-001 charges 12.35 EUR; to Julia (not a member), 13.00 EUR; with code JOHN-999, InvalidMemberError. |
| FR3 | Selling 5 copies of The Odyssey with a stock of 4 raises InsufficientStockError and the stock is still 4; Faust + member + PAPYRUS10 charges 18.67 EUR; coupon NOSUCH → InvalidCouponError. |
| FR4 | After selling and restarting the program, the stock read from catalog.json reflects the sale; killing the process mid-save does not leave a corrupt JSON. |
| FR5 | A: all 6 endpoints respond with the right status codes (checked with the test client). B: listing, detail, sale and admin all working (checked with Client). |
| FR6 | Running the report over sales_2026.csv prints 520 total units and Saturday as the best day (130 u), and leaves report_2026.png on disk. |
| FR7 | After a sale rejected for lack of stock, data/papyrus.log contains a WARNING line with the title and the units requested. |
| NFR1-4 | pytest all green; mypy (or a manual review) finds no unannotated public functions; a search for unjustified generic except Exception returns zero; the README lets someone else start the project without asking you anything. |
The amounts come from the course-long formula: price × 1.04 (VAT) × 0.95 (member), rounded to 2 decimals, with the coupon applied on top of that result. Check it against the four canonical books: The Odyssey 12.50 → 13.00 / 12.35; Hamlet 9.95 → 10.35 / 9.83; Don Quixote 15.90 → 16.54 / 15.71; Faust 21.00 → 21.84 / 20.75.
Scope: what stays out
Just as important as the what is the what not. Deciding scope is engineering; wanting to do everything is the surest way to finish nothing.
| Out of scope | Why | Where to pick it up |
|---|---|---|
| Real authentication (users, passwords, sessions) | Requires serious security (hashing, CSRF, tokens); done badly it is worse than absent | Extensions in 12-05 |
| Payments | Integration with external gateways, beyond the syllabus | Extensions in 12-05 |
| Production deployment | Servers, HTTPS, domains: a different trade | Extensions in 12-05 |
| A SQL database in track A | JSON/CSV are enough for Papyrus's volume and are what M6 practiced | Extensions in 12-05 |
| An elaborate front end (JavaScript, advanced CSS) | This is a Python course; minimal HTML is enough | Extensions in 12-05 |
If you finish and want more, perfect: 12-05 ranks these extensions by difficulty. But first you ship the agreed scope.
Two tracks
FR5 is satisfied by one of two paths. Choose based on what you want to strengthen, not on which one "sounds better":
| Track A — Flask API | Track B — Django site | |
|---|---|---|
| What you build | A JSON REST API on top of your papyrus package |
A web application with templates, forms and admin |
| Course foundation | 10-02 and 10-03 | 10-04 and 10-05 |
| Persistence | Your own JSON/CSV repositories (FR4 in full) | Django's ORM for the catalog; sales stay in CSV |
| Relative effort | Less on the interface, more on persistence (all of it is yours) | More on the interface (templates, forms), less on persistence (the ORM gives you a lot for free) |
| Strengthens | API design, HTTP status codes, layer separation | Full frameworks, conventions, ORMs |
| Typical risk | Forgetting to validate the JSON input | Putting business logic in the views |
| Choose it if… | You lean towards pure backend or data work | You lean towards product web development |
Both tracks share 70 % of the project (the whole core, sales persistence, the report and the business tests). The decision affects only milestone H4 of the plan.
Map of requirements → course modules
Nothing in this project is new. Every requirement points to modules where you already learned it — this table is your cheat sheet for "where to go back and review":
| Requirement | What it needs | Module(s) where you learned it |
|---|---|---|
| FR1 catalog | dataclass Book, dictionaries as an index, warehouse functions |
M5 (05-06), M4 (04-03), M3 |
| FR2 members | Validation, InvalidMemberError, constants |
M2, M7 (07-04), M1 |
| FR3 sales and coupons | Conditionals, custom exceptions, apply_coupon, rounding |
M2, M7, M3, M1 (floats) |
| FR4 persistence | JSON, CSV, pathlib, atomic save with a context manager |
M6, M8 (08-04) |
| FR5-A Flask API | Routes, JSON, status codes, errorhandler |
M10 (10-02, 10-03) |
| FR5-B Django site | Models, views, templates, ModelForm, admin |
M10 (10-04, 10-05) |
| FR6 report | pandas (groupby), Matplotlib (savefig) |
M11 (11-03, 11-04) |
| FR7 logging | logging with levels and a file |
M7 (07-05) |
| NFR1 tests | pytest, fixtures, parametrize, tmp_path |
M9 (09-01 to 09-03) |
| NFR2 type hints | Annotations as specification | M8 (08-01) |
| NFR3 errors | The PapyrusError hierarchy |
M7 (07-04) |
| NFR4 README | Virtual environments, pip, requirements | M1 (01-06) |
And what about M11 as a whole, regression included? It comes back in 12-05: the final report will include a recommendation in the style of the Sant Jordi order — Sant Jordi being Catalonia's Book Day — (prediction 172, Ana's order 190: the model recommends, the person decides).
How to work through this module
The five lessons follow the real life cycle of a project: requirements (this one), design (12-02), construction (12-03), verification (12-04) and delivery (12-05). Work with your editor open: every lesson ends with milestones you must complete before the next one. Reuse your code from earlier modules without shame — reusing your own work is what professionals do — but copy it into a new project and clean it as you bring it over: the final project deserves to start in a clean folder.
Common Mistakes and Tips
- Starting to code today. The temptation is enormous and the mistake is a classic: without the 12-02 design you will end up rewriting. One day of design saves three of refactoring.
- Widening the scope halfway through ("while I'm at it, I'll add a login…"). It's called scope creep and it kills more personal projects than any bug. Jot the idea down for 12-05 and move on.
- Choosing track B "because Django is more professional". Neither is more professional; they are different trades. Choose by what you want to strengthen.
- Treating the acceptance criteria as decoration. They are the contract: in 12-04 they become tests, one by one. If a criterion seems ambiguous now, rewrite it now — that is far cheaper than discovering the ambiguity with the code already written.
- Skipping the canonical amounts. 12.35, 9.83, 15.71, 20.75 have been the truth all course long. If your system produces a different number, the bug is yours, not the table's.
Exercises
This module replaces practice exercises with project milestones: real work on your deliverable, with verification criteria.
- Milestone 0.1 — Choose your track and put it in writing. Create the project folder (
papyrus_online/) with aDECISIONS.mdfile and write 3-5 lines: which track you choose and why, in terms of what you want to strengthen. This file will grow throughout the module. - Milestone 0.2 — Audit of reusable pieces. Go through your M1-M11 code and list in
DECISIONS.mdwhich files you will reuse (with their origin) and which you will build from scratch. At a minimum: reviewmodels.py,errors.py,coupons.py,warehouse.py, your M9 tests and your M10 app. - Milestone 0.3 — One more criterion. Write an additional acceptance criterion for the requirement you understand least, in the format of the table (concrete action → verifiable result). Writing it will force you to understand the requirement.
Solutions
Reference answers — what matters is that yours exist and are concrete:
- A valid example: "Track A. In M10, Django felt more guided and Flask left me with more doubts about HTTP status codes and input validation; I want to close those doubts. Track A also forces me to build the persistence in full, which is where I feel least confident."
- Typical reusables:
models.py(M5, addingMember),errors.py(M7, as is),coupons.py(M7, as is),apply_couponand the M1 constants; from scratch or heavily reworked:warehouse.py(it becomes services + repositories in 12-02), the interface (rebuilt on the new design), the report (a new script on top of what M11 taught). - An example for FR4: "I run a sale, kill the process, start again: the catalog loads without errors and the stock is consistent (either the whole sale was applied, or none of it — never halfway)." If your criterion doesn't say how to verify it, it isn't a criterion: it's a wish.
Conclusion
You now have the contract: seven functional requirements, four non-functional ones, acceptance criteria you can actually check (and which in 12-04 will become tests), a scope with clear borders and two tracks you have already chosen between. You have also seen that there is nothing new to learn: every requirement points to a module where you already lived the solution. What is new is the assembly — and assembly starts with the blueprint. In the next lesson we will turn these requirements into a plan: milestones in a deliberate order, a layered architecture, data schemas and function contracts written before any code. Resist the pull of the editor one more day: first the blueprint, then the bricks.
Python Programming Course
Module 1: Introduction to Python
- Introduction to Python
- Setting Up the Development Environment
- Python Syntax and Basic Data Types
- Variables and Constants
- Basic Input and Output
- Virtual Environments and Package Management
Module 2: Control Structures
Module 3: Functions and Modules
- Defining Functions
- Function Arguments
- Lambda Functions
- Modules and Packages
- Standard Library Overview
Module 4: Data Structures
Module 5: Object-Oriented Programming
Module 6: File Handling
Module 7: Error and Exception Handling
- Introduction to Exceptions
- Handling Exceptions
- Raising Exceptions
- Custom Exceptions
- Best Practices and Error Logging
Module 8: Advanced Topics
- Type Hints
- Decorators
- Generators
- Context Managers
- Concurrency: Threads and Processes
- Asyncio for Asynchronous Programming
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with unittest
- Testing with pytest
- Test-Driven Development
- Debugging Techniques
- Using pdb for Debugging
Module 10: Web Development with Python
- Introduction to Web Development
- Flask Framework Fundamentals
- Building REST APIs with Flask
- Introduction to Django
- Building Web Applications with Django
Module 11: Data Science with Python
- Introduction to Data Science
- NumPy for Numerical Computing
- Pandas for Data Manipulation
- Matplotlib for Data Visualization
- Introduction to Machine Learning with scikit-learn
