← Back
Home › Systems › Sales, marketing and customer service

trade-promotion-management

Lets a consumer-goods manufacturer plan, fund, execute and settle the discounts it gives retailers, with edits at any level of the plan flowing down without breaking totals.

SAP, Salesforce Consumer Goods Cloud, Model N (pharma); used by P&G, PepsiCo, Nestlé

Open the trade-promotion-management pagetrade-promotion-mana… Hdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDAG workflow executiondistributed: Run a graph of dependent tasks by releasing each one when its inputs are done, longest chain first, and skip any task whose input hashes are unchanged.›Deterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Durable workflow executiondistributed: Ordinary code made crash-proof by logging every side-effect result and replaying the code from that log; activities run at least once, workflow logic must be deterministic.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›Double-entry ledgerfinance: Append-only journal of transactions whose postings sum to zero per currency; balances are derived, so money can only move, never appear or vanish.›Largest remainder roundingfinance: Splitting an amount into parts that sum exactly (largest remainder / Hamilton) for allocations, splits and prorationsMoney representationfinance: Store money as integer minor units with an explicit currency exponent, compute in wider integers, and round once with a named rounding mode.›Reconciliationfinance: Match internal ledger records against bank and processor statements by reference, then amount and date window; every unmatched or mismatched item becomes an exception.›Assignment problem (Hungarian /Jonker–Volgenant)graphs: Optimal one-to-one matching of rows to columns in O(n³) by growing a matching along shortest augmenting paths over dual potentials.›Process supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›Gradient-boosted decision trees(GBDT)machine-learning: Add small regression trees one at a time, each fitted to the loss gradient; histogram splits over 256 bins make it the strongest tabular default.›Linear and logistic regressionmachine-learning: Fit a weighted sum of features to a number (least squares) or a probability (logistic); the baseline every model must beat.›X12 edi messagingnetworking: ANSI X12 and EDIFACT interchanges: segment/element delimiters, ISA/GS/ST envelopes, control numbers and 997/CONTRL acknowledgementsSimplex methodoptimization: Solves linear programs by walking along the edges of the feasible polytope from vertex to vertex, one basis swap (pivot) at a time; the workhorse behind MIP solvers.›Record linkagesearch-sort: Probabilistic record matching (Fellegi-Sunter) with blocking for patient or entity indexesCoverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›Dual control approvalsecurity: Four-eyes / maker-checker approval with quorum policies for sensitive operationsMutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›Row-level security(multi-tenant isolation in the…security: Append a policy predicate to every query inside the database, so a forgotten WHERE tenant_id = ? in application code cannot leak another tenant's rows.›Deep forecasting modelsstatistics: Neural forecasters (DeepAR, N-BEATS, TFT, patch transformers) with probabilistic outputs across many seriesPropensity score matchingstatistics: Estimate treatment propensity then match or weight to estimate effects from observational dataTime-series forecasting(baselines, model families, ba…statistics: The end-to-end recipe: fix horizon and uncertainty, beat naive baselines, choose a model family, and validate with rolling-origin backtests and scale-free metrics.›Bitemporal datastorage: Valid time plus transaction time on every record for as-of queries, back-dating and correctionsClosure tablestorage: Storing trees and hierarchies in relational tables (closure table, materialized path) for fast subtree queriesOlap cube write backstorage: Write a value at an aggregate cell of a cube and spread it to leaf cells (proportional, even or by driver) keeping totals consistentPoint-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›Rollup aggregationstorage: Fold raw events into one mergeable partial aggregate per time bucket and dimension key, then merge fine buckets into coarser ones, so long ranges read few rows and stay exact.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Rete rules enginesystems: Match many rules against changing facts by caching partial matches in a shared network, so each change only does the work it actually affects.›Topological sortgraphs: Order the vertices of a DAG so every edge points forward; the core of build systems, schedulers and dependency resolution, with cycle detection for free.›Content-addressed storagestorage: Store each blob under the hash of its bytes: immutable, deduplicated by construction, self-verifying on every read, and composable into Merkle trees.›Exponential backoff with jittersystems: Retry after delays that grow geometrically and are randomized, so failed clients spread out instead of hammering a recovering service in synchronized waves.›Cooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerDurable job queuedistributed: Persisted tasks that workers lease for a visibility timeout, ack on success and retry with backoff; at-least-once execution that survives worker and queue crashes.›Fault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildEvent sourcingfinance: Persist state changes as an append-only stream of immutable events per entity; current state is a fold over them, guarded by expected-version appends.›Hierarchical timing wheelsystems: Timers bucketed by expiry into wheels of slots, coarser wheels for far deadlines: O(1) insert and cancel for millions of timeouts.›Leasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Health checkingsystems: Decide per backend whether to route to it using probes and real-traffic errors, with consecutive-count hysteresis, capped ejection and a panic fallback.›Idempotency keysfinance: Client-chosen unique key per logical operation, stored with a request fingerprint and the final response, so retries replay the first result instead of acting twice.›+ 39 moreBFS and DFSgraphs: The two basic graph traversals; BFS gives hop distances and levels, DFS gives discovery/finish order for cycles, components and topological order.›Merkle dagdata-structures: Content-addressed DAG of hashed nodes (git trees/commits) for dedupe and verifiable historyAtomic file replacementhardware-os: Write temp file, fsync, rename over target, fsync directory: crash-safe whole-file updatesDead-letter queuedistributed: A side queue for messages that keep failing: after N delivery attempts or a permanent error, park them with the reason so the main queue keeps moving.›Transparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelCryptographic hashes (SHA-2,BLAKE3)security: Collision-resistant fingerprints of data; SHA-256 for interoperability, BLAKE3 for speed, HMAC/HKDF for keyed uses.›Optimistic concurrency controlstorage: Run transactions without locks, record what they read, and validate at commit that nothing changed; fastest when conflicts are rare.›Slab allocatorsystems: Pre-carve pages into equal-size slots per size class with an in-place free list: O(1) alloc and free, no external fragmentation, cache-warm reuse.›Fencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›+ 11 moreEbpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signalsSeccomp bpfsecurity: Syscall filtering with seccomp BPF programs+ 6 more

How it is built

Architecture overview

Trade promotion management (TPM) is the software a consumer-goods manufacturer uses to plan, fund, execute and settle the discounts it gives retailers. A promotion is an agreement such as "feature this product in the weekly flyer for three weeks, with a lower shelf price, in return for an off-invoice allowance and a lump-sum fee". The system holds that agreement as a structured deal, predicts what it will do to sales, promises money for it (an accrual), pays it out later (as a credit note, a bill-back or by short-paying invoices, a deduction) and finally compares what the money bought with what a no-promotion world would have sold (post-event ROI). Pharma gross-to-net rebate management is a variant of the same loop with contracts and rebates instead of flyers and allowances.

Three things make the system hard. The plan is a hierarchy (customer, account, product group, SKU, week) and people edit it at any level, so a number typed at a total must flow down to the leaves without breaking the totals. Money is owed in the future on rules that change retroactively, so every amount needs valid time as well as record time. And the retailer's side of the story arrives as messy documents: short-payments with cryptic reason codes that have to be matched back to deals, sometimes months later.

Parts:

 retailer POS/scan data ─┐                       ┌─► optimiser (LP / assignment)
 shipments (EDI 852/867)─┼─► baseline forecast ──┤
 history of events ──────┘      │   ▲             └─► uplift model ──► predicted volume, cost, ROI
                                ▼   │ actuals                              │
 planner ──► planning cube (hierarchy, write-down, roll-up) ◄───────────────┘
                 │ approved plan
                 ▼
          deal rules (Rete) ──► accrual postings ──► ledger (double entry)
                 ▲                                        │
 invoices, credit notes, deductions (EDI 810/812/820) ──► matching ──► reconcile ──► settle or dispute
                 └──── workflow + approvals drive every step; row-level security wraps all reads ────┘

The system has two clocks. The planning clock is interactive: a changed discount should give a new forecast, cost and ROI within seconds. The settlement clock is slow batch: deductions arrive for weeks after an event ends, and each must find its deal.

How the parts fit together

Baseline and demand forecast

A promotion's uplift is measured against a baseline, so the baseline comes first. Each series (a product at a customer, per week) is forecast by the recipe in Time-series forecasting (baselines, model families, backtests): fix the horizon and the uncertainty, beat naive baselines, pick a model family and validate with rolling-origin backtests. Weekly retail sales have trend, a yearly season and holiday spikes. Exponential smoothing (ETS, Holt-Winters) keeps a level, trend and seasonal state per series, one pass and O(1) memory, which matters when there are millions of series; STL decomposition (seasonal-trend with loess) separates trend and seasonality with iterated loess so that past promotion weeks can be cleaned out of the history before the baseline is fitted. Promoted weeks must not be learned as normal demand, or the baseline absorbs the uplift it is compared against.

Validation uses Time-series cross-validation (rolling origin): train on the past, test on the next h weeks, move the origin forward. Intermittent items that sell zero most weeks need a specialist such as Croston's method; ARIMA is the classical alternative.

When there are many related series, a global model beats one model per series. Neural forecasters such as DeepAR (Salinas et al. 2017), N-BEATS (Oreshkin et al. 2019) and the Temporal Fusion Transformer (Lim et al. 2019) share parameters across series and emit quantiles, so the plan can show a range instead of a point. That is deep-forecasting-models (no page yet); the cheaper family above is the alternative, chosen per series group on backtest error. Gradient-boosted decision trees (GBDT) fit the same problem as a tabular regression on lag, calendar, price and promotion features, and are strong when many external drivers exist.

The uplift model receives a baseline per cell plus its error distribution; actual shipments and scan data flow back as new history after every cycle.

Uplift and ROI model

For a planned event the system predicts incremental volume as a function of discount depth, mechanic (price cut, display, feature, multi-buy), duration and calendar. The simplest version is a Linear and logistic regression on the log of volume with promotion features and customer-level effects; it is explainable to a sales team, which matters because planners override numbers they do not trust. Gradient-boosted decision trees (GBDT) capture interactions (a deep discount on a feature week is worth more than the sum of the two) at the cost of explainability.

Two effects must be modelled or ROI is overstated: pull-forward (the weeks after the event dip) and cannibalisation (a promoted pack takes volume from siblings).

Historical promotions are not randomised: retailers promote products that were selling well anyway. propensity-score-matching (no page yet) estimates how likely each product-week was to be promoted given its characteristics and then compares promoted with similar un-promoted weeks, or reweights by the inverse propensity; the propensity score is due to Rosenbaum and Rubin (1983). It is the tool to check whether the uplift in the data is causal or just selection.

ROI per event is incremental margin minus promotion cost (allowance times units, fees, display), divided by the cost.

Promotion optimiser

Given a budget, a calendar and a list of candidate events, the optimiser answers "which events, which weeks, which depths". When the response is approximated as linear in the decision variables, the problem is a linear program solved by Simplex method: maximise predicted margin subject to total spend, per-customer limits, minimum and maximum events per week and no overlapping events on one SKU. Where each slot takes exactly one event from a candidate list, the structure is a matching and Assignment problem (Hungarian / Jonker–Volgenant) solves it in O(n³). Nonlinear response curves are approximated piecewise-linearly, which keeps the problem an LP. The system must show the constraint that binds, because that is what planners negotiate.

Planning cube

The plan lives in a multi-dimensional structure: customer hierarchy (country, banner, store group), product hierarchy (category, brand, SKU) and time (year, month, week). closure-table (no page yet) stores each hierarchy relationally with one row per ancestor-descendant pair, so "all SKUs under this brand" is a single indexed lookup, and moving a branch is a bounded update. Rollup aggregation keeps partial sums per bucket and merges them upward, so a quarterly total is a handful of reads and an edit changes only the buckets on its path.

The distinctive operation is write-back at an aggregate cell: the planner types a new total for a brand and quarter, and olap-cube-write-back (no page yet) spreads the change to the leaf cells proportionally to a driver (last year's volume, the baseline), evenly or by a rule, while locked cells stay fixed. Spreading produces fractions, and plans in units or cents must stay integers that add up exactly: largest-remainder-rounding (no page yet) gives each part its floor and hands the leftover units to the parts with the largest remainders, so the leaves sum to the typed total.

Deal rules and accruals

A deal is a set of conditions and payments: products and customers it covers, the dates it is valid, the mechanic (off-invoice per case, scan-based per unit sold at the register, bill-back, lump sum), tiers and caps. Each shipment or sale must be tested against all active deals to decide which accrue and how much. A Rete rules engine compiles the rules into a network that caches partial matches, so a new sale is matched against thousands of deals by touching only the nodes it can affect, rather than rescanning every rule.

Because rules and facts both change retroactively (a deal is extended after the event, a price is corrected, a late scan file arrives), every record needs two time axes: when it was true in the business (valid time) and when the system learned it (transaction time). bitemporal-data (no page yet) keeps both, which makes "what did we think we owed at quarter end, and what do we owe now" two queries on the same table and keeps audit trails honest.

Amounts are stored as integer minor units with an explicit currency, rounded once with a named mode (Money representation); per-unit rates multiplied by large quantities are computed in wider integers. Every accrual becomes postings in a Double-entry ledger: debit promotion expense, credit accrued liability, with the deal id on the posting. Payment and release reverse the liability. The ledger is append-only, so corrections are new postings, never edits. Balances (accrued, paid, remaining per deal) are derived from it.

Workflow and approvals

A promotion moves through states: draft, submitted, approved, agreed with the retailer, executing, ended, claims open, settled. Some steps are a dependency graph, such as "forecast and ROI must exist before approval, approval before the deal is published to the retailer portal". DAG workflow execution releases each task when its inputs are done and skips tasks whose inputs failed. Steps that wait on people and external parties for weeks (the retailer's confirmation, a claim window of 60 or 90 days) are long-lived processes that must survive deploys and crashes; Durable workflow execution logs each side-effect result and replays the code from the log to resume. Both sit on a Durable job queue for leased, retried work. The DAG engine suits batch pipelines (nightly forecast, accrual run), the durable workflow the per-promotion life cycle.

dual-control-approval (no page yet) (maker-checker) protects the sensitive steps: the person who creates or changes a deal above a threshold cannot approve it, and large settlements need a second approver with a quorum policy.

Retailer data and settlement

Trade data arrives mostly as EDI. x12-edi-messaging (no page yet) parses ANSI X12 interchanges: segment and element delimiters, the ISA/GS/ST envelopes, control numbers for duplicate and gap detection, and the functional acknowledgements (997) that tell the sender a file was received. Typical document types in this domain are invoice (810), credit/debit adjustment (812), payment order and remittance advice (820), product activity data (852) and product transfer and resale report (867).

The core settlement problem is deduction matching. The retailer short-pays an invoice by a round amount with a reason code and a reference to its own promotion number. The manufacturer must find the deal, accept or dispute the claim and clear the receivable. Reference numbers are often missing or mistyped, so record-linkage (no page yet) scores candidate matches on retailer, product, date window and amount, with blocking to keep the candidate set small, and routes low-confidence matches to a person. Reconciliation then matches payments and deductions against ledger postings by reference, then amount and date window; every unmatched or mismatched item becomes an exception in a work queue with an owner and an age. Valid claims release the accrual, invalid ones become disputes with evidence from the deal master (what was agreed, when).

The parsers for retailer files are the system's exposed input surface: an EDI interchange or a portal export is written by someone outside the company, with their own quirks, truncations and encodings. Coverage-guided fuzzing targets exactly these readers (the X12 envelope and segment parser and the delimited-file importers). It mutates real sample files, runs the instrumented parser, and keeps every input that reaches a new code edge, so malformed control numbers, missing trailers and oversized fields are found before a retailer sends one. A crash or a silently half-read file here would otherwise surface as a missing or double-counted deduction.

Access control

A key account manager should see only their customers, and one customer's terms must never leak to another. Row-level security (multi-tenant isolation in the data layer) appends a policy predicate to every query in the database, so a forgotten WHERE customer_group = ? in application code cannot expose rows.

Row-level security answers which rows a user sees; it says nothing about which machines may talk to the system at all. Mutual TLS covers that side: the portal that receives retailer files, the EDI gateway, the forecast and rule-engine workers and the ledger database each present a certificate, so every internal call and every retailer connection carries a verified, rotatable machine identity. A retailer's certificate maps to one customer group, which sets the database session's customer scope before row-level security applies, so a compromised partner connection can only ever reach that partner's rows.

Operations

Responsiveness. hdr-histogram (no page yet) records the latency of the key interactive flow: a planner edits a total and the write-down, roll-up and ROI recomputation come back. A log-linear histogram keeps bounded relative error across milliseconds to seconds, so p50 and p99 of cube edits are watched per hierarchy depth and per customer size. This is how the "cube edits bite first" limit under Scaling is seen before planners complain, and it is the number the partial-aggregate and batching changes are judged against.

Keeping workers alive. The forecast batch, the rule engine, the EDI ingest and the workflow workers run as long-lived processes under process supervision: a parent starts them, reaps exits and restarts them under a delay and burst limit, so a crashing ingest worker does not loop on the same bad file and flood the queue. On a deploy it stops them with SIGTERM before SIGKILL. Graceful draining makes that stop safe: a worker stops taking new jobs and new approvals, finishes the posting or write-back it holds until a deadline, and hands unfinished work back to the durable queue, so a release never leaves a half-posted accrual.

Ledger availability and recovery. The ledger and deal store are the system of record, so they are held by primary-backup replication: one node orders all writes and ships them to a standby, and the acknowledgement rule decides what a crash may lose. Postings are acknowledged only once the standby has them. Replication does not protect against a wrong write, though, since a bad bulk edit is copied faithfully. Point-in-time recovery does: base backups plus an archived change log rebuild the data as of any instant, for example just before a mis-applied retroactive deal change re-opened months of postings. The recovered copy is compared with the bitemporal history and the correcting entries are posted from there.

Testing the rules. Deterministic simulation testing runs the whole settlement path (EDI arrival, rule firing, posting, duplicate screening, retries, worker crashes) in one thread under a seeded scheduler with simulated clock and storage. A rare ordering, such as a deduction arriving before its shipment or the same file sent twice while a worker restarts, then replays exactly from its seed, which is how double posting and totals drift are caught.

Data and control flow

Planning one promotion:

  1. A planner picks customer, products, dates and mechanic in the planning cube; the deal draft gets an id and a version.
  2. The baseline for each affected cell is read from the forecast; the uplift model predicts volume, cost and ROI; pull-forward and cannibalisation terms adjust neighbours.
  3. Totals roll up the hierarchy; if the planner overrides a total, write-back spreads it down and rounding keeps integers exact.
  4. The optimiser may suggest a better schedule under the budget.
  5. Approval runs (dual control above thresholds); the approved version is frozen and published to the rule engine and the customer.

Settling it:

  1. Shipments and sell-out arrive (EDI or portal files); the rule engine decides what accrues; postings go into the ledger.
  2. Retailer invoices, credit notes and deductions arrive; control numbers screen duplicates.
  3. Each deduction is linked to a deal by reference or by record linkage; matches are reconciled with the ledger.
  4. Valid claims are paid or offset, invalid ones disputed, and accruals released when the claim window closes.
  5. Post-event analysis compares actual volume with the baseline and writes actuals back as history for the forecast.

Scaling

  1. Cube edits bite first: write-back across a wide hierarchy touches many leaf cells. Levers: partial aggregates per bucket so only the edit's path is recomputed, columnar storage, batched write-down.
  2. Forecasting volume: millions of series per cycle. Levers: simple per-series models where they win, a global model for related series, a weekly batch, refits only for series whose actuals changed.
  3. Rule matching: deals times shipments grows fast at a large customer. Levers: the Rete network's caching, bucketing deals by customer and product, batching shipments.
  4. Deduction backlog: matching is manual at the tail. Tune the match threshold so the human queue holds only genuinely ambiguous items; measure queue age, not only volume.
  5. Retroactive changes: a changed deal can re-open months of postings; bitemporal storage re-computes the delta and posts only the difference.

Failure modes

FailureSymptomCauseFix
Inflated upliftROI looks great, finance sees no liftpromoted weeks in the baseline history, or promotions chosen on strong sellersclean history before fitting, check with propensity matching, holdout tests
Totals driftplan at brand level differs from the sum of SKUs by a few unitsindependent rounding of spread valueslargest-remainder allocation, integer cells, assert sum equality after every write-back
Wrong accrual after a retro changequarter-end accrual does not match what is later paiddeal edited without keeping what was believed earlierbitemporal records, post the delta as a correcting entry
Double postingan invoice or file counted twiceEDI file re-sent, no control-number checktrack interchange control numbers, idempotent ingestion
Orphan deductionsdeductions with no deal pile upmissing retailer references, fuzzy matching too strictlinkage with blocking and a review queue, reason-code mapping per retailer
Data leak across teamsa user sees another customer's termsfilter done in application code and forgotten in one queryrow-level security in the database

Real implementations

namewhat it isrepolicencenotes
SAP Trade Promotion Management and Consumer Products solutionscommercial TPM inside the SAP stackproprietaryproprietary
Salesforce Consumer Goods Cloudcommercial CRM-based trade promotion and retail executionproprietaryproprietary
Model Ncommercial revenue management with rebate and gross-to-net for life sciencesproprietaryproprietarypharma variant
Prophetopen forecasting library for seasonal series with holidaysfacebook/prophetMITbuilding block for baselines
statsforecastfast classical forecasters (exponential smoothing, ARIMA, Croston) for many seriesNixtla/statsforecastApache-2.0building block for baselines
LightGBMgradient-boosted treeslightgbm-org/LightGBMMITuplift and feature-rich forecasts
Temporaldurable workflow enginetemporalio/temporalMITpromotion life cycle
OR-Toolsoptimisation solvers, including LP and assignmentgoogle/or-toolssee repothe optimiser

Sources

  • R. Hyndman and G. Athanasopoulos, Forecasting: Principles and Practice, 3rd ed.: baselines, exponential smoothing, rolling-origin evaluation. book
  • D. Salinas et al., DeepAR: Probabilistic Forecasting with Autoregressive Recurrent Networks, 2017. paper
  • B. Oreshkin et al., N-BEATS: Neural basis expansion analysis for interpretable time series forecasting, 2019. paper
  • B. Lim et al., Temporal Fusion Transformers for Interpretable Multi-horizon Time Series Forecasting, 2019. paper
  • P. Rosenbaum and D. Rubin, The central role of the propensity score in observational studies for causal effects, Biometrika 70(1), 1983.
  • I. Fellegi and A. Sunter, A theory for record linkage, Journal of the American Statistical Association 64, 1969.
  • ASC X12, the standards body for the EDI transaction sets named above: x12.org

Similar systems

Share the most building blocks with this one.