Case Study

Demand Forecasting

Preserve forecasting continuity when product codes change. Build a REST API that links old and new codes into product families.

Open Live API → See an Example

The Problem

Accurate demand forecasting drives production planning, inventory management, and distribution. It depends on historical demand data — knowing how much a product sold in previous months to predict the next.

But product codes change constantly: reformulations, repackaging, regulatory requirements, market restructuring. Each change breaks the demand history — the new code starts with zero history, even though it's the same product.

× Without Chains

Only the current code's history is visible. Years of data are lost.

1003

✓ With Chains

All predecessors are linked. Full history is preserved.

1001 1002 1003

Walkthrough Example

A shampoo product goes through two code changes over three years. Here's how the chain system tracks it:

1

January 2021 — Introduction

Product code 1001 is introduced in Poland (PL) as an IPC code.

2

July 2022 — First Code Change

The formula is updated. A new code 1002 is introduced, chained to 1001, and 1001 is discontinued. The system records the link.

3

April 2024 — Second Code Change

Repackaged into a new size. Code 1003 replaces 1002. Same process: introduce, chain, discontinue.

The resulting Product Family:

1001 2021-01-01 – 2022-06-30 discontinued
1002 2022-07-01 – 2024-03-31 discontinued
1003 2024-04-01 – present active

Product Family PL-IPC-0001 — all three codes share the same demand history.

Splits & Merges

Families can also split (one code becomes two) or merge (two codes become one), forming a directed acyclic graph (DAG):

Split

A
B C

Merge

D E
F

Key Concepts

📄

Generation

A single code's lifespan — from introduction to discontinuation. One code can have multiple non-overlapping generations.

🔗

Chain

A link between two codes: a successor replaces a predecessor. Creates edges in the product family graph.

🌐

Product Family

All generations connected through chains form a family (DAG). Forecasting aggregates demand across the entire family.

🔎

Resolution

Look up which family a code belongs to on a given date, or find all active codes in a family on a given date.

Event Transition Types

Each event contains one or more transitions. There are three types:

TypeWhat It DoesRequired Fields
INTRO Introduces a new product code — makes it active introduction_code, date
DISCONT Discontinues an active code — sets its end date discontinuation_code, date
chain Links a successor to a predecessor — creates a family edge introduction_code, discontinuation_code, date

Example Event Payload

{
  "iso_country_code": "PL",
  "transitions_write": [
    {
      "code_type_id": "IPC",
      "type": "INTRO",
      "date": "2023-06-01",
      "introduction_code": 1002
    },
    {
      "code_type_id": "IPC",
      "type": "chain",
      "date": "2023-06-01",
      "introduction_code": 1002,
      "discontinuation_code": 1001
    },
    {
      "code_type_id": "IPC",
      "type": "DISCONT",
      "date": "2023-06-01",
      "discontinuation_code": 1001
    }
  ]
}

Validation Rules

Your API must reject these invalid scenarios with HTTP 400:

#Invalid ScenarioReason
1Double introduction of an active codeOverlapping generations
2Overlapping generation (intro at earlier date)Time intervals must not overlap
3Double discontinuationCode already discontinued
4Chain with non-existing discontinuation codePredecessor must exist
5Chain with non-existing introduction codeSuccessor must exist
6Discontinuation of never-introduced codeNothing to discontinue
7Chain missing discontinuation_codeStructurally invalid
8Intro missing introduction_codeStructurally invalid
9Discont missing discontinuation_codeStructurally invalid
10Chain where intro == discont codeCannot self-replace
11Invalid country codeReference data missing
12Invalid code typeReference data missing
13Missing dateStructurally invalid

How Testing Works

The provided test script validates your API in three phases:

Setup

POST /api/setup/ seeds reference data (countries & code types) and returns HTTP 200.

Event Ingestion

~100 events are posted. Valid events must return 201, invalid ones must return 400. Then recomputation is triggered.

Family Queries

Resolve and reverse-resolve queries verify that families were computed correctly.

API Endpoints

Setup

MethodEndpointDescription
POST/api/setup/Seed reference data, return 200

Events

MethodEndpointDescription
GET/api/events/List all events
POST/api/events/Create event with transitions
GET/api/events/{id}/Get single event
DELETE/api/events/{id}/Delete event

Product Families

MethodEndpointDescription
GET/api/product-families/List all families
GET/api/product-families/{id}/Get family with generations
POST/api/product-families/recompute/Trigger recomputation

Resolution

MethodEndpointDescription
GET/api/resolve/Code + date + country → family
GET/api/resolve/reverse/Family + date → active codes
POST/api/resolve/bulk/Bulk resolve multiple codes

Data Model

The system is built around events containing code transitions, which produce generations grouped into product families.

erDiagram
    COUNTRY {
        string code PK
        string name
    }

    EVENT {
        int id PK
        string iso_country_code FK
        string comment
    }

    CODE_TYPE {
        string id PK
        string type
    }

    CODE_TRANSITION {
        int id PK
        int event_id FK
        string code_type_id FK
        string type
        date date
    }

    INTRODUCTION {
        int id PK
        int code_transition_id FK
        bigint introduction_code
    }

    DISCONTINUATION {
        int id PK
        int code_transition_id FK
        bigint discontinuation_code
    }

    CHAIN {
        int id PK
        int code_transition_id FK
        bigint introduction_code
        bigint discontinuation_code
    }

    PRODUCT_FAMILY {
        int id PK
        string code_type_id FK
        string identifier
        string iso_country_code FK
    }

    GENERATION {
        int id PK
        int product_family_id FK
        int introduction_id FK
        int discontinuation_id FK
    }

    GENERATION_LINK {
        int id PK
        int predecessor_id FK
        int successor_id FK
        int source_transition_id FK
    }

    COUNTRY ||--o{ EVENT : "has"
    COUNTRY ||--o{ PRODUCT_FAMILY : "has"
    EVENT ||--o{ CODE_TRANSITION : "has"
    CODE_TYPE ||--o{ CODE_TRANSITION : "has"
    CODE_TRANSITION ||--o| INTRODUCTION : "is"
    CODE_TRANSITION ||--o| DISCONTINUATION : "is"
    CODE_TRANSITION ||--o| CHAIN : "is"
    CODE_TYPE ||--o{ PRODUCT_FAMILY : "has"
    PRODUCT_FAMILY ||--o{ GENERATION : "has"
    CODE_TRANSITION ||--o{ GENERATION : "introduction"
    CODE_TRANSITION ||--o{ GENERATION : "discontinuation"
    GENERATION ||--o{ GENERATION_LINK : "predecessor"
    GENERATION ||--o{ GENERATION_LINK : "successor"
    CODE_TRANSITION ||--o{ GENERATION_LINK : "source"
        

Reference Data

Countries

PLPoland
DEGermany
USUnited States
JPJapan
CNChina

Code Types

IPCInternal Product Code
GTINGlobal Trade Item Number