Course overview/Practice and extend3 of 4

Capstone: add returns to the pipeline

Back to course

Practice and extend - Step 14 of 15

Capstone: add returns to the pipeline

Extend the project, prove the result, and assess the finished pipeline.

The request

Finance now wants recognized revenue after partial returns. A return should reduce revenue on the date the return happened, not on the original order date.

Extend the same project instead of starting over. Your finished model should answer:

What was each customer's net recognized revenue on each activity date?

One row represents one customer on one activity date. The two columns customer_id + activity_date identify each row together.

For this capstone, assume every return references an earlier paid order and has the same source currency as that order. A return linked to an unknown or non-paid order needs a visible exception or a failing check. Real finance reporting also needs an approved policy for return eligibility and revenue recognition.

Plan with your coding agent

AI Prompt

Read the current course project and this capstone request. Do not edit files yet.

Propose a file-by-file plan for adding returns. For each new or changed file, explain:

  • what one row represents
  • how it depends on existing tables
  • how returns connect to orders and customers
  • how positive order revenue and negative return events combine
  • which checks catch unknown orders, duplicate returns, and duplicate customer-date rows
  • how the requested date range is applied

Call out any choice that is not defined by the request. Wait for my approval before implementing the plan.

Add the return source

Create commerce/assets/raw/returns.csv:

return_id,order_id,return_ts,return_amount
R001,1001,2026-01-02 11:00:00,10.00
R002,1006,2026-01-04 09:30:00,20.00

The final source state should also keep order 1005 as paid, following the earlier source-correction exercise.

Build the extension

Add these assets:

  1. raw.returns, a DuckDB seed for the return file.
  2. staging.stg_returns, which standardizes IDs, timestamps, dates, and amount.
  3. analytics.fct_returns, which joins returns to orders to find the customer.
  4. analytics.customer_daily_net_revenue, which combines positive paid-order events and negative return events.

Use the patterns from the course, but write the SQL yourself. The finished table flow should be:

raw.orders -> staging.stg_orders -> analytics.fct_orders ------┐
                                                               ├-> analytics.customer_daily_net_revenue
raw.returns -> staging.stg_returns -> analytics.fct_returns ---┘

raw.customers -> staging.stg_customers -> analytics.dim_customers
                                                               └-> analytics.customer_daily_net_revenue

Required behaviour

The net revenue reporting table must:

  • Use one row per customer per activity date.
  • Keep paid order revenue positive and return amounts negative.
  • Include recognized_revenue, returned_amount, and net_revenue as separate columns.
  • Treat recognized_revenue as this course's paid-order metric, not an accounting revenue-recognition calculation.
  • Join each return to exactly one order and each customer to exactly one dimension row.
  • Use time_interval to replace the requested activity_date range on each run.
  • Filter events with both start_date and end_date included, matching the dates Bruin deletes.
  • Reject null keys, duplicate return IDs, negative return amounts, and duplicate customer-date rows.
  • Document the return timing rule, model owner, business owner, and source-currency limitation.

If a return references an unknown order, fail a check or route it to a visible exception model. Do not silently drop it.

Build with your coding agent

AI Prompt

Implement the approved returns plan using the files, table rules, and checks in this capstone.

Validate the project and render analytics.customer_daily_net_revenue for January 1 through January 4. Because this is a new time_interval table, create it with one --full-refresh run. Then run January 2 through January 4 twice without --full-refresh. Query the seven expected customer-date rows and the total net revenue, and confirm the rows and totals stay the same.

Show the diff, commands, check results, output rows, and rubric score. Do not commit or push anything. If the result differs from the expected values, stop and trace the difference to source rows before changing SQL.

Prove the result

Validate, render, and run the complete interval:

bruin validate commerce/pipeline.yml
bruin render commerce/assets/analytics/customer_daily_net_revenue.sql \
  --start-date 2026-01-01 \
  --end-date 2026-01-04
bruin run commerce/pipeline.yml \
  --start-date 2026-01-01 \
  --end-date 2026-01-04 \
  --full-refresh

Query the reporting table:

bruin query --connection duckdb-default \
  --description "inspect capstone customer daily net revenue rows" \
  --query "SELECT customer_id, activity_date, recognized_revenue, returned_amount, net_revenue FROM analytics.customer_daily_net_revenue ORDER BY activity_date, customer_id;"

Your seven customer-date rows should match these net values:

customer_idactivity_datenet_revenuereason
C0012026-01-0149.00Paid order 1001
C0012026-01-02-10.00Partial return R001
C0022026-01-0285.50Paid order 1003
C0032026-01-0225.00Paid order 1004
C0022026-01-0342.00Corrected paid order 1005
C0042026-01-03120.00Paid order 1006
C0042026-01-04-20.00Partial return R002

The total net revenue should be 291.50.

Run January 2 through January 4 twice without --full-refresh:

bruin run commerce/pipeline.yml \
  --start-date 2026-01-02 \
  --end-date 2026-01-04

bruin run commerce/pipeline.yml \
  --start-date 2026-01-02 \
  --end-date 2026-01-04

Row counts and totals must remain unchanged.

Capture local review evidence

Update the Bruin header in analytics.customer_daily_net_revenue with the return timing rule, model owner, business owner, and source-currency limitation. Then inspect the local diff and confirm the checked output and repeat-run results match this lesson.

Use this rubric:

AreaPointsEvidence
Row and metric definition2Customer plus activity date, with the return timing rule documented
Table structure1Raw, staging, order, return, customer, and reporting tables have explicit dependencies
Join safety1Return-to-order and order-to-customer joins do not duplicate rows
Data quality2Source, row-identifier, business-rule, and customer-date checks pass
Safe repeat runs2Repeating the same date range leaves rows and totals unchanged
Asset documentation1The reporting asset header records the owners and source-currency limitation
Local review evidence1Focused diff, checked examples, and repeat-run output are present

A score of 10 out of 10 means the capstone meets the course requirements. Fix any missing row definition, join check, data check, or repeat-run evidence before treating the model as ready for shared use.

Optional extensions

The core course ends with the SQL project. If your project also uses Python, continue with Python assets and Python materialization. If your team uses coding agents, add Bruin MCP after the project has tests and a review process.

Sign up to our newsletter

Practical updates on open-source data pipelines, AI analysts, governance, and what we are shipping at Bruin.

The signup form is hosted by Brevo. Accept cookies to load it.