Skip to main content

How Synplex Explodes ATO Demand to Components

For assembly-to-order BOMs, demand forecasted against the parent SKU must be distributed down to each component SKU so that replenishment recommendations for components are accurate. This page explains exactly how that explosion works.


The Problem It Solves

Without demand explosion: A component that is only ever used inside a BOM has zero direct sales history. Synplex sees no demand for it and never recommends a reorder — even if 80 units of the parent SKU are forecasted to sell next month. For example, the CPU in a "Custom PC – Base" BOM has 0 direct sales, so without explosion its replenishment recommendation would be none.

With demand explosion: Synplex multiplies the parent SKU forecast by the component's required quantity per unit. If "Custom PC – Base" is forecasted at 80 units/month and the BOM requires CPU × 1, Synplex attributes 80 units/month of demand to the CPU and generates a correct reorder recommendation.


What Triggers a Demand Explosion

The recalculateComponentDemandHistory background action is enqueued for a specific component (identified by its inventoryItemId) whenever:

  • A BOM is activated (status: draft → active)
  • A BOM is archived (status: active → archived) — zeroes out the component's BOM demand contribution
  • A BOM type changes on an active BOM (assemble-to-order ↔ pre-assembled)
  • A BOM component is added, updated, or removed

Each enqueue is keyed as component-{inventoryItemId} within the bom-structure-{shopId} queue, so rapid successive changes to the same component are deduplicated — only one recalculation runs.


How the Explosion Works Step by Step

Step 1 — Reverse lookup: find all active parent BOMs

Starting from the component's inventoryItemId, Synplex finds every active BOM that contains this component and records the required quantity per unit for each.

Example — Component: RAM 16GB (inventoryItemId: 987)

BOM nameRequired qty per unit
"Custom PC – Base"RAM × 2
"Workstation Bundle"RAM × 4
If no active BOMs are found, the action exits immediately — nothing to calculate.

Step 2 — Fetch all future demand plans for the parent SKUs

Synplex loads every demandPlan record for all parent variant IDs where monthFirstDay is today or later, filtered to included locations only.

Parent SKULocationMonthUnits
"Custom PC – Base"LondonAug 202580
"Custom PC – Base"LondonSep 202595
"Workstation Bundle"LondonAug 202520
"Workstation Bundle"LondonSep 202525
Pagination is handled automatically — all pages are fetched before proceeding.

Step 3 — In-memory matrix aggregation

For each parent demand plan, Synplex multiplies the planned sales quantity by the component's required quantity for that BOM, then accumulates the result per (locationId, monthFirstDay) pair.

Contributions for RAM 16GB:

  • "Custom PC – Base" × 2 per unit: London Aug 2025 = 80 × 2 = 160 / London Sep 2025 = 95 × 2 = 190
  • "Workstation Bundle" × 4 per unit: London Aug 2025 = 20 × 4 = 80 / London Sep 2025 = 25 × 4 = 100

Resulting demand matrix for RAM 16GB:

LocationMonthAggregated demand
LondonAug 2025160 + 80 = 240 units
LondonSep 2025190 + 100 = 290 units

Step 4 — Fetch existing component demand plans

Synplex loads the existing demandPlan records that already have a plannedBOMQuantity for this component, so it can diff against them before writing.

Step 5 — Upsert with change detection

For each entry in the demand matrix, Synplex checks whether the existing plannedBOMQuantity differs from the newly calculated value by more than 1% (the change threshold). If the change is insignificant, the write is skipped to avoid unnecessary database load.

Example — change below threshold, write skipped:

  • Existing plannedBOMQuantity for London Aug 2025: 238
  • New calculated value: 240
  • Change: |240 − 238| ÷ 238 = 0.84% → below 1% threshold
  • Result: write skipped

Example — change above threshold, record upserted:

  • Existing plannedBOMQuantity for London Sep 2025: 180
  • New calculated value: 290
  • Change: |290 − 180| ÷ 180 = 61% → above threshold
  • Result: demandPlan upserted with plannedBOMQuantity = 290

Records are upserted on their recordKey field, composed as: {shopId}-{componentVariantId}-{inventoryLevelId}-{YYYY-MM}

This ensures exactly one plannedBOMQuantity row exists per component × location × month.

Step 6 — Orphan cleanup

If a location or month that previously had BOM demand no longer appears in the new demand matrix (e.g. a location was excluded, or a parent BOM was archived), its plannedBOMQuantity is set to 0 rather than deleted. This preserves the plan row for historical reference while removing its effect on replenishment calculations.


How Component Demand Plans Feed Replenishment

The plannedBOMQuantity field on a demandPlan record is combined with the component's own direct plannedSalesQuantity when Synplex runs the inventory simulation for that component.

Example — RAM 16GB · London · August 2025:

  • plannedSalesQuantity: 0 (RAM is never sold standalone)
  • plannedBOMQuantity: 240 (exploded from parent BOMs)
  • Total demand used in simulation: 240 units

refreshVariantMetrics uses this total demand to calculate:

  • reorderDate
  • coverageDate
  • recommendedOrderQty
  • stockAssessment (healthy / running low / out of stock / overstocked)

The component appears in the demand plan, inventory report, and supply plan with accurate figures — as if it had 240 units of its own direct demand — even though it is never sold standalone.


What Happens When an ATO BOM Is Archived

When a BOM is archived, recalculateComponentDemandHistory is enqueued for every component with triggeredBy: "status-archived". The action runs through the same steps above but finds no active BOMs for the component, so the demand matrix is empty. All existing plannedBOMQuantity values are then zeroed out.

The cascade on archive:

  1. recalculateComponentDemandHistory enqueued per component
  2. No active BOMs found in Step 1
  3. Demand matrix is empty
  4. All plannedBOMQuantity rows set to 0 — component replenishment recommendations revert to direct sales demand only

Troubleshooting

Component shows no BOM demand despite active ATO BOM

  • BOM status is "active" — demand explosion only runs for active BOMs
  • BOM type is "assemble-to-order" — pre-assembled BOMs use a different demand attribution path
  • Parent SKU has demand plans with plannedSalesQuantity > 0 — if the parent has no forecast, there is nothing to explode
  • Component's inventoryItemId is linked on its BomComponent record — missing links are visible in the BOM detail and result in no quantity being attributed
  • The location is marked as "included" in location settings

BOM demand seems too high or too low

  • Component quantity on the BOM record is correct (e.g. RAM × 2 per unit, not × 1)
  • All parent BOMs using this component are accounted for — a component shared across multiple BOMs accumulates demand from all of them
  • Parent demand plans are up to date — trigger a forecast refresh if the parent SKU's plan looks stale

BOM demand updated correctly but reorder date did not change

The demand explosion writes to plannedBOMQuantity on demandPlan records. The reorderDate is computed separately by refreshVariantMetrics, which reads those demand plans as input to the inventory simulation.

If the reorderDate has not updated, refreshVariantMetrics has not yet run for this component since the demand explosion completed. It runs on a schedule and also when inventory levels change. Wait for the next scheduled run or trigger a manual variant metrics refresh from the variant detail page.