---
# === IDENTITY ===
id: business/erp-integration/demand-planning-forecasting-integration/2026
canonical_question: "How do you integrate ERP with demand planning tools like Anaplan, Oracle Demantra, or SAP IBP?"
aliases:
  - "ERP demand planning integration architecture"
  - "How to connect ERP historical sales data to Anaplan or SAP IBP for forecasting"
  - "Demand forecast to MRP integration pattern"
  - "Closed-loop demand planning ERP data pipeline"
entity_type: erp_integration
domain: business > erp-integration > demand-planning-forecasting-integration
region: global
jurisdiction: global
temporal_scope: 2025-2026

# === SYSTEM PROFILE ===
systems:
  - name: "SAP IBP for Demand"
    vendor: "SAP"
    version: "2408"
    edition: "Standard"
    deployment: cloud
    api_surface: "CPI-DS, OData, SOAP"
  - name: "Anaplan"
    vendor: "Anaplan"
    version: "Integration API v2.0"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST (Integration API v2.0), CloudWorks"
  - name: "Oracle Planning Cloud (Demantra successor)"
    vendor: "Oracle"
    version: "24B"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST, SOAP, EPM Automate"
  - name: "Blue Yonder Luminate Planning"
    vendor: "Blue Yonder"
    version: "2024.x"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST API"
  - name: "Kinaxis Maestro (RapidResponse)"
    vendor: "Kinaxis"
    version: "2024.x"
    edition: "Enterprise"
    deployment: cloud
    api_surface: "REST API, RapidResponse Integration Workbench"

# === VERIFICATION ===
last_verified: 2026-03-03
confidence: 0.84
version: 1.0
first_published: 2026-03-03

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: volatile
  last_breaking_change: "SAP IBP 2408 — CPI-DS replaced by SAP Integration Suite for new implementations (2024-08)"
  next_review: 2026-08-30
  change_sensitivity: high

# === CONSTRAINTS ===
constraints:
  - "Forecast granularity mismatch: demand planning tools forecast at product-location-week; ERP MRP typically runs at material-plant-day — aggregation/disaggregation logic is mandatory"
  - "Historical data volume: 2-3 years of daily transaction data can exceed 50M rows for mid-size companies — batch/bulk APIs required, not real-time"
  - "Consensus review step is human-in-the-loop: no API can replace S&OP meeting sign-off — integration must support manual override persistence"
  - "Frozen forecast horizon: most ERPs lock MRP demand inside a frozen zone (typically 1-4 weeks) — writing forecasts inside the frozen window is rejected or overridden"
  - "Unit of measure alignment: ERP stores in base UoM (eaches), demand planning often uses cases/pallets — conversion factor must be applied at integration boundary"
  - "Calendar alignment: ERP uses fiscal calendar; demand planning often uses ISO weeks or 4-4-5 retail calendar — period mapping tables required"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need only SAP IBP API capability details without integration patterns"
    use_instead: "business/erp-integration/sap-ibp-api-capabilities/2026"
  - condition: "Need Anaplan API rate limits and authentication specifics only"
    use_instead: "business/erp-integration/anaplan-api-capabilities/2026"
  - condition: "Need supply chain planning comparison (not integration playbook)"
    use_instead: "business/erp-integration/demand-planning-tools-comparison/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: erp_system
    question: "Which ERP system is the source of historical sales data?"
    type: choice
    options:
      - "SAP S/4HANA or ECC"
      - "Oracle ERP Cloud or E-Business Suite"
      - "Microsoft Dynamics 365"
      - "NetSuite"
      - "Other"
  - key: planning_tool
    question: "Which demand planning tool are you integrating with?"
    type: choice
    options:
      - "SAP IBP"
      - "Anaplan"
      - "Oracle Planning Cloud / Demantra"
      - "Blue Yonder"
      - "Kinaxis RapidResponse"
      - "Other / custom"
  - key: integration_pattern
    question: "What integration pattern do you need?"
    type: choice
    options:
      - "batch/bulk (scheduled nightly or weekly)"
      - "near-real-time (hourly incremental)"
      - "event-driven (on transaction commit)"
  - key: data_volume
    question: "What is your monthly transaction volume?"
    type: choice
    options:
      - "< 100,000 transactions/month"
      - "100,000-1,000,000 transactions/month"
      - "> 1,000,000 transactions/month"

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/business/erp-integration/demand-planning-forecasting-integration/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-03-03)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "business/erp-integration/sap-ibp-api-capabilities/2026"
      label: "SAP IBP API Capabilities"
    - id: "business/erp-integration/anaplan-api-capabilities/2026"
      label: "Anaplan API Capabilities"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "business/erp-integration/supply-planning-mrp-integration/2026"
      label: "Supply Planning / MRP Integration (downstream of demand planning)"

# === SOURCES ===
sources:
  - id: src1
    title: "Anaplan Integration API v2.0 Documentation"
    author: Anaplan
    url: https://help.anaplan.com/integration-api-v20-399496b0-d66e-4a84-895a-8d1ffdee2e6b
    type: official_docs
    published: 2025-01-15
    reliability: authoritative
  - id: src2
    title: "Integrating SAP IBP with SAP S/4HANA Private Cloud for Demand Planning Using CI-DS"
    author: SAP
    url: https://community.sap.com/t5/enterprise-resource-planning-blog-posts-by-sap/integrating-sap-ibp-with-sap-s-4hana-private-cloud-for-demand-planning/ba-p/14219426
    type: technical_blog
    published: 2024-09-15
    reliability: high
  - id: src3
    title: "Oracle Cloud EPM Planning REST APIs"
    author: Oracle
    url: https://docs.oracle.com/en/cloud/saas/enterprise-performance-management-common/prest/planning_rest_apis.html
    type: official_docs
    published: 2025-01-10
    reliability: authoritative
  - id: src4
    title: "Demand Planning KPIs and Metrics — MAPE, WMAPE, and Forecast Bias"
    author: DemandPlanning.net
    url: https://demandplanning.net/mape-wmape-and-forecast-bias/
    type: community_resource
    published: 2024-06-20
    reliability: moderate_high
  - id: src5
    title: "SAP IBP CPI-DS Integration FAQ and Mini-Series"
    author: SAP
    url: https://help.sap.com/docs/SUPPORT_CONTENT/sibp/3354621788.html
    type: official_docs
    published: 2024-08-01
    reliability: authoritative
  - id: src6
    title: "Anaplan CloudWorks Documentation"
    author: Anaplan
    url: https://help.anaplan.com/96f951fe-52fc-45a3-b6cb-16b7fe38e1aa
    type: official_docs
    published: 2025-02-01
    reliability: authoritative
  - id: src7
    title: "How to Measure Forecast Accuracy in ERP Systems"
    author: Procuzy
    url: https://procuzy.com/blog/how-to-measure-forecast-accuracy-in-erp-systems/
    type: technical_blog
    published: 2025-03-10
    reliability: moderate_high
  - id: src8
    title: "The Ultimate Guide to ERP Forecasting"
    author: Open Source Integrators
    url: https://www.opensourceintegrators.com/publications/ultimate-guide-erp-forecasting
    type: technical_blog
    published: 2024-11-15
    reliability: moderate_high
