Preserve forecasting continuity when product codes change. Build a REST API that links old and new codes into product families.
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.
Only the current code's history is visible. Years of data are lost.
All predecessors are linked. Full history is preserved.
A shampoo product goes through two code changes over three years. Here's how the chain system tracks it:
Product code 1001 is introduced in Poland (PL) as an IPC code.
The formula is updated. A new code 1002 is introduced, chained to 1001, and 1001 is discontinued. The system records the link.
Repackaged into a new size. Code 1003 replaces 1002. Same process: introduce, chain, discontinue.
The resulting Product Family:
Product Family PL-IPC-0001 — all three codes share the same demand history.
Families can also split (one code becomes two) or merge (two codes become one), forming a directed acyclic graph (DAG):
A single code's lifespan — from introduction to discontinuation. One code can have multiple non-overlapping generations.
A link between two codes: a successor replaces a predecessor. Creates edges in the product family graph.
All generations connected through chains form a family (DAG). Forecasting aggregates demand across the entire family.
Look up which family a code belongs to on a given date, or find all active codes in a family on a given date.
Each event contains one or more transitions. There are three types:
| Type | What It Does | Required 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 |
{
"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
}
]
}
Your API must reject these invalid scenarios with HTTP 400:
| # | Invalid Scenario | Reason |
|---|---|---|
| 1 | Double introduction of an active code | Overlapping generations |
| 2 | Overlapping generation (intro at earlier date) | Time intervals must not overlap |
| 3 | Double discontinuation | Code already discontinued |
| 4 | Chain with non-existing discontinuation code | Predecessor must exist |
| 5 | Chain with non-existing introduction code | Successor must exist |
| 6 | Discontinuation of never-introduced code | Nothing to discontinue |
| 7 | Chain missing discontinuation_code | Structurally invalid |
| 8 | Intro missing introduction_code | Structurally invalid |
| 9 | Discont missing discontinuation_code | Structurally invalid |
| 10 | Chain where intro == discont code | Cannot self-replace |
| 11 | Invalid country code | Reference data missing |
| 12 | Invalid code type | Reference data missing |
| 13 | Missing date | Structurally invalid |
The provided test script validates your API in three phases:
POST /api/setup/ seeds reference data (countries & code types) and returns HTTP 200.
~100 events are posted. Valid events must return 201, invalid ones must return 400. Then recomputation is triggered.
Resolve and reverse-resolve queries verify that families were computed correctly.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/setup/ | Seed reference data, return 200 |
| Method | Endpoint | Description |
|---|---|---|
| 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 |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/product-families/ | List all families |
| GET | /api/product-families/{id}/ | Get family with generations |
| POST | /api/product-families/recompute/ | Trigger recomputation |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/resolve/ | Code + date + country → family |
| GET | /api/resolve/reverse/ | Family + date → active codes |
| POST | /api/resolve/bulk/ | Bulk resolve multiple codes |
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"
| PL | Poland |
| DE | Germany |
| US | United States |
| JP | Japan |
| CN | China |
| IPC | Internal Product Code |
| GTIN | Global Trade Item Number |