The pipeline

TL;DR

How data moves through CCC in four steps, ingest to classify to calculate to report, and what a batch is.

CCC does four things in order: ingest, classify, calculate, report. Almost every question about a figure resolves to "which of these four has happened yet for this record".

The four steps

Ingest pulls from seven upstream systems into CCC's own tables. Each source has its own jobs and its own cadence, and several deliver only a recent window rather than all history. See sources.

Classify assigns flags to each transaction and each of its lines: whether it contains a bundle, a combideal, an engraving, a private label item, which payment method was used, which source and medium brought it in. These land in *classifications and *lineclassifications tables. See classifications.

Calculate evaluates calculation rules against the classified transactions and books money into cm_impact_* tables, then aggregates per order into cm_impact_salesorders.

Report builds the income statement and feeds the screens, the exports and the downstream tools.

How work is queued

Most jobs are driven by the batches table. A batch has a type, a settings blob, and a status: todo, in progress, finished, or won't do. A job claims a batch of its own type, does the work, and queues the next step.

Two things follow that matter when reading a figure.

A step can be waiting rather than broken. A classify batch that has not run yet leaves the transaction present but unclassified, and a calculate batch that has not run leaves it classified but with no margin.

Two queues are not the batches table. Both are a processed = 0 flag on their own table, and re-queuing one order means setting that flag back to zero rather than inserting a batch:

TableHolds
cm_impact_salesordersRealised margin, the figures calculated from transactions that exist
cm_expectedimpact_salesordersExpected margin, queued by classification and calculated by its own worker

The realised queue has a header banner. The expected queue does not, so a backlog there is visible only through its healthcheck.

Both start at the modern era. Every queueing site skips a transaction dated before ingest.modernera, so an order older than that is imported and never calculated. See configuration and boundaries.

A job refuses to run while the batch types it depends on are still queued or running. That is deliberate: calculating margin from half-imported transactions would produce a confident wrong number. The cost is that pausing one widely depended-on step holds up everything downstream of it, not just its own import.

Whether a step runs at all is a database switch, not a change to the schedule. A newly added step arrives switched off.

Evidence

Step names and their models from AGENTS.md and the job groups under app/jobs/. Batch mechanics from app/models/batches.php. The sales-order queue from app/jobs/calculate/impactsalesorders.php. Basis: code-checked against the current implementation.

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