---

# Demand Planning & Forecasting Integration Playbook

## TL;DR

- **Bottom line**: Extract 2-3 years of historical sales from ERP via batch API, push to demand planning tool (Anaplan/SAP IBP/Oracle Planning Cloud), run statistical + ML forecasts, then write consensus forecast back to ERP as planned independent requirements (PIRs) or demand forecasts that feed MRP.
- **Key limit**: Historical data extraction is the bottleneck — 50M+ transaction rows require bulk APIs (Anaplan Integration API v2.0, SAP CPI-DS, Oracle EPM Automate), not real-time endpoints.
- **Watch out for**: Granularity mismatch between planning tool (product-location-week) and ERP MRP (material-plant-day) — missing disaggregation logic causes either over-procurement or stockouts.
- **Best for**: Companies with $50M+ revenue, 500+ SKUs, multi-location distribution, and seasonal or promotional demand patterns that outgrow basic ERP forecasting.
- **Feedback loop**: Actual sales vs. forecast comparison (MAPE, bias) must flow back into the planning tool monthly — without this, forecast accuracy degrades 5-15% annually.

## System Profile

This playbook covers the end-to-end integration pattern for connecting ERP systems with dedicated demand planning tools. The pattern is ERP-agnostic in principle but details the specific API surfaces for the five major demand planning platforms. The ERP serves as the system of record for historical transactions (sales orders, shipments, invoices) and as the execution system that consumes the approved forecast (MRP, procurement). The demand planning tool is the analytical engine that generates, adjusts, and publishes the demand signal.

| System | Role | API Surface | Direction |
|---|---|---|---|
| SAP S/4HANA / ECC | ERP — source of historical sales, target for forecast consumption (PIRs) | OData v4, BAPI/RFC, IDoc | Bidirectional |
| Oracle ERP Cloud | ERP — source of historical sales, target for forecast consumption | REST API, FBDI (CSV), BI Publisher | Bidirectional |
| Microsoft Dynamics 365 F&O | ERP — source of historical sales, target for forecast consumption | OData v4, Data Management Framework | Bidirectional |
| SAP IBP for Demand | Demand planning — statistical forecasting, consensus planning | CPI-DS, OData, SAP Integration Suite | Inbound (historical) + Outbound (forecast) |
| Anaplan | Demand planning — connected planning, scenario modeling | Integration API v2.0, CloudWorks, Anaplan Data Orchestrator | Inbound (historical) + Outbound (forecast) |
| Oracle Planning Cloud | Demand planning — replaces Demantra for cloud customers | REST API, EPM Automate, Smart View | Inbound (historical) + Outbound (forecast) |
| Blue Yonder Luminate | Demand planning — ML-driven demand sensing | REST API, file-based integration | Inbound (historical) + Outbound (forecast) |
| Kinaxis Maestro | Demand planning — concurrent planning, what-if scenarios | REST API, Integration Workbench | Inbound (historical) + Outbound (forecast) |
| iPaaS (MuleSoft / Boomi / Workato) | Middleware — orchestration, transformation, error handling | N/A | Orchestrator |

## Integration Flow Architecture

The demand planning integration follows a six-phase closed-loop pattern. Every phase has distinct data objects, frequencies, and failure modes.

```
Phase 1: EXTRACT — Historical Data from ERP
         (Sales orders, shipments, invoices, returns, promotions)
         Frequency: Nightly batch or weekly full refresh
         Volume: 2-3 years x daily granularity
              │
              ▼
Phase 2: TRANSFORM — Cleansing & Harmonization
         (UoM conversion, calendar mapping, hierarchy alignment,
          outlier detection, promo flag tagging)
         Location: Middleware or staging layer
              │
              ▼
Phase 3: LOAD — Into Demand Planning Tool
         (Master data sync first, then time-series actuals)
         API: Anaplan Integration API v2.0 / SAP CPI-DS / Oracle EPM Automate
              │
              ▼
Phase 4: FORECAST — Generate & Review
         (Statistical baseline → ML enrichment → consensus adjustment)
         Human-in-the-loop: S&OP review meeting
              │
              ▼
Phase 5: PUBLISH — Approved Forecast to ERP
         (Planned Independent Requirements / Demand Forecasts / Forecast Schedule)
         Write to ERP MRP input tables
         Frequency: Weekly or bi-weekly after S&OP sign-off
              │
              ▼
Phase 6: FEEDBACK — Accuracy Measurement
         (Actual vs. Forecast at same granularity)
         Metrics: MAPE, WMAPE, Bias, Forecast Value Added (FVA)
         Flows back to Phase 3 for model retraining
```

## API Surfaces & Capabilities

