How ProfitRoot verifies D2C margin.
Most tools present every uploaded number as truth. ProfitRoot separates what your data proves from what it merely calculates -- and refuses the numbers it can't back.
The core claim
A Shopify Orders export proves Net Sales, not Contribution Margin. Orders carry the revenue side -- gross, discounts, refunds, order and line counts. Every cost lane (COGS, payment fees, fulfillment, shipping, returns handling, ad spend) lives in a different file. Rendering margin from Orders alone would be fabrication, so a lane stays gated until its source is uploaded and reconciles.
What each source proves
| Source | Proves | Unlocks | Status |
|---|---|---|---|
| From Shopify | |||
| Shopify Orders | Gross, discount, refund, net sales, orders, units, AOV, SKU mix | Revenue Bridge | Reconciled |
| Product Cost | Product COGS (unit cost x quantity) | Gross Margin | Needs source |
| Shopify Products | Catalog, product / variant names | Product mix / catalog joins | Unavailable |
| Shopify Payments | Payment fees, gross vs net settled | Payment fees + cash receipts | Needs source |
| Shopify Inventory | On-hand, stock movement | Inventory visibility (scenario) | Scenario |
| Not from a Shopify export | |||
| 3PL / Carrier | Fulfillment + shipping cost | Logistics cost-to-serve | Needs source |
| Ad Spend | Media spend | CM after marketing | Unavailable |
| Accounting (QuickBooks / Xero) | Booked cost, blended lanes | GL control tie-out / opex | Unavailable |
No source proves a cost lane from Orders alone -- COGS, payment fees, fulfillment, shipping, returns and ad spend each require their own file. Net Sales is the single reconciling line (order grain); AOV and CM% are derived from reconciled totals, never stored as if they were sources.
The reconciliation contract
Every source that reconciles runs a contract that compares a source total (read from the raw file) against the mapped total (what the parsed objects carry) and reports the residualand whether it passed. Three guards run at order grain, each proven to fire by a test:
- Coverage -- no order is silently excluded; every order is mapped or excluded with a reason.
- Reconciliation -- no value dropped or double-counted; the mapped total walks to the source total within tolerance.
- Membership-uniqueness -- no order double-mapped; each order maps to exactly one canonical order.
A residual over tolerance, a missing required column, or a double-count is a hard halt -- no setting can downgrade it. Two coverage numbers are always shown together, row coverage and value coverage: a few excluded rows can hide a large excluded dollar amount, so neither number alone is honest.
The rules we hold
- No figure without provenance -- every number traces to a source row.
- No synthetic allocation -- order-level amounts are never spread to lines by a made-up rule.
- No silent exclusion -- excluded rows are recorded loud, with a reason.
- Declared is not available -- a source we don't yet read renders as "coming soon," never as a fabricated number.
- Net Sales is the tie-out -- AOV and CM% are derived from it, never stored as if they were sources.
What we do not claim
We are specific about what ProfitRoot cannot do yet -- including what a real Shopify export taught us we had wrong. See Known limitations.