Consumption contracts

TL;DR

The identity, date, unit, grain and freshness rules that make an aggregate over CCC data correct.

For anyone aggregating CCC data outside the application. Each rule here has produced a wrong number that looked right.

Identity

A customer is computed, not imported. It is a group of orders sharing identifying keys, rebuilt rather than patched.

  • A customer id is a durable starting point, not fixed membership. Absorbed rows remain; follow mergedinto until the live row. See merges and splits.
  • Historic figures move. A merge renumbers sequences and changes past new-customer counts. A report run in March and rerun in June can legitimately differ.
  • If a figure must be stable, capture it when you publish it. CCC reconstructs from current understanding; it does not keep what was believed at the time.

Dates

Two bases, and they answer different questions.

BasisUsed by
createddateTransaction listing date filters
trandateCustomer sequence, rule activation and the impact listing
Accounting periodLedger totals and income statement periods

They can disagree about which order came first. Pick deliberately and state which you used.

createddatetime is subordinate and may be absent on older records.

Units and signs

  • Columns carrying _eur_ are euro.
  • The engine preserves signs and adds components. Positive costs occur in the seeds; see the signed example.
  • The interface may invert signs for readability. A query returns the stored convention.
  • ns_items.weight is in an unknown unit, with at least four distinct unit identifiers across the catalogue. Do not convert or compare weights.
  • Currency is converted per transaction, at the rate stored on that transaction. The amount_eur_* columns are already euro and need no further conversion; exchangerate and amount_foreign_invat are there when you need the original. Do not apply a period average on top.

Grain, and the join that multiplies

An order has many lines, and many impacts. Joining an order to either and summing the order total counts it once per joined row.

To aggregateJoin to
Order-level marginThe per-order impact table, one row per order
Line-level detailThe line tables, and do not carry order totals through

This is the most common silent error in CCC consumption.

Freshness

  • Margin moves after the order does, as downstream transactions arrive and rules re-run.
  • A stale customer dimension leaves margin correct, so a margin figure being current says nothing about customer counts.
  • The unauthenticated health endpoint reports per-step freshness. Key on its stable job field, never on its numeric ids, which are reassigned on every deploy.

Absence

Three different things that all look like a gap:

  1. A filter excluded it.
  2. A step has not run yet.
  3. No source ever supplied that period.

Only the third is permanent. Object documents carry the provenance that distinguishes them.

Evidence

Date bases from app/models/customers.php:1190-1192 and AGENTS.md, Models and database. Sign convention from app/models/impact.php:88-91. Weight unit and the health endpoint's unstable ids from AGENTS.md. Currency conversion from the exchangerate column stored per transaction. Basis: code-checked against the current implementation.

About this document
  • Type
    reference
  • Status
    active
  • Updated
    2026-09-14
  • Created
    2026-09-13