| Planning Tool | Import API | Export API | Bulk Support | Max Payload | Auth Method | Scheduling |
|---|---|---|---|---|---|---|
| SAP IBP | CPI-DS (CI-DS), SAP Integration Suite iFlows | CPI-DS, OData | Yes — CPI-DS batch jobs | 10M records/job (CPI-DS) | OAuth 2.0 + X.509 certificate | CPI-DS job scheduling, BTP Job Scheduling Service |
| Anaplan | Integration API v2.0 (bulk import) | Integration API v2.0 (bulk export) | Yes — file-based bulk | 1M cells per import action | OAuth 2.0 (client credentials) or certificate auth | CloudWorks, Anaplan Data Orchestrator |
| Oracle Planning Cloud | EPM Automate (CLI), REST API, Smart View | EPM Automate, REST API | Yes — EPM Automate batch | 500MB per file (EPM Automate) | OAuth 2.0, basic auth (legacy) | EPM Automate scripts, Oracle Integration Cloud |
| Blue Yonder | REST API, file-based (SFTP/S3) | REST API, file-based | Yes — file drops | Vendor-specific limits | OAuth 2.0, API key | Vendor-managed scheduling |
| Kinaxis | Integration Workbench, REST API | Integration Workbench, REST API | Yes — batch via Workbench | Vendor-specific limits | OAuth 2.0, SAML | Workbench scheduling, external orchestrator |

[src1, src2, src3, src5, src6]

## Rate Limits & Quotas

### Per-Request Limits

| Planning Tool | Limit Type | Value | Notes |
|---|---|---|---|
| Anaplan Integration API v2.0 | Max cells per import action | 1,000,000 | Split larger datasets into multiple actions |
| Anaplan Integration API v2.0 | Max file upload size | 50 MB per chunk | Use chunked upload for larger files |
| Anaplan Integration API v2.0 | Max concurrent API requests | 10 per workspace | Queue excess requests |
| SAP IBP (CPI-DS) | Max records per extraction template | 10,000,000 | Use time-based filtering to reduce scope |
| Oracle Planning Cloud | Max file size (EPM Automate) | 500 MB | Split data by planning dimension |
| Oracle Planning Cloud REST | Max records per REST call | 500,000 | Paginate larger result sets |

[src1, src3, src5]

### Rolling / Daily Limits

| Planning Tool | Limit Type | Value | Window | Notes |
|---|---|---|---|---|
| Anaplan | API calls | Tenant-level fair use (no hard cap published) | Rolling | Monitor via Admin console; throttling at high concurrency |
| SAP IBP | CPI-DS job executions | Based on BTP entitlement | 24h | Track via SAP BTP cockpit |
| Oracle Planning Cloud | EPM Automate operations | Varies by subscription tier | 24h | Enterprise tier has higher limits |
| All tools | Concurrent integration jobs | Typically 3-5 parallel | Per tenant | Exceeding causes queueing, not rejection |

[src1, src5, src6]

## Authentication

| Planning Tool | Flow | Use When | Token Lifetime | Notes |
|---|---|---|---|---|
| Anaplan | OAuth 2.0 Client Credentials | Server-to-server batch integration | 35 min (access token) | Register app in Anaplan Admin; rotate client secret quarterly |
| Anaplan | Certificate-based auth | High-security environments | Session-based | X.509 certificate uploaded to Anaplan |
| SAP IBP | OAuth 2.0 + X.509 Certificate | CPI-DS and Integration Suite connections | Configurable (BTP) | Managed via SAP BTP destination service |
| SAP IBP | Basic Auth (Communication Arrangement) | Legacy CPI-DS setups | Session-based | Being deprecated in favor of OAuth |
| Oracle Planning Cloud | OAuth 2.0 | REST API and EPM Automate | 60 min (default) | Managed via Oracle Identity Cloud Service |
| Oracle Planning Cloud | Basic Auth | EPM Automate CLI scripts | Session-based | Still widely used; moving to OAuth |

[src1, src2, src3, src6]

### Authentication Gotchas

- Anaplan OAuth tokens expire after 35 minutes — long-running bulk imports must refresh mid-job or use the token refresh endpoint before expiry [src1]
- SAP IBP CPI-DS requires a Communication Arrangement in S/4HANA with specific communication scenarios (SAP_COM_0XXX) — missing scenarios cause silent connection failures with misleading "unauthorized" errors [src2, src5]
- Oracle EPM Automate basic auth counts against the user's concurrent session limit — automated jobs can lock out interactive users [src3]

## Constraints

- Forecast granularity mismatch between planning tool (product-location-week) and ERP MRP (material-plant-day) requires disaggregation logic — ERP will reject forecasts at the wrong granularity level
- Historical data must include returns, credits, and cancellations with proper sign convention — excluding these inflates the baseline forecast by 3-8% on average
- Consensus review (S&OP) is a human-in-the-loop step that cannot be automated via API — the integration must preserve manual adjustments and not overwrite them on the next sync
- Frozen forecast horizon in ERP (typically 1-4 weeks for MRP) means forecast writes inside this window are rejected or ignored — integration must respect the planning fence
- Multi-currency environments require exchange rate alignment — planning tool and ERP must use the same rate type (spot, budget, or period-average) to avoid demand distortion
- Product lifecycle events (new product introduction, phase-out) require special handling — no historical data exists for NPI, and phase-out products need demand ramp-down curves, not statistical forecasts

## Integration Pattern Decision Tree

