# Bundle Component Demand & Shortage Report (`h_murdock/bundle-component-demand-audit`) Actor

Aggregate component demand across simultaneous one-level bundles and standalone orders. Compare one stock pool and trace shortages, missing stock and duplicate or nested BOM conflicts. Mapped CSV or JSON; downloadable reports; no store writes.

- **URL**: https://apify.com/h_murdock/bundle-component-demand-audit.md
- **Developed by:** [Gilad Ronen](https://apify.com/h_murdock) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / completed report

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bundle Component Demand & Shortage Report

Check whether one inventory snapshot covers the components required by a planned batch of bundles and standalone orders. When two bundles share a component, both consume the same declared stock pool. This report adds their requirements together before comparing demand with available units, so independent per-bundle availability cannot hide a shared-component shortage.

Use it before a promotional batch, a kit packing session, or a new exported order plan. You supply inventory, one-level bundle definitions, and planned demand. The Actor reads those supplied records and returns exact integer calculations with source references. It makes no store changes and needs no shop credentials or external API calls.

### What you provide

Declare a `snapshotId`, one `stockPoolId`, a real UTC `asOf` timestamp, and inventory/demand coverage as `complete`, `incomplete`, or `unknown`. Declare `missingInventoryPolicy: "unresolved"`: missing stock never means zero. Use explicit `availableUnits: 0` when zero inventory is known.

Each table accepts a JSON array or pasted CSV. Supply `inventory` rows with `recordId`, `componentSku`, and `availableUnits`; `bom` rows with `recordId`, `bundleSku`, `componentSku`, and `unitsPerBundle`; and `demand` rows with `recordId`, `itemType` (`bundle` or `component`), `sku`, and `quantity`. Supply `bom: []` for standalone-only demand. At least one demand row is required; an explicit quantity-zero row represents an empty plan.

Identifiers are literal strings: `"001"` and `"1"` are different SKUs. Record IDs must be unique within each table. Multiple demand records for the same SKU are valid and intentionally aggregate. Quantities are integers between zero and 1,000,000; units per bundle start at one. Fractional, signed, grouped, and exponent-form CSV quantities are rejected.

For CSV, remove the corresponding array and provide `inventoryCsv`, `bomCsv`, or `demandCsv`. Canonical headers match the field names above. Alternate export headers require the corresponding `inventoryColumns`, `bomColumns`, or `demandColumns` mapping, declaring every canonical key and a distinct header. Canonical mapping examples are harmless with arrays; a custom mapping requires CSV. UTF-8 BOM, quoted commas, and quoted multiline identifiers are parsed; identifiers cannot contain control characters, so multiline identifiers themselves are rejected. Duplicate headers, extra unmapped columns, irregular rows, and duplicate record IDs fail validation. Export only the intended fields.

### What you receive

One dataset item contains the full report. Component rows show known aggregate demand, bundle and standalone subtotals, exact required and available units when known, shortfall, status, and references to the inventory, demand, and BOM evidence. The complete JSON also contains every resolved demand contribution and unresolved demand record. CSV source references count logical records, with header record one; they do not claim physical line numbers for quoted multiline CSV.

Download the complete JSON, component CSV, findings CSV, or standalone HTML. CSV cells neutralize formula prefixes, and the HTML escapes submitted content and loads no external resources. Download the HTML and open it locally. These exports support human review; the Actor does not allocate scarce stock among orders or reserve inventory.

The supplied demo includes two bundles sharing SKU `001` and a standalone order. They require 13 units together against 10 available, producing a shortage of three. Other components are covered, illustrating both results in one report.

### Uncertainty and limits

Repeated equal or conflicting inventory is unresolved and never summed. Duplicate BOM edges invalidate the whole affected bundle. Undefined, nested, self-referencing, or cyclic bundles remain unresolved; no recursive manufacturing expansion occurs. Stock on bundle SKUs and standalone component demand naming a bundle are unsupported and flagged. An undefined, nested, or cyclic requested bundle, or unsupported component-as-bundle demand, has an unknown affected component set. Every component’s final required quantity and shortfall then remains unresolved. Known resolved demand and positive shortage lower bounds are preserved. Duplicate one-level edges invalidate their known direct components without assuming a quantity. Overall outcome remains unresolved when any demand cannot be expanded.

Incomplete or unknown coverage cannot establish exact totals. Known positive shortages may appear as lower bounds when demand is incomplete; they are not final quantities. The input must fit 4 MB and 5,000 combined rows. Expanded demand is limited to 10,000 component contributions. Output size is checked before a billable event. Split dense or oversized snapshots; do not split shared stock pools and treat each fragment as independently fulfillable.

### Pricing and data

The configured price is $0.25 for one completed report, including platform usage, with no startup or dataset-item fee. Validation failure and insufficient budget produce no completed-report event. Reports containing shortages or unresolved evidence are still completed reports and billable. Repeating the same run after an interruption restores its exports and retains one dataset item and one event; partial exports can exist before completion. Starting a new run is billable again.

Process only data you are allowed to provide. Prefer opaque identifiers over customer details. Inputs and reports are stored in Apify run storage. This is a finite snapshot calculation, not forecasting, live inventory sync, manufacturing planning, allocation optimization, or a fulfillment guarantee.

# Actor input Schema

## `snapshotId` (type: `string`):

Caller snapshot identifier; opaque literal string, leading zeros preserved.

## `stockPoolId` (type: `string`):

One explicit stock pool only. Combine stock only when quantities refer to this same declared available-to-use pool. This Actor does not sum warehouses.

## `asOf` (type: `string`):

Snapshot reference time, strict real UTC YYYY-MM-DDTHH:mm:ss\[.sss]Z; no clock or stale-stock inference.

## `inventoryCoverage` (type: `string`):

Declare whether the supplied snapshot covers the entire scope. Incomplete or unknown coverage prevents exact inventory or demand/shortfall results.

## `demandCoverage` (type: `string`):

Declare whether the supplied snapshot covers the entire scope. Incomplete or unknown coverage prevents exact inventory or demand/shortfall results.

## `missingInventoryPolicy` (type: `string`):

Missing inventory is unresolved, never zero. Keep zero as an explicit availableUnits:0 row. This commercial policy must be declared; it has no runtime fallback.

## `inventory` (type: `array`):

Rows: recordId, componentSku, availableUnits (integer 0–1,000,000). One row per component per stock pool. Repeated SKU stock is flagged unresolved, never summed. Supply this array (including \[] when empty) OR CSV, never both. Unknown row fields rejected; unique record IDs within each table. Combined maximum 5,000 rows and 4 MB JSON.

## `inventoryCsv` (type: `string`):

Optional alternative to inventory array: REMOVE that array when supplying nonempty CSV. Exact canonical headers unless mapping supplied. UTF-8 BOM and quoted fields accepted; duplicate/unknown headers, irregular rows, signed/fractional/exponent/grouped numbers rejected. No URL fetch.

## `inventoryColumns` (type: `object`):

Optional mapping from EVERY canonical key (recordId, componentSku, availableUnits) to a distinct CSV header. Canonical mapping is harmless with arrays; custom mapping requires inventoryCsv. Omit for canonical CSV headers.

## `bom` (type: `array`):

Rows: recordId, bundleSku, componentSku, unitsPerBundle (integer 1–1,000,000). Only one level. Duplicate edges and nested/cyclic kits are unresolved. Supply \[] for standalone-only demand. Supply this array (including \[] when empty) OR CSV, never both. Unknown row fields rejected; unique record IDs within each table. Combined maximum 5,000 rows and 4 MB JSON.

## `bomCsv` (type: `string`):

Optional alternative to bom array: REMOVE that array when supplying nonempty CSV. Exact canonical headers unless mapping supplied. UTF-8 BOM and quoted fields accepted; duplicate/unknown headers, irregular rows, signed/fractional/exponent/grouped numbers rejected. No URL fetch.

## `bomColumns` (type: `object`):

Optional mapping from EVERY canonical key (recordId, bundleSku, componentSku, unitsPerBundle) to a distinct CSV header. Canonical mapping is harmless with arrays; custom mapping requires bomCsv. Omit for canonical CSV headers.

## `demand` (type: `array`):

Rows: recordId, itemType (bundle or component), sku, quantity (integer 0–1,000,000). Repeated SKU demand with distinct record IDs is intentionally aggregated. At least one row, including explicit quantity 0 for an empty plan. Supply this array (including \[] when empty) OR CSV, never both. Unknown row fields rejected; unique record IDs within each table. Combined maximum 5,000 rows and 4 MB JSON.

## `demandCsv` (type: `string`):

Optional alternative to demand array: REMOVE that array when supplying nonempty CSV. Exact canonical headers unless mapping supplied. UTF-8 BOM and quoted fields accepted; duplicate/unknown headers, irregular rows, signed/fractional/exponent/grouped numbers rejected. No URL fetch.

## `demandColumns` (type: `object`):

Optional mapping from EVERY canonical key (recordId, itemType, sku, quantity) to a distinct CSV header. Canonical mapping is harmless with arrays; custom mapping requires demandCsv. Omit for canonical CSV headers.

## Actor input object example

```json
{
  "snapshotId": "promo-2026-10-02",
  "stockPoolId": "warehouse-main",
  "asOf": "2026-10-02T06:00:00Z",
  "inventoryCoverage": "complete",
  "demandCoverage": "complete",
  "missingInventoryPolicy": "unresolved",
  "inventory": [
    {
      "recordId": "stock-01",
      "componentSku": "001",
      "availableUnits": 10
    },
    {
      "recordId": "stock-02",
      "componentSku": "002",
      "availableUnits": 8
    },
    {
      "recordId": "stock-03",
      "componentSku": "003",
      "availableUnits": 6
    }
  ],
  "inventoryColumns": {
    "recordId": "recordId",
    "componentSku": "componentSku",
    "availableUnits": "availableUnits"
  },
  "bom": [
    {
      "recordId": "bom-01",
      "bundleSku": "gift-a",
      "componentSku": "001",
      "unitsPerBundle": 2
    },
    {
      "recordId": "bom-02",
      "bundleSku": "gift-a",
      "componentSku": "002",
      "unitsPerBundle": 1
    },
    {
      "recordId": "bom-03",
      "bundleSku": "gift-b",
      "componentSku": "001",
      "unitsPerBundle": 1
    },
    {
      "recordId": "bom-04",
      "bundleSku": "gift-b",
      "componentSku": "003",
      "unitsPerBundle": 2
    }
  ],
  "bomColumns": {
    "recordId": "recordId",
    "bundleSku": "bundleSku",
    "componentSku": "componentSku",
    "unitsPerBundle": "unitsPerBundle"
  },
  "demand": [
    {
      "recordId": "plan-01",
      "itemType": "bundle",
      "sku": "gift-a",
      "quantity": 4
    },
    {
      "recordId": "plan-02",
      "itemType": "bundle",
      "sku": "gift-b",
      "quantity": 3
    },
    {
      "recordId": "plan-03",
      "itemType": "component",
      "sku": "001",
      "quantity": 2
    }
  ],
  "demandColumns": {
    "recordId": "recordId",
    "itemType": "itemType",
    "sku": "sku",
    "quantity": "quantity"
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One complete report with component summary rows, demand contributions and findings.

## `json` (type: `string`):

No description

## `evidence` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `html` (type: `string`):

Escaped standalone HTML; download and open locally.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "snapshotId": "promo-2026-10-02",
    "stockPoolId": "warehouse-main",
    "asOf": "2026-10-02T06:00:00Z",
    "inventoryCoverage": "complete",
    "demandCoverage": "complete",
    "missingInventoryPolicy": "unresolved",
    "inventory": [
        {
            "recordId": "stock-01",
            "componentSku": "001",
            "availableUnits": 10
        },
        {
            "recordId": "stock-02",
            "componentSku": "002",
            "availableUnits": 8
        },
        {
            "recordId": "stock-03",
            "componentSku": "003",
            "availableUnits": 6
        }
    ],
    "inventoryCsv": "",
    "inventoryColumns": {
        "recordId": "recordId",
        "componentSku": "componentSku",
        "availableUnits": "availableUnits"
    },
    "bom": [
        {
            "recordId": "bom-01",
            "bundleSku": "gift-a",
            "componentSku": "001",
            "unitsPerBundle": 2
        },
        {
            "recordId": "bom-02",
            "bundleSku": "gift-a",
            "componentSku": "002",
            "unitsPerBundle": 1
        },
        {
            "recordId": "bom-03",
            "bundleSku": "gift-b",
            "componentSku": "001",
            "unitsPerBundle": 1
        },
        {
            "recordId": "bom-04",
            "bundleSku": "gift-b",
            "componentSku": "003",
            "unitsPerBundle": 2
        }
    ],
    "bomCsv": "",
    "bomColumns": {
        "recordId": "recordId",
        "bundleSku": "bundleSku",
        "componentSku": "componentSku",
        "unitsPerBundle": "unitsPerBundle"
    },
    "demand": [
        {
            "recordId": "plan-01",
            "itemType": "bundle",
            "sku": "gift-a",
            "quantity": 4
        },
        {
            "recordId": "plan-02",
            "itemType": "bundle",
            "sku": "gift-b",
            "quantity": 3
        },
        {
            "recordId": "plan-03",
            "itemType": "component",
            "sku": "001",
            "quantity": 2
        }
    ],
    "demandCsv": "",
    "demandColumns": {
        "recordId": "recordId",
        "itemType": "itemType",
        "sku": "sku",
        "quantity": "quantity"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("h_murdock/bundle-component-demand-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "snapshotId": "promo-2026-10-02",
    "stockPoolId": "warehouse-main",
    "asOf": "2026-10-02T06:00:00Z",
    "inventoryCoverage": "complete",
    "demandCoverage": "complete",
    "missingInventoryPolicy": "unresolved",
    "inventory": [
        {
            "recordId": "stock-01",
            "componentSku": "001",
            "availableUnits": 10,
        },
        {
            "recordId": "stock-02",
            "componentSku": "002",
            "availableUnits": 8,
        },
        {
            "recordId": "stock-03",
            "componentSku": "003",
            "availableUnits": 6,
        },
    ],
    "inventoryCsv": "",
    "inventoryColumns": {
        "recordId": "recordId",
        "componentSku": "componentSku",
        "availableUnits": "availableUnits",
    },
    "bom": [
        {
            "recordId": "bom-01",
            "bundleSku": "gift-a",
            "componentSku": "001",
            "unitsPerBundle": 2,
        },
        {
            "recordId": "bom-02",
            "bundleSku": "gift-a",
            "componentSku": "002",
            "unitsPerBundle": 1,
        },
        {
            "recordId": "bom-03",
            "bundleSku": "gift-b",
            "componentSku": "001",
            "unitsPerBundle": 1,
        },
        {
            "recordId": "bom-04",
            "bundleSku": "gift-b",
            "componentSku": "003",
            "unitsPerBundle": 2,
        },
    ],
    "bomCsv": "",
    "bomColumns": {
        "recordId": "recordId",
        "bundleSku": "bundleSku",
        "componentSku": "componentSku",
        "unitsPerBundle": "unitsPerBundle",
    },
    "demand": [
        {
            "recordId": "plan-01",
            "itemType": "bundle",
            "sku": "gift-a",
            "quantity": 4,
        },
        {
            "recordId": "plan-02",
            "itemType": "bundle",
            "sku": "gift-b",
            "quantity": 3,
        },
        {
            "recordId": "plan-03",
            "itemType": "component",
            "sku": "001",
            "quantity": 2,
        },
    ],
    "demandCsv": "",
    "demandColumns": {
        "recordId": "recordId",
        "itemType": "itemType",
        "sku": "sku",
        "quantity": "quantity",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("h_murdock/bundle-component-demand-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "snapshotId": "promo-2026-10-02",
  "stockPoolId": "warehouse-main",
  "asOf": "2026-10-02T06:00:00Z",
  "inventoryCoverage": "complete",
  "demandCoverage": "complete",
  "missingInventoryPolicy": "unresolved",
  "inventory": [
    {
      "recordId": "stock-01",
      "componentSku": "001",
      "availableUnits": 10
    },
    {
      "recordId": "stock-02",
      "componentSku": "002",
      "availableUnits": 8
    },
    {
      "recordId": "stock-03",
      "componentSku": "003",
      "availableUnits": 6
    }
  ],
  "inventoryCsv": "",
  "inventoryColumns": {
    "recordId": "recordId",
    "componentSku": "componentSku",
    "availableUnits": "availableUnits"
  },
  "bom": [
    {
      "recordId": "bom-01",
      "bundleSku": "gift-a",
      "componentSku": "001",
      "unitsPerBundle": 2
    },
    {
      "recordId": "bom-02",
      "bundleSku": "gift-a",
      "componentSku": "002",
      "unitsPerBundle": 1
    },
    {
      "recordId": "bom-03",
      "bundleSku": "gift-b",
      "componentSku": "001",
      "unitsPerBundle": 1
    },
    {
      "recordId": "bom-04",
      "bundleSku": "gift-b",
      "componentSku": "003",
      "unitsPerBundle": 2
    }
  ],
  "bomCsv": "",
  "bomColumns": {
    "recordId": "recordId",
    "bundleSku": "bundleSku",
    "componentSku": "componentSku",
    "unitsPerBundle": "unitsPerBundle"
  },
  "demand": [
    {
      "recordId": "plan-01",
      "itemType": "bundle",
      "sku": "gift-a",
      "quantity": 4
    },
    {
      "recordId": "plan-02",
      "itemType": "bundle",
      "sku": "gift-b",
      "quantity": 3
    },
    {
      "recordId": "plan-03",
      "itemType": "component",
      "sku": "001",
      "quantity": 2
    }
  ],
  "demandCsv": "",
  "demandColumns": {
    "recordId": "recordId",
    "itemType": "itemType",
    "sku": "sku",
    "quantity": "quantity"
  }
}' |
apify call h_murdock/bundle-component-demand-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,h_murdock/bundle-component-demand-audit"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4tnS9dSR1jSIfkngo/builds/yVqwstjljaGigPOhX/openapi.json
