Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions v1/retail_planning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ tags:

## What this template is for

Retailers face interconnected decisions: which items will sell, which customers are at risk of leaving, what discounts to offer, and how much inventory to stock. Traditionally these are solved in isolation -- demand forecasting in one silo, pricing optimization in another, supply planning in a third. This template shows how to unify them in a single **predict-then-optimize** pipeline using RelationalAI.
Retailers face interconnected decisions: which items will sell, which customers are at risk of leaving, what discounts to offer, and how much inventory to stock. Traditionally these are solved in isolation -- demand forecasting in one silo, pricing optimization in another, supply planning is a third. This template shows how to unify them in a single **predict-then-optimize** pipeline using RelationalAI.

**Start with `retail_planning_local.py`** -- it trains a real sales-regression GNN on a bundled H&M subset (CPU, no external data), aggregates predictions per article, and runs both optimizers. A few minutes end-to-end. It's the quickest way to see the whole pattern working.

**Then adapt the pattern to your own Snowflake data** using `retail_planning.py` as a reference. It trains three GNNs (sales regression, customer-churn classification, user-article link prediction) against the full Kaggle H&M dataset in Snowflake, aggregates all three signals into an adjusted demand estimate, and feeds that into the same two optimizers. The H&M pipeline is the worked example -- the structure (graph concepts → GNN tasks → aggregation bridge → prescriptive constraints) is what carries over to your own retail, pricing, or demand-planning data.

> [!IMPORTANT]
> The RelationalAI **predictive reasoner (GNN)** used in this template is in
> early access. The API surface (`GNN`, `PropertyTransformer`, task
> private preview. The API surface (`GNN`, `PropertyTransformer`, task
> relationships) may still change between releases; check the
> `rai-predictive-modeling` and `rai-predictive-training` skills for the
> current guidance before adapting to production data.
Expand All @@ -54,7 +54,7 @@ Assumes familiarity with Python, basic ML concepts (classification, regression,
- **Runners**:
- `retail_planning_local.py` -- **primary, runnable out of the box.** Trains a sales-regression GNN on the bundled HM_MINI subset and solves both optimizers.
- `retail_planning.py` -- **reference pattern** for adapting the same pipeline to your own Snowflake data. Trains three GNNs (sales, churn, purchase) against a full H&M dataset in Snowflake.
- **Model**: Three GNN tasks on the H&M knowledge graph (Customer, Article, Transaction), two prescriptive problems consuming their output
- **Model**: Three GNN tasks on the H&M knowledge graph (Customer, Article, Transaction), two prescriptive problems consuming their output.
- **Sample data**:
- `data/hm_mini/` -- bundled H&M subset (~10K customers / 5K articles / 9.6K transactions) with sales task splits. This is what the local runner trains on.
- `data/*.csv` -- optimizer parameters: discounts, weeks, article inventory, production capacity.
Expand Down Expand Up @@ -83,7 +83,7 @@ you'll additionally need:
### Tools

- Python >= 3.10
- RelationalAI Python SDK (`relationalai`) >= 1.0.14
- RelationalAI Python SDK (`relationalai`) == 1.4.1

## Quickstart

Expand Down Expand Up @@ -113,7 +113,30 @@ you'll additionally need:
rai init
```

5. Run the local demo on the bundled H&M subset (CPU, a few minutes):
After `rai init` generates the config file, add the following to your `raiconfig.yaml`:

```yaml
data:
ensure_change_tracking: true
```

5. Set up the experiment schema in Snowflake:

Before running, create the database and schema used to store GNN experiments, and grant the required permissions to the RAI Native App. Run the following in a Snowflake worksheet:

```sql
CREATE DATABASE IF NOT EXISTS HM_MINI;
CREATE SCHEMA IF NOT EXISTS HM_MINI.EXPERIMENTS;
GRANT USAGE ON DATABASE HM_MINI TO APPLICATION RELATIONALAI;
GRANT USAGE ON SCHEMA HM_MINI.EXPERIMENTS TO APPLICATION RELATIONALAI;
GRANT CREATE EXPERIMENT ON SCHEMA HM_MINI.EXPERIMENTS TO APPLICATION RELATIONALAI;
GRANT CREATE MODEL ON SCHEMA HM_MINI.EXPERIMENTS TO APPLICATION RELATIONALAI;
```

> [!NOTE]
> Replace `RELATIONALAI` with the `rai_app_name` you set in `raiconfig.yaml` if it differs.

6. Run the local demo on the bundled H&M subset (CPU, a few minutes):
```bash
python retail_planning_local.py
```
Expand Down Expand Up @@ -381,16 +404,6 @@ dp.minimize(prod_cost_total + hold_cost_total + unmet_cost_total)
collapsed to the mean — revisit the PropertyTransformer and task setup.
</details>

<details>
<summary><code>has_time_column=True</code> fails validation</summary>

Known limitation flagged in the rai-predictive-training skill: when the concept
carrying `time_col` (here, `Transaction`) is used only as an edge intermediary,
validation can fail with "no time column defined in data tables." Workaround:
set `has_time_column=False` on the affected GNN and remove the `"at"` clause
from its Relationship templates until the GNN team resolves this.
</details>

<details>
<summary>Sales regression R² is low or negative</summary>

Expand Down
2 changes: 1 addition & 1 deletion v1/retail_planning/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description = "RelationalAI template: retail_planning (PyRel v1)"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"relationalai==1.0.14",
"relationalai[gnn]==1.4.1",
"pandas>=2.0",
]

Expand Down
2 changes: 2 additions & 0 deletions v1/retail_planning/retail_planning_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@
pc.pc_initial_inventory(prod_data.initial_inventory),
)

ProdCapacity.opt_article = model.Relationship(
f"{ProdCapacity} has {OptArticle:opt_article}")
model.define(ProdCapacity.opt_article(OptArticle)).where(
ProdCapacity.pc_article_id == OptArticle.opt_article_id)

Expand Down
Loading