```
START — Integrate ERP with Demand Planning Tool
├── What type of data is flowing?
│   ├── Historical actuals (ERP → Planning tool)
│   │   ├── Volume < 100K records?
│   │   │   └── REST API (Anaplan v2.0, Oracle REST, OData)
│   │   ├── Volume 100K-10M records?
│   │   │   └── Bulk API (Anaplan bulk import, CPI-DS, EPM Automate)
│   │   └── Volume > 10M records?
│   │       └── File-based (CSV/SFTP) + staged loading
│   ├── Master data (bidirectional sync)
│   │   ├── Product hierarchy → push from ERP (source of truth)
│   │   ├── Location/plant master → push from ERP
│   │   ├── Customer hierarchy → push from ERP or CRM
│   │   └── Planning-specific attributes → push from planning tool
│   ├── Approved forecast (Planning tool → ERP)
│   │   ├── SAP: Write as Planned Independent Requirements (PIRs) via BAPI_REQUIREMENTS_CREATE or MD_SET_ACTION_PLORDER
│   │   ├── Oracle: Import as Demand Forecast via FBDI (CSV load)
│   │   ├── D365: Write via Demand Forecast Entity (OData)
│   │   └── Always: respect frozen horizon, apply disaggregation
│   └── Accuracy feedback (ERP actuals vs. published forecast)
│       └── Scheduled weekly/monthly comparison job
│           └── Output: MAPE, Bias, FVA per product-location
├── What middleware to use?
│   ├── SAP ecosystem → SAP Integration Suite (BTP) or CPI-DS
│   ├── Oracle ecosystem → Oracle Integration Cloud (OIC)
│   ├── Multi-vendor → MuleSoft, Boomi, Workato, or Informatica
│   └── Budget-constrained → Custom scripts + SFTP + cron
└── Error handling?
    ├── Master data sync failures → queue and retry; block dependent jobs
    ├── Historical load failures → partial load OK; log gaps for reconciliation
    ├── Forecast write failures → CRITICAL — alert immediately; MRP runs on stale data
    └── Accuracy feedback failures → non-blocking; degrade gracefully
```

## Quick Reference — Integration Process Flow

| Step | Source System | Action | Target System | Data Objects | Frequency | Failure Handling |
|---|---|---|---|---|---|---|
| 1 | ERP | Extract sales history (orders, shipments, invoices, returns) | Staging / Data Lake | Sales transactions, quantities, dates, locations | Nightly (incremental) or weekly (full) | Retry 3x; alert if >2h late |
| 2 | ERP | Extract master data (products, locations, customers, hierarchies) | Staging / Data Lake | Material master, plant master, customer master, BOM | Daily (delta) | Block downstream jobs on failure |
| 3 | Staging | Transform: UoM conversion, calendar mapping, outlier flagging | Staging | Cleansed actuals at planning granularity | After extraction completes | Log transform exceptions; proceed with clean records |
| 4 | Staging | Load actuals into planning tool | Demand Planning Tool | Time-series actuals (product x location x period) | After transform completes | Retry; validate record counts match source |
| 5 | Demand Planning Tool | Generate statistical forecast + ML enrichment | Demand Planning Tool | Baseline forecast | Weekly (automated) | Alert if model convergence fails |
| 6 | Demand Planning Tool | Consensus review (S&OP meeting) — manual adjustments | Demand Planning Tool | Adjusted forecast with override flags | Weekly/monthly (manual) | N/A — human process |
| 7 | Demand Planning Tool | Export approved forecast | Staging / Middleware | Forecast quantities at ERP granularity | After S&OP sign-off | Validate disaggregation totals match aggregate |
| 8 | Staging | Disaggregate forecast (week → day, case → each) | Staging | Daily material-plant demand | After export | Validate sum of daily = weekly total |
| 9 | Staging | Write forecast to ERP (PIRs / demand forecast entries) | ERP | Planned Independent Requirements, demand forecast | After disaggregation | Skip frozen horizon records; log rejections |
| 10 | ERP | MRP run consumes forecast → generates planned orders → purchase requisitions | ERP | Planned orders, purchase requisitions | Per ERP schedule (daily/nightly) | Standard ERP MRP exception handling |
| 11 | ERP + Planning Tool | Accuracy feedback: actual vs. forecast comparison | Demand Planning Tool | MAPE, WMAPE, Bias, FVA by product-location | Monthly | Non-blocking; log for trend analysis |

## Step-by-Step Integration Guide

### 1. Extract Historical Sales Data from ERP

The foundation of demand planning is clean historical data. Extract 2-3 years of daily sales transactions including orders, shipments, returns, and credit memos. Include promotional flags, pricing, and customer segment when available. [src2, src8]

**SAP S/4HANA**: Use CPI-DS with standard extraction templates or custom CDS views. Key CDS views: `I_SalesOrderItem`, `I_BillingDocumentItem`, `I_DeliveryDocumentItem`.

```sql
-- SAP CDS View for sales history extraction (simplified)
-- Target: demand planning actuals at material-plant-day granularity
SELECT
  material         AS product_id,
  plant            AS location_id,
  billing_date     AS transaction_date,
  SUM(quantity)    AS shipped_qty,
  SUM(net_amount)  AS revenue,
  currency_code,
  sales_org,
  distribution_channel
FROM i_billingdocumentitem
WHERE billing_date >= ADD_DAYS(CURRENT_DATE, -1095)  -- 3 years
  AND billing_type IN ('F1', 'F2')  -- invoices only, exclude pro-forma
  AND cancelled_billing_doc = ''     -- exclude cancelled
GROUP BY material, plant, billing_date, currency_code, sales_org, distribution_channel
```

**Oracle ERP Cloud**: Use BI Publisher reports or REST API to extract from `FscmTopModelAM.SalesOrderHeaderAM` or `FscmTopModelAM.OrderFulfillmentLineAM`.

**Verify**: Record count in extract matches ERP transaction count for the same period (tolerance: <0.1% variance).

### 2. Transform and Cleanse Historical Data

Raw ERP data must be transformed to match the demand planning tool's data model. Key transformations include UoM conversion, calendar period mapping, outlier detection, and hierarchy alignment. [src4, src7]

