Intermediate Project 10 — Shipping Quote Engine¶
Difficulty: Intermediate · Estimated effort: 12–18 hours · Prerequisites: Module 12 — Templates, Module 14 — The STL with OOP, Module 15 — SOLID and Design Patterns · Combines: rules that compose instead of branching, a priced result with a breakdown, a rules file, and a golden-file test suite
This is one of the designated integration projects. It must use, explicitly and in writing: multiple classes · relationships between classes · encapsulation · inheritance or composition · polymorphism · the STL · exception handling.
No complete solution is provided. The rule set is stated as pricing behaviour; turning it into composable objects is the project.
1. Scenario¶
A courier prices a parcel from its weight, its dimensions, the service level, the destination zone and whether it is insured. Discounts stack: a business account gets a percentage off, a promotion may cap the total, and a surcharge applies to oversized items. Customers must be shown a breakdown, not just a number, because that is what they query at the counter.
The previous tool was a 400-line function of nested ifs, and the last three pricing bugs were
all caused by two rules being applied in an order nobody had written down.
2. Problem statement¶
Build a quote engine in which each pricing rule is its own object, rules are composed into a pipeline, the order of application is explicit, and the result is a breakdown of every step with the running total. The engine must be driven by a rules file so a rate change is not a code change, and the whole thing must be covered by golden-file tests: input description in, expected breakdown out.
3. User requirements¶
- [ ] Give a parcel's dimensions, weight, service and zone, and get a price.
- [ ] See the breakdown: each rule, the amount it added or removed, and the running total.
- [ ] Change rates in a file and see the new prices without rebuilding.
- [ ] Compare two service levels for the same parcel.
- [ ] Be told exactly what is wrong when a parcel is outside the accepted range.
- [ ] Get identical output for identical input, always — golden files depend on it.
4. Functional requirements¶
- [ ] A
Parcelvalue type: dimensions, weight, service level, zone, insurance flag, account kind. - [ ] A
Ruleabstraction applying itself to a running total and recording a line of breakdown. - [ ] At least six concrete rules: base rate, per-kilogram, oversized, zone multiplier, insurance, account discount — plus one cap rule.
- [ ] A
QuoteEngineowning an ordered collection of rules, loaded from a file. - [ ] A
Quoteresult: the final amount plus an ordered list of breakdown lines. - [ ] Money in integer minor units, with one documented rounding rule and where it is applied.
- [ ] Errors for a malformed rules file, an unsupported zone, and a parcel outside the limits.
5. Non-functional requirements¶
- [ ] Rules are composable: adding a rule is a new file plus a registration line, not an edit to the engine.
- [ ] Rule order is data, visible in the rules file, and named in the breakdown.
- [ ] A rule that cannot apply records that fact rather than silently contributing zero.
- [ ] The engine holds no mutable state between quotes: quoting is a pure function.
- [ ] Builds warning-free with
g++ -std=c++17 -Wall -Wextra -Werror.
6. Suggested features¶
- A
Template<T>for loading simplekey=valuerule parameters. - A quote comparison report across all service levels, as a table.
- A rule that depends on another rule's output, with the dependency declared rather than assumed.
- A second output format (CSV) behind the same interface, for the finance team.
7. OOP concepts involved¶
| Concept | Required | Where it appears |
|---|---|---|
| Multiple classes | yes | Parcel, Rule + six implementations, QuoteEngine, Quote, breakdown line, errors |
| Relationships | yes | Engine ◆ ordered rules; quote ──> breakdown lines; parcel passed by const reference |
| Encapsulation | yes | A quote cannot go below the documented floor, and the breakdown must sum to the total |
| Inheritance or composition | yes | Rules behind an interface; a rule that composes two others must say so |
| Polymorphism | yes | Each rule applies itself; the engine contains no rule-specific code |
| The STL | yes | std::vector for the ordered pipeline, std::map for rule parameters, algorithms for the cap |
| Exception handling | yes | Rule-file errors, unsupported zones and out-of-range parcels each throw a distinct type |
8. Suggested design considerations¶
Getting the breakdown to sum correctly is the real invariant. final == sum(lines) is a
property you can test on every quote the golden files contain. It also catches the classic bug
where a cap rule adjusts the total but forgets to record the adjustment.
Order is part of the pricing. A percentage discount before or after a cap gives different answers. Store the order in the rules file, print it in the breakdown, and document one example where order changes the price.
Purity makes golden files viable. If quoting depends on hidden state, promised identical output is a lie. Keep the engine stateless between quotes and the golden-file suite becomes trivially trustworthy.
Do not let Parcel validate by throwing in a getter. Validation belongs at construction or
in one validate() call — a getter that throws makes every caller a potential failure site.
9. Minimum viable version¶
Parcel, a base rate, a per-kilogram rule and an oversized rule, an engine running them in a
fixed order, and a breakdown printed with a running total.
10. Intermediate extension¶
Add the remaining rules, the rules file, and the ordering rule, then build three golden files:
a light small parcel, a heavy oversized one, and one hitting the cap. Verify final ==
sum(lines) for each.
11. Advanced extension¶
Add the dependency-aware rule and the CSV output format, then add a seventh rule loaded from the same file and record the change cost — new files, edited files, and whether the engine needed touching at all. Then answer in writing: which rule was hardest to make composable, and why?
12. Testing checklist¶
- [ ] A minimum-size parcel quotes the documented minimum price
- [ ] Doubling the weight increases the price by exactly the per-kilogram amount (not by a multiplier), as documented
- [ ] An oversized parcel attracts the surcharge exactly once, even if it is oversized in two dimensions
- [ ] The breakdown lines sum exactly to the final total, for every golden case
- [ ] A cap applies after the discount, and the order is visible in the breakdown
- [ ] Reversing two rules in the file changes the price in the documented case
- [ ] An unsupported zone is refused with the zone in the message
- [ ] A parcel outside the accepted range is refused before any rule runs
- [ ] Quoting the same parcel twice gives byte-identical output (golden-file equality)
- [ ] Adding a seventh rule required no change to the engine — or you record exactly what it did
- [ ] Zero-weight and zero-amount rules behave as documented rather than crashing
13. Documentation checklist¶
- [ ] The rules file format and every parameter it accepts
- [ ] The rule order policy and one example where order changes the price
- [ ] The rounding rule and where in the pipeline it is applied
- [ ] The class diagram with the rule interface and its implementations
- [ ] The golden files and how to regenerate them, with the reason you must never regenerate them to make a test pass
14. Self-evaluation rubric¶
| Criterion | Excellent looks like | Weight |
|---|---|---|
| Rule composition | Rules independent, ordered by data, added without editing the engine | 30% |
| Correctness | Breakdown sums to the total in every case; rounding documented | 25% |
| Testability | Golden files plus a purity guarantee; determinism verified | 20% |
| Failure handling | Bad rule files and out-of-range parcels refused with context | 10% |
| STL use | Ordered container and parameter store chosen with reasons | 5% |
| Documentation | Order policy, rounding and golden-file policy written down | 10% |
Related: Intermediate projects · Project roadmap · All projects · Module 15 — SOLID and Design Patterns