Skip to content

Capstone — build a pipeline end to end

You’ve learned every piece. This capstone puts them together into one real pipeline: no new features, just the whole track working as a unit. You’ll build it, validate it before it runs, and run it.

Build a customer pipeline that:

  1. Reads customers.csv with a declared schema (status, lifetime_value).
  2. Keeps only active customers and computes a tier: "gold" when lifetime_value is over a threshold, else "standard". The threshold must be a pipeline variable so a channel can change it per tenant.
  3. Routes gold customers down one branch and everyone else down another.
  4. Writes two outputs: gold.csv and standard.csv.
  5. Summarizes per tier (a count of customers and their total lifetime value) with an aggregate, written to summary.csv.
  6. Continues past bad rows, sending failures to a dead-letter queue.

Each requirement is something the track already taught:

RequirementFrom lesson
Source + declared schema01: Sources & schema
filter + emit + the if/then/else tier02: Transforms & CXL
route with a condition and a default03: Composition
Per-tier summary with an aggregate (count + sum)05: Groups & windows
Threshold as a $vars value a channel can override04: Channels
error_handling + dlq07: Errors & the DLQ
Confirm it with --dry-run / --explain08: Validate before you run

Start from this scaffold (the source is given) and add the transform, the route, the aggregate, the three output nodes, and the error_handling block. Then check yourself against the reference below.

pipeline:
name: customer_capstone
vars:
gold_threshold: { type: int, default: 10000 }
nodes:
- type: source
name: customers
config:
name: customers
type: csv
path: ./data/customers.csv
options: { has_header: true }
schema:
- { name: status, type: string }
- { name: lifetime_value, type: string }
# TODO: classify (filter active; emit tier from $vars.gold_threshold)
# TODO: route by_tier (gold vs default standard)
# TODO: aggregate tier_summary (group_by tier; count + sum), also reading from classify
# TODO: two outputs — gold.csv and standard.csv
# TODO: summary output — summary.csv from tier_summary
# TODO: error_handling with a dlq

Here is the complete pipeline. Predict what lands in each output, then run it.

pipeline.yaml // editable

The inactive customer was dropped by filter status == "active", so it appears in neither output. Of the two active customers, 80000 clears the 10000 threshold (gold) and 4000 does not (standard), and each is routed to its own file. The same classified rows also fan out to the aggregate, which groups by tier and reports one customer and the matching total in each group, so summary.csv counts the two tiers without the dropped inactive row ever appearing.

The habit from lesson 08: never ship a pipeline you haven’t validated. Before a real run,

clinker run customer_capstone.yaml --dry-run # config + schema valid?
clinker run customer_capstone.yaml --explain # the plan: nodes, wiring, outputs

--dry-run catches a misspelled input:, an undeclared schema type, or a CXL compile error in a second; --explain shows the shape (source → classify → route + aggregate → three outputs) so you can confirm the wiring matches the brief, including that classify fans out to both the route and the aggregate.