```python
# Input:  Raw ERP sales extract (CSV, millions of rows)
# Output: Cleansed actuals at planning granularity (product x location x week)

import pandas as pd
import numpy as np

def transform_sales_to_planning_granularity(raw_df):
    """Transform daily ERP sales to weekly planning granularity."""

    # 1. UoM conversion — ERP stores in eaches, planning uses cases
    uom_factors = load_uom_conversion_table()  # product_id -> case_qty
    raw_df = raw_df.merge(uom_factors, on='product_id', how='left')
    raw_df['planning_qty'] = raw_df['shipped_qty'] / raw_df['case_qty'].fillna(1)

    # 2. Calendar mapping — ERP fiscal calendar to ISO weeks
    raw_df['iso_year_week'] = pd.to_datetime(raw_df['transaction_date']).dt.isocalendar().week
    raw_df['iso_year'] = pd.to_datetime(raw_df['transaction_date']).dt.isocalendar().year

    # 3. Outlier detection — flag values > 3 std dev from rolling 8-week mean
    weekly_agg = raw_df.groupby(['product_id', 'location_id', 'iso_year', 'iso_year_week']).agg(
        total_qty=('planning_qty', 'sum'),
        total_revenue=('revenue', 'sum')
    ).reset_index()

    rolling_mean = weekly_agg.groupby(['product_id', 'location_id'])['total_qty'].transform(
        lambda x: x.rolling(8, min_periods=4).mean()
    )
    rolling_std = weekly_agg.groupby(['product_id', 'location_id'])['total_qty'].transform(
        lambda x: x.rolling(8, min_periods=4).std()
    )
    weekly_agg['is_outlier'] = abs(weekly_agg['total_qty'] - rolling_mean) > (3 * rolling_std)

    # 4. Promotional tagging (if promo calendar available)
    promo_calendar = load_promo_calendar()  # product_id, week, promo_flag
    weekly_agg = weekly_agg.merge(promo_calendar, on=['product_id', 'iso_year_week'], how='left')
    weekly_agg['promo_flag'] = weekly_agg['promo_flag'].fillna(False)

    return weekly_agg
```

**Verify**: `SUM(planning_qty * case_qty)` in transformed output equals `SUM(shipped_qty)` in raw extract (tolerance: 0.0% — must be exact).

### 3. Load Actuals into Demand Planning Tool

Master data must be loaded before time-series data. Always sync product and location hierarchies first, then load actuals. [src1, src5, src6]

**Anaplan — Integration API v2.0**:

```python
# Input:  Cleansed weekly actuals CSV
# Output: Anaplan model populated with historical data

import requests

ANAPLAN_API = "https://api.anaplan.com/2/0"

def load_actuals_to_anaplan(workspace_id, model_id, file_id, csv_data, token):
    """Upload actuals to Anaplan via Integration API v2.0 bulk import."""
    headers = {"Authorization": f"AnaplanAuthToken {token}",
               "Content-Type": "application/octet-stream"}

    # Step 1: Upload file (chunked if > 50MB)
    chunk_size = 50 * 1024 * 1024  # 50MB chunks
    for i, chunk in enumerate(chunked(csv_data, chunk_size)):
        url = f"{ANAPLAN_API}/workspaces/{workspace_id}/models/{model_id}/files/{file_id}/chunks/{i}"
        resp = requests.put(url, headers=headers, data=chunk)
        resp.raise_for_status()

    # Step 2: Mark upload complete
    url = f"{ANAPLAN_API}/workspaces/{workspace_id}/models/{model_id}/files/{file_id}/complete"
    requests.post(url, headers=headers).raise_for_status()

    # Step 3: Run import action
    import_id = "ACTUALS_IMPORT_ACTION_ID"
    url = f"{ANAPLAN_API}/workspaces/{workspace_id}/models/{model_id}/imports/{import_id}/tasks"
    resp = requests.post(url, headers=headers, json={"localeName": "en_US"})
    task_id = resp.json()["task"]["taskId"]

    # Step 4: Poll for completion (token refresh if > 35 min)
    return poll_task_completion(workspace_id, model_id, import_id, task_id, token)
```

**SAP IBP — CPI-DS**: Use standard CPI-DS templates (e.g., `SAPIBP1_TEMPLATE_HIST_ACTUAL`) to load time-series key figures into the Unified Planning Area. Configure the extraction source connection to S/4HANA and target connection to IBP. [src2, src5]

**Verify**: Record count in planning tool matches extract count. Spot-check 5 random product-location-week combinations against ERP source.

### 4. Generate and Review Forecast

The planning tool generates a statistical baseline forecast, then applies ML enrichment (demand sensing, external signals). The consensus review step is human-in-the-loop. [src8]

```
Forecast Generation Pipeline:
1. Statistical baseline: Holt-Winters, ARIMA, or Croston's (intermittent demand)
2. ML enrichment: Gradient boosting on external features (weather, events, economic indicators)
3. Promotional uplift: Override baseline with promo lift factors from historical promo response
4. New product forecast: Analog/like-item approach — map NPI to similar products' launch curves
5. Consensus adjustment: S&OP team reviews top 20% of SKUs by revenue; adjusts remaining via exception management
```

**Verify**: Forecast value added (FVA) at each step — if ML enrichment or consensus adjustment worsens accuracy vs. baseline, the step adds cost without value.

### 5. Export Approved Forecast to ERP

After S&OP sign-off, disaggregate the forecast from planning granularity (product-location-week) to ERP granularity (material-plant-day) and write to the ERP's forecast consumption mechanism. [src2, src3]

**SAP S/4HANA — Planned Independent Requirements (PIRs)**:

```python
# Input:  Approved weekly forecast from planning tool
# Output: PIRs created in SAP S/4HANA at daily granularity

# Option A: BAPI call via SAP RFC
# BAPI_REQUIREMENTS_CREATE — creates PIR (MD61 equivalent)
pir_data = {
    "MATERIAL": "PRODUCT-001",
    "PLANT": "1000",
    "VERSION": "00",       # Active version
    "REQMTSTYPE": "LSF",   # Forecast requirements
    "PERIODS": [
        {"DATE": "2026-03-10", "QUANTITY": 150.0, "UNIT": "EA"},
        {"DATE": "2026-03-11", "QUANTITY": 155.0, "UNIT": "EA"},
        # ... daily disaggregation for the week
    ]
}
# Frozen horizon check: skip dates inside planning fence (e.g., < 2 weeks out)
```

**Oracle ERP Cloud — FBDI (File-Based Data Import)**:

```bash
# Upload forecast via FBDI CSV to Oracle ERP Cloud
# File format: ForecastDesignator, Item, Organization, ForecastDate, Quantity, UOM
# Load via ERP Cloud's Scheduled Processes > Import Demand Forecasts
```

**Verify**: `MRP Live` or `MRP run` in ERP picks up new forecast quantities. Check planned orders generated against expected quantities.

