Skip to content

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 Parcel value type: dimensions, weight, service level, zone, insurance flag, account kind.
  • [ ] A Rule abstraction 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 QuoteEngine owning an ordered collection of rules, loaded from a file.
  • [ ] A Quote result: 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 simple key=value rule 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