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
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:
raw.returns, a DuckDB seed for the return file.staging.stg_returns, which standardizes IDs, timestamps, dates, and amount.analytics.fct_returns, which joins returns to orders to find the customer.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, andnet_revenueas separate columns. - Treat
recognized_revenueas 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_intervalto replace the requestedactivity_daterange on each run. - Filter events with both
start_dateandend_dateincluded, 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
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_id | activity_date | net_revenue | reason |
|---|---|---|---|
| C001 | 2026-01-01 | 49.00 | Paid order 1001 |
| C001 | 2026-01-02 | -10.00 | Partial return R001 |
| C002 | 2026-01-02 | 85.50 | Paid order 1003 |
| C003 | 2026-01-02 | 25.00 | Paid order 1004 |
| C002 | 2026-01-03 | 42.00 | Corrected paid order 1005 |
| C004 | 2026-01-03 | 120.00 | Paid order 1006 |
| C004 | 2026-01-04 | -20.00 | Partial 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:
| Area | Points | Evidence |
|---|---|---|
| Row and metric definition | 2 | Customer plus activity date, with the return timing rule documented |
| Table structure | 1 | Raw, staging, order, return, customer, and reporting tables have explicit dependencies |
| Join safety | 1 | Return-to-order and order-to-customer joins do not duplicate rows |
| Data quality | 2 | Source, row-identifier, business-rule, and customer-date checks pass |
| Safe repeat runs | 2 | Repeating the same date range leaves rows and totals unchanged |
| Asset documentation | 1 | The reporting asset header records the owners and source-currency limitation |
| Local review evidence | 1 | Focused 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.