### 6. Implement Forecast Accuracy Feedback Loop

The closed-loop feedback mechanism compares actual sales to the published forecast at the same granularity. This feeds back into the planning tool for model recalibration. [src4, src7]

```python
# Input:  Published forecast + actual sales for the same period
# Output: Accuracy metrics (MAPE, WMAPE, Bias) per product-location

import numpy as np

def calculate_forecast_accuracy(actuals, forecasts):
    """Calculate standard demand planning accuracy metrics."""
    # Align on product-location-period
    merged = actuals.merge(forecasts, on=['product_id', 'location_id', 'period'], how='inner')

    # MAPE — Mean Absolute Percentage Error
    # Exclude zero-actual periods (MAPE undefined when actual = 0)
    non_zero = merged[merged['actual_qty'] > 0]
    non_zero['ape'] = abs(non_zero['actual_qty'] - non_zero['forecast_qty']) / non_zero['actual_qty']
    mape = non_zero['ape'].mean() * 100  # As percentage

    # WMAPE — Weighted MAPE (preferred for demand planning)
    wmape = (abs(merged['actual_qty'] - merged['forecast_qty']).sum() /
             merged['actual_qty'].sum()) * 100

    # Bias — directional error (positive = over-forecast, negative = under-forecast)
    bias = ((merged['forecast_qty'] - merged['actual_qty']).sum() /
            merged['actual_qty'].sum()) * 100

    # Forecast Value Added (FVA) — did the forecast beat naive model?
    naive_forecast = merged['actual_qty'].shift(52)  # Same period last year
    naive_mape = (abs(merged['actual_qty'] - naive_forecast) / merged['actual_qty']).mean() * 100

    return {
        'mape': round(mape, 1),
        'wmape': round(wmape, 1),
        'bias': round(bias, 1),
        'fva_vs_naive': round(naive_mape - mape, 1),  # Positive = forecast adds value
        'record_count': len(merged),
        'zero_actual_excluded': len(merged) - len(non_zero)
    }
```

**Target accuracy benchmarks** [src4]:
- Consumer packaged goods: WMAPE 20-30%
- Industrial / B2B: WMAPE 30-40%
- Fashion / seasonal: WMAPE 40-60%
- Spare parts / intermittent: WMAPE 50-70%

**Verify**: Bias should be within +/-5%. Persistent positive bias (>10%) indicates systematic over-forecasting; persistent negative bias indicates under-forecasting. Both require model recalibration.

## Data Mapping

### Field Mapping Reference — ERP to Demand Planning Tool

| Source Field (ERP) | Target Field (Planning Tool) | Type | Transform | Gotcha |
|---|---|---|---|---|
| SAP: MATNR / Oracle: ITEM_NUMBER | Product ID | String | Direct (pad SAP to 18 chars) | SAP leading zeros; Oracle may use item description as key |
| SAP: WERKS / Oracle: ORGANIZATION_CODE | Location ID | String | Direct | Multi-plant companies may need location hierarchy mapping |
| SAP: FKDAT (billing date) | Period (ISO week) | Date → Period | Convert daily to weekly using ISO calendar | Fiscal calendar misalignment causes period-boundary errors |
| SAP: FKIMG (billed qty) | Actuals Quantity | Decimal | Apply UoM conversion factor | Base UoM in ERP may differ from planning UoM |
| SAP: MEINS (base UoM) | UoM | String | Map ERP UoM to planning UoM code | "EA" in ERP may be "Each" in planning tool |
| SAP: NETWR (net value) | Revenue | Currency | Convert to planning currency at period-end rate | Multi-currency: use consistent exchange rate source |
| ERP: Customer segment | Demand stream | String | Map to planning hierarchy | Customer classification may not align to demand channels |
| ERP: Sales order type | Demand type flag | String | Map: standard order = base demand; promo order = promo demand | Separating promo vs. base is critical for forecast accuracy |

### Data Type Gotchas

- SAP quantities are stored with 3 decimal places (QUAN type); most planning tools use 2 decimal places — rounding at the conversion boundary causes cumulative drift over large datasets [src2]
- Oracle ERP Cloud uses `NULL` for zero demand periods; planning tools interpret missing periods differently (some assume zero, others exclude from calculations) — always explicitly insert zero records [src3]
- Date timezone handling: SAP stores dates without timezone (plant-local assumed); planning tools typically use UTC — a shipment on Friday 23:00 UTC can appear as Saturday in the planning tool, shifting it to the next ISO week [src2]
- Negative quantities (returns, credit memos) must be handled explicitly — some planning tool import processes silently drop negative values, inflating the demand baseline [src1]

## Error Handling & Failure Points

### Common Error Codes

| Error | System | Meaning | Cause | Resolution |
|---|---|---|---|---|
| 429 Too Many Requests | Anaplan API | Rate limit exceeded | Too many concurrent imports | Exponential backoff: wait 2^n seconds, max 5 retries |
| AUTH_TOKEN_EXPIRED | Anaplan API | OAuth token expired mid-import | Bulk import exceeded 35-min token lifetime | Implement token refresh logic; refresh at 30-min mark |
| CPI-DS Job Failed: TIMEOUT | SAP IBP | CPI-DS extraction timed out | Query scope too large (>10M records without time filter) | Add time-based filtering to CPI-DS template; split by year |
| PIR_FROZEN_HORIZON | SAP ERP | Forecast write rejected inside frozen zone | Trying to write PIR inside planning fence | Filter out dates inside frozen horizon before write |
| FBDI_VALIDATION_ERROR | Oracle ERP | Forecast import rejected | Invalid item-org combination or UoM mismatch | Validate master data alignment before forecast load |
| HIERARCHY_MISMATCH | Any | Product/location not found in planning tool | Master data out of sync between ERP and planning tool | Always run master data sync before time-series load |
| DUPLICATE_KEY | Any | Duplicate forecast record for same product-period | Re-run of export without clearing previous forecast | Use upsert pattern; version forecast with timestamp |

### Failure Points in Production

- **Master data sync lag**: New products added to ERP on Monday are not in the planning tool until Tuesday night's sync — any actuals for the new product on Monday are silently dropped. Fix: `Run master data sync before actuals load; never assume master data is current`. [src2]
- **Partial load treated as complete**: Bulk import loads 950,000 of 1,000,000 records but the job reports success — the 50,000 failed records (bad UoM, missing location) are in the error log but nobody reviews it. Fix: `Validate post-load record count against source count; alert on >0.1% variance`. [src1]
- **Forecast overwrites manual adjustments**: Automated forecast export runs after S&OP meeting and overwrites the consensus adjustments made during the meeting. Fix: `Use version-controlled forecasts; export to a "system" version, not the "consensus" version`. [src5]
- **Stale forecast in ERP after planning tool outage**: Planning tool is down for maintenance; ERP continues running MRP against the last loaded forecast (now 2+ weeks stale). Fix: `Add forecast age check to MRP pre-processing; alert if forecast is >7 days old`. [src8]
- **Calendar boundary errors**: ISO week 1 can start in December of the previous year — forecast for "Week 1, 2026" is mapped to late December 2025 in the ERP, causing Q4 demand distortion. Fix: `Use explicit date ranges (start_date, end_date) instead of week numbers for the last/first 2 weeks of the year`.

## Anti-Patterns

### Wrong: Loading all historical data every night (full refresh)

```python
# BAD — full refresh of 3 years of data every night
# Wastes API calls, risks rate limits, takes 4+ hours
def nightly_full_refresh():
    all_data = extract_all_sales_last_3_years()  # 50M rows
    delete_all_actuals_in_planning_tool()
    load_all_actuals(all_data)
```

### Correct: Incremental delta load with periodic full reconciliation

```python
# GOOD — incremental delta + weekly full reconciliation
def nightly_incremental():
    last_load_timestamp = get_last_successful_load_timestamp()
    delta = extract_sales_since(last_load_timestamp)  # Typically 10K-100K rows
    upsert_actuals(delta)  # Upsert, not insert — handles corrections
    update_load_timestamp()

def weekly_full_reconciliation():
    """Run once per week to catch any missed deltas."""
    full_count_erp = get_erp_record_count(last_3_years)
    full_count_planning = get_planning_tool_record_count(last_3_years)
    if abs(full_count_erp - full_count_planning) / full_count_erp > 0.001:
        trigger_full_reload()
    else:
        log("Reconciliation passed — counts match within 0.1%")
```

### Wrong: Writing forecast directly to ERP without disaggregation

```python
# BAD — writing weekly forecast to ERP that expects daily granularity
# MRP sees entire week's demand on Monday → orders everything immediately
def write_forecast_to_erp(weekly_forecast):
    for row in weekly_forecast:
        create_pir(material=row.product, plant=row.location,
                   date=row.week_start_date,  # All demand on Monday!
                   quantity=row.weekly_qty)
```

### Correct: Disaggregate using historical daily patterns

```python
# GOOD — disaggregate weekly forecast using historical daily demand profile
def write_disaggregated_forecast(weekly_forecast, daily_profiles):
    for row in weekly_forecast:
        profile = daily_profiles.get((row.product, row.location),
                                     DEFAULT_PROFILE)  # Mon-Sun weights
        for day_offset, weight in enumerate(profile):
            daily_date = row.week_start_date + timedelta(days=day_offset)
            if daily_date >= get_frozen_horizon_date():  # Skip frozen zone
                continue
            daily_qty = round(row.weekly_qty * weight, 2)
            if daily_qty > 0:
                upsert_pir(material=row.product, plant=row.location,
                           date=daily_date, quantity=daily_qty)
```

### Wrong: Ignoring forecast accuracy feedback

```python
# BAD — fire and forget: publish forecast, never measure accuracy
# Forecast degrades 5-15% per year without recalibration
def monthly_planning_cycle():
    generate_forecast()
    get_consensus()
    publish_to_erp()
    # Done! Never look back...
```

### Correct: Closed-loop accuracy measurement with automated alerts

```python
# GOOD — monthly accuracy measurement with threshold alerts
def monthly_accuracy_review():
    metrics = calculate_forecast_accuracy(actuals_last_month, forecast_last_month)
    store_metrics_to_dashboard(metrics)

    if metrics['wmape'] > 40:
        alert("WMAPE exceeds 40% — review forecast model parameters")
    if abs(metrics['bias']) > 10:
        alert(f"Forecast bias at {metrics['bias']}% — "
              f"{'over' if metrics['bias'] > 0 else 'under'}-forecasting detected")
    if metrics['fva_vs_naive'] < 0:
        alert("Forecast worse than naive (same-period-last-year) — "
              "model may be adding noise, not signal")
```

## Common Pitfalls

- **Pitfall: Not separating base demand from promotional demand**: Promotional spikes get blended into the statistical baseline, permanently inflating the forecast for non-promo periods. Fix: `Tag promotional orders in ERP (order reason code, campaign ID), extract separately, and model promo lift as an overlay on base demand`. [src8]
- **Pitfall: Using shipment data instead of demand data**: Shipments reflect supply constraints (stockouts, allocation), not true demand. A product that was out of stock for 2 weeks shows zero shipments — the forecasting model learns "demand dropped to zero." Fix: `Use order data (gross demand) as the primary signal; supplement with shipment data for fulfillment analysis; impute unconstrained demand for stockout periods`. [src4]
- **Pitfall: Aggregating too early**: Aggregating to monthly at extraction time loses the daily signal needed for demand sensing and short-term forecasting. Fix: `Always extract at the finest granularity available (daily, or even transactional); aggregate in the planning tool where the user can choose the appropriate level`. [src7]
- **Pitfall: No master data governance**: Product hierarchies differ between ERP (finance-oriented) and planning tool (demand-oriented). A product reorganization in ERP breaks the planning tool's hierarchy mapping mid-forecast cycle. Fix: `Maintain a master data mapping layer (MDM) between ERP and planning tool; version-control hierarchy changes; freeze hierarchy during forecast cycle`. [src2]
- **Pitfall: Treating forecast accuracy as a single number**: Company-level MAPE of 25% hides that A-items are at 15% and C-items at 60%. Improvement efforts target the wrong SKUs. Fix: `Segment accuracy reporting by ABC classification, product family, demand pattern (smooth, erratic, intermittent, lumpy), and forecast horizon (1-week, 4-week, 13-week)`. [src4]
- **Pitfall: Over-engineering real-time integration**: Demand planning does not benefit from real-time data — forecasts are reviewed weekly or monthly. Real-time integration adds complexity, cost, and failure modes without improving forecast accuracy. Fix: `Use batch integration (nightly or hourly at most); reserve real-time for demand sensing use cases only (weather disruption, social media viral events)`. [src8]

## Diagnostic Commands

```bash
# Verify Anaplan API connectivity and authentication
curl -X POST "https://us1a.app.anaplan.com/token/authenticate" \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"client_credentials","client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_SECRET"}'
# Expected: 200 OK with tokenInfo.tokenValue

# Check Anaplan model metadata and list available import actions
curl -X GET "https://api.anaplan.com/2/0/workspaces/{workspace_id}/models/{model_id}/imports" \
  -H "Authorization: AnaplanAuthToken {token}"
# Expected: list of import definitions with IDs

# SAP IBP — check CPI-DS job status via BTP cockpit API
# Or monitor via SAP Integration Suite > Monitor > Past Runs

# Oracle Planning Cloud — check EPM Automate job status
epmautomate.sh liststatus
# Expected: list of recent job executions with status

# Validate record counts post-load (generic — any planning tool)
# Compare: source extract count vs. planning tool loaded count
echo "ERP extract: $(wc -l < /data/exports/sales_history.csv) records"
echo "Planning tool: $(curl -s $PLANNING_API/actuals/count) records"
# Expected: counts match within 0.1%

# Check forecast age in ERP (SAP example — last PIR update date)
# Transaction MD62 or custom CDS view on PBIM table
# Alert if last forecast load > 7 days ago
```

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| 500+ SKUs with seasonal or promotional demand patterns | <100 SKUs with stable demand | ERP's built-in forecast module (MRP forecast profiles) |
| Multi-location distribution requiring location-level forecasting | Single location / single warehouse | Spreadsheet-based planning or ERP native tools |
| S&OP process requires collaborative consensus forecasting | No formal S&OP process — just reorder-point replenishment | Min-max or reorder-point settings in ERP |
| Need ML/AI enrichment beyond exponential smoothing | Purely make-to-order business (no forecast needed) | Order-driven planning (MTO/CTO) in ERP |
| Demand sensing for short-horizon (1-4 weeks) accuracy improvement | Long-term strategic capacity planning only | S&OP / IBP for supply module |
| Multi-channel demand (retail + ecommerce + wholesale) | Single-channel B2B with contract-based demand | EDI-based demand from customer forecasts |

## Cross-System Comparison — Demand Planning Tools

| Capability | SAP IBP | Anaplan | Oracle Planning Cloud | Blue Yonder | Kinaxis |
|---|---|---|---|---|---|
| ERP integration depth | Deep (native S/4HANA) | Broad (any ERP via API) | Deep (Oracle ERP) | Broad (any ERP) | Broad (any ERP) |
| Primary integration method | CPI-DS / Integration Suite | Integration API v2.0 / CloudWorks | EPM Automate / OIC | REST API / file-based | Integration Workbench |
| Bulk data loading | CPI-DS batch (10M+ records) | Bulk API (1M cells/action) | EPM Automate (500MB/file) | File-based (SFTP) | Workbench batch |
| Real-time capability | Limited (event mesh in roadmap) | Anaplan Data Orchestrator (near-real-time) | REST API (small datasets) | Demand sensing (proprietary) | Concurrent planning engine |
| Statistical forecasting | Holt-Winters, Croston's, causal | Basic (relies on formulas/scripts) | Advanced (multiple algorithms) | ML-native (advanced) | ML-native (advanced) |
| ML / demand sensing | SAP Analytics Cloud integration | External ML integration required | Oracle AI / ML built-in | Core strength | Core strength |
| Consensus workflow | Built-in S&OP workflow | Flexible (model-driven) | Built-in EPM workflow | Built-in | Built-in |
| New product introduction | Analog-based forecasting | Custom model logic | Lifecycle planning module | ML-based NPI | Analog + ML |
| Pricing | Premium (BTP entitlement required) | Premium (per-workspace) | Mid-range (EPM subscription) | Premium (enterprise license) | Premium (per-user) |
| Best fit for | SAP-centric enterprises | Multi-vendor, finance-led planning | Oracle-centric enterprises | Retail, CPG, demand sensing | Complex manufacturing, multi-tier |

## Important Caveats

- Integration complexity and timeline: a full demand planning integration typically takes 3-6 months for a single ERP + planning tool combination; budget 6-12 months for multi-ERP, multi-region deployments
- Data quality is the binding constraint — the most sophisticated ML models cannot compensate for incomplete or inaccurate historical data; invest in data cleansing before tool selection
- Vendor lock-in risk: SAP IBP + S/4HANA and Oracle Planning Cloud + Oracle ERP are tightly coupled — switching either the ERP or the planning tool post-implementation requires rebuilding the integration layer
- Licensing costs are separate: the demand planning tool license, the middleware license (iPaaS), and any BTP/cloud entitlements are billed independently — total integration cost often exceeds the planning tool license itself
- API versioning: Anaplan Integration API v1 is deprecated (sunset 2024); SAP CPI-DS is being replaced by SAP Integration Suite for new implementations; Oracle EPM Automate basic auth is being phased out — always target the current recommended API surface
- Forecast accuracy benchmarks vary dramatically by industry, product lifecycle stage, and forecast horizon — do not compare your WMAPE to benchmarks without controlling for these variables

## Related Units

- [SAP IBP API Capabilities](/business/erp-integration/sap-ibp-api-capabilities/2026)
- [Anaplan API Capabilities](/business/erp-integration/anaplan-api-capabilities/2026)
- [Supply Planning / MRP Integration](/business/erp-integration/supply-planning-mrp-integration